@radix-ng/primitives 1.0.0-beta.5 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/composite/README.md +3 -0
- package/fesm2022/radix-ng-primitives-accordion.mjs +20 -44
- package/fesm2022/radix-ng-primitives-accordion.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-checkbox.mjs +134 -58
- package/fesm2022/radix-ng-primitives-checkbox.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-composite.mjs +599 -0
- package/fesm2022/radix-ng-primitives-composite.mjs.map +1 -0
- package/fesm2022/radix-ng-primitives-drawer.mjs +442 -2
- package/fesm2022/radix-ng-primitives-drawer.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-menu.mjs +315 -68
- package/fesm2022/radix-ng-primitives-menu.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-menubar.mjs +91 -36
- package/fesm2022/radix-ng-primitives-menubar.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-navigation-menu.mjs +281 -88
- package/fesm2022/radix-ng-primitives-navigation-menu.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-popover.mjs +40 -15
- package/fesm2022/radix-ng-primitives-popover.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-popper.mjs +73 -65
- package/fesm2022/radix-ng-primitives-popper.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-radio.mjs +63 -27
- package/fesm2022/radix-ng-primitives-radio.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-scroll-area.mjs +56 -25
- package/fesm2022/radix-ng-primitives-scroll-area.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-select.mjs +59 -29
- package/fesm2022/radix-ng-primitives-select.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-slider.mjs +57 -13
- package/fesm2022/radix-ng-primitives-slider.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-tabs.mjs +335 -73
- package/fesm2022/radix-ng-primitives-tabs.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-toggle-group.mjs +66 -21
- package/fesm2022/radix-ng-primitives-toggle-group.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-toggle.mjs +29 -11
- package/fesm2022/radix-ng-primitives-toggle.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-toolbar.mjs +68 -36
- package/fesm2022/radix-ng-primitives-toolbar.mjs.map +1 -1
- package/navigation-menu/README.md +5 -2
- package/package.json +6 -10
- package/types/radix-ng-primitives-accordion.d.ts +12 -16
- package/types/radix-ng-primitives-checkbox.d.ts +98 -70
- package/types/radix-ng-primitives-composite.d.ts +195 -0
- package/types/radix-ng-primitives-drawer.d.ts +40 -2
- package/types/radix-ng-primitives-menu.d.ts +46 -16
- package/types/radix-ng-primitives-menubar.d.ts +12 -5
- package/types/radix-ng-primitives-navigation-menu.d.ts +65 -33
- package/types/radix-ng-primitives-popover.d.ts +9 -5
- package/types/radix-ng-primitives-popper.d.ts +1 -0
- package/types/radix-ng-primitives-radio.d.ts +11 -9
- package/types/radix-ng-primitives-scroll-area.d.ts +4 -1
- package/types/radix-ng-primitives-select.d.ts +46 -32
- package/types/radix-ng-primitives-slider.d.ts +19 -4
- package/types/radix-ng-primitives-tabs.d.ts +69 -14
- package/types/radix-ng-primitives-toggle-group.d.ts +27 -16
- package/types/radix-ng-primitives-toggle.d.ts +5 -5
- package/types/radix-ng-primitives-toolbar.d.ts +84 -69
- package/collection/README.md +0 -1
- package/fesm2022/radix-ng-primitives-collection.mjs +0 -72
- package/fesm2022/radix-ng-primitives-collection.mjs.map +0 -1
- package/fesm2022/radix-ng-primitives-roving-focus.mjs +0 -388
- package/fesm2022/radix-ng-primitives-roving-focus.mjs.map +0 -1
- package/roving-focus/README.md +0 -3
- package/types/radix-ng-primitives-collection.d.ts +0 -44
- package/types/radix-ng-primitives-roving-focus.d.ts +0 -187
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, computed, Directive, input, signal, TemplateRef, booleanAttribute, effect, untracked,
|
|
2
|
+
import { inject, computed, Directive, ElementRef, input, signal, TemplateRef, booleanAttribute, effect, untracked, output, DestroyRef, isDevMode, model, numberAttribute, ViewContainerRef, Renderer2, NgModule } from '@angular/core';
|
|
3
3
|
import * as i1 from '@radix-ng/primitives/popper';
|
|
4
4
|
import { RdxPopperContentWrapper, RdxPopperArrow, RdxPopperContent, provideRdxPopperContentWrapper, provideRdxPopperContentConfig, RdxPopper } from '@radix-ng/primitives/popper';
|
|
5
5
|
import * as i2 from '@radix-ng/primitives/core';
|
|
6
|
-
import { createContext, ENTER, SPACE,
|
|
7
|
-
import * as i1$1 from '@radix-ng/primitives/
|
|
8
|
-
import {
|
|
6
|
+
import { createContext, ENTER, SPACE, ARROW_LEFT, ARROW_RIGHT, ARROW_UP, ARROW_DOWN, RDX_FLOATING_ROOT_CONTEXT, RDX_FLOATING_REGISTRATION, TAB, HOME, END, rdxDevError, useGraceArea, createFloatingRootContext, useTransitionStatus, provideFloatingTree, provideFloatingRootContext, RdxFloatingNodeRegistration, createCancelableChangeEventDetails, injectDocument, getMaxTransitionDuration } from '@radix-ng/primitives/core';
|
|
7
|
+
import * as i1$1 from '@radix-ng/primitives/composite';
|
|
8
|
+
import { RdxCompositeItem, RdxCompositeRoot } from '@radix-ng/primitives/composite';
|
|
9
9
|
import { RdxDismiss } from '@radix-ng/primitives/dismissable-layer';
|
|
10
10
|
import * as i1$2 from '@radix-ng/primitives/portal';
|
|
11
11
|
import { RdxPortalPresence } from '@radix-ng/primitives/portal';
|
|
@@ -49,24 +49,30 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
49
49
|
class RdxNavigationMenuBackdrop {
|
|
50
50
|
constructor() {
|
|
51
51
|
this.rootContext = injectNavigationMenuRootContext();
|
|
52
|
+
const host = inject(ElementRef).nativeElement;
|
|
53
|
+
host.style.setProperty('-webkit-user-select', 'none');
|
|
54
|
+
host.style.webkitUserSelect = 'none';
|
|
52
55
|
}
|
|
53
56
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuBackdrop, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
54
|
-
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 }); }
|
|
57
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxNavigationMenuBackdrop, isStandalone: true, selector: "[rdxNavigationMenuBackdrop]", host: { attributes: { "role": "presentation" }, properties: { "attr.hidden": "rootContext.present() ? undefined : \"\"", "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\"", "style.user-select": "\"none\"" } }, ngImport: i0 }); }
|
|
55
58
|
}
|
|
56
59
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuBackdrop, decorators: [{
|
|
57
60
|
type: Directive,
|
|
58
61
|
args: [{
|
|
59
62
|
selector: '[rdxNavigationMenuBackdrop]',
|
|
60
63
|
host: {
|
|
64
|
+
role: 'presentation',
|
|
65
|
+
'[attr.hidden]': 'rootContext.present() ? undefined : ""',
|
|
61
66
|
'[attr.data-open]': 'rootContext.isOpen() ? "" : undefined',
|
|
62
67
|
'[attr.data-closed]': 'rootContext.isOpen() ? undefined : ""',
|
|
63
68
|
'[attr.data-starting-style]': 'rootContext.transitionStatus() === "starting" ? "" : undefined',
|
|
64
69
|
'[attr.data-ending-style]': 'rootContext.transitionStatus() === "ending" ? "" : undefined',
|
|
65
70
|
'[attr.data-instant]': 'rootContext.instant() ? "" : undefined',
|
|
66
|
-
'[attr.data-state]': 'rootContext.isOpen() ? "open" : "closed"'
|
|
71
|
+
'[attr.data-state]': 'rootContext.isOpen() ? "open" : "closed"',
|
|
72
|
+
'[style.user-select]': '"none"'
|
|
67
73
|
}
|
|
68
74
|
}]
|
|
69
|
-
}] });
|
|
75
|
+
}], ctorParameters: () => [] });
|
|
70
76
|
|
|
71
77
|
/**
|
|
72
78
|
* Generate a short unique id segment.
|
|
@@ -250,10 +256,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
250
256
|
}] });
|
|
251
257
|
|
|
252
258
|
/**
|
|
253
|
-
* A navigation link.
|
|
259
|
+
* A navigation link. Can close the menu on selection when `closeOnClick` is enabled.
|
|
254
260
|
*
|
|
255
|
-
* Used both as a top-level
|
|
256
|
-
*
|
|
261
|
+
* Used both as a top-level navigation item and inside content. Top-level links join the list's
|
|
262
|
+
* composite collection, matching Base UI's CompositeItem-backed NavigationMenu.Link.
|
|
257
263
|
*/
|
|
258
264
|
class RdxNavigationMenuLink {
|
|
259
265
|
constructor() {
|
|
@@ -266,7 +272,7 @@ class RdxNavigationMenuLink {
|
|
|
266
272
|
/**
|
|
267
273
|
* Whether selecting the link should close the menu.
|
|
268
274
|
*/
|
|
269
|
-
this.closeOnClick = input(
|
|
275
|
+
this.closeOnClick = input(false, { ...(ngDevMode ? { debugName: "closeOnClick" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
270
276
|
/**
|
|
271
277
|
* Emits when the link is selected. Call `preventDefault()` to keep the menu open.
|
|
272
278
|
*/
|
|
@@ -285,12 +291,13 @@ class RdxNavigationMenuLink {
|
|
|
285
291
|
}
|
|
286
292
|
}
|
|
287
293
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuLink, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
288
|
-
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 }); }
|
|
294
|
+
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" } }, hostDirectives: [{ directive: i1$1.RdxCompositeItem }], ngImport: i0 }); }
|
|
289
295
|
}
|
|
290
296
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuLink, decorators: [{
|
|
291
297
|
type: Directive,
|
|
292
298
|
args: [{
|
|
293
299
|
selector: '[rdxNavigationMenuLink]',
|
|
300
|
+
hostDirectives: [RdxCompositeItem],
|
|
294
301
|
host: {
|
|
295
302
|
'[attr.data-active]': 'active() ? "" : undefined',
|
|
296
303
|
'[attr.aria-current]': 'active() ? "page" : undefined',
|
|
@@ -301,18 +308,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
301
308
|
}], 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"] }] } });
|
|
302
309
|
|
|
303
310
|
/**
|
|
304
|
-
* Contains the navigation menu items
|
|
311
|
+
* Contains the navigation menu items and coordinates composite keyboard focus between triggers.
|
|
305
312
|
*/
|
|
306
313
|
class RdxNavigationMenuList {
|
|
307
314
|
constructor() {
|
|
308
315
|
this.rootContext = injectNavigationMenuRootContext();
|
|
309
|
-
this.
|
|
316
|
+
this.compositeRoot = inject(RdxCompositeRoot, { self: true });
|
|
317
|
+
const unregisterList = this.rootContext.registerList(inject(ElementRef).nativeElement);
|
|
318
|
+
inject(DestroyRef).onDestroy(unregisterList);
|
|
310
319
|
effect(() => {
|
|
311
|
-
this.
|
|
312
|
-
this.
|
|
313
|
-
this.
|
|
320
|
+
this.compositeRoot.setOrientation(this.rootContext.orientation());
|
|
321
|
+
this.compositeRoot.setDir(this.rootContext.dir());
|
|
322
|
+
this.compositeRoot.setLoopFocus(false);
|
|
314
323
|
});
|
|
315
324
|
}
|
|
325
|
+
onKeydown(event) {
|
|
326
|
+
if (this.rootContext.nested) {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
const horizontal = this.rootContext.orientation() === 'horizontal';
|
|
330
|
+
const shouldStop = horizontal
|
|
331
|
+
? event.key === ARROW_LEFT || event.key === ARROW_RIGHT
|
|
332
|
+
: event.key === ARROW_UP || event.key === ARROW_DOWN;
|
|
333
|
+
if (shouldStop) {
|
|
334
|
+
event.stopPropagation();
|
|
335
|
+
}
|
|
336
|
+
}
|
|
316
337
|
onPointerLeave(event) {
|
|
317
338
|
if (event.pointerType === 'touch') {
|
|
318
339
|
return;
|
|
@@ -320,16 +341,16 @@ class RdxNavigationMenuList {
|
|
|
320
341
|
this.rootContext.closeOnHover(event);
|
|
321
342
|
}
|
|
322
343
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuList, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
323
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxNavigationMenuList, isStandalone: true, selector: "[rdxNavigationMenuList]", host: {
|
|
344
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxNavigationMenuList, isStandalone: true, selector: "[rdxNavigationMenuList]", host: { listeners: { "keydown": "onKeydown($event)", "pointerleave": "onPointerLeave($event)" }, properties: { "attr.data-orientation": "rootContext.orientation()" } }, hostDirectives: [{ directive: i1$1.RdxCompositeRoot }], ngImport: i0 }); }
|
|
324
345
|
}
|
|
325
346
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuList, decorators: [{
|
|
326
347
|
type: Directive,
|
|
327
348
|
args: [{
|
|
328
349
|
selector: '[rdxNavigationMenuList]',
|
|
329
|
-
hostDirectives: [
|
|
350
|
+
hostDirectives: [RdxCompositeRoot],
|
|
330
351
|
host: {
|
|
331
|
-
role: 'menubar',
|
|
332
352
|
'[attr.data-orientation]': 'rootContext.orientation()',
|
|
353
|
+
'(keydown)': 'onKeydown($event)',
|
|
333
354
|
'(pointerleave)': 'onPointerLeave($event)'
|
|
334
355
|
}
|
|
335
356
|
}]
|
|
@@ -347,11 +368,10 @@ class RdxNavigationMenuPopup {
|
|
|
347
368
|
this.elementRef = inject(ElementRef);
|
|
348
369
|
this.side = computed(() => this.wrapper?.placedSide(), ...(ngDevMode ? [{ debugName: "side" }] : /* istanbul ignore next */ []));
|
|
349
370
|
this.align = computed(() => this.wrapper?.placedAlign(), ...(ngDevMode ? [{ debugName: "align" }] : /* istanbul ignore next */ []));
|
|
350
|
-
|
|
351
|
-
this.labelledBy = computed(() => {
|
|
371
|
+
this.id = computed(() => {
|
|
352
372
|
const value = this.rootContext.value() ?? this.rootContext.previousValue();
|
|
353
|
-
return value ? this.rootContext.
|
|
354
|
-
}, ...(ngDevMode ? [{ debugName: "
|
|
373
|
+
return value ? `${this.rootContext.baseId}-popup-${value}` : `${this.rootContext.baseId}-popup`;
|
|
374
|
+
}, ...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
|
|
355
375
|
/**
|
|
356
376
|
* Event handler called when the escape key is down. Can be prevented.
|
|
357
377
|
*/
|
|
@@ -366,7 +386,9 @@ class RdxNavigationMenuPopup {
|
|
|
366
386
|
this.focusOutside = output();
|
|
367
387
|
const destroyRef = inject(DestroyRef);
|
|
368
388
|
const unregisterTransitionElement = this.rootContext.registerTransitionElement(this.elementRef.nativeElement);
|
|
389
|
+
const unregisterPopup = this.rootContext.registerPopup(this.elementRef.nativeElement);
|
|
369
390
|
destroyRef.onDestroy(unregisterTransitionElement);
|
|
391
|
+
destroyRef.onDestroy(unregisterPopup);
|
|
370
392
|
// The popup is this layer's floating element (the inside surface for containment checks). The
|
|
371
393
|
// triggers are registered as "inside" on the shared root context (in `registerTrigger`), so a
|
|
372
394
|
// press / focus on a sibling trigger to switch items — or back on the active trigger — never
|
|
@@ -402,16 +424,23 @@ class RdxNavigationMenuPopup {
|
|
|
402
424
|
this.rootContext.closeOnHover(event);
|
|
403
425
|
}
|
|
404
426
|
/**
|
|
405
|
-
* Keyboard navigation inside the open panel
|
|
406
|
-
* DOM order, Home/End jump to the first/last,
|
|
407
|
-
*
|
|
427
|
+
* Keyboard navigation inside the open panel mirrors Base UI's CompositeRoot-backed content:
|
|
428
|
+
* arrow keys move between panel focusables in DOM order, Home/End jump to the first/last, Up from
|
|
429
|
+
* the first item returns focus to the trigger, and Tab exits the portalled panel through the
|
|
430
|
+
* logical top-level navigation order. Escape is handled by the dismissal capability.
|
|
408
431
|
*/
|
|
409
432
|
onKeydown(event) {
|
|
410
|
-
if (event.key !== ARROW_DOWN &&
|
|
433
|
+
if (event.key !== ARROW_DOWN &&
|
|
434
|
+
event.key !== ARROW_UP &&
|
|
435
|
+
event.key !== ARROW_LEFT &&
|
|
436
|
+
event.key !== ARROW_RIGHT &&
|
|
437
|
+
event.key !== TAB &&
|
|
438
|
+
event.key !== HOME &&
|
|
439
|
+
event.key !== END) {
|
|
411
440
|
return;
|
|
412
441
|
}
|
|
413
442
|
// If the key originates from a nested navigation menu rendered inside this popup, let that
|
|
414
|
-
// menu's own
|
|
443
|
+
// menu's own composite list / popup handle it — otherwise both react and focus jumps/skips.
|
|
415
444
|
const nestedRoot = event.target.closest('[rdxNavigationMenuRoot]');
|
|
416
445
|
if (nestedRoot && this.elementRef.nativeElement.contains(nestedRoot)) {
|
|
417
446
|
return;
|
|
@@ -420,8 +449,12 @@ class RdxNavigationMenuPopup {
|
|
|
420
449
|
if (candidates.length === 0) {
|
|
421
450
|
return;
|
|
422
451
|
}
|
|
423
|
-
event.preventDefault();
|
|
424
452
|
const currentIndex = candidates.indexOf(document.activeElement);
|
|
453
|
+
if (event.key === TAB) {
|
|
454
|
+
this.handleTabKey(event, candidates, currentIndex);
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
event.preventDefault();
|
|
425
458
|
if (event.key === HOME) {
|
|
426
459
|
focusFirst([candidates[0]]);
|
|
427
460
|
return;
|
|
@@ -435,6 +468,18 @@ class RdxNavigationMenuPopup {
|
|
|
435
468
|
focusFirst([candidates[next]]);
|
|
436
469
|
return;
|
|
437
470
|
}
|
|
471
|
+
if (event.key === ARROW_RIGHT || event.key === ARROW_LEFT) {
|
|
472
|
+
const moveNext = this.rootContext.dir() === 'rtl' ? event.key === ARROW_LEFT : event.key === ARROW_RIGHT;
|
|
473
|
+
const next = moveNext
|
|
474
|
+
? currentIndex < candidates.length - 1
|
|
475
|
+
? currentIndex + 1
|
|
476
|
+
: 0
|
|
477
|
+
: currentIndex > 0
|
|
478
|
+
? currentIndex - 1
|
|
479
|
+
: candidates.length - 1;
|
|
480
|
+
focusFirst([candidates[next]]);
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
438
483
|
// ArrowUp: from the first item, return focus to the trigger; otherwise move to the previous.
|
|
439
484
|
if (currentIndex <= 0) {
|
|
440
485
|
this.rootContext.trigger()?.focus();
|
|
@@ -443,8 +488,50 @@ class RdxNavigationMenuPopup {
|
|
|
443
488
|
focusFirst([candidates[currentIndex - 1]]);
|
|
444
489
|
}
|
|
445
490
|
}
|
|
491
|
+
handleTabKey(event, candidates, currentIndex) {
|
|
492
|
+
if (event.altKey || event.ctrlKey || event.metaKey) {
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
const isFirst = currentIndex <= 0;
|
|
496
|
+
const isLast = currentIndex === candidates.length - 1;
|
|
497
|
+
if (event.shiftKey) {
|
|
498
|
+
if (!isFirst) {
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
event.preventDefault();
|
|
502
|
+
this.rootContext.trigger()?.focus();
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
if (!isLast) {
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
const nextItem = this.getNextTopLevelItem();
|
|
509
|
+
if (!nextItem) {
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
event.preventDefault();
|
|
513
|
+
nextItem.focus();
|
|
514
|
+
}
|
|
515
|
+
getNextTopLevelItem() {
|
|
516
|
+
const activeTrigger = this.rootContext.trigger();
|
|
517
|
+
if (!activeTrigger) {
|
|
518
|
+
return undefined;
|
|
519
|
+
}
|
|
520
|
+
const items = this.getTopLevelItems();
|
|
521
|
+
const currentIndex = items.indexOf(activeTrigger);
|
|
522
|
+
return currentIndex === -1 ? undefined : items[currentIndex + 1];
|
|
523
|
+
}
|
|
524
|
+
getTopLevelItems() {
|
|
525
|
+
const list = this.rootContext.list();
|
|
526
|
+
if (!list) {
|
|
527
|
+
return this.rootContext.triggers();
|
|
528
|
+
}
|
|
529
|
+
return Array.from(list.querySelectorAll('[rdxNavigationMenuTrigger], [rdxNavigationMenuLink]'))
|
|
530
|
+
.filter((item) => !item.hasAttribute('disabled'))
|
|
531
|
+
.filter((item) => item.getAttribute('aria-hidden') !== 'true');
|
|
532
|
+
}
|
|
446
533
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuPopup, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
447
|
-
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: { "
|
|
534
|
+
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: { "tabindex": "-1" }, listeners: { "pointerenter": "rootContext.cancelHoverClose()", "pointerleave": "onPointerLeave($event)", "keydown": "onKeydown($event)" }, properties: { "id": "id()", "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()", "style.--popup-width.px": "rootContext.size()?.width", "style.--popup-height.px": "rootContext.size()?.height" } }, hostDirectives: [{ directive: i1.RdxPopperContent }], ngImport: i0 }); }
|
|
448
535
|
}
|
|
449
536
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuPopup, decorators: [{
|
|
450
537
|
type: Directive,
|
|
@@ -452,9 +539,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
452
539
|
selector: '[rdxNavigationMenuPopup]',
|
|
453
540
|
hostDirectives: [RdxPopperContent],
|
|
454
541
|
host: {
|
|
455
|
-
role: 'menu',
|
|
456
542
|
tabindex: '-1',
|
|
457
|
-
'[
|
|
543
|
+
'[id]': 'id()',
|
|
458
544
|
'[attr.data-open]': 'rootContext.isOpen() ? "" : undefined',
|
|
459
545
|
'[attr.data-closed]': 'rootContext.isOpen() ? undefined : ""',
|
|
460
546
|
'[attr.data-starting-style]': 'rootContext.transitionStatus() === "starting" ? "" : undefined',
|
|
@@ -463,6 +549,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
463
549
|
'[attr.data-state]': 'rootContext.isOpen() ? "open" : "closed"',
|
|
464
550
|
'[attr.data-side]': 'side()',
|
|
465
551
|
'[attr.data-align]': 'align()',
|
|
552
|
+
'[style.--popup-width.px]': 'rootContext.size()?.width',
|
|
553
|
+
'[style.--popup-height.px]': 'rootContext.size()?.height',
|
|
466
554
|
'(pointerenter)': 'rootContext.cancelHoverClose()',
|
|
467
555
|
'(pointerleave)': 'onPointerLeave($event)',
|
|
468
556
|
'(keydown)': 'onKeydown($event)'
|
|
@@ -473,7 +561,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
473
561
|
/**
|
|
474
562
|
* Structural directive that teleports the navigation menu popup into a container (default
|
|
475
563
|
* `document.body`) while the menu is open, and keeps it mounted until any CSS exit `@keyframes`
|
|
476
|
-
* finishes.
|
|
564
|
+
* finishes. Set `[keepMounted]="true"` to keep the portal mounted while closed.
|
|
477
565
|
*
|
|
478
566
|
* Apply it with the `*` microsyntax on the positioner —
|
|
479
567
|
* `<div *rdxNavigationMenuPortal rdxNavigationMenuPositioner>` — or as an explicit
|
|
@@ -481,8 +569,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
481
569
|
* `[container]`.
|
|
482
570
|
*/
|
|
483
571
|
class RdxNavigationMenuPortal {
|
|
572
|
+
constructor() {
|
|
573
|
+
this.rootContext = injectNavigationMenuRootContext();
|
|
574
|
+
/**
|
|
575
|
+
* Keep the portal mounted while the menu is closed.
|
|
576
|
+
*/
|
|
577
|
+
this.keepMounted = input(false, { ...(ngDevMode ? { debugName: "keepMounted" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
578
|
+
this.present = computed(() => this.rootContext.present() || this.keepMounted(), ...(ngDevMode ? [{ debugName: "present" }] : /* istanbul ignore next */ []));
|
|
579
|
+
}
|
|
484
580
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuPortal, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
485
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "
|
|
581
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxNavigationMenuPortal, isStandalone: true, selector: "ng-template[rdxNavigationMenuPortal]", inputs: { keepMounted: { classPropertyName: "keepMounted", publicName: "keepMounted", isSignal: true, isRequired: false, transformFunction: null } }, providers: [provideRdxPresenceContext(() => ({ present: inject(RdxNavigationMenuPortal).present }))], exportAs: ["rdxNavigationMenuPortal"], hostDirectives: [{ directive: i1$2.RdxPortalPresence, inputs: ["container", "container"] }], ngImport: i0 }); }
|
|
486
582
|
}
|
|
487
583
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuPortal, decorators: [{
|
|
488
584
|
type: Directive,
|
|
@@ -490,9 +586,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
490
586
|
selector: 'ng-template[rdxNavigationMenuPortal]',
|
|
491
587
|
exportAs: 'rdxNavigationMenuPortal',
|
|
492
588
|
hostDirectives: [{ directive: RdxPortalPresence, inputs: ['container'] }],
|
|
493
|
-
providers: [provideRdxPresenceContext(() => ({ present:
|
|
589
|
+
providers: [provideRdxPresenceContext(() => ({ present: inject(RdxNavigationMenuPortal).present }))]
|
|
494
590
|
}]
|
|
495
|
-
}] });
|
|
591
|
+
}], propDecorators: { keepMounted: [{ type: i0.Input, args: [{ isSignal: true, alias: "keepMounted", required: false }] }] } });
|
|
496
592
|
/**
|
|
497
593
|
* Dev-mode guard: `rdxNavigationMenuPortal` used to be an attribute directive on a `<div>`. It is now
|
|
498
594
|
* structural, so the old `<div rdxNavigationMenuPortal>` markup would silently stop portaling — fail
|
|
@@ -532,13 +628,28 @@ class RdxNavigationMenuPositioner extends RdxPopperContentWrapper {
|
|
|
532
628
|
this.triggerEl = signal(null, ...(ngDevMode ? [{ debugName: "triggerEl" }] : /* istanbul ignore next */ []));
|
|
533
629
|
this.containerEl = signal(this.containerRef.nativeElement, ...(ngDevMode ? [{ debugName: "containerEl" }] : /* istanbul ignore next */ []));
|
|
534
630
|
this.graceArea = useGraceArea(this.triggerEl, this.containerEl);
|
|
631
|
+
const destroyRef = inject(DestroyRef);
|
|
535
632
|
effect(() => this.triggerEl.set(this.rootContext.trigger() ?? null));
|
|
633
|
+
effect((onCleanup) => {
|
|
634
|
+
const list = this.rootContext.list();
|
|
635
|
+
const inTransit = this.graceArea.isPointerInTransit();
|
|
636
|
+
if (!list || !inTransit) {
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
const previous = list.style.pointerEvents;
|
|
640
|
+
list.style.pointerEvents = 'none';
|
|
641
|
+
onCleanup(() => {
|
|
642
|
+
if (!destroyRef.destroyed) {
|
|
643
|
+
list.style.pointerEvents = previous;
|
|
644
|
+
}
|
|
645
|
+
});
|
|
646
|
+
});
|
|
536
647
|
// Keep the menu open while the pointer travels from the trigger to the popup; close once it
|
|
537
648
|
// leaves the grace area between them.
|
|
538
649
|
this.graceArea.onPointerExit(() => this.rootContext.closeOnHover());
|
|
539
650
|
}
|
|
540
651
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuPositioner, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
541
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxNavigationMenuPositioner, isStandalone: true, selector: "[rdxNavigationMenuPositioner]", host: { properties: { "attr.data-open": "rootContext.isOpen() ? \"\" : undefined", "attr.data-closed": "rootContext.isOpen() ? undefined : \"\"", "attr.data-instant": "rootContext.instant() ? \"\" : undefined" } }, providers: [
|
|
652
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxNavigationMenuPositioner, isStandalone: true, selector: "[rdxNavigationMenuPositioner]", host: { properties: { "attr.data-open": "rootContext.isOpen() ? \"\" : undefined", "attr.data-closed": "rootContext.isOpen() ? undefined : \"\"", "attr.data-instant": "rootContext.instant() ? \"\" : undefined", "style.--positioner-width.px": "rootContext.size()?.width", "style.--positioner-height.px": "rootContext.size()?.height" } }, providers: [
|
|
542
653
|
...provideRdxPopperContentWrapper(RdxNavigationMenuPositioner),
|
|
543
654
|
provideRdxPopperContentConfig({ arrowPadding: 5, collisionPadding: 5, updatePositionStrategy: 'always' })
|
|
544
655
|
], usesInheritance: true, ngImport: i0 }); }
|
|
@@ -554,7 +665,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
554
665
|
host: {
|
|
555
666
|
'[attr.data-open]': 'rootContext.isOpen() ? "" : undefined',
|
|
556
667
|
'[attr.data-closed]': 'rootContext.isOpen() ? undefined : ""',
|
|
557
|
-
'[attr.data-instant]': 'rootContext.instant() ? "" : undefined'
|
|
668
|
+
'[attr.data-instant]': 'rootContext.instant() ? "" : undefined',
|
|
669
|
+
'[style.--positioner-width.px]': 'rootContext.size()?.width',
|
|
670
|
+
'[style.--positioner-height.px]': 'rootContext.size()?.height'
|
|
558
671
|
}
|
|
559
672
|
}]
|
|
560
673
|
}], ctorParameters: () => [] });
|
|
@@ -598,10 +711,6 @@ class RdxNavigationMenuRoot {
|
|
|
598
711
|
*/
|
|
599
712
|
this.dirInput = input(undefined, { ...(ngDevMode ? { debugName: "dirInput" } : /* istanbul ignore next */ {}), alias: 'dir' });
|
|
600
713
|
this.dir = injectDirection(this.dirInput);
|
|
601
|
-
/**
|
|
602
|
-
* Whether keyboard navigation loops from the last item back to the first and vice versa.
|
|
603
|
-
*/
|
|
604
|
-
this.loop = input(false, { ...(ngDevMode ? { debugName: "loop" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
605
714
|
/**
|
|
606
715
|
* How long to wait before opening the menu on hover, in milliseconds.
|
|
607
716
|
*/
|
|
@@ -635,7 +744,10 @@ class RdxNavigationMenuRoot {
|
|
|
635
744
|
this.present = computed(() => this.isOpen() || this.preventUnmountOnClose(), ...(ngDevMode ? [{ debugName: "present" }] : /* istanbul ignore next */ []));
|
|
636
745
|
this.trigger = signal(undefined, ...(ngDevMode ? [{ debugName: "trigger" }] : /* istanbul ignore next */ []));
|
|
637
746
|
this.triggers = signal([], ...(ngDevMode ? [{ debugName: "triggers" }] : /* istanbul ignore next */ []));
|
|
747
|
+
this.list = signal(undefined, ...(ngDevMode ? [{ debugName: "list" }] : /* istanbul ignore next */ []));
|
|
638
748
|
this.contents = signal(new Map(), ...(ngDevMode ? [{ debugName: "contents" }] : /* istanbul ignore next */ []));
|
|
749
|
+
this.popup = signal(undefined, ...(ngDevMode ? [{ debugName: "popup" }] : /* istanbul ignore next */ []));
|
|
750
|
+
this.size = signal(null, ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
639
751
|
this.activeContent = computed(() => {
|
|
640
752
|
const value = this.value() ?? this.previousValue();
|
|
641
753
|
return value ? this.contents().get(value) : undefined;
|
|
@@ -688,7 +800,7 @@ class RdxNavigationMenuRoot {
|
|
|
688
800
|
if (change.eventDetails.isCanceled()) {
|
|
689
801
|
return;
|
|
690
802
|
}
|
|
691
|
-
this.instant.set(changedTriggerWhileOpen
|
|
803
|
+
this.instant.set(changedTriggerWhileOpen);
|
|
692
804
|
if (changedTriggerWhileOpen) {
|
|
693
805
|
this.scheduleInstantReset();
|
|
694
806
|
if (previousTrigger && nextTrigger) {
|
|
@@ -702,6 +814,9 @@ class RdxNavigationMenuRoot {
|
|
|
702
814
|
this.preventUnmountOnClose.set(value === null ? change.shouldPreventUnmountOnClose() : false);
|
|
703
815
|
this.value.set(value);
|
|
704
816
|
this.onValueChange.emit(value);
|
|
817
|
+
if (this.nested && value === null && reason === 'link-select') {
|
|
818
|
+
this.parentRoot?.close(reason, event);
|
|
819
|
+
}
|
|
705
820
|
}
|
|
706
821
|
open(value, trigger, reason = 'none', event) {
|
|
707
822
|
this.clearHoverTimers();
|
|
@@ -786,6 +901,14 @@ class RdxNavigationMenuRoot {
|
|
|
786
901
|
});
|
|
787
902
|
};
|
|
788
903
|
}
|
|
904
|
+
registerList(list) {
|
|
905
|
+
this.list.set(list);
|
|
906
|
+
return () => {
|
|
907
|
+
if (this.list() === list) {
|
|
908
|
+
this.list.set(undefined);
|
|
909
|
+
}
|
|
910
|
+
};
|
|
911
|
+
}
|
|
789
912
|
registerContent(entry) {
|
|
790
913
|
this.contents.update((contents) => new Map(contents).set(entry.value, entry));
|
|
791
914
|
return () => {
|
|
@@ -799,6 +922,17 @@ class RdxNavigationMenuRoot {
|
|
|
799
922
|
});
|
|
800
923
|
};
|
|
801
924
|
}
|
|
925
|
+
registerPopup(element) {
|
|
926
|
+
this.popup.set(element);
|
|
927
|
+
return () => {
|
|
928
|
+
if (this.popup() === element) {
|
|
929
|
+
this.popup.set(undefined);
|
|
930
|
+
}
|
|
931
|
+
};
|
|
932
|
+
}
|
|
933
|
+
setSize(size) {
|
|
934
|
+
this.size.set(size);
|
|
935
|
+
}
|
|
802
936
|
registerTransitionElement(element) {
|
|
803
937
|
return this.transition.registerElement(element);
|
|
804
938
|
}
|
|
@@ -851,7 +985,7 @@ class RdxNavigationMenuRoot {
|
|
|
851
985
|
});
|
|
852
986
|
}
|
|
853
987
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuRoot, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
854
|
-
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 }, dirInput: { classPropertyName: "dirInput", publicName: "dir", isSignal: true, isRequired: false, transformFunction: null },
|
|
988
|
+
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 }, dirInput: { classPropertyName: "dirInput", publicName: "dir", 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: { properties: { "attr.data-orientation": "orientation()", "attr.data-nested": "nested ? \"\" : undefined", "attr.dir": "dir()" } }, providers: [
|
|
855
989
|
provideNavigationMenuRootContext(context),
|
|
856
990
|
// Base UI wraps every NavigationMenu.Root in a FloatingNode and only creates FloatingTree at the
|
|
857
991
|
// top boundary. `provideFloatingTree()` is inherit-or-create, so nested navigation menus join the
|
|
@@ -879,20 +1013,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
879
1013
|
],
|
|
880
1014
|
hostDirectives: [RdxPopper, RdxFloatingNodeRegistration],
|
|
881
1015
|
host: {
|
|
882
|
-
role: 'navigation',
|
|
883
|
-
'aria-label': 'Main',
|
|
884
1016
|
'[attr.data-orientation]': 'orientation()',
|
|
1017
|
+
'[attr.data-nested]': 'nested ? "" : undefined',
|
|
885
1018
|
'[attr.dir]': 'dir()'
|
|
886
1019
|
}
|
|
887
1020
|
}]
|
|
888
|
-
}], 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 }] }], dirInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "dir", required: false }] }],
|
|
1021
|
+
}], 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 }] }], dirInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "dir", 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"] }] } });
|
|
889
1022
|
function contextFor(root) {
|
|
890
1023
|
return {
|
|
891
1024
|
nested: root.nested,
|
|
892
1025
|
baseId: root.baseId,
|
|
893
1026
|
orientation: root.orientation,
|
|
894
1027
|
dir: root.dir,
|
|
895
|
-
loop: root.loop,
|
|
896
1028
|
value: root.value,
|
|
897
1029
|
previousValue: root.previousValue.asReadonly(),
|
|
898
1030
|
isOpen: root.isOpen,
|
|
@@ -901,7 +1033,11 @@ function contextFor(root) {
|
|
|
901
1033
|
transitionStatus: root.transitionStatus,
|
|
902
1034
|
trigger: root.trigger.asReadonly(),
|
|
903
1035
|
triggers: root.triggers.asReadonly(),
|
|
1036
|
+
list: root.list.asReadonly(),
|
|
1037
|
+
contents: root.contents.asReadonly(),
|
|
904
1038
|
activeContent: root.activeContent,
|
|
1039
|
+
popup: root.popup.asReadonly(),
|
|
1040
|
+
size: root.size.asReadonly(),
|
|
905
1041
|
contentId: (value) => root.contentId(value),
|
|
906
1042
|
triggerId: (value) => root.triggerId(value),
|
|
907
1043
|
setValue: (value, reason, event) => root.setValue(value, reason, event),
|
|
@@ -912,8 +1048,11 @@ function contextFor(root) {
|
|
|
912
1048
|
closeOnHover: (event) => root.closeOnHover(event),
|
|
913
1049
|
cancelHoverOpen: () => root.cancelHoverOpen(),
|
|
914
1050
|
cancelHoverClose: () => root.cancelHoverClose(),
|
|
1051
|
+
setSize: (size) => root.setSize(size),
|
|
915
1052
|
registerTrigger: (value, trigger) => root.registerTrigger(value, trigger),
|
|
1053
|
+
registerList: (list) => root.registerList(list),
|
|
916
1054
|
registerContent: (entry) => root.registerContent(entry),
|
|
1055
|
+
registerPopup: (element) => root.registerPopup(element),
|
|
917
1056
|
registerTransitionElement: (element) => root.registerTransitionElement(element),
|
|
918
1057
|
registerViewport: (onTriggerChange) => root.registerViewport(onTriggerChange)
|
|
919
1058
|
};
|
|
@@ -941,7 +1080,6 @@ class RdxNavigationMenuTrigger {
|
|
|
941
1080
|
constructor() {
|
|
942
1081
|
this.item = inject(RdxNavigationMenuItem);
|
|
943
1082
|
this.rootContext = injectNavigationMenuRootContext();
|
|
944
|
-
this.rovingFocusItem = inject(RdxRovingFocusItemDirective, { self: true });
|
|
945
1083
|
this.elementRef = inject(ElementRef);
|
|
946
1084
|
this.document = injectDocument();
|
|
947
1085
|
/**
|
|
@@ -955,9 +1093,6 @@ class RdxNavigationMenuTrigger {
|
|
|
955
1093
|
this.open = computed(() => this.item.isOpen(), ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
|
|
956
1094
|
// Host element is available in the constructor; the trigger ref does not depend on inputs.
|
|
957
1095
|
this.item.triggerRef.set(this.elementRef.nativeElement);
|
|
958
|
-
effect(() => this.rovingFocusItem.setFocusable(!this.disabled()));
|
|
959
|
-
// `value` is an input on the item, so read it in an effect (kept in sync if it ever changes).
|
|
960
|
-
effect(() => this.rovingFocusItem.setTabStopId(this.item.value()));
|
|
961
1096
|
effect((onCleanup) => {
|
|
962
1097
|
const value = this.item.value();
|
|
963
1098
|
const element = this.elementRef.nativeElement;
|
|
@@ -985,17 +1120,6 @@ class RdxNavigationMenuTrigger {
|
|
|
985
1120
|
}
|
|
986
1121
|
this.rootContext.cancelHoverOpen();
|
|
987
1122
|
}
|
|
988
|
-
/**
|
|
989
|
-
* Open-follows-focus: while the menu is already open, moving keyboard focus (arrow keys via
|
|
990
|
-
* roving) to another trigger switches the shared popup to that item — matching Base UI, so the
|
|
991
|
-
* open menu visibly responds to arrow-key navigation. Focus never *opens* a closed menu.
|
|
992
|
-
*/
|
|
993
|
-
onFocus() {
|
|
994
|
-
if (this.disabled() || !this.rootContext.isOpen() || this.open()) {
|
|
995
|
-
return;
|
|
996
|
-
}
|
|
997
|
-
this.rootContext.open(this.item.value(), this.elementRef.nativeElement, 'trigger-focus');
|
|
998
|
-
}
|
|
999
1123
|
onKeydown(event) {
|
|
1000
1124
|
if (this.disabled()) {
|
|
1001
1125
|
return;
|
|
@@ -1009,10 +1133,13 @@ class RdxNavigationMenuTrigger {
|
|
|
1009
1133
|
}
|
|
1010
1134
|
return;
|
|
1011
1135
|
}
|
|
1136
|
+
if (this.rootContext.nested) {
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1012
1139
|
if (event.key === entryKey) {
|
|
1013
1140
|
event.preventDefault();
|
|
1014
1141
|
if (!this.open()) {
|
|
1015
|
-
this.rootContext.open(this.item.value(), this.elementRef.nativeElement, '
|
|
1142
|
+
this.rootContext.open(this.item.value(), this.elementRef.nativeElement, 'list-navigation', event);
|
|
1016
1143
|
}
|
|
1017
1144
|
this.focusContent();
|
|
1018
1145
|
}
|
|
@@ -1048,19 +1175,18 @@ class RdxNavigationMenuTrigger {
|
|
|
1048
1175
|
requestAnimationFrame(tryFocus);
|
|
1049
1176
|
}
|
|
1050
1177
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuTrigger, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1051
|
-
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)"
|
|
1178
|
+
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)" }, properties: { "id": "item.triggerId()", "attr.aria-controls": "open() ? rootContext.popup()?.id : undefined", "attr.aria-expanded": "open()", "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.RdxCompositeItem }], ngImport: i0 }); }
|
|
1052
1179
|
}
|
|
1053
1180
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuTrigger, decorators: [{
|
|
1054
1181
|
type: Directive,
|
|
1055
1182
|
args: [{
|
|
1056
1183
|
selector: 'button[rdxNavigationMenuTrigger]',
|
|
1057
|
-
hostDirectives: [
|
|
1184
|
+
hostDirectives: [RdxCompositeItem],
|
|
1058
1185
|
host: {
|
|
1059
1186
|
type: 'button',
|
|
1060
1187
|
'[id]': 'item.triggerId()',
|
|
1061
|
-
'[attr.aria-controls]': '
|
|
1188
|
+
'[attr.aria-controls]': 'open() ? rootContext.popup()?.id : undefined',
|
|
1062
1189
|
'[attr.aria-expanded]': 'open()',
|
|
1063
|
-
'[attr.aria-haspopup]': '"menu"',
|
|
1064
1190
|
'[attr.data-state]': 'open() ? "open" : "closed"',
|
|
1065
1191
|
'[attr.data-popup-open]': 'open() ? "" : undefined',
|
|
1066
1192
|
'[attr.data-disabled]': 'disabled() ? "" : undefined',
|
|
@@ -1068,8 +1194,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
1068
1194
|
'(click)': 'onClick($event)',
|
|
1069
1195
|
'(pointerenter)': 'onPointerEnter($event)',
|
|
1070
1196
|
'(pointerleave)': 'onPointerLeave($event)',
|
|
1071
|
-
'(keydown)': 'onKeydown($event)'
|
|
1072
|
-
'(focus)': 'onFocus()'
|
|
1197
|
+
'(keydown)': 'onKeydown($event)'
|
|
1073
1198
|
}
|
|
1074
1199
|
}]
|
|
1075
1200
|
}], ctorParameters: () => [], propDecorators: { disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], openOnHover: [{ type: i0.Input, args: [{ isSignal: true, alias: "openOnHover", required: false }] }] } });
|
|
@@ -1093,37 +1218,66 @@ class RdxNavigationMenuViewport {
|
|
|
1093
1218
|
this.transitioning = signal(false, ...(ngDevMode ? [{ debugName: "transitioning" }] : /* istanbul ignore next */ []));
|
|
1094
1219
|
this.size = signal(null, ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
1095
1220
|
this.current = null;
|
|
1221
|
+
this.rendered = new Map();
|
|
1096
1222
|
this.previousElement = null;
|
|
1097
1223
|
this.resizeObserver = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(() => this.measure()) : null;
|
|
1098
|
-
this.activeContent = computed(() => this.rootContext.
|
|
1224
|
+
this.activeContent = computed(() => this.rootContext.isOpen() || this.rootContext.transitionStatus() === 'ending'
|
|
1225
|
+
? this.rootContext.activeContent()
|
|
1226
|
+
: undefined, ...(ngDevMode ? [{ debugName: "activeContent" }] : /* istanbul ignore next */ []));
|
|
1227
|
+
this.contents = computed(() => this.rootContext.contents(), ...(ngDevMode ? [{ debugName: "contents" }] : /* istanbul ignore next */ []));
|
|
1099
1228
|
const unregister = this.rootContext.registerViewport((previous, next) => {
|
|
1100
1229
|
this.pendingDirection = getActivationDirection(previous, next);
|
|
1101
1230
|
});
|
|
1102
1231
|
effect(() => {
|
|
1103
1232
|
const entry = this.activeContent();
|
|
1104
|
-
|
|
1233
|
+
const contents = this.contents();
|
|
1234
|
+
untracked(() => this.sync(contents, entry));
|
|
1105
1235
|
});
|
|
1106
1236
|
inject(DestroyRef).onDestroy(() => {
|
|
1107
1237
|
unregister();
|
|
1108
1238
|
this.resizeObserver?.disconnect();
|
|
1109
1239
|
this.clearCleanupTimer();
|
|
1110
1240
|
this.removePrevious();
|
|
1111
|
-
this.
|
|
1241
|
+
this.rendered.forEach((content) => content.view.destroy());
|
|
1242
|
+
this.rendered.clear();
|
|
1243
|
+
this.current = null;
|
|
1112
1244
|
});
|
|
1113
1245
|
}
|
|
1246
|
+
sync(contents, activeEntry) {
|
|
1247
|
+
for (const content of [...this.rendered.values()]) {
|
|
1248
|
+
if (content.value === activeEntry?.value) {
|
|
1249
|
+
continue;
|
|
1250
|
+
}
|
|
1251
|
+
const latestEntry = contents.get(content.value);
|
|
1252
|
+
if (!latestEntry || !latestEntry.forceMount()) {
|
|
1253
|
+
if (content !== this.current) {
|
|
1254
|
+
this.destroyRendered(content);
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
for (const entry of contents.values()) {
|
|
1259
|
+
if (entry.value !== activeEntry?.value && entry.forceMount() && !this.rendered.has(entry.value)) {
|
|
1260
|
+
this.markInactive(this.createRendered(entry));
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
this.render(activeEntry);
|
|
1264
|
+
}
|
|
1114
1265
|
render(entry) {
|
|
1115
1266
|
if (!entry) {
|
|
1267
|
+
this.deactivateCurrent();
|
|
1116
1268
|
return;
|
|
1117
1269
|
}
|
|
1118
1270
|
if (this.current?.value === entry.value) {
|
|
1119
1271
|
return;
|
|
1120
1272
|
}
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1273
|
+
this.deactivateCurrent();
|
|
1274
|
+
const next = this.rendered.get(entry.value) ?? this.createRendered(entry);
|
|
1275
|
+
this.markCurrent(next);
|
|
1276
|
+
this.current = next;
|
|
1277
|
+
this.resizeObserver?.observe(next.measureTarget);
|
|
1278
|
+
this.measure();
|
|
1279
|
+
}
|
|
1280
|
+
createRendered(entry) {
|
|
1127
1281
|
const view = this.viewContainerRef.createEmbeddedView(entry.templateRef);
|
|
1128
1282
|
view.detectChanges();
|
|
1129
1283
|
const element = this.renderer.createElement('div');
|
|
@@ -1138,9 +1292,48 @@ class RdxNavigationMenuViewport {
|
|
|
1138
1292
|
// and stretches to the viewport, so measuring it would feed the viewport its own width back
|
|
1139
1293
|
// and the popup would balloon to fill the page. The content root sizes to its content.
|
|
1140
1294
|
const measureTarget = element.firstElementChild ?? element;
|
|
1141
|
-
|
|
1142
|
-
this.
|
|
1143
|
-
|
|
1295
|
+
const rendered = { value: entry.value, entry, view, element, measureTarget };
|
|
1296
|
+
this.rendered.set(entry.value, rendered);
|
|
1297
|
+
return rendered;
|
|
1298
|
+
}
|
|
1299
|
+
deactivateCurrent() {
|
|
1300
|
+
const current = this.current;
|
|
1301
|
+
if (!current) {
|
|
1302
|
+
return;
|
|
1303
|
+
}
|
|
1304
|
+
this.resizeObserver?.unobserve(current.measureTarget);
|
|
1305
|
+
if (current.entry.forceMount()) {
|
|
1306
|
+
this.markInactive(current);
|
|
1307
|
+
}
|
|
1308
|
+
else {
|
|
1309
|
+
this.startLeave(current.element);
|
|
1310
|
+
current.view.destroy();
|
|
1311
|
+
this.rendered.delete(current.value);
|
|
1312
|
+
}
|
|
1313
|
+
this.current = null;
|
|
1314
|
+
}
|
|
1315
|
+
markCurrent(content) {
|
|
1316
|
+
content.element.hidden = false;
|
|
1317
|
+
content.element.removeAttribute('aria-hidden');
|
|
1318
|
+
content.element.removeAttribute('inert');
|
|
1319
|
+
content.element.setAttribute('data-current', '');
|
|
1320
|
+
content.element.removeAttribute('data-previous');
|
|
1321
|
+
}
|
|
1322
|
+
markInactive(content) {
|
|
1323
|
+
this.resizeObserver?.unobserve(content.measureTarget);
|
|
1324
|
+
content.element.hidden = true;
|
|
1325
|
+
content.element.setAttribute('aria-hidden', 'true');
|
|
1326
|
+
content.element.setAttribute('inert', '');
|
|
1327
|
+
content.element.removeAttribute('data-current');
|
|
1328
|
+
content.element.removeAttribute('data-previous');
|
|
1329
|
+
}
|
|
1330
|
+
destroyRendered(content) {
|
|
1331
|
+
this.resizeObserver?.unobserve(content.measureTarget);
|
|
1332
|
+
content.view.destroy();
|
|
1333
|
+
this.rendered.delete(content.value);
|
|
1334
|
+
if (this.current === content) {
|
|
1335
|
+
this.current = null;
|
|
1336
|
+
}
|
|
1144
1337
|
}
|
|
1145
1338
|
startLeave(element) {
|
|
1146
1339
|
this.removePrevious();
|
|
@@ -1185,11 +1378,13 @@ class RdxNavigationMenuViewport {
|
|
|
1185
1378
|
}
|
|
1186
1379
|
const size = this.size();
|
|
1187
1380
|
if (!size || size.width !== width || size.height !== height) {
|
|
1188
|
-
|
|
1381
|
+
const nextSize = { width, height };
|
|
1382
|
+
this.size.set(nextSize);
|
|
1383
|
+
this.rootContext.setSize(nextSize);
|
|
1189
1384
|
}
|
|
1190
1385
|
}
|
|
1191
1386
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuViewport, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1192
|
-
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"
|
|
1387
|
+
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" } }, ngImport: i0 }); }
|
|
1193
1388
|
}
|
|
1194
1389
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuViewport, decorators: [{
|
|
1195
1390
|
type: Directive,
|
|
@@ -1201,9 +1396,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
1201
1396
|
'[attr.data-state]': 'rootContext.isOpen() ? "open" : "closed"',
|
|
1202
1397
|
'[attr.data-orientation]': 'rootContext.orientation()',
|
|
1203
1398
|
'[attr.data-activation-direction]': 'activationDirection()',
|
|
1204
|
-
'[attr.data-transitioning]': 'transitioning() ? "" : undefined'
|
|
1205
|
-
'[style.--popup-width.px]': 'size()?.width',
|
|
1206
|
-
'[style.--popup-height.px]': 'size()?.height'
|
|
1399
|
+
'[attr.data-transitioning]': 'transitioning() ? "" : undefined'
|
|
1207
1400
|
}
|
|
1208
1401
|
}]
|
|
1209
1402
|
}], ctorParameters: () => [], propDecorators: { forceMount: [{ type: i0.Input, args: [{ isSignal: true, alias: "forceMount", required: false }] }] } });
|