@radix-ng/primitives 0.32.4 → 0.33.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/core/index.d.ts +1 -0
- package/core/src/focus-initial.directive.d.ts +9 -0
- package/fesm2022/radix-ng-primitives-accordion.mjs +19 -19
- package/fesm2022/radix-ng-primitives-alert-dialog.mjs +22 -22
- package/fesm2022/radix-ng-primitives-aspect-ratio.mjs +3 -3
- package/fesm2022/radix-ng-primitives-avatar.mjs +16 -16
- package/fesm2022/radix-ng-primitives-checkbox.mjs +16 -16
- package/fesm2022/radix-ng-primitives-collapsible.mjs +9 -9
- package/fesm2022/radix-ng-primitives-config.mjs +3 -3
- package/fesm2022/radix-ng-primitives-context-menu.mjs +34 -34
- package/fesm2022/radix-ng-primitives-core.mjs +26 -7
- package/fesm2022/radix-ng-primitives-core.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-dialog.mjs +25 -25
- package/fesm2022/radix-ng-primitives-dropdown-menu.mjs +34 -34
- package/fesm2022/radix-ng-primitives-hover-card.mjs +28 -28
- package/fesm2022/radix-ng-primitives-label.mjs +3 -3
- package/fesm2022/radix-ng-primitives-menu.mjs +37 -37
- package/fesm2022/radix-ng-primitives-menubar.mjs +31 -31
- package/fesm2022/radix-ng-primitives-navigation-menu.mjs +1646 -0
- package/fesm2022/radix-ng-primitives-navigation-menu.mjs.map +1 -0
- package/fesm2022/radix-ng-primitives-pagination.mjs +28 -28
- package/fesm2022/radix-ng-primitives-popover.mjs +50 -32
- package/fesm2022/radix-ng-primitives-popover.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-progress.mjs +10 -10
- package/fesm2022/radix-ng-primitives-radio.mjs +12 -12
- package/fesm2022/radix-ng-primitives-roving-focus.mjs +6 -6
- package/fesm2022/radix-ng-primitives-select.mjs +34 -34
- package/fesm2022/radix-ng-primitives-separator.mjs +3 -3
- package/fesm2022/radix-ng-primitives-slider.mjs +31 -31
- package/fesm2022/radix-ng-primitives-stepper.mjs +25 -25
- package/fesm2022/radix-ng-primitives-switch.mjs +13 -13
- package/fesm2022/radix-ng-primitives-tabs.mjs +16 -16
- package/fesm2022/radix-ng-primitives-toggle-group.mjs +9 -9
- package/fesm2022/radix-ng-primitives-toggle.mjs +6 -6
- package/fesm2022/radix-ng-primitives-toolbar.mjs +22 -22
- package/fesm2022/radix-ng-primitives-tooltip.mjs +28 -29
- package/fesm2022/radix-ng-primitives-tooltip.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-visually-hidden.mjs +31 -19
- package/fesm2022/radix-ng-primitives-visually-hidden.mjs.map +1 -1
- package/hover-card/src/hover-card-content.directive.d.ts +2 -2
- package/hover-card/src/hover-card-root.directive.d.ts +4 -4
- package/navigation-menu/README.md +3 -0
- package/navigation-menu/index.d.ts +28 -0
- package/navigation-menu/src/navigation-menu-a11y.component.d.ts +15 -0
- package/navigation-menu/src/navigation-menu-content.directive.d.ts +31 -0
- package/navigation-menu/src/navigation-menu-indicator.directive.d.ts +29 -0
- package/navigation-menu/src/navigation-menu-item.directive.d.ts +44 -0
- package/navigation-menu/src/navigation-menu-link.directive.d.ts +17 -0
- package/navigation-menu/src/navigation-menu-list.directive.d.ts +38 -0
- package/navigation-menu/src/navigation-menu-sub.directive.d.ts +19 -0
- package/navigation-menu/src/navigation-menu-trigger.directive.d.ts +33 -0
- package/navigation-menu/src/navigation-menu-viewport.directive.d.ts +38 -0
- package/navigation-menu/src/navigation-menu.directive.d.ts +72 -0
- package/navigation-menu/src/navigation-menu.token.d.ts +36 -0
- package/navigation-menu/src/navigation-menu.types.d.ts +13 -0
- package/navigation-menu/src/utils.d.ts +44 -0
- package/package.json +11 -7
- package/popover/src/popover-content-attributes.component.d.ts +7 -1
- package/popover/src/popover-content.directive.d.ts +2 -2
- package/popover/src/popover-root.directive.d.ts +4 -4
- package/tooltip/src/tooltip-content.directive.d.ts +2 -2
- package/tooltip/src/tooltip-root.directive.d.ts +4 -4
- package/visually-hidden/src/visually-hidden-input-bubble.directive.d.ts +4 -2
- package/visually-hidden/src/visually-hidden.directive.d.ts +2 -0
@@ -0,0 +1,1646 @@
|
|
1
|
+
import * as i0 from '@angular/core';
|
2
|
+
import { EventEmitter, Output, Input, Component, InjectionToken, inject, ElementRef, input, contentChild, signal, Directive, NgZone, TemplateRef, booleanAttribute, Renderer2, computed, effect, untracked, runInInjectionContext, contentChildren, forwardRef, numberAttribute, output, ViewContainerRef, NgModule } from '@angular/core';
|
3
|
+
import { RdxVisuallyHiddenDirective } from '@radix-ng/primitives/visually-hidden';
|
4
|
+
import { injectDocument, ESCAPE, ENTER, SPACE, TAB, injectWindow, ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT, ARROW_UP } from '@radix-ng/primitives/core';
|
5
|
+
import * as i1 from '@radix-ng/primitives/roving-focus';
|
6
|
+
import { RdxRovingFocusItemDirective, RdxRovingFocusGroupDirective } from '@radix-ng/primitives/roving-focus';
|
7
|
+
import { FocusKeyManager } from '@angular/cdk/a11y';
|
8
|
+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
9
|
+
import { Subject, map, debounce, timer, tap } from 'rxjs';
|
10
|
+
|
11
|
+
class RdxNavigationMenuFocusProxyComponent {
|
12
|
+
constructor() {
|
13
|
+
this.triggerElement = null;
|
14
|
+
this.contentElement = null;
|
15
|
+
this.proxyFocus = new EventEmitter();
|
16
|
+
}
|
17
|
+
onFocus(event) {
|
18
|
+
const prevFocusedElement = event.relatedTarget;
|
19
|
+
const wasTriggerFocused = prevFocusedElement === this.triggerElement;
|
20
|
+
const wasFocusFromContent = this.contentElement ? this.contentElement.contains(prevFocusedElement) : false;
|
21
|
+
if (wasTriggerFocused || !wasFocusFromContent) {
|
22
|
+
this.proxyFocus.emit(wasTriggerFocused ? 'start' : 'end');
|
23
|
+
}
|
24
|
+
}
|
25
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: RdxNavigationMenuFocusProxyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
26
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.4", type: RdxNavigationMenuFocusProxyComponent, isStandalone: true, selector: "rdx-navigation-menu-focus-proxy", inputs: { triggerElement: "triggerElement", contentElement: "contentElement" }, outputs: { proxyFocus: "proxyFocus" }, ngImport: i0, template: `
|
27
|
+
<span
|
28
|
+
[attr.tabindex]="0"
|
29
|
+
[attr.aria-hidden]="true"
|
30
|
+
(focus)="onFocus($event)"
|
31
|
+
rdxVisuallyHidden
|
32
|
+
feature="focusable"
|
33
|
+
></span>
|
34
|
+
`, isInline: true, dependencies: [{ kind: "directive", type: RdxVisuallyHiddenDirective, selector: "[rdxVisuallyHidden]", inputs: ["feature"] }] }); }
|
35
|
+
}
|
36
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: RdxNavigationMenuFocusProxyComponent, decorators: [{
|
37
|
+
type: Component,
|
38
|
+
args: [{
|
39
|
+
selector: 'rdx-navigation-menu-focus-proxy',
|
40
|
+
template: `
|
41
|
+
<span
|
42
|
+
[attr.tabindex]="0"
|
43
|
+
[attr.aria-hidden]="true"
|
44
|
+
(focus)="onFocus($event)"
|
45
|
+
rdxVisuallyHidden
|
46
|
+
feature="focusable"
|
47
|
+
></span>
|
48
|
+
`,
|
49
|
+
imports: [RdxVisuallyHiddenDirective]
|
50
|
+
}]
|
51
|
+
}], propDecorators: { triggerElement: [{
|
52
|
+
type: Input
|
53
|
+
}], contentElement: [{
|
54
|
+
type: Input
|
55
|
+
}], proxyFocus: [{
|
56
|
+
type: Output
|
57
|
+
}] } });
|
58
|
+
class RdxNavigationMenuAriaOwnsComponent {
|
59
|
+
constructor() {
|
60
|
+
this.contentId = '';
|
61
|
+
}
|
62
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: RdxNavigationMenuAriaOwnsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
63
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.4", type: RdxNavigationMenuAriaOwnsComponent, isStandalone: true, selector: "rdx-navigation-menu-aria-owns", inputs: { contentId: "contentId" }, ngImport: i0, template: `
|
64
|
+
<span [attr.aria-owns]="contentId" rdxVisuallyHidden feature="fully-hidden"></span>
|
65
|
+
`, isInline: true, dependencies: [{ kind: "directive", type: RdxVisuallyHiddenDirective, selector: "[rdxVisuallyHidden]", inputs: ["feature"] }] }); }
|
66
|
+
}
|
67
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: RdxNavigationMenuAriaOwnsComponent, decorators: [{
|
68
|
+
type: Component,
|
69
|
+
args: [{
|
70
|
+
selector: 'rdx-navigation-menu-aria-owns',
|
71
|
+
template: `
|
72
|
+
<span [attr.aria-owns]="contentId" rdxVisuallyHidden feature="fully-hidden"></span>
|
73
|
+
`,
|
74
|
+
imports: [RdxVisuallyHiddenDirective]
|
75
|
+
}]
|
76
|
+
}], propDecorators: { contentId: [{
|
77
|
+
type: Input
|
78
|
+
}] } });
|
79
|
+
|
80
|
+
const RDX_NAVIGATION_MENU_TOKEN = new InjectionToken('RdxNavigationMenuToken');
|
81
|
+
function injectNavigationMenu() {
|
82
|
+
return inject(RDX_NAVIGATION_MENU_TOKEN);
|
83
|
+
}
|
84
|
+
function isRootNavigationMenu(context) {
|
85
|
+
return context.isRootMenu;
|
86
|
+
}
|
87
|
+
function provideNavigationMenuContext(provider) {
|
88
|
+
return {
|
89
|
+
provide: RDX_NAVIGATION_MENU_TOKEN,
|
90
|
+
useExisting: provider
|
91
|
+
};
|
92
|
+
}
|
93
|
+
|
94
|
+
var RdxNavigationMenuAnimationStatus;
|
95
|
+
(function (RdxNavigationMenuAnimationStatus) {
|
96
|
+
RdxNavigationMenuAnimationStatus["OPEN_STARTED"] = "open_started";
|
97
|
+
RdxNavigationMenuAnimationStatus["OPEN_ENDED"] = "open_ended";
|
98
|
+
RdxNavigationMenuAnimationStatus["CLOSED_STARTED"] = "closed_started";
|
99
|
+
RdxNavigationMenuAnimationStatus["CLOSED_ENDED"] = "closed_ended";
|
100
|
+
})(RdxNavigationMenuAnimationStatus || (RdxNavigationMenuAnimationStatus = {}));
|
101
|
+
/**
|
102
|
+
* A stub class solely used to query a single type of focusable element in the navigation menu.
|
103
|
+
*/
|
104
|
+
class RdxNavigationMenuFocusableOption {
|
105
|
+
focus() {
|
106
|
+
throw new Error('Method not implemented.');
|
107
|
+
}
|
108
|
+
}
|
109
|
+
|
110
|
+
const ROOT_CONTENT_DISMISS$1 = 'navigationMenu.rootContentDismiss';
|
111
|
+
/**
|
112
|
+
* Generate a unique ID
|
113
|
+
*/
|
114
|
+
function generateId() {
|
115
|
+
return Math.random().toString(36).substring(2, 11);
|
116
|
+
}
|
117
|
+
/**
|
118
|
+
* Get the open state for data-state attribute
|
119
|
+
*/
|
120
|
+
function getOpenStateLabel(open) {
|
121
|
+
return open ? 'open' : 'closed';
|
122
|
+
}
|
123
|
+
/**
|
124
|
+
* Create a trigger ID from base ID and value
|
125
|
+
*/
|
126
|
+
function makeTriggerId(baseId, value) {
|
127
|
+
return `${baseId}-trigger-${value}`;
|
128
|
+
}
|
129
|
+
/**
|
130
|
+
* Create a content ID from base ID and value
|
131
|
+
*/
|
132
|
+
function makeContentId(baseId, value) {
|
133
|
+
return `${baseId}-content-${value}`;
|
134
|
+
}
|
135
|
+
/**
|
136
|
+
* Get the motion attribute for animations
|
137
|
+
*/
|
138
|
+
function getMotionAttribute(currentValue, previousValue, itemValue, itemValues, dir) {
|
139
|
+
// reverse values in RTL
|
140
|
+
const values = dir === 'rtl' ? [...itemValues].reverse() : itemValues;
|
141
|
+
const currentIndex = values.indexOf(currentValue);
|
142
|
+
const prevIndex = values.indexOf(previousValue);
|
143
|
+
const isSelected = itemValue === currentValue;
|
144
|
+
const wasSelected = prevIndex === values.indexOf(itemValue);
|
145
|
+
// only update selected and last selected content
|
146
|
+
if (!isSelected && !wasSelected)
|
147
|
+
return null;
|
148
|
+
// don't provide direction on initial open
|
149
|
+
if (currentIndex !== -1 && prevIndex !== -1) {
|
150
|
+
// if moving to this item from another
|
151
|
+
if (isSelected && prevIndex !== -1) {
|
152
|
+
return currentIndex > prevIndex ? 'from-end' : 'from-start';
|
153
|
+
}
|
154
|
+
// if leaving this item for another
|
155
|
+
if (wasSelected && currentIndex !== -1) {
|
156
|
+
return currentIndex > prevIndex ? 'to-start' : 'to-end';
|
157
|
+
}
|
158
|
+
}
|
159
|
+
// otherwise entering/leaving the list entirely
|
160
|
+
return isSelected ? 'from-start' : 'from-end';
|
161
|
+
}
|
162
|
+
/**
|
163
|
+
* Focus the first element in a list of candidates
|
164
|
+
* @param candidates Array of elements that can receive focus
|
165
|
+
* @param preventScroll Whether to prevent scrolling when focusing
|
166
|
+
* @param activateKeyboardNav Whether to dispatch a dummy keydown event to activate keyboard navigation handlers
|
167
|
+
* @returns Whether focus was successfully moved
|
168
|
+
*/
|
169
|
+
function focusFirst(candidates, preventScroll = false, activateKeyboardNav = true) {
|
170
|
+
const prevFocusedElement = document.activeElement;
|
171
|
+
// sort candidates by tabindex to ensure proper order
|
172
|
+
const sortedCandidates = [...candidates].sort((a, b) => {
|
173
|
+
const aIndex = a.tabIndex || 0;
|
174
|
+
const bIndex = b.tabIndex || 0;
|
175
|
+
return aIndex - bIndex;
|
176
|
+
});
|
177
|
+
const success = sortedCandidates.some((candidate) => {
|
178
|
+
// if focus is already where we want it, do nothing
|
179
|
+
if (candidate === prevFocusedElement)
|
180
|
+
return true;
|
181
|
+
try {
|
182
|
+
candidate.focus({ preventScroll });
|
183
|
+
return document.activeElement !== prevFocusedElement;
|
184
|
+
}
|
185
|
+
catch (e) {
|
186
|
+
console.error('Error focusing element:', e);
|
187
|
+
return false;
|
188
|
+
}
|
189
|
+
});
|
190
|
+
// if focus was moved successfully and we want to activate keyboard navigation,
|
191
|
+
// dispatch a dummy keypress to ensure keyboard handlers are activated
|
192
|
+
if (success && activateKeyboardNav && document.activeElement !== prevFocusedElement) {
|
193
|
+
try {
|
194
|
+
// dispatch a no-op keydown event to activate any keyboard handlers
|
195
|
+
document.activeElement?.dispatchEvent(new KeyboardEvent('keydown', {
|
196
|
+
bubbles: true,
|
197
|
+
cancelable: true,
|
198
|
+
key: 'Tab',
|
199
|
+
code: 'Tab'
|
200
|
+
}));
|
201
|
+
}
|
202
|
+
catch (e) {
|
203
|
+
console.error('Error dispatching keyboard event:', e);
|
204
|
+
}
|
205
|
+
}
|
206
|
+
return success;
|
207
|
+
}
|
208
|
+
/**
|
209
|
+
* Get all tabbable candidates in a container
|
210
|
+
*/
|
211
|
+
function getTabbableCandidates(container) {
|
212
|
+
if (!container || !container.querySelectorAll)
|
213
|
+
return [];
|
214
|
+
const TABBABLE_SELECTOR = 'a[href], button:not([disabled]), input:not([disabled]):not([type="hidden"]), ' +
|
215
|
+
'select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"]), ' +
|
216
|
+
'[contenteditable="true"]:not([tabindex="-1"])';
|
217
|
+
// use querySelector for better browser support
|
218
|
+
const elements = Array.from(container.querySelectorAll(TABBABLE_SELECTOR));
|
219
|
+
// filter out elements that are hidden, have display:none, etc.
|
220
|
+
return elements.filter((element) => {
|
221
|
+
if (element.tabIndex < 0)
|
222
|
+
return false;
|
223
|
+
if (element.hasAttribute('disabled'))
|
224
|
+
return false;
|
225
|
+
if (element.hasAttribute('aria-hidden') && element.getAttribute('aria-hidden') === 'true')
|
226
|
+
return false;
|
227
|
+
// Check if element or any parent is hidden
|
228
|
+
let current = element;
|
229
|
+
while (current) {
|
230
|
+
const style = window.getComputedStyle(current);
|
231
|
+
if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') {
|
232
|
+
return false;
|
233
|
+
}
|
234
|
+
current = current.parentElement;
|
235
|
+
}
|
236
|
+
return true;
|
237
|
+
});
|
238
|
+
}
|
239
|
+
/**
|
240
|
+
* Remove elements from tab order and return a function to restore them
|
241
|
+
*/
|
242
|
+
function removeFromTabOrder(candidates) {
|
243
|
+
const originalValues = new Map();
|
244
|
+
candidates.forEach((candidate) => {
|
245
|
+
// Store original tabindex
|
246
|
+
originalValues.set(candidate, candidate.getAttribute('tabindex'));
|
247
|
+
// Set to -1 to remove from tab order
|
248
|
+
candidate.setAttribute('tabindex', '-1');
|
249
|
+
});
|
250
|
+
// Return restore function
|
251
|
+
return () => {
|
252
|
+
candidates.forEach((candidate) => {
|
253
|
+
const originalValue = originalValues.get(candidate);
|
254
|
+
if (originalValue == null) {
|
255
|
+
candidate.removeAttribute('tabindex');
|
256
|
+
}
|
257
|
+
else {
|
258
|
+
candidate.setAttribute('tabindex', originalValue);
|
259
|
+
}
|
260
|
+
});
|
261
|
+
};
|
262
|
+
}
|
263
|
+
/**
|
264
|
+
* Wrap array around itself at given start index
|
265
|
+
*/
|
266
|
+
function wrapArray(array, startIndex) {
|
267
|
+
return array.map((_, index) => array[(startIndex + index) % array.length]);
|
268
|
+
}
|
269
|
+
|
270
|
+
class RdxNavigationMenuItemDirective {
|
271
|
+
constructor() {
|
272
|
+
this.elementRef = inject(ElementRef);
|
273
|
+
this.context = injectNavigationMenu();
|
274
|
+
this.value = input('');
|
275
|
+
/**
|
276
|
+
* @ignore
|
277
|
+
*/
|
278
|
+
this.triggerOrLink = contentChild(RdxNavigationMenuFocusableOption);
|
279
|
+
this.triggerRef = signal(null);
|
280
|
+
this.contentRef = signal(null);
|
281
|
+
this.focusProxyRef = signal(null);
|
282
|
+
this.wasEscapeCloseRef = signal(false);
|
283
|
+
this._restoreContentTabOrderRef = signal(null);
|
284
|
+
}
|
285
|
+
get restoreContentTabOrderRef() {
|
286
|
+
return this._restoreContentTabOrderRef;
|
287
|
+
}
|
288
|
+
/**
|
289
|
+
* Handle keyboard entry into content from trigger
|
290
|
+
*/
|
291
|
+
onEntryKeyDown() {
|
292
|
+
// Check if we're using a viewport in a root menu
|
293
|
+
if (isRootNavigationMenu(this.context) && this.context.viewport && this.context.viewport()) {
|
294
|
+
const viewport = this.context.viewport();
|
295
|
+
if (viewport) {
|
296
|
+
// find tabbable elements in the viewport
|
297
|
+
const candidates = getTabbableCandidates(viewport);
|
298
|
+
if (candidates.length) {
|
299
|
+
this.ensureTabOrder();
|
300
|
+
// focus the first element
|
301
|
+
focusFirst(candidates);
|
302
|
+
return;
|
303
|
+
}
|
304
|
+
}
|
305
|
+
}
|
306
|
+
// fallback to content if no viewport or no tabbable elements in viewport
|
307
|
+
if (this.contentRef()) {
|
308
|
+
// restore tab order if needed
|
309
|
+
const restoreFn = this._restoreContentTabOrderRef();
|
310
|
+
if (restoreFn)
|
311
|
+
restoreFn();
|
312
|
+
// find and focus first tabbable element
|
313
|
+
const candidates = getTabbableCandidates(this.contentRef());
|
314
|
+
if (candidates.length) {
|
315
|
+
focusFirst(candidates);
|
316
|
+
}
|
317
|
+
}
|
318
|
+
}
|
319
|
+
focus() {
|
320
|
+
this.triggerOrLink()?.focus();
|
321
|
+
}
|
322
|
+
/**
|
323
|
+
* Ensure elements are in the tab order by restoring any previously removed tabindex values
|
324
|
+
*/
|
325
|
+
ensureTabOrder() {
|
326
|
+
const restoreFn = this._restoreContentTabOrderRef();
|
327
|
+
if (restoreFn) {
|
328
|
+
restoreFn();
|
329
|
+
this._restoreContentTabOrderRef.set(null);
|
330
|
+
}
|
331
|
+
}
|
332
|
+
/**
|
333
|
+
* Handle focus coming from the focus proxy element
|
334
|
+
* @param side Which side the focus is coming from (start = from trigger, end = from after content)
|
335
|
+
*/
|
336
|
+
onFocusProxyEnter(side = 'start') {
|
337
|
+
// check for viewport first
|
338
|
+
if (isRootNavigationMenu(this.context) && this.context.viewport && this.context.viewport()) {
|
339
|
+
const viewport = this.context.viewport();
|
340
|
+
if (viewport) {
|
341
|
+
const candidates = getTabbableCandidates(viewport);
|
342
|
+
if (candidates.length) {
|
343
|
+
this.ensureTabOrder();
|
344
|
+
// focus first or last element depending on direction
|
345
|
+
focusFirst(side === 'start' ? candidates : [...candidates].reverse());
|
346
|
+
return;
|
347
|
+
}
|
348
|
+
}
|
349
|
+
}
|
350
|
+
// fallback to content
|
351
|
+
if (this.contentRef()) {
|
352
|
+
// restore tab order if needed
|
353
|
+
const restoreFn = this._restoreContentTabOrderRef();
|
354
|
+
if (restoreFn)
|
355
|
+
restoreFn();
|
356
|
+
// find and focus appropriate element based on direction
|
357
|
+
const candidates = getTabbableCandidates(this.contentRef());
|
358
|
+
if (candidates.length) {
|
359
|
+
// Focus first or last element depending on which direction we're coming from
|
360
|
+
focusFirst(side === 'start' ? candidates : [...candidates].reverse());
|
361
|
+
}
|
362
|
+
}
|
363
|
+
}
|
364
|
+
/**
|
365
|
+
* Handle focus moving outside of the content
|
366
|
+
* Remove elements from tab order when not focused
|
367
|
+
*/
|
368
|
+
onContentFocusOutside() {
|
369
|
+
// get all tabbable elements from both viewport and content
|
370
|
+
let allCandidates = [];
|
371
|
+
// check viewport first
|
372
|
+
if (isRootNavigationMenu(this.context) && this.context.viewport && this.context.viewport()) {
|
373
|
+
const viewport = this.context.viewport();
|
374
|
+
if (viewport) {
|
375
|
+
allCandidates = getTabbableCandidates(viewport);
|
376
|
+
}
|
377
|
+
}
|
378
|
+
// ... also check direct content
|
379
|
+
if (this.contentRef()) {
|
380
|
+
const contentCandidates = getTabbableCandidates(this.contentRef());
|
381
|
+
allCandidates = [...allCandidates, ...contentCandidates];
|
382
|
+
}
|
383
|
+
// remove from tab order and store restore function
|
384
|
+
if (allCandidates.length) {
|
385
|
+
this._restoreContentTabOrderRef.set(removeFromTabOrder(allCandidates));
|
386
|
+
}
|
387
|
+
}
|
388
|
+
/**
|
389
|
+
* Handle content being closed from root menu
|
390
|
+
*/
|
391
|
+
onRootContentClose() {
|
392
|
+
this.onContentFocusOutside();
|
393
|
+
}
|
394
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: RdxNavigationMenuItemDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
395
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "19.2.4", type: RdxNavigationMenuItemDirective, isStandalone: true, selector: "[rdxNavigationMenuItem]", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.value": "value()" } }, queries: [{ propertyName: "triggerOrLink", first: true, predicate: RdxNavigationMenuFocusableOption, descendants: true, isSignal: true }], exportAs: ["rdxNavigationMenuItem"], ngImport: i0 }); }
|
396
|
+
}
|
397
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: RdxNavigationMenuItemDirective, decorators: [{
|
398
|
+
type: Directive,
|
399
|
+
args: [{
|
400
|
+
selector: '[rdxNavigationMenuItem]',
|
401
|
+
host: {
|
402
|
+
'[attr.value]': 'value()'
|
403
|
+
},
|
404
|
+
exportAs: 'rdxNavigationMenuItem'
|
405
|
+
}]
|
406
|
+
}] });
|
407
|
+
|
408
|
+
class RdxNavigationMenuContentDirective {
|
409
|
+
constructor() {
|
410
|
+
this.elementRef = inject(ElementRef);
|
411
|
+
this.ngZone = inject(NgZone);
|
412
|
+
this.template = inject(TemplateRef);
|
413
|
+
this.document = injectDocument();
|
414
|
+
this.item = inject(RdxNavigationMenuItemDirective);
|
415
|
+
this.context = injectNavigationMenu();
|
416
|
+
/**
|
417
|
+
* Used to keep the content rendered and available in the DOM, even when closed.
|
418
|
+
* Useful for animations or SEO.
|
419
|
+
* @default false
|
420
|
+
*/
|
421
|
+
this.forceMount = input(false, { transform: booleanAttribute });
|
422
|
+
/** @ignore */
|
423
|
+
this.contentId = makeContentId(this.context.baseId, this.item.value());
|
424
|
+
/** @ignore */
|
425
|
+
this.triggerId = makeTriggerId(this.context.baseId, this.item.value());
|
426
|
+
this.escapeHandler = null;
|
427
|
+
}
|
428
|
+
set rdxNavigationMenuContent(value) {
|
429
|
+
// structural directive requires this input even if unused
|
430
|
+
}
|
431
|
+
/** @ignore */
|
432
|
+
ngOnInit() {
|
433
|
+
this.item.contentRef.set(this.elementRef.nativeElement);
|
434
|
+
// register template with viewport in root menu via context
|
435
|
+
if (isRootNavigationMenu(this.context) && this.context.onViewportContentChange) {
|
436
|
+
this.context.onViewportContentChange(this.item.value(), {
|
437
|
+
ref: this.elementRef,
|
438
|
+
templateRef: this.template,
|
439
|
+
forceMount: this.forceMount(),
|
440
|
+
value: this.item.value(),
|
441
|
+
getMotionAttribute: this.getMotionAttribute.bind(this),
|
442
|
+
additionalAttrs: {
|
443
|
+
id: this.contentId,
|
444
|
+
'aria-labelledby': this.triggerId,
|
445
|
+
role: 'menu'
|
446
|
+
}
|
447
|
+
});
|
448
|
+
}
|
449
|
+
// add Escape key handler
|
450
|
+
this.escapeHandler = (event) => {
|
451
|
+
if (event.key === ESCAPE && this.context.value() === this.item.value()) {
|
452
|
+
// mark that this close was triggered by Escape
|
453
|
+
this.item.wasEscapeCloseRef.set(true);
|
454
|
+
// close the content
|
455
|
+
if (this.context.onItemDismiss) {
|
456
|
+
this.context.onItemDismiss();
|
457
|
+
}
|
458
|
+
// refocus the trigger
|
459
|
+
setTimeout(() => {
|
460
|
+
const trigger = this.item.triggerRef();
|
461
|
+
if (trigger)
|
462
|
+
trigger.focus();
|
463
|
+
}, 0);
|
464
|
+
event.preventDefault();
|
465
|
+
event.stopPropagation();
|
466
|
+
}
|
467
|
+
};
|
468
|
+
this.ngZone.runOutsideAngular(() => {
|
469
|
+
if (this.escapeHandler) {
|
470
|
+
this.document.addEventListener('keydown', this.escapeHandler);
|
471
|
+
}
|
472
|
+
});
|
473
|
+
}
|
474
|
+
/** @ignore */
|
475
|
+
ngOnDestroy() {
|
476
|
+
// unregister from viewport
|
477
|
+
if (isRootNavigationMenu(this.context) && this.context.onViewportContentRemove) {
|
478
|
+
this.context.onViewportContentRemove(this.item.value());
|
479
|
+
}
|
480
|
+
// remove escape key handler
|
481
|
+
if (this.escapeHandler) {
|
482
|
+
this.document.removeEventListener('keydown', this.escapeHandler);
|
483
|
+
this.escapeHandler = null;
|
484
|
+
}
|
485
|
+
}
|
486
|
+
/** @ignore - Compute motion attribute for animations */
|
487
|
+
getMotionAttribute() {
|
488
|
+
if (!isRootNavigationMenu(this.context))
|
489
|
+
return null;
|
490
|
+
const itemValues = Array.from(this.context.viewportContent?.() ?? new Map()).map(([value]) => value);
|
491
|
+
return getMotionAttribute(this.context.value(), this.context.previousValue(), this.item.value(), itemValues, this.context.dir);
|
492
|
+
}
|
493
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: RdxNavigationMenuContentDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
494
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.4", 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 }); }
|
495
|
+
}
|
496
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: RdxNavigationMenuContentDirective, decorators: [{
|
497
|
+
type: Directive,
|
498
|
+
args: [{
|
499
|
+
selector: '[rdxNavigationMenuContent]'
|
500
|
+
}]
|
501
|
+
}], propDecorators: { rdxNavigationMenuContent: [{
|
502
|
+
type: Input,
|
503
|
+
args: [{ transform: booleanAttribute }]
|
504
|
+
}] } });
|
505
|
+
|
506
|
+
class RdxNavigationMenuIndicatorDirective {
|
507
|
+
constructor() {
|
508
|
+
this.context = injectNavigationMenu();
|
509
|
+
this.elementRef = inject(ElementRef);
|
510
|
+
this.renderer = inject(Renderer2);
|
511
|
+
/**
|
512
|
+
* Used to keep the indicator rendered and available in the DOM, even when hidden.
|
513
|
+
* Useful for animations.
|
514
|
+
* @default false
|
515
|
+
*/
|
516
|
+
this.forceMount = input(false, { transform: booleanAttribute });
|
517
|
+
/** @ignore */
|
518
|
+
this._position = signal(null);
|
519
|
+
/** @ignore */
|
520
|
+
this._activeTrigger = signal(null);
|
521
|
+
/** @ignore */
|
522
|
+
this._resizeObserver = new ResizeObserver(() => this.updatePosition());
|
523
|
+
this.isVisible = computed(() => Boolean(this.context.value() || this.forceMount()));
|
524
|
+
// set up effect for tracking active trigger and position
|
525
|
+
effect(() => {
|
526
|
+
// this effect runs when the current value changes
|
527
|
+
const value = this.context.value();
|
528
|
+
untracked(() => {
|
529
|
+
if (value && isRootNavigationMenu(this.context)) {
|
530
|
+
this.findAndSetActiveTrigger();
|
531
|
+
}
|
532
|
+
});
|
533
|
+
});
|
534
|
+
// initialize observers for position tracking
|
535
|
+
runInInjectionContext(this.context, () => {
|
536
|
+
if (isRootNavigationMenu(this.context) && this.context.indicatorTrack) {
|
537
|
+
const track = this.context.indicatorTrack();
|
538
|
+
if (track) {
|
539
|
+
// observe size changes on the track
|
540
|
+
this._resizeObserver.observe(track);
|
541
|
+
}
|
542
|
+
// initial position update if menu is open
|
543
|
+
if (this.context.value()) {
|
544
|
+
setTimeout(() => this.findAndSetActiveTrigger(), 0);
|
545
|
+
}
|
546
|
+
}
|
547
|
+
});
|
548
|
+
}
|
549
|
+
/** @ignore */
|
550
|
+
ngOnDestroy() {
|
551
|
+
this._resizeObserver.disconnect();
|
552
|
+
}
|
553
|
+
/** @ignore */
|
554
|
+
findAndSetActiveTrigger() {
|
555
|
+
if (!isRootNavigationMenu(this.context) || !this.context.indicatorTrack)
|
556
|
+
return;
|
557
|
+
const track = this.context.indicatorTrack();
|
558
|
+
if (!track)
|
559
|
+
return;
|
560
|
+
// find all triggers within the track
|
561
|
+
const triggers = Array.from(track.querySelectorAll('[rdxNavigationMenuTrigger]'));
|
562
|
+
// find the active trigger based on the current menu value
|
563
|
+
const activeTrigger = triggers.find((trigger) => {
|
564
|
+
const item = trigger.closest('[rdxNavigationMenuItem]');
|
565
|
+
if (!item)
|
566
|
+
return false;
|
567
|
+
const value = item.getAttribute('value');
|
568
|
+
return value === this.context.value();
|
569
|
+
});
|
570
|
+
if (activeTrigger && activeTrigger !== this._activeTrigger()) {
|
571
|
+
this._activeTrigger.set(activeTrigger);
|
572
|
+
this.updatePosition();
|
573
|
+
}
|
574
|
+
}
|
575
|
+
/** @ignore */
|
576
|
+
updatePosition() {
|
577
|
+
const trigger = this._activeTrigger();
|
578
|
+
if (!trigger)
|
579
|
+
return;
|
580
|
+
const isHorizontal = this.context.orientation === 'horizontal';
|
581
|
+
// calculate new position
|
582
|
+
const newPosition = {
|
583
|
+
size: isHorizontal ? trigger.offsetWidth : trigger.offsetHeight,
|
584
|
+
offset: isHorizontal ? trigger.offsetLeft : trigger.offsetTop
|
585
|
+
};
|
586
|
+
// only update if position has changed
|
587
|
+
if (JSON.stringify(newPosition) !== JSON.stringify(this._position())) {
|
588
|
+
this._position.set(newPosition);
|
589
|
+
// apply position styles
|
590
|
+
const styles = isHorizontal
|
591
|
+
? {
|
592
|
+
position: 'absolute',
|
593
|
+
left: '0',
|
594
|
+
width: `${newPosition.size}px`,
|
595
|
+
transform: `translateX(${newPosition.offset}px)`
|
596
|
+
}
|
597
|
+
: {
|
598
|
+
position: 'absolute',
|
599
|
+
top: '0',
|
600
|
+
height: `${newPosition.size}px`,
|
601
|
+
transform: `translateY(${newPosition.offset}px)`
|
602
|
+
};
|
603
|
+
Object.entries(styles).forEach(([key, value]) => {
|
604
|
+
this.renderer.setStyle(this.elementRef.nativeElement, key, value);
|
605
|
+
});
|
606
|
+
}
|
607
|
+
}
|
608
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: RdxNavigationMenuIndicatorDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
609
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.4", type: RdxNavigationMenuIndicatorDirective, isStandalone: true, selector: "[rdxNavigationMenuIndicator]", inputs: { forceMount: { classPropertyName: "forceMount", publicName: "forceMount", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "aria-hidden": "true" }, properties: { "attr.data-state": "isVisible() ? \"visible\" : \"hidden\"", "attr.data-orientation": "context.orientation", "style.display": "isVisible() ? null : \"none\"" } }, ngImport: i0 }); }
|
610
|
+
}
|
611
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: RdxNavigationMenuIndicatorDirective, decorators: [{
|
612
|
+
type: Directive,
|
613
|
+
args: [{
|
614
|
+
selector: '[rdxNavigationMenuIndicator]',
|
615
|
+
host: {
|
616
|
+
'[attr.data-state]': 'isVisible() ? "visible" : "hidden"',
|
617
|
+
'[attr.data-orientation]': 'context.orientation',
|
618
|
+
'[style.display]': 'isVisible() ? null : "none"',
|
619
|
+
'aria-hidden': 'true'
|
620
|
+
}
|
621
|
+
}]
|
622
|
+
}], ctorParameters: () => [] });
|
623
|
+
|
624
|
+
const LINK_SELECT = 'navigationMenu.linkSelect';
|
625
|
+
const ROOT_CONTENT_DISMISS = 'navigationMenu.rootContentDismiss';
|
626
|
+
class RdxNavigationMenuLinkDirective extends RdxNavigationMenuFocusableOption {
|
627
|
+
constructor() {
|
628
|
+
super(...arguments);
|
629
|
+
this.rovingFocusItem = inject(RdxRovingFocusItemDirective, { self: true });
|
630
|
+
this.uniqueId = generateId();
|
631
|
+
this.active = input(false, { transform: booleanAttribute });
|
632
|
+
this.onSelect = input();
|
633
|
+
this.elementRef = inject(ElementRef);
|
634
|
+
}
|
635
|
+
ngOnInit() {
|
636
|
+
this.rovingFocusItem.tabStopId = this.elementRef.nativeElement.id || `link-${this.uniqueId}`;
|
637
|
+
}
|
638
|
+
focus() {
|
639
|
+
this.elementRef.nativeElement.focus();
|
640
|
+
}
|
641
|
+
onClick(event) {
|
642
|
+
const target = event.target;
|
643
|
+
// dispatch link select event
|
644
|
+
const linkSelectEvent = new CustomEvent(LINK_SELECT, {
|
645
|
+
bubbles: true,
|
646
|
+
cancelable: true
|
647
|
+
});
|
648
|
+
// add one-time listener for onSelect handler
|
649
|
+
const onSelect = this.onSelect();
|
650
|
+
if (onSelect) {
|
651
|
+
target.addEventListener(LINK_SELECT, onSelect, { once: true });
|
652
|
+
}
|
653
|
+
// dispatch event
|
654
|
+
target.dispatchEvent(linkSelectEvent);
|
655
|
+
// if not prevented and not meta key, dismiss content
|
656
|
+
if (!linkSelectEvent.defaultPrevented && !event.metaKey) {
|
657
|
+
const dismissEvent = new CustomEvent(ROOT_CONTENT_DISMISS, {
|
658
|
+
bubbles: true,
|
659
|
+
cancelable: true
|
660
|
+
});
|
661
|
+
target.dispatchEvent(dismissEvent);
|
662
|
+
}
|
663
|
+
}
|
664
|
+
onKeydown(event) {
|
665
|
+
// activate link on Enter or Space
|
666
|
+
if (event.key === ENTER || event.key === SPACE) {
|
667
|
+
// prevent default behavior like scrolling (Space) or form submission (Enter) BEFORE simulating the click.
|
668
|
+
event.preventDefault();
|
669
|
+
// simulate a click event on the link element itself
|
670
|
+
const clickEvent = new MouseEvent('click', { bubbles: true, cancelable: true });
|
671
|
+
this.elementRef.nativeElement.dispatchEvent(clickEvent);
|
672
|
+
return;
|
673
|
+
}
|
674
|
+
}
|
675
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: RdxNavigationMenuLinkDirective, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
|
676
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.4", type: RdxNavigationMenuLinkDirective, isStandalone: true, selector: "[rdxNavigationMenuLink]", inputs: { active: { classPropertyName: "active", publicName: "active", isSignal: true, isRequired: false, transformFunction: null }, onSelect: { classPropertyName: "onSelect", publicName: "onSelect", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "click": "onClick($event)", "keydown": "onKeydown($event)" }, properties: { "attr.data-active": "active() ? \"\" : undefined", "attr.aria-current": "active() ? \"page\" : undefined" } }, providers: [{ provide: RdxNavigationMenuFocusableOption, useExisting: RdxNavigationMenuLinkDirective }], usesInheritance: true, hostDirectives: [{ directive: i1.RdxRovingFocusItemDirective, inputs: ["focusable", "focusable"] }], ngImport: i0 }); }
|
677
|
+
}
|
678
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: RdxNavigationMenuLinkDirective, decorators: [{
|
679
|
+
type: Directive,
|
680
|
+
args: [{
|
681
|
+
selector: '[rdxNavigationMenuLink]',
|
682
|
+
hostDirectives: [{ directive: RdxRovingFocusItemDirective, inputs: ['focusable'] }],
|
683
|
+
host: {
|
684
|
+
'[attr.data-active]': 'active() ? "" : undefined',
|
685
|
+
'[attr.aria-current]': 'active() ? "page" : undefined',
|
686
|
+
'(click)': 'onClick($event)',
|
687
|
+
'(keydown)': 'onKeydown($event)'
|
688
|
+
},
|
689
|
+
providers: [{ provide: RdxNavigationMenuFocusableOption, useExisting: RdxNavigationMenuLinkDirective }]
|
690
|
+
}]
|
691
|
+
}] });
|
692
|
+
|
693
|
+
class RdxNavigationMenuListDirective {
|
694
|
+
constructor() {
|
695
|
+
this.context = injectNavigationMenu();
|
696
|
+
this.elementRef = inject((ElementRef));
|
697
|
+
this.renderer = inject(Renderer2);
|
698
|
+
this.rovingFocusGroup = inject(RdxRovingFocusGroupDirective, { self: true });
|
699
|
+
/**
|
700
|
+
* @private
|
701
|
+
* @ignore
|
702
|
+
*/
|
703
|
+
this.items = contentChildren(forwardRef(() => RdxNavigationMenuItemDirective), { descendants: true });
|
704
|
+
}
|
705
|
+
/**
|
706
|
+
* @ignore
|
707
|
+
*/
|
708
|
+
ngAfterContentInit() {
|
709
|
+
const items = this.items();
|
710
|
+
this.keyManager = new FocusKeyManager(items);
|
711
|
+
if (this.context.orientation === 'horizontal') {
|
712
|
+
this.keyManager.withHorizontalOrientation(this.context.dir || 'ltr');
|
713
|
+
}
|
714
|
+
else {
|
715
|
+
this.keyManager.withVerticalOrientation();
|
716
|
+
}
|
717
|
+
}
|
718
|
+
/**
|
719
|
+
* @ignore
|
720
|
+
*/
|
721
|
+
ngAfterViewInit() {
|
722
|
+
this.rovingFocusGroup.orientation = this.context.orientation;
|
723
|
+
this.rovingFocusGroup.dir = this.context.dir;
|
724
|
+
// looping typically only applies to the root menu bar
|
725
|
+
if (isRootNavigationMenu(this.context)) {
|
726
|
+
this.rovingFocusGroup.loop = this.context.loop ?? false;
|
727
|
+
}
|
728
|
+
else {
|
729
|
+
this.rovingFocusGroup.loop = false;
|
730
|
+
}
|
731
|
+
if (isRootNavigationMenu(this.context) && this.context.onIndicatorTrackChange) {
|
732
|
+
const listElement = this.elementRef.nativeElement;
|
733
|
+
const parent = listElement.parentNode;
|
734
|
+
// ensure parent exists and list hasn't already been wrapped
|
735
|
+
if (parent && !listElement.parentElement?.hasAttribute('data-radix-navigation-menu-list-wrapper')) {
|
736
|
+
// create a wrapper div with relative positioning
|
737
|
+
const wrapper = this.renderer.createElement('div');
|
738
|
+
this.renderer.setAttribute(wrapper, 'data-radix-navigation-menu-list-wrapper', ''); // Add marker
|
739
|
+
this.renderer.setStyle(wrapper, 'position', 'relative');
|
740
|
+
// insert the wrapper before the list element in the parent
|
741
|
+
this.renderer.insertBefore(parent, wrapper, listElement);
|
742
|
+
// move the list element inside the new wrapper
|
743
|
+
this.renderer.appendChild(wrapper, listElement);
|
744
|
+
// register the wrapper element as the track for the indicator positioning
|
745
|
+
this.context.onIndicatorTrackChange(wrapper);
|
746
|
+
}
|
747
|
+
else if (listElement.parentElement?.hasAttribute('data-radix-navigation-menu-list-wrapper')) {
|
748
|
+
// if wrapper somehow already exists, ensure context has the correct reference
|
749
|
+
this.context.onIndicatorTrackChange(listElement.parentElement);
|
750
|
+
}
|
751
|
+
}
|
752
|
+
}
|
753
|
+
/**
|
754
|
+
* @ignore
|
755
|
+
*/
|
756
|
+
onKeydown(event) {
|
757
|
+
if (!this.keyManager.activeItem) {
|
758
|
+
this.keyManager.setFirstItemActive();
|
759
|
+
}
|
760
|
+
if (event.key === TAB && event.shiftKey) {
|
761
|
+
if (this.keyManager.activeItemIndex === 0)
|
762
|
+
return;
|
763
|
+
this.keyManager.setPreviousItemActive();
|
764
|
+
event.preventDefault();
|
765
|
+
}
|
766
|
+
else if (event.key === TAB) {
|
767
|
+
const items = this.items();
|
768
|
+
if (this.keyManager.activeItemIndex === items.length - 1) {
|
769
|
+
return;
|
770
|
+
}
|
771
|
+
this.keyManager.setNextItemActive();
|
772
|
+
event.preventDefault();
|
773
|
+
}
|
774
|
+
else {
|
775
|
+
this.keyManager.onKeydown(event);
|
776
|
+
}
|
777
|
+
}
|
778
|
+
/**
|
779
|
+
* @ignore
|
780
|
+
*/
|
781
|
+
setActiveItem(item) {
|
782
|
+
this.keyManager.setActiveItem(item);
|
783
|
+
}
|
784
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: RdxNavigationMenuListDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
785
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "19.2.4", 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 }); }
|
786
|
+
}
|
787
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: RdxNavigationMenuListDirective, decorators: [{
|
788
|
+
type: Directive,
|
789
|
+
args: [{
|
790
|
+
selector: '[rdxNavigationMenuList]',
|
791
|
+
hostDirectives: [RdxRovingFocusGroupDirective],
|
792
|
+
host: {
|
793
|
+
role: 'menubar',
|
794
|
+
'(keydown)': 'onKeydown($event)'
|
795
|
+
}
|
796
|
+
}]
|
797
|
+
}] });
|
798
|
+
|
799
|
+
// define action types for clearer intent
|
800
|
+
var RdxNavigationMenuAction;
|
801
|
+
(function (RdxNavigationMenuAction) {
|
802
|
+
RdxNavigationMenuAction["OPEN"] = "open";
|
803
|
+
RdxNavigationMenuAction["CLOSE"] = "close";
|
804
|
+
})(RdxNavigationMenuAction || (RdxNavigationMenuAction = {}));
|
805
|
+
class RdxNavigationMenuDirective {
|
806
|
+
// State
|
807
|
+
#value;
|
808
|
+
#previousValue;
|
809
|
+
#indicatorTrack;
|
810
|
+
#viewport;
|
811
|
+
#viewportContent;
|
812
|
+
#rootNavigationMenu;
|
813
|
+
#userDismissedByClick;
|
814
|
+
#isOpenDelayed;
|
815
|
+
// pointer tracking
|
816
|
+
#isPointerOverContent;
|
817
|
+
#isPointerOverTrigger;
|
818
|
+
constructor() {
|
819
|
+
this.elementRef = inject(ElementRef);
|
820
|
+
this.document = injectDocument();
|
821
|
+
this.window = injectWindow();
|
822
|
+
// State
|
823
|
+
this.#value = signal('');
|
824
|
+
this.#previousValue = signal('');
|
825
|
+
this.baseId = `rdx-nav-menu-${generateId()}`;
|
826
|
+
this.#indicatorTrack = signal(null);
|
827
|
+
this.#viewport = signal(null);
|
828
|
+
this.#viewportContent = signal(new Map());
|
829
|
+
this.#rootNavigationMenu = signal(this.elementRef.nativeElement);
|
830
|
+
this.#userDismissedByClick = signal(false);
|
831
|
+
this.userDismissedByClick = () => this.#userDismissedByClick();
|
832
|
+
this.resetUserDismissed = () => this.#userDismissedByClick.set(false);
|
833
|
+
// delay timers
|
834
|
+
this.openTimerRef = 0;
|
835
|
+
this.closeTimerRef = 0;
|
836
|
+
this.skipDelayTimerRef = 0;
|
837
|
+
this.#isOpenDelayed = signal(true);
|
838
|
+
// pointer tracking
|
839
|
+
this.#isPointerOverContent = signal(false);
|
840
|
+
this.#isPointerOverTrigger = signal(false);
|
841
|
+
this.documentMouseLeaveHandler = null;
|
842
|
+
this.actionSubject$ = new Subject();
|
843
|
+
this.orientation = 'horizontal';
|
844
|
+
this.dir = 'ltr';
|
845
|
+
this.delayDuration = 200;
|
846
|
+
this.skipDelayDuration = 300;
|
847
|
+
this.loop = false;
|
848
|
+
this.cssAnimation = false;
|
849
|
+
this.cssOpeningAnimation = false;
|
850
|
+
this.cssClosingAnimation = false;
|
851
|
+
this.isRootMenu = true;
|
852
|
+
this.cssAnimationStatus = signal(null);
|
853
|
+
// exposed state as functions for the token
|
854
|
+
this.value = () => this.#value();
|
855
|
+
this.previousValue = () => this.#previousValue();
|
856
|
+
this.rootNavigationMenu = () => this.#rootNavigationMenu();
|
857
|
+
this.indicatorTrack = () => this.#indicatorTrack();
|
858
|
+
this.viewport = () => this.#viewport();
|
859
|
+
this.viewportContent = () => this.#viewportContent();
|
860
|
+
// exposed pointer state
|
861
|
+
this.setTriggerPointerState = (isOver) => this.#isPointerOverTrigger.set(isOver);
|
862
|
+
this.setContentPointerState = (isOver) => this.#isPointerOverContent.set(isOver);
|
863
|
+
this.isPointerInSystem = () => this.#isPointerOverContent() || this.#isPointerOverTrigger();
|
864
|
+
// exposed animation state
|
865
|
+
this.getCssAnimation = () => this.cssAnimation;
|
866
|
+
this.getCssOpeningAnimation = () => this.cssOpeningAnimation;
|
867
|
+
this.getCssClosingAnimation = () => this.cssClosingAnimation;
|
868
|
+
effect(() => {
|
869
|
+
const value = this.#value();
|
870
|
+
if (value) {
|
871
|
+
this.window.clearTimeout(this.skipDelayTimerRef);
|
872
|
+
if (this.skipDelayDuration > 0) {
|
873
|
+
this.#isOpenDelayed.set(false);
|
874
|
+
}
|
875
|
+
}
|
876
|
+
else {
|
877
|
+
// menu is closed, start skip delay timer
|
878
|
+
this.window.clearTimeout(this.skipDelayTimerRef);
|
879
|
+
this.skipDelayTimerRef = this.window.setTimeout(() => {
|
880
|
+
this.#isOpenDelayed.set(true);
|
881
|
+
}, this.skipDelayDuration);
|
882
|
+
}
|
883
|
+
});
|
884
|
+
this.actionSubject$
|
885
|
+
.pipe(map((config) => {
|
886
|
+
// different delays for open vs close (better ux)
|
887
|
+
const duration = config.action === RdxNavigationMenuAction.OPEN ? this.delayDuration : 150;
|
888
|
+
return { ...config, duration };
|
889
|
+
}), debounce((config) => timer(config.duration)), tap((config) => {
|
890
|
+
switch (config.action) {
|
891
|
+
case RdxNavigationMenuAction.OPEN:
|
892
|
+
if (config.itemValue) {
|
893
|
+
this.setValue(config.itemValue);
|
894
|
+
}
|
895
|
+
break;
|
896
|
+
case RdxNavigationMenuAction.CLOSE:
|
897
|
+
// only close if not hovering over any part of the system
|
898
|
+
if (!this.isPointerInSystem()) {
|
899
|
+
this.setValue('');
|
900
|
+
}
|
901
|
+
break;
|
902
|
+
}
|
903
|
+
}), takeUntilDestroyed())
|
904
|
+
.subscribe();
|
905
|
+
// set up document mouseleave handler to close menu when mouse leaves window
|
906
|
+
this.documentMouseLeaveHandler = () => this.handleClose();
|
907
|
+
this.document.addEventListener('mouseleave', this.documentMouseLeaveHandler);
|
908
|
+
}
|
909
|
+
ngOnDestroy() {
|
910
|
+
this.window.clearTimeout(this.openTimerRef);
|
911
|
+
this.window.clearTimeout(this.closeTimerRef);
|
912
|
+
this.window.clearTimeout(this.skipDelayTimerRef);
|
913
|
+
// clean up document event listener
|
914
|
+
if (this.documentMouseLeaveHandler) {
|
915
|
+
document.removeEventListener('mouseleave', this.documentMouseLeaveHandler);
|
916
|
+
}
|
917
|
+
}
|
918
|
+
onIndicatorTrackChange(track) {
|
919
|
+
this.#indicatorTrack.set(track);
|
920
|
+
}
|
921
|
+
onViewportChange(viewport) {
|
922
|
+
this.#viewport.set(viewport);
|
923
|
+
}
|
924
|
+
onTriggerEnter(itemValue) {
|
925
|
+
// skip opening if user explicitly dismissed this menu
|
926
|
+
if (this.#userDismissedByClick() && itemValue === this.#previousValue()) {
|
927
|
+
return;
|
928
|
+
}
|
929
|
+
this.window.clearTimeout(this.openTimerRef);
|
930
|
+
this.window.clearTimeout(this.closeTimerRef);
|
931
|
+
if (this.#isOpenDelayed()) {
|
932
|
+
this.handleDelayedOpen(itemValue);
|
933
|
+
}
|
934
|
+
else {
|
935
|
+
this.handleOpen(itemValue);
|
936
|
+
}
|
937
|
+
}
|
938
|
+
onTriggerLeave() {
|
939
|
+
this.window.clearTimeout(this.openTimerRef);
|
940
|
+
this.startCloseTimer();
|
941
|
+
}
|
942
|
+
onContentEnter() {
|
943
|
+
this.window.clearTimeout(this.closeTimerRef);
|
944
|
+
}
|
945
|
+
onContentLeave() {
|
946
|
+
this.startCloseTimer();
|
947
|
+
}
|
948
|
+
handleClose() {
|
949
|
+
this.actionSubject$.next({ action: RdxNavigationMenuAction.CLOSE });
|
950
|
+
}
|
951
|
+
onItemSelect(itemValue) {
|
952
|
+
const wasOpen = this.#value() === itemValue;
|
953
|
+
const newValue = wasOpen ? '' : itemValue;
|
954
|
+
// if user is closing an open menu, mark as user-dismissed
|
955
|
+
if (wasOpen) {
|
956
|
+
this.#userDismissedByClick.set(true);
|
957
|
+
}
|
958
|
+
else {
|
959
|
+
this.#userDismissedByClick.set(false);
|
960
|
+
}
|
961
|
+
this.setValue(newValue);
|
962
|
+
}
|
963
|
+
onItemDismiss() {
|
964
|
+
this.setValue('');
|
965
|
+
}
|
966
|
+
onViewportContentChange(contentValue, contentData) {
|
967
|
+
const newMap = new Map(this.#viewportContent());
|
968
|
+
newMap.set(contentValue, contentData);
|
969
|
+
this.#viewportContent.set(newMap);
|
970
|
+
}
|
971
|
+
onViewportContentRemove(contentValue) {
|
972
|
+
const newMap = new Map(this.#viewportContent());
|
973
|
+
if (newMap.has(contentValue)) {
|
974
|
+
newMap.delete(contentValue);
|
975
|
+
this.#viewportContent.set(newMap);
|
976
|
+
}
|
977
|
+
}
|
978
|
+
setValue(value) {
|
979
|
+
// Store previous value before changing
|
980
|
+
this.#previousValue.set(this.#value());
|
981
|
+
this.#value.set(value);
|
982
|
+
}
|
983
|
+
startCloseTimer() {
|
984
|
+
this.window.clearTimeout(this.closeTimerRef);
|
985
|
+
this.closeTimerRef = this.window.setTimeout(() => {
|
986
|
+
// only close if not hovering over any part of the system
|
987
|
+
if (!this.isPointerInSystem()) {
|
988
|
+
this.setValue('');
|
989
|
+
}
|
990
|
+
}, 150);
|
991
|
+
}
|
992
|
+
handleOpen(itemValue) {
|
993
|
+
this.window.clearTimeout(this.closeTimerRef);
|
994
|
+
this.setValue(itemValue);
|
995
|
+
}
|
996
|
+
handleDelayedOpen(itemValue) {
|
997
|
+
const isOpenItem = this.#value() === itemValue;
|
998
|
+
if (isOpenItem) {
|
999
|
+
// if the item is already open, clear close timer
|
1000
|
+
this.window.clearTimeout(this.closeTimerRef);
|
1001
|
+
}
|
1002
|
+
else {
|
1003
|
+
// otherwise, start the open timer
|
1004
|
+
this.openTimerRef = this.window.setTimeout(() => {
|
1005
|
+
this.window.clearTimeout(this.closeTimerRef);
|
1006
|
+
this.setValue(itemValue);
|
1007
|
+
}, this.delayDuration);
|
1008
|
+
}
|
1009
|
+
}
|
1010
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: RdxNavigationMenuDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
1011
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "16.1.0", version: "19.2.4", type: RdxNavigationMenuDirective, isStandalone: true, selector: "[rdxNavigationMenu]", inputs: { orientation: "orientation", dir: "dir", delayDuration: ["delayDuration", "delayDuration", numberAttribute], skipDelayDuration: ["skipDelayDuration", "skipDelayDuration", numberAttribute], loop: ["loop", "loop", booleanAttribute], cssAnimation: ["cssAnimation", "cssAnimation", booleanAttribute], cssOpeningAnimation: ["cssOpeningAnimation", "cssOpeningAnimation", booleanAttribute], cssClosingAnimation: ["cssClosingAnimation", "cssClosingAnimation", booleanAttribute] }, host: { attributes: { "aria-label": "Main", "role": "navigation" }, properties: { "attr.data-orientation": "orientation", "attr.dir": "dir" } }, providers: [provideNavigationMenuContext(RdxNavigationMenuDirective)], exportAs: ["rdxNavigationMenu"], ngImport: i0 }); }
|
1012
|
+
}
|
1013
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: RdxNavigationMenuDirective, decorators: [{
|
1014
|
+
type: Directive,
|
1015
|
+
args: [{
|
1016
|
+
selector: '[rdxNavigationMenu]',
|
1017
|
+
providers: [provideNavigationMenuContext(RdxNavigationMenuDirective)],
|
1018
|
+
host: {
|
1019
|
+
'[attr.data-orientation]': 'orientation',
|
1020
|
+
'[attr.dir]': 'dir',
|
1021
|
+
'aria-label': 'Main',
|
1022
|
+
role: 'navigation'
|
1023
|
+
},
|
1024
|
+
exportAs: 'rdxNavigationMenu'
|
1025
|
+
}]
|
1026
|
+
}], ctorParameters: () => [], propDecorators: { orientation: [{
|
1027
|
+
type: Input
|
1028
|
+
}], dir: [{
|
1029
|
+
type: Input
|
1030
|
+
}], delayDuration: [{
|
1031
|
+
type: Input,
|
1032
|
+
args: [{ transform: numberAttribute }]
|
1033
|
+
}], skipDelayDuration: [{
|
1034
|
+
type: Input,
|
1035
|
+
args: [{ transform: numberAttribute }]
|
1036
|
+
}], loop: [{
|
1037
|
+
type: Input,
|
1038
|
+
args: [{ transform: booleanAttribute }]
|
1039
|
+
}], cssAnimation: [{
|
1040
|
+
type: Input,
|
1041
|
+
args: [{ transform: booleanAttribute }]
|
1042
|
+
}], cssOpeningAnimation: [{
|
1043
|
+
type: Input,
|
1044
|
+
args: [{ transform: booleanAttribute }]
|
1045
|
+
}], cssClosingAnimation: [{
|
1046
|
+
type: Input,
|
1047
|
+
args: [{ transform: booleanAttribute }]
|
1048
|
+
}] } });
|
1049
|
+
|
1050
|
+
class RdxNavigationMenuSubDirective {
|
1051
|
+
constructor() {
|
1052
|
+
this.orientation = input('horizontal');
|
1053
|
+
this.valueChange = output();
|
1054
|
+
this.value = signal('');
|
1055
|
+
this.previousValue = signal('');
|
1056
|
+
this.baseId = `rdx-nav-menu-sub-${generateId()}`;
|
1057
|
+
this.isRootMenu = false;
|
1058
|
+
this.parent = inject(RdxNavigationMenuDirective, { optional: true });
|
1059
|
+
}
|
1060
|
+
set defaultValue(val) {
|
1061
|
+
if (val)
|
1062
|
+
this.value.set(val);
|
1063
|
+
}
|
1064
|
+
get dir() {
|
1065
|
+
if (!this.parent) {
|
1066
|
+
return 'ltr';
|
1067
|
+
}
|
1068
|
+
return this.parent.dir || 'ltr';
|
1069
|
+
}
|
1070
|
+
get rootNavigationMenu() {
|
1071
|
+
return this.parent?.rootNavigationMenu() || null;
|
1072
|
+
}
|
1073
|
+
onTriggerEnter(itemValue) {
|
1074
|
+
this.setValue(itemValue);
|
1075
|
+
}
|
1076
|
+
onItemSelect(itemValue) {
|
1077
|
+
this.setValue(itemValue);
|
1078
|
+
}
|
1079
|
+
onItemDismiss() {
|
1080
|
+
this.setValue('');
|
1081
|
+
}
|
1082
|
+
setValue(value) {
|
1083
|
+
this.previousValue.set(this.value());
|
1084
|
+
this.value.set(value);
|
1085
|
+
this.valueChange.emit(value);
|
1086
|
+
}
|
1087
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: RdxNavigationMenuSubDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
1088
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.4", type: RdxNavigationMenuSubDirective, isStandalone: true, selector: "[rdxNavigationMenuSub]", inputs: { orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, defaultValue: { classPropertyName: "defaultValue", publicName: "defaultValue", isSignal: false, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange" }, host: { properties: { "attr.data-orientation": "orientation()" } }, providers: [provideNavigationMenuContext(RdxNavigationMenuSubDirective)], ngImport: i0 }); }
|
1089
|
+
}
|
1090
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: RdxNavigationMenuSubDirective, decorators: [{
|
1091
|
+
type: Directive,
|
1092
|
+
args: [{
|
1093
|
+
selector: '[rdxNavigationMenuSub]',
|
1094
|
+
providers: [provideNavigationMenuContext(RdxNavigationMenuSubDirective)],
|
1095
|
+
host: {
|
1096
|
+
'[attr.data-orientation]': 'orientation()'
|
1097
|
+
}
|
1098
|
+
}]
|
1099
|
+
}], propDecorators: { defaultValue: [{
|
1100
|
+
type: Input
|
1101
|
+
}] } });
|
1102
|
+
|
1103
|
+
class RdxNavigationMenuTriggerDirective extends RdxNavigationMenuFocusableOption {
|
1104
|
+
constructor() {
|
1105
|
+
super();
|
1106
|
+
this.context = injectNavigationMenu();
|
1107
|
+
this.item = inject(RdxNavigationMenuItemDirective);
|
1108
|
+
this.list = inject(RdxNavigationMenuListDirective);
|
1109
|
+
this.rovingFocusItem = inject(RdxRovingFocusItemDirective, { self: true });
|
1110
|
+
this.elementRef = inject(ElementRef);
|
1111
|
+
this.viewContainerRef = inject(ViewContainerRef);
|
1112
|
+
this.disabled = input(false, { transform: booleanAttribute });
|
1113
|
+
this.triggerId = makeTriggerId(this.context.baseId, this.item.value());
|
1114
|
+
this.contentId = makeContentId(this.context.baseId, this.item.value());
|
1115
|
+
this.open = computed(() => {
|
1116
|
+
return this.item.value() === this.context.value();
|
1117
|
+
});
|
1118
|
+
this.focusProxyRef = null;
|
1119
|
+
this.ariaOwnsRef = null;
|
1120
|
+
this.hasPointerMoveOpened = false;
|
1121
|
+
this.wasClickClose = false;
|
1122
|
+
effect(() => {
|
1123
|
+
this.rovingFocusItem.focusable = !this.disabled();
|
1124
|
+
});
|
1125
|
+
effect(() => {
|
1126
|
+
const isOpen = this.open();
|
1127
|
+
untracked(() => {
|
1128
|
+
// handle focus proxy and aria-owns when open state changes
|
1129
|
+
if (isOpen) {
|
1130
|
+
this.createAccessibilityComponents();
|
1131
|
+
}
|
1132
|
+
else {
|
1133
|
+
this.removeAccessibilityComponents();
|
1134
|
+
if (!this.item.wasEscapeCloseRef()) {
|
1135
|
+
this.item.onRootContentClose();
|
1136
|
+
}
|
1137
|
+
this.hasPointerMoveOpened = false;
|
1138
|
+
}
|
1139
|
+
});
|
1140
|
+
});
|
1141
|
+
}
|
1142
|
+
ngOnInit() {
|
1143
|
+
this.item.triggerRef.set(this.elementRef.nativeElement);
|
1144
|
+
// configure the static part of the roving focus item directive instance
|
1145
|
+
this.rovingFocusItem.tabStopId = this.item.value();
|
1146
|
+
}
|
1147
|
+
ngOnDestroy() {
|
1148
|
+
this.removeAccessibilityComponents();
|
1149
|
+
}
|
1150
|
+
focus() {
|
1151
|
+
this.elementRef.nativeElement.focus();
|
1152
|
+
}
|
1153
|
+
createAccessibilityComponents() {
|
1154
|
+
if (this.focusProxyRef || this.ariaOwnsRef) {
|
1155
|
+
return;
|
1156
|
+
}
|
1157
|
+
// create focus proxy component
|
1158
|
+
this.focusProxyRef = this.viewContainerRef.createComponent(RdxNavigationMenuFocusProxyComponent);
|
1159
|
+
this.focusProxyRef.instance.triggerElement = this.elementRef.nativeElement;
|
1160
|
+
this.focusProxyRef.instance.contentElement = this.item.contentRef();
|
1161
|
+
this.focusProxyRef.instance.proxyFocus.subscribe((direction) => {
|
1162
|
+
this.item.onFocusProxyEnter(direction);
|
1163
|
+
});
|
1164
|
+
// store reference in item directive
|
1165
|
+
this.item.focusProxyRef.set(this.focusProxyRef.location.nativeElement);
|
1166
|
+
// only add aria-owns component if using viewport
|
1167
|
+
if (isRootNavigationMenu(this.context) && this.context.viewport && this.context.viewport()) {
|
1168
|
+
this.ariaOwnsRef = this.viewContainerRef.createComponent(RdxNavigationMenuAriaOwnsComponent);
|
1169
|
+
this.ariaOwnsRef.instance.contentId = this.contentId;
|
1170
|
+
}
|
1171
|
+
}
|
1172
|
+
removeAccessibilityComponents() {
|
1173
|
+
if (this.focusProxyRef) {
|
1174
|
+
this.focusProxyRef.destroy();
|
1175
|
+
this.focusProxyRef = null;
|
1176
|
+
this.item.focusProxyRef.set(null);
|
1177
|
+
}
|
1178
|
+
if (this.ariaOwnsRef) {
|
1179
|
+
this.ariaOwnsRef.destroy();
|
1180
|
+
this.ariaOwnsRef = null;
|
1181
|
+
}
|
1182
|
+
}
|
1183
|
+
onPointerEnter() {
|
1184
|
+
// ignore if disabled or not the root menu (hover logic primarily for root)
|
1185
|
+
if (this.disabled() || !isRootNavigationMenu(this.context))
|
1186
|
+
return;
|
1187
|
+
this.wasClickClose = false; // Reset click close flag on enter
|
1188
|
+
this.item.wasEscapeCloseRef.set(false); // Reset escape flag
|
1189
|
+
this.context.setTriggerPointerState?.(true); // Update context state
|
1190
|
+
// if the menu isn't already open for this item, trigger the enter logic (handles delays)
|
1191
|
+
if (!this.open()) {
|
1192
|
+
this.context.onTriggerEnter?.(this.item.value());
|
1193
|
+
}
|
1194
|
+
}
|
1195
|
+
onPointerMove(event) {
|
1196
|
+
// ignore if not a mouse event, disabled, closed by click/escape, or already opened by this move
|
1197
|
+
if (event.pointerType !== 'mouse' ||
|
1198
|
+
this.disabled() ||
|
1199
|
+
this.wasClickClose ||
|
1200
|
+
this.item.wasEscapeCloseRef() ||
|
1201
|
+
this.hasPointerMoveOpened ||
|
1202
|
+
!isRootNavigationMenu(this.context)) {
|
1203
|
+
return;
|
1204
|
+
}
|
1205
|
+
// trigger enter logic (handles delays) and mark that this move initiated an open attempt
|
1206
|
+
this.context.onTriggerEnter?.(this.item.value());
|
1207
|
+
this.hasPointerMoveOpened = true;
|
1208
|
+
}
|
1209
|
+
onPointerLeave(event) {
|
1210
|
+
// ignore if not a mouse event or disabled
|
1211
|
+
if (event.pointerType !== 'mouse' || this.disabled() || !isRootNavigationMenu(this.context)) {
|
1212
|
+
return;
|
1213
|
+
}
|
1214
|
+
this.context.setTriggerPointerState?.(false); // Update context state
|
1215
|
+
this.context.onTriggerLeave?.(); // Trigger leave logic (handles delays)
|
1216
|
+
this.hasPointerMoveOpened = false; // Reset flag
|
1217
|
+
// reset user dismissal flag if pointer leaves the whole system (trigger + content)
|
1218
|
+
if (this.context.resetUserDismissed) {
|
1219
|
+
// relay slightly to allow pointer movement to content area without resetting dismissal state
|
1220
|
+
setTimeout(() => {
|
1221
|
+
if (!this.context.isPointerInSystem?.()) {
|
1222
|
+
this.context.resetUserDismissed?.();
|
1223
|
+
}
|
1224
|
+
}, 50); // small delay for tolerance
|
1225
|
+
}
|
1226
|
+
}
|
1227
|
+
onClick() {
|
1228
|
+
if (this.disabled())
|
1229
|
+
return;
|
1230
|
+
// manually set the `KeyManager` active item to this trigger
|
1231
|
+
this.list.setActiveItem(this.item);
|
1232
|
+
if (this.context.onItemSelect) {
|
1233
|
+
this.context.onItemSelect(this.item.value());
|
1234
|
+
// track if this click action resulted in closing the menu
|
1235
|
+
this.wasClickClose = !this.open();
|
1236
|
+
// reset escape flag if menu was opened by click
|
1237
|
+
if (this.open()) {
|
1238
|
+
this.item.wasEscapeCloseRef.set(false);
|
1239
|
+
}
|
1240
|
+
}
|
1241
|
+
}
|
1242
|
+
onKeydown(event) {
|
1243
|
+
if (this.disabled())
|
1244
|
+
return;
|
1245
|
+
if (event.key === ENTER || event.key === SPACE) {
|
1246
|
+
event.preventDefault(); // prevent default button behavior
|
1247
|
+
this.onClick();
|
1248
|
+
// if menu was opened by this keypress, move focus into the content
|
1249
|
+
if (this.open()) {
|
1250
|
+
// defer focus slightly to ensure content is ready
|
1251
|
+
setTimeout(() => this.item.onEntryKeyDown(), 0);
|
1252
|
+
}
|
1253
|
+
return;
|
1254
|
+
}
|
1255
|
+
const isHorizontal = this.context.orientation === 'horizontal';
|
1256
|
+
const isRTL = this.context.dir === 'rtl';
|
1257
|
+
// handle `ArrowDown` specifically for viewport navigation
|
1258
|
+
if (event.key === ARROW_DOWN || event.key === TAB) {
|
1259
|
+
if (event.key === ARROW_DOWN) {
|
1260
|
+
event.preventDefault();
|
1261
|
+
}
|
1262
|
+
// if the menu is open, focus into the content
|
1263
|
+
if (this.open()) {
|
1264
|
+
if (event.key === TAB) {
|
1265
|
+
// needed to ensure that the `keyManager` on the list directive does not activate
|
1266
|
+
// any focus updates, shifting focus to the subsequent focusable list item
|
1267
|
+
event.stopImmediatePropagation();
|
1268
|
+
}
|
1269
|
+
// direct focus handling for viewport case
|
1270
|
+
if (isRootNavigationMenu(this.context) && this.context.viewport && this.context.viewport()) {
|
1271
|
+
// get the viewport element
|
1272
|
+
const viewport = this.context.viewport();
|
1273
|
+
if (viewport) {
|
1274
|
+
// find all tabbable elements in the viewport
|
1275
|
+
const tabbables = getTabbableCandidates(viewport);
|
1276
|
+
if (tabbables.length > 0) {
|
1277
|
+
// focus the first tabbable element directly
|
1278
|
+
setTimeout(() => {
|
1279
|
+
tabbables[0].focus();
|
1280
|
+
}, 0);
|
1281
|
+
return;
|
1282
|
+
}
|
1283
|
+
}
|
1284
|
+
}
|
1285
|
+
// fallback to the standard entry key down approach
|
1286
|
+
setTimeout(() => this.item.onEntryKeyDown(), 0);
|
1287
|
+
return;
|
1288
|
+
}
|
1289
|
+
// if not open but in horizontal orientation, emulate right key navigation
|
1290
|
+
if (isHorizontal) {
|
1291
|
+
const nextEvent = new KeyboardEvent('keydown', {
|
1292
|
+
key: isRTL ? ARROW_LEFT : ARROW_RIGHT,
|
1293
|
+
bubbles: true
|
1294
|
+
});
|
1295
|
+
this.elementRef.nativeElement.dispatchEvent(nextEvent);
|
1296
|
+
return;
|
1297
|
+
}
|
1298
|
+
}
|
1299
|
+
// handle ArrowUp in horizontal orientation
|
1300
|
+
if (isHorizontal && event.key === ARROW_UP) {
|
1301
|
+
event.preventDefault();
|
1302
|
+
// emulate a left key press to move to the previous item
|
1303
|
+
const nextEvent = new KeyboardEvent('keydown', {
|
1304
|
+
key: isRTL ? ARROW_RIGHT : ARROW_LEFT,
|
1305
|
+
bubbles: true
|
1306
|
+
});
|
1307
|
+
this.elementRef.nativeElement.dispatchEvent(nextEvent);
|
1308
|
+
return;
|
1309
|
+
}
|
1310
|
+
// handle vertical navigation and entry into content
|
1311
|
+
const verticalEntryKey = isRTL ? ARROW_LEFT : ARROW_RIGHT;
|
1312
|
+
const entryKey = isHorizontal ? ARROW_DOWN : verticalEntryKey;
|
1313
|
+
if (this.item.contentRef() && event.key === entryKey && event.key !== ARROW_DOWN) {
|
1314
|
+
// Skip if it's ArrowDown as we already handled it above
|
1315
|
+
event.preventDefault();
|
1316
|
+
if (!this.open()) {
|
1317
|
+
// if closed, open the menu first
|
1318
|
+
this.context.onItemSelect?.(this.item.value());
|
1319
|
+
// defer focus movement into content until after state update and render
|
1320
|
+
setTimeout(() => this.item.onEntryKeyDown(), 0);
|
1321
|
+
}
|
1322
|
+
else {
|
1323
|
+
// if already open, just move focus into the content
|
1324
|
+
this.item.onEntryKeyDown();
|
1325
|
+
}
|
1326
|
+
return;
|
1327
|
+
}
|
1328
|
+
}
|
1329
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: RdxNavigationMenuTriggerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
1330
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.4", type: RdxNavigationMenuTriggerDirective, isStandalone: true, selector: "[rdxNavigationMenuTrigger]", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "type": "button" }, listeners: { "pointerenter": "onPointerEnter()", "pointermove": "onPointerMove($event)", "pointerleave": "onPointerLeave($event)", "click": "onClick()", "keydown": "onKeydown($event)" }, properties: { "id": "triggerId", "attr.data-state": "open() ? \"open\" : \"closed\"", "attr.data-orientation": "context.orientation", "attr.data-disabled": "disabled() ? \"\" : undefined", "disabled": "disabled() ? true : null", "attr.aria-expanded": "open()", "attr.aria-controls": "contentId", "attr.aria-haspopup": "\"menu\"" } }, providers: [{ provide: RdxNavigationMenuFocusableOption, useExisting: RdxNavigationMenuTriggerDirective }], usesInheritance: true, hostDirectives: [{ directive: i1.RdxRovingFocusItemDirective }], ngImport: i0 }); }
|
1331
|
+
}
|
1332
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: RdxNavigationMenuTriggerDirective, decorators: [{
|
1333
|
+
type: Directive,
|
1334
|
+
args: [{
|
1335
|
+
selector: '[rdxNavigationMenuTrigger]',
|
1336
|
+
hostDirectives: [RdxRovingFocusItemDirective],
|
1337
|
+
host: {
|
1338
|
+
'[id]': 'triggerId',
|
1339
|
+
'[attr.data-state]': 'open() ? "open" : "closed"',
|
1340
|
+
'[attr.data-orientation]': 'context.orientation',
|
1341
|
+
'[attr.data-disabled]': 'disabled() ? "" : undefined',
|
1342
|
+
'[disabled]': 'disabled() ? true : null',
|
1343
|
+
'[attr.aria-expanded]': 'open()',
|
1344
|
+
'[attr.aria-controls]': 'contentId',
|
1345
|
+
'[attr.aria-haspopup]': '"menu"',
|
1346
|
+
'(pointerenter)': 'onPointerEnter()',
|
1347
|
+
'(pointermove)': 'onPointerMove($event)',
|
1348
|
+
'(pointerleave)': 'onPointerLeave($event)',
|
1349
|
+
'(click)': 'onClick()',
|
1350
|
+
'(keydown)': 'onKeydown($event)',
|
1351
|
+
type: 'button'
|
1352
|
+
},
|
1353
|
+
providers: [{ provide: RdxNavigationMenuFocusableOption, useExisting: RdxNavigationMenuTriggerDirective }]
|
1354
|
+
}]
|
1355
|
+
}], ctorParameters: () => [] });
|
1356
|
+
|
1357
|
+
class RdxNavigationMenuViewportDirective {
|
1358
|
+
get open() {
|
1359
|
+
return Boolean(this.context.value() || this.forceMount());
|
1360
|
+
}
|
1361
|
+
onKeydown(event) {
|
1362
|
+
// only handle if viewport is open
|
1363
|
+
if (!this.open)
|
1364
|
+
return;
|
1365
|
+
// get all tabbable elements in the viewport
|
1366
|
+
const tabbableElements = getTabbableCandidates(this.elementRef.nativeElement);
|
1367
|
+
if (!tabbableElements.length)
|
1368
|
+
return;
|
1369
|
+
// find the currently focused element
|
1370
|
+
const activeElement = this.document.activeElement;
|
1371
|
+
const currentIndex = tabbableElements.findIndex((el) => el === activeElement);
|
1372
|
+
if (event.key === ARROW_DOWN) {
|
1373
|
+
event.preventDefault();
|
1374
|
+
if (currentIndex >= 0 && currentIndex < tabbableElements.length - 1) {
|
1375
|
+
// focus the next element
|
1376
|
+
tabbableElements[currentIndex + 1].focus();
|
1377
|
+
}
|
1378
|
+
else if (currentIndex === -1 || currentIndex === tabbableElements.length - 1) {
|
1379
|
+
// if no element is focused or we're at the end, focus the first element
|
1380
|
+
tabbableElements[0].focus();
|
1381
|
+
}
|
1382
|
+
}
|
1383
|
+
else if (event.key === ARROW_UP) {
|
1384
|
+
event.preventDefault();
|
1385
|
+
if (currentIndex > 0) {
|
1386
|
+
// focus the previous element
|
1387
|
+
tabbableElements[currentIndex - 1].focus();
|
1388
|
+
}
|
1389
|
+
else if (currentIndex === 0) {
|
1390
|
+
// if at the first element, loop to the last element
|
1391
|
+
tabbableElements[tabbableElements.length - 1].focus();
|
1392
|
+
}
|
1393
|
+
else if (currentIndex === -1) {
|
1394
|
+
// if no element is focused, focus the last element
|
1395
|
+
tabbableElements[tabbableElements.length - 1].focus();
|
1396
|
+
}
|
1397
|
+
}
|
1398
|
+
}
|
1399
|
+
constructor() {
|
1400
|
+
this.context = injectNavigationMenu();
|
1401
|
+
this.document = injectDocument();
|
1402
|
+
this.window = injectWindow();
|
1403
|
+
this.elementRef = inject(ElementRef);
|
1404
|
+
this.viewContainerRef = inject(ViewContainerRef);
|
1405
|
+
this.renderer = inject(Renderer2);
|
1406
|
+
/**
|
1407
|
+
* Used to keep the viewport rendered and available in the DOM, even when closed.
|
1408
|
+
* Useful for animations.
|
1409
|
+
* @default false
|
1410
|
+
*/
|
1411
|
+
this.forceMount = input(false, { transform: booleanAttribute });
|
1412
|
+
this._contentNodes = signal(new Map());
|
1413
|
+
this._activeContentNode = signal(null);
|
1414
|
+
this._viewportSize = signal(null);
|
1415
|
+
this._resizeObserver = new ResizeObserver(() => this.updateSize());
|
1416
|
+
// compute the active content value - either current value if open, or previous value if closing
|
1417
|
+
this.activeContentValue = computed(() => {
|
1418
|
+
return this.open ? this.context.value() : this.context.previousValue();
|
1419
|
+
});
|
1420
|
+
// size for viewport CSS variables
|
1421
|
+
this.viewportSize = computed(() => this._viewportSize());
|
1422
|
+
// setup effect to manage content
|
1423
|
+
effect(() => {
|
1424
|
+
const activeValue = this.activeContentValue();
|
1425
|
+
const open = this.open;
|
1426
|
+
untracked(() => {
|
1427
|
+
// handle visibility based on open state
|
1428
|
+
this.renderer.setStyle(this.elementRef.nativeElement, 'display', open ? 'block' : 'none');
|
1429
|
+
if (isRootNavigationMenu(this.context) && this.context.viewportContent) {
|
1430
|
+
const viewportContent = this.context.viewportContent();
|
1431
|
+
if (viewportContent.has(activeValue)) {
|
1432
|
+
const contentData = viewportContent.get(activeValue);
|
1433
|
+
// only render content when we have a templateRef
|
1434
|
+
if (contentData?.templateRef) {
|
1435
|
+
this.renderContent(contentData.templateRef, activeValue);
|
1436
|
+
}
|
1437
|
+
}
|
1438
|
+
}
|
1439
|
+
});
|
1440
|
+
});
|
1441
|
+
}
|
1442
|
+
ngOnInit() {
|
1443
|
+
// register viewport with context
|
1444
|
+
if (isRootNavigationMenu(this.context) && this.context.onViewportChange) {
|
1445
|
+
this.context.onViewportChange(this.elementRef.nativeElement);
|
1446
|
+
}
|
1447
|
+
}
|
1448
|
+
ngOnDestroy() {
|
1449
|
+
this._resizeObserver.disconnect();
|
1450
|
+
// clear all views
|
1451
|
+
this._contentNodes().forEach((node) => {
|
1452
|
+
if (node.embeddedView) {
|
1453
|
+
node.embeddedView.destroy();
|
1454
|
+
}
|
1455
|
+
});
|
1456
|
+
// unregister viewport
|
1457
|
+
if (isRootNavigationMenu(this.context) && this.context.onViewportChange) {
|
1458
|
+
this.context.onViewportChange(null);
|
1459
|
+
}
|
1460
|
+
}
|
1461
|
+
getOpenState() {
|
1462
|
+
return getOpenStateLabel(this.open);
|
1463
|
+
}
|
1464
|
+
onPointerEnter() {
|
1465
|
+
if (isRootNavigationMenu(this.context) && this.context.onContentEnter) {
|
1466
|
+
this.context.onContentEnter();
|
1467
|
+
}
|
1468
|
+
// update pointer tracking state
|
1469
|
+
if (isRootNavigationMenu(this.context) && this.context.setContentPointerState) {
|
1470
|
+
this.context.setContentPointerState(true);
|
1471
|
+
}
|
1472
|
+
}
|
1473
|
+
onPointerLeave() {
|
1474
|
+
if (isRootNavigationMenu(this.context) && this.context.onContentLeave) {
|
1475
|
+
this.context.onContentLeave();
|
1476
|
+
}
|
1477
|
+
// Update pointer tracking state
|
1478
|
+
if (isRootNavigationMenu(this.context) && this.context.setContentPointerState) {
|
1479
|
+
this.context.setContentPointerState(false);
|
1480
|
+
}
|
1481
|
+
}
|
1482
|
+
updateSize() {
|
1483
|
+
const activeNode = this._activeContentNode()?.element;
|
1484
|
+
if (!activeNode)
|
1485
|
+
return;
|
1486
|
+
// force layout recalculation while keeping element in the DOM
|
1487
|
+
this.window.getComputedStyle(activeNode).getPropertyValue('width');
|
1488
|
+
const firstChild = activeNode.firstChild;
|
1489
|
+
const width = Math.ceil(firstChild.offsetWidth);
|
1490
|
+
const height = Math.ceil(firstChild.offsetHeight);
|
1491
|
+
// update size with valid dimensions (but only if not zero)
|
1492
|
+
if (width !== 0 && height !== 0) {
|
1493
|
+
this._viewportSize.set({ width, height });
|
1494
|
+
}
|
1495
|
+
}
|
1496
|
+
renderContent(templateRef, contentValue) {
|
1497
|
+
// check if we already have a view for this content
|
1498
|
+
let contentNode = this._contentNodes().get(contentValue);
|
1499
|
+
if (!contentNode) {
|
1500
|
+
try {
|
1501
|
+
// create a new embedded view
|
1502
|
+
const embeddedView = this.viewContainerRef.createEmbeddedView(templateRef);
|
1503
|
+
embeddedView.detectChanges();
|
1504
|
+
// create a container for the view
|
1505
|
+
const container = this.renderer.createElement('div');
|
1506
|
+
this.renderer.setAttribute(container, 'class', 'NavigationMenuContentWrapper');
|
1507
|
+
this.renderer.setAttribute(container, 'data-content-value', contentValue);
|
1508
|
+
this.renderer.setStyle(container, 'width', '100%');
|
1509
|
+
const viewportContent = this.context.viewportContent && this.context.viewportContent();
|
1510
|
+
if (!viewportContent)
|
1511
|
+
return;
|
1512
|
+
const contentData = viewportContent.get(contentValue);
|
1513
|
+
// apply motion attribute if available
|
1514
|
+
if (contentData?.getMotionAttribute) {
|
1515
|
+
const motionAttr = contentData.getMotionAttribute();
|
1516
|
+
if (motionAttr) {
|
1517
|
+
this.renderer.setAttribute(container, 'data-motion', motionAttr);
|
1518
|
+
}
|
1519
|
+
}
|
1520
|
+
// apply additional a11y attributes to the first root node
|
1521
|
+
if (contentData?.additionalAttrs && embeddedView.rootNodes.length > 0) {
|
1522
|
+
const rootNode = embeddedView.rootNodes[0];
|
1523
|
+
// check if rootNode has setAttribute (is an Element)
|
1524
|
+
if (rootNode.setAttribute) {
|
1525
|
+
Object.entries(contentData.additionalAttrs).forEach(([attr, value]) => {
|
1526
|
+
// don't override existing attributes that the user might have set manually
|
1527
|
+
if (!rootNode.hasAttribute(attr) || attr === 'id') {
|
1528
|
+
this.renderer.setAttribute(rootNode, attr, value);
|
1529
|
+
}
|
1530
|
+
});
|
1531
|
+
}
|
1532
|
+
}
|
1533
|
+
// add each root node to the container
|
1534
|
+
embeddedView.rootNodes.forEach((node) => {
|
1535
|
+
this.renderer.appendChild(container, node);
|
1536
|
+
});
|
1537
|
+
// set styles for proper measurement and display
|
1538
|
+
this.renderer.setStyle(container, 'position', 'relative');
|
1539
|
+
this.renderer.setStyle(container, 'visibility', 'visible');
|
1540
|
+
this.renderer.setStyle(container, 'pointer-events', 'auto');
|
1541
|
+
this.renderer.setStyle(container, 'display', 'block');
|
1542
|
+
// store in cache
|
1543
|
+
contentNode = { embeddedView, element: container };
|
1544
|
+
const newMap = new Map(this._contentNodes());
|
1545
|
+
newMap.set(contentValue, contentNode);
|
1546
|
+
this._contentNodes.set(newMap);
|
1547
|
+
}
|
1548
|
+
catch (error) {
|
1549
|
+
console.error('Error in renderContent:', error);
|
1550
|
+
return;
|
1551
|
+
}
|
1552
|
+
}
|
1553
|
+
if (contentNode) {
|
1554
|
+
this.updateActiveContent(contentNode);
|
1555
|
+
}
|
1556
|
+
}
|
1557
|
+
updateActiveContent(contentNode) {
|
1558
|
+
if (contentNode !== this._activeContentNode()) {
|
1559
|
+
// clear viewport
|
1560
|
+
while (this.elementRef.nativeElement.firstChild) {
|
1561
|
+
this.renderer.removeChild(this.elementRef.nativeElement, this.elementRef.nativeElement.firstChild);
|
1562
|
+
}
|
1563
|
+
// add content to viewport
|
1564
|
+
this.renderer.appendChild(this.elementRef.nativeElement, contentNode.element);
|
1565
|
+
// update active content reference
|
1566
|
+
this._activeContentNode.set(contentNode);
|
1567
|
+
// setup resize observation
|
1568
|
+
this._resizeObserver.disconnect();
|
1569
|
+
this._resizeObserver.observe(contentNode.element);
|
1570
|
+
// measure after adding to DOM
|
1571
|
+
setTimeout(() => this.updateSize(), 0);
|
1572
|
+
// measure again after a frame to catch any style changes
|
1573
|
+
requestAnimationFrame(() => this.updateSize());
|
1574
|
+
}
|
1575
|
+
}
|
1576
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: RdxNavigationMenuViewportDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
1577
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.4", 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": "getOpenState()", "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 }); }
|
1578
|
+
}
|
1579
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: RdxNavigationMenuViewportDirective, decorators: [{
|
1580
|
+
type: Directive,
|
1581
|
+
args: [{
|
1582
|
+
selector: '[rdxNavigationMenuViewport]',
|
1583
|
+
host: {
|
1584
|
+
'[attr.data-state]': 'getOpenState()',
|
1585
|
+
'[attr.data-orientation]': 'context.orientation',
|
1586
|
+
'[style.--radix-navigation-menu-viewport-width.px]': 'viewportSize()?.width',
|
1587
|
+
'[style.--radix-navigation-menu-viewport-height.px]': 'viewportSize()?.height',
|
1588
|
+
'(keydown)': 'onKeydown($event)',
|
1589
|
+
'(pointerenter)': 'onPointerEnter()',
|
1590
|
+
'(pointerleave)': 'onPointerLeave()'
|
1591
|
+
}
|
1592
|
+
}]
|
1593
|
+
}], ctorParameters: () => [] });
|
1594
|
+
|
1595
|
+
const _imports = [
|
1596
|
+
RdxNavigationMenuDirective,
|
1597
|
+
RdxNavigationMenuSubDirective,
|
1598
|
+
RdxNavigationMenuListDirective,
|
1599
|
+
RdxNavigationMenuItemDirective,
|
1600
|
+
RdxNavigationMenuTriggerDirective,
|
1601
|
+
RdxNavigationMenuLinkDirective,
|
1602
|
+
RdxNavigationMenuIndicatorDirective,
|
1603
|
+
RdxNavigationMenuContentDirective,
|
1604
|
+
RdxNavigationMenuViewportDirective,
|
1605
|
+
RdxNavigationMenuFocusProxyComponent,
|
1606
|
+
RdxNavigationMenuAriaOwnsComponent
|
1607
|
+
];
|
1608
|
+
class RdxNavigationMenuModule {
|
1609
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: RdxNavigationMenuModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
1610
|
+
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.4", ngImport: i0, type: RdxNavigationMenuModule, imports: [RdxNavigationMenuDirective,
|
1611
|
+
RdxNavigationMenuSubDirective,
|
1612
|
+
RdxNavigationMenuListDirective,
|
1613
|
+
RdxNavigationMenuItemDirective,
|
1614
|
+
RdxNavigationMenuTriggerDirective,
|
1615
|
+
RdxNavigationMenuLinkDirective,
|
1616
|
+
RdxNavigationMenuIndicatorDirective,
|
1617
|
+
RdxNavigationMenuContentDirective,
|
1618
|
+
RdxNavigationMenuViewportDirective,
|
1619
|
+
RdxNavigationMenuFocusProxyComponent,
|
1620
|
+
RdxNavigationMenuAriaOwnsComponent], exports: [RdxNavigationMenuDirective,
|
1621
|
+
RdxNavigationMenuSubDirective,
|
1622
|
+
RdxNavigationMenuListDirective,
|
1623
|
+
RdxNavigationMenuItemDirective,
|
1624
|
+
RdxNavigationMenuTriggerDirective,
|
1625
|
+
RdxNavigationMenuLinkDirective,
|
1626
|
+
RdxNavigationMenuIndicatorDirective,
|
1627
|
+
RdxNavigationMenuContentDirective,
|
1628
|
+
RdxNavigationMenuViewportDirective,
|
1629
|
+
RdxNavigationMenuFocusProxyComponent,
|
1630
|
+
RdxNavigationMenuAriaOwnsComponent] }); }
|
1631
|
+
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: RdxNavigationMenuModule }); }
|
1632
|
+
}
|
1633
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: RdxNavigationMenuModule, decorators: [{
|
1634
|
+
type: NgModule,
|
1635
|
+
args: [{
|
1636
|
+
imports: [..._imports],
|
1637
|
+
exports: [..._imports]
|
1638
|
+
}]
|
1639
|
+
}] });
|
1640
|
+
|
1641
|
+
/**
|
1642
|
+
* Generated bundle index. Do not edit.
|
1643
|
+
*/
|
1644
|
+
|
1645
|
+
export { RDX_NAVIGATION_MENU_TOKEN, RdxNavigationMenuAction, RdxNavigationMenuAnimationStatus, RdxNavigationMenuAriaOwnsComponent, RdxNavigationMenuContentDirective, RdxNavigationMenuDirective, RdxNavigationMenuFocusProxyComponent, RdxNavigationMenuFocusableOption, RdxNavigationMenuIndicatorDirective, RdxNavigationMenuItemDirective, RdxNavigationMenuLinkDirective, RdxNavigationMenuListDirective, RdxNavigationMenuModule, RdxNavigationMenuSubDirective, RdxNavigationMenuTriggerDirective, RdxNavigationMenuViewportDirective, injectNavigationMenu, isRootNavigationMenu, provideNavigationMenuContext };
|
1646
|
+
//# sourceMappingURL=radix-ng-primitives-navigation-menu.mjs.map
|