@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.
Files changed (62) hide show
  1. package/composite/README.md +3 -0
  2. package/fesm2022/radix-ng-primitives-accordion.mjs +20 -44
  3. package/fesm2022/radix-ng-primitives-accordion.mjs.map +1 -1
  4. package/fesm2022/radix-ng-primitives-checkbox.mjs +134 -58
  5. package/fesm2022/radix-ng-primitives-checkbox.mjs.map +1 -1
  6. package/fesm2022/radix-ng-primitives-composite.mjs +599 -0
  7. package/fesm2022/radix-ng-primitives-composite.mjs.map +1 -0
  8. package/fesm2022/radix-ng-primitives-drawer.mjs +442 -2
  9. package/fesm2022/radix-ng-primitives-drawer.mjs.map +1 -1
  10. package/fesm2022/radix-ng-primitives-menu.mjs +315 -68
  11. package/fesm2022/radix-ng-primitives-menu.mjs.map +1 -1
  12. package/fesm2022/radix-ng-primitives-menubar.mjs +91 -36
  13. package/fesm2022/radix-ng-primitives-menubar.mjs.map +1 -1
  14. package/fesm2022/radix-ng-primitives-navigation-menu.mjs +281 -88
  15. package/fesm2022/radix-ng-primitives-navigation-menu.mjs.map +1 -1
  16. package/fesm2022/radix-ng-primitives-popover.mjs +40 -15
  17. package/fesm2022/radix-ng-primitives-popover.mjs.map +1 -1
  18. package/fesm2022/radix-ng-primitives-popper.mjs +73 -65
  19. package/fesm2022/radix-ng-primitives-popper.mjs.map +1 -1
  20. package/fesm2022/radix-ng-primitives-radio.mjs +63 -27
  21. package/fesm2022/radix-ng-primitives-radio.mjs.map +1 -1
  22. package/fesm2022/radix-ng-primitives-scroll-area.mjs +56 -25
  23. package/fesm2022/radix-ng-primitives-scroll-area.mjs.map +1 -1
  24. package/fesm2022/radix-ng-primitives-select.mjs +59 -29
  25. package/fesm2022/radix-ng-primitives-select.mjs.map +1 -1
  26. package/fesm2022/radix-ng-primitives-slider.mjs +57 -13
  27. package/fesm2022/radix-ng-primitives-slider.mjs.map +1 -1
  28. package/fesm2022/radix-ng-primitives-tabs.mjs +335 -73
  29. package/fesm2022/radix-ng-primitives-tabs.mjs.map +1 -1
  30. package/fesm2022/radix-ng-primitives-toggle-group.mjs +66 -21
  31. package/fesm2022/radix-ng-primitives-toggle-group.mjs.map +1 -1
  32. package/fesm2022/radix-ng-primitives-toggle.mjs +29 -11
  33. package/fesm2022/radix-ng-primitives-toggle.mjs.map +1 -1
  34. package/fesm2022/radix-ng-primitives-toolbar.mjs +68 -36
  35. package/fesm2022/radix-ng-primitives-toolbar.mjs.map +1 -1
  36. package/navigation-menu/README.md +5 -2
  37. package/package.json +6 -10
  38. package/types/radix-ng-primitives-accordion.d.ts +12 -16
  39. package/types/radix-ng-primitives-checkbox.d.ts +98 -70
  40. package/types/radix-ng-primitives-composite.d.ts +195 -0
  41. package/types/radix-ng-primitives-drawer.d.ts +40 -2
  42. package/types/radix-ng-primitives-menu.d.ts +46 -16
  43. package/types/radix-ng-primitives-menubar.d.ts +12 -5
  44. package/types/radix-ng-primitives-navigation-menu.d.ts +65 -33
  45. package/types/radix-ng-primitives-popover.d.ts +9 -5
  46. package/types/radix-ng-primitives-popper.d.ts +1 -0
  47. package/types/radix-ng-primitives-radio.d.ts +11 -9
  48. package/types/radix-ng-primitives-scroll-area.d.ts +4 -1
  49. package/types/radix-ng-primitives-select.d.ts +46 -32
  50. package/types/radix-ng-primitives-slider.d.ts +19 -4
  51. package/types/radix-ng-primitives-tabs.d.ts +69 -14
  52. package/types/radix-ng-primitives-toggle-group.d.ts +27 -16
  53. package/types/radix-ng-primitives-toggle.d.ts +5 -5
  54. package/types/radix-ng-primitives-toolbar.d.ts +84 -69
  55. package/collection/README.md +0 -1
  56. package/fesm2022/radix-ng-primitives-collection.mjs +0 -72
  57. package/fesm2022/radix-ng-primitives-collection.mjs.map +0 -1
  58. package/fesm2022/radix-ng-primitives-roving-focus.mjs +0 -388
  59. package/fesm2022/radix-ng-primitives-roving-focus.mjs.map +0 -1
  60. package/roving-focus/README.md +0 -3
  61. package/types/radix-ng-primitives-collection.d.ts +0 -44
  62. package/types/radix-ng-primitives-roving-focus.d.ts +0 -187
@@ -7,19 +7,83 @@ import { createContext, createFloatingRootContext, useTransitionStatus, createCa
7
7
  import { injectDirection } from '@radix-ng/primitives/direction-provider';
8
8
  import * as i3 from '@radix-ng/primitives/floating-focus-manager';
9
9
  import { getInteractionTypeFromEvent, RdxFloatingFocusManager, provideFloatingFocusManagerConfig, createRdxTriggerInteraction, useTriggerFocusGuards } from '@radix-ng/primitives/floating-focus-manager';
10
+ import * as i1$1 from '@radix-ng/primitives/composite';
11
+ import { isElementVisible, RdxCompositeListItem, RdxCompositeList } from '@radix-ng/primitives/composite';
10
12
  import { outputFromObservable, outputToObservable } from '@angular/core/rxjs-interop';
11
13
  import { RdxDismiss } from '@radix-ng/primitives/dismissable-layer';
12
14
  import { RdxFocusScope } from '@radix-ng/primitives/focus-scope';
13
- import * as i1$1 from '@radix-ng/primitives/portal';
15
+ import * as i1$2 from '@radix-ng/primitives/portal';
14
16
  import { RdxPortalPresence } from '@radix-ng/primitives/portal';
15
17
  import { provideRdxPresenceContext } from '@radix-ng/primitives/presence';
16
18
  import { isPlatformBrowser } from '@angular/common';
17
19
 
20
+ /** Selector for menu items within a popup. Disabled items stay focusable to match Base UI. */
21
+ const RDX_MENU_ITEM_SELECTOR = [
22
+ '[rdxMenuItem]',
23
+ '[rdxMenuCheckboxItem]',
24
+ '[rdxMenuRadioItem]',
25
+ '[rdxMenuLinkItem]',
26
+ '[rdxMenuSubTrigger]'
27
+ ].join(',');
28
+ function getFocusableMenuItems(popup) {
29
+ return Array.from(popup.querySelectorAll(RDX_MENU_ITEM_SELECTOR)).filter((item) => item.closest('[rdxMenuPopup]') === popup);
30
+ }
31
+ function getCompositeMenuItems(list) {
32
+ const map = list.itemMap();
33
+ return list
34
+ .items()
35
+ .map(({ element }) => {
36
+ const metadata = map.get(element);
37
+ if (!metadata || !isElementVisible(element)) {
38
+ return null;
39
+ }
40
+ return {
41
+ element,
42
+ index: metadata.index,
43
+ label: getMenuItemLabel(element, metadata),
44
+ metadata
45
+ };
46
+ })
47
+ .filter((item) => item !== null);
48
+ }
49
+ function getDomMenuItems(popup) {
50
+ return getFocusableMenuItems(popup).map((element, index) => ({
51
+ element,
52
+ index,
53
+ label: getMenuItemLabel(element),
54
+ metadata: {
55
+ index,
56
+ type: getMenuItemType(element),
57
+ disabled: element.hasAttribute('data-disabled'),
58
+ label: element.dataset['label']
59
+ }
60
+ }));
61
+ }
62
+ function getMenuItemLabel(element, metadata) {
63
+ return (metadata?.label ?? element.dataset['label'] ?? element.textContent?.trim() ?? '').toLowerCase();
64
+ }
65
+ function getMenuItemType(element) {
66
+ if (element.hasAttribute('rdxMenuCheckboxItem')) {
67
+ return 'checkbox-item';
68
+ }
69
+ if (element.hasAttribute('rdxMenuRadioItem')) {
70
+ return 'radio-item';
71
+ }
72
+ if (element.hasAttribute('rdxMenuLinkItem')) {
73
+ return 'link-item';
74
+ }
75
+ if (element.hasAttribute('rdxMenuSubTrigger')) {
76
+ return 'submenu-trigger';
77
+ }
78
+ return 'regular-item';
79
+ }
80
+
18
81
  const [injectRdxMenuRootContext, provideRdxMenuRootContext] = createContext('RdxMenuRootContext', 'components/menu');
19
82
  function buildContext(instance) {
20
83
  return {
21
84
  isOpen: instance.open,
22
85
  present: instance.present,
86
+ activeIndex: instance.activeIndex.asReadonly(),
23
87
  disabled: instance.effectiveDisabled,
24
88
  modal: instance.effectiveModal,
25
89
  loopFocus: instance.loopFocus,
@@ -41,6 +105,7 @@ function buildContext(instance) {
41
105
  beforeContentFocusGuard: instance.beforeContentFocusGuard.asReadonly(),
42
106
  transitionStatus: instance.transitionStatus,
43
107
  close: (reason, event) => instance.close(reason, event),
108
+ setActiveIndex: (index) => instance.setActiveIndex(index),
44
109
  closeEntireMenu: (reason, event) => instance.closeEntireMenu(reason, event),
45
110
  toggle: (reason, event) => instance.toggle(reason, event),
46
111
  show: (autoFocus, reason, event) => instance.show(autoFocus, reason, event),
@@ -109,6 +174,7 @@ class RdxMenuRoot {
109
174
  this.popupElement = signal(undefined, ...(ngDevMode ? [{ debugName: "popupElement" }] : /* istanbul ignore next */ []));
110
175
  this.beforeContentFocusGuard = signal(null, ...(ngDevMode ? [{ debugName: "beforeContentFocusGuard" }] : /* istanbul ignore next */ []));
111
176
  this.transitionStatus = this.transition.status;
177
+ this.activeIndex = signal(null, ...(ngDevMode ? [{ debugName: "activeIndex" }] : /* istanbul ignore next */ []));
112
178
  /** Whether the popup grabs focus when it opens. Set false for menubar hover-switching. */
113
179
  this.autoFocus = signal('first', ...(ngDevMode ? [{ debugName: "autoFocus" }] : /* istanbul ignore next */ []));
114
180
  this.isSubmenu = signal(false, ...(ngDevMode ? [{ debugName: "isSubmenu" }] : /* istanbul ignore next */ []));
@@ -164,6 +230,11 @@ class RdxMenuRoot {
164
230
  this.preventUnmountOnClose.set(false);
165
231
  }
166
232
  });
233
+ effect(() => {
234
+ if (!this.open()) {
235
+ this.activeIndex.set(null);
236
+ }
237
+ });
167
238
  let previousOpen = this.open();
168
239
  effect(() => {
169
240
  const open = this.open();
@@ -177,7 +248,12 @@ class RdxMenuRoot {
177
248
  if (this.effectiveDisabled()) {
178
249
  return;
179
250
  }
180
- this.autoFocus.set(autoFocus === true ? 'first' : autoFocus);
251
+ const nextAutoFocus = autoFocus === true ? 'first' : autoFocus;
252
+ this.autoFocus.set(nextAutoFocus);
253
+ if (this.open()) {
254
+ this.focusOpenPopup(nextAutoFocus);
255
+ return;
256
+ }
181
257
  if (!this.open()) {
182
258
  const change = this.createOpenChangeEvent(true, reason, event);
183
259
  this.onOpenChange.emit(change.payload);
@@ -225,6 +301,14 @@ class RdxMenuRoot {
225
301
  markAsContextMenu() {
226
302
  this.isContextMenu.set(true);
227
303
  }
304
+ setActiveIndex(index) {
305
+ if (this.effectiveDisabled() && index !== null) {
306
+ return;
307
+ }
308
+ if (this.activeIndex() !== index) {
309
+ this.activeIndex.set(index);
310
+ }
311
+ }
228
312
  setAllowMouseUpTrigger(value) {
229
313
  if (this.parentRoot) {
230
314
  this.parentRoot.setAllowMouseUpTrigger(value);
@@ -310,6 +394,23 @@ class RdxMenuRoot {
310
394
  }
311
395
  };
312
396
  }
397
+ focusOpenPopup(autoFocus) {
398
+ if (autoFocus === false) {
399
+ return;
400
+ }
401
+ const popup = this.popupElement();
402
+ if (!popup) {
403
+ return;
404
+ }
405
+ if (autoFocus === 'popup') {
406
+ this.setActiveIndex(null);
407
+ popup.focus({ preventScroll: true });
408
+ return;
409
+ }
410
+ const items = getFocusableMenuItems(popup);
411
+ const item = autoFocus === 'last' ? items.at(-1) : items[0];
412
+ (item ?? popup).focus({ preventScroll: true });
413
+ }
313
414
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxMenuRoot, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
314
415
  static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxMenuRoot, isStandalone: true, selector: "[rdxMenuRoot],[rdxMenuSubmenuRoot]", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, defaultOpen: { classPropertyName: "defaultOpen", publicName: "defaultOpen", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, modal: { classPropertyName: "modal", publicName: "modal", isSignal: true, isRequired: false, transformFunction: null }, loopFocus: { classPropertyName: "loopFocus", publicName: "loopFocus", isSignal: true, isRequired: false, transformFunction: null }, highlightItemOnHover: { classPropertyName: "highlightItemOnHover", publicName: "highlightItemOnHover", 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 }, closeParentOnEsc: { classPropertyName: "closeParentOnEsc", publicName: "closeParentOnEsc", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange", onOpenChange: "onOpenChange", onOpenChangeComplete: "onOpenChangeComplete" }, providers: [
315
416
  provideRdxMenuRootContext(contextFactory),
@@ -413,6 +514,7 @@ const checkboxItemContextFactory = () => {
413
514
  class RdxMenuCheckboxItem {
414
515
  constructor() {
415
516
  this.rootContext = injectRdxMenuRootContext(true);
517
+ this.listItem = inject(RdxCompositeListItem, { self: true });
416
518
  this.elementRef = inject(ElementRef);
417
519
  this.isFocused = signal(false, ...(ngDevMode ? [{ debugName: "isFocused" }] : /* istanbul ignore next */ []));
418
520
  /** Whether this item is disabled. */
@@ -425,19 +527,28 @@ class RdxMenuCheckboxItem {
425
527
  this.checked = model(false, ...(ngDevMode ? [{ debugName: "checked" }] : /* istanbul ignore next */ []));
426
528
  /** Emits when the checked state changes. */
427
529
  this.onCheckedChange = output();
428
- this.highlighted = computed(() => this.isFocused(), ...(ngDevMode ? [{ debugName: "highlighted" }] : /* istanbul ignore next */ []));
530
+ this.highlighted = computed(() => this.rootContext?.activeIndex() === this.listItem.index() || this.isFocused(), ...(ngDevMode ? [{ debugName: "highlighted" }] : /* istanbul ignore next */ []));
429
531
  this.effectiveDisabled = computed(() => this.disabled() || (this.rootContext?.disabled() ?? false), ...(ngDevMode ? [{ debugName: "effectiveDisabled" }] : /* istanbul ignore next */ []));
430
532
  // Expose helpers for host bindings
431
533
  this.isIndeterminate = isIndeterminate;
432
534
  this.getCheckedState = getCheckedState;
535
+ effect(() => {
536
+ this.listItem.setMetadata({
537
+ type: 'checkbox-item',
538
+ disabled: this.effectiveDisabled(),
539
+ label: this.label()
540
+ });
541
+ });
433
542
  }
434
543
  onFocus() {
435
- if (!this.effectiveDisabled()) {
544
+ if (!this.rootContext?.disabled()) {
436
545
  this.isFocused.set(true);
437
546
  }
547
+ this.setActiveIndex();
438
548
  }
439
549
  onBlur() {
440
550
  this.isFocused.set(false);
551
+ this.clearActiveIndex();
441
552
  }
442
553
  onPointerMove(event) {
443
554
  if (event.defaultPrevented || event.pointerType !== 'mouse' || this.effectiveDisabled()) {
@@ -446,6 +557,7 @@ class RdxMenuCheckboxItem {
446
557
  if (this.rootContext && !this.rootContext.highlightItemOnHover()) {
447
558
  return;
448
559
  }
560
+ this.setActiveIndex();
449
561
  if (this.elementRef.nativeElement.ownerDocument.activeElement !== this.elementRef.nativeElement) {
450
562
  this.elementRef.nativeElement.focus({ preventScroll: true });
451
563
  }
@@ -455,6 +567,8 @@ class RdxMenuCheckboxItem {
455
567
  return;
456
568
  }
457
569
  if (this.elementRef.nativeElement.ownerDocument.activeElement === this.elementRef.nativeElement) {
570
+ this.isFocused.set(false);
571
+ this.clearActiveIndex();
458
572
  this.elementRef.nativeElement.closest('[rdxMenuPopup]')?.focus({ preventScroll: true });
459
573
  }
460
574
  }
@@ -485,8 +599,22 @@ class RdxMenuCheckboxItem {
485
599
  this.checked.set(next);
486
600
  this.onCheckedChange.emit(next);
487
601
  }
602
+ setActiveIndex() {
603
+ if (!this.rootContext || this.rootContext.disabled()) {
604
+ return;
605
+ }
606
+ const index = this.listItem.index();
607
+ if (index !== -1) {
608
+ this.rootContext.setActiveIndex(index);
609
+ }
610
+ }
611
+ clearActiveIndex() {
612
+ if (this.rootContext?.activeIndex() === this.listItem.index()) {
613
+ this.rootContext.setActiveIndex(null);
614
+ }
615
+ }
488
616
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxMenuCheckboxItem, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
489
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxMenuCheckboxItem, isStandalone: true, selector: "[rdxMenuCheckboxItem]", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, closeOnClick: { classPropertyName: "closeOnClick", publicName: "closeOnClick", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, checked: { classPropertyName: "checked", publicName: "checked", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { checked: "checkedChange", onCheckedChange: "onCheckedChange" }, host: { attributes: { "role": "menuitemcheckbox", "tabindex": "-1" }, listeners: { "focus": "onFocus()", "blur": "onBlur()", "pointermove": "onPointerMove($event)", "pointerleave": "onPointerLeave($event)", "mouseup": "onMouseUp($event)", "click": "onItemClick()", "keydown.enter": "onActivate($event)", "keydown.space": "onActivate($event)" }, properties: { "attr.aria-checked": "isIndeterminate(checked()) ? \"mixed\" : checked()", "attr.data-state": "getCheckedState(checked())", "attr.data-disabled": "effectiveDisabled() ? \"\" : undefined", "attr.aria-disabled": "effectiveDisabled() ? true : undefined", "attr.data-highlighted": "highlighted() ? \"\" : undefined", "attr.data-label": "label() ?? undefined" } }, providers: [provideRdxMenuCheckboxItemContext(checkboxItemContextFactory)], exportAs: ["rdxMenuCheckboxItem"], ngImport: i0 }); }
617
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxMenuCheckboxItem, isStandalone: true, selector: "[rdxMenuCheckboxItem]", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, closeOnClick: { classPropertyName: "closeOnClick", publicName: "closeOnClick", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, checked: { classPropertyName: "checked", publicName: "checked", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { checked: "checkedChange", onCheckedChange: "onCheckedChange" }, host: { attributes: { "role": "menuitemcheckbox" }, listeners: { "focus": "onFocus()", "blur": "onBlur()", "pointermove": "onPointerMove($event)", "pointerleave": "onPointerLeave($event)", "mouseup": "onMouseUp($event)", "click": "onItemClick()", "keydown.enter": "onActivate($event)", "keydown.space": "onActivate($event)" }, properties: { "attr.tabindex": "rootContext?.isOpen() && highlighted() ? 0 : -1", "attr.aria-checked": "isIndeterminate(checked()) ? \"mixed\" : checked()", "attr.data-state": "getCheckedState(checked())", "attr.data-disabled": "effectiveDisabled() ? \"\" : undefined", "attr.aria-disabled": "effectiveDisabled() ? true : undefined", "attr.data-highlighted": "highlighted() ? \"\" : undefined", "attr.data-label": "label() ?? undefined" } }, providers: [provideRdxMenuCheckboxItemContext(checkboxItemContextFactory)], exportAs: ["rdxMenuCheckboxItem"], hostDirectives: [{ directive: i1$1.RdxCompositeListItem }], ngImport: i0 }); }
490
618
  }
491
619
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxMenuCheckboxItem, decorators: [{
492
620
  type: Directive,
@@ -494,9 +622,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
494
622
  selector: '[rdxMenuCheckboxItem]',
495
623
  exportAs: 'rdxMenuCheckboxItem',
496
624
  providers: [provideRdxMenuCheckboxItemContext(checkboxItemContextFactory)],
625
+ hostDirectives: [RdxCompositeListItem],
497
626
  host: {
498
627
  role: 'menuitemcheckbox',
499
- tabindex: '-1',
628
+ '[attr.tabindex]': 'rootContext?.isOpen() && highlighted() ? 0 : -1',
500
629
  '[attr.aria-checked]': 'isIndeterminate(checked()) ? "mixed" : checked()',
501
630
  '[attr.data-state]': 'getCheckedState(checked())',
502
631
  '[attr.data-disabled]': 'effectiveDisabled() ? "" : undefined',
@@ -513,7 +642,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
513
642
  '(keydown.space)': 'onActivate($event)'
514
643
  }
515
644
  }]
516
- }], propDecorators: { disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], closeOnClick: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnClick", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], checked: [{ type: i0.Input, args: [{ isSignal: true, alias: "checked", required: false }] }, { type: i0.Output, args: ["checkedChange"] }], onCheckedChange: [{ type: i0.Output, args: ["onCheckedChange"] }] } });
645
+ }], ctorParameters: () => [], propDecorators: { disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], closeOnClick: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnClick", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], checked: [{ type: i0.Input, args: [{ isSignal: true, alias: "checked", required: false }] }, { type: i0.Output, args: ["checkedChange"] }], onCheckedChange: [{ type: i0.Output, args: ["onCheckedChange"] }] } });
517
646
 
518
647
  /**
519
648
  * Renders when the parent checkbox item is checked or indeterminate.
@@ -607,6 +736,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
607
736
  class RdxMenuItem {
608
737
  constructor() {
609
738
  this.rootContext = injectRdxMenuRootContext(true);
739
+ this.listItem = inject(RdxCompositeListItem, { self: true });
610
740
  this.elementRef = inject(ElementRef);
611
741
  this.isFocused = signal(false, ...(ngDevMode ? [{ debugName: "isFocused" }] : /* istanbul ignore next */ []));
612
742
  /** Whether this item is disabled. */
@@ -617,16 +747,25 @@ class RdxMenuItem {
617
747
  this.label = input(undefined, ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
618
748
  /** Emits when the item is selected. */
619
749
  this.onSelect = output();
620
- this.highlighted = computed(() => this.isFocused(), ...(ngDevMode ? [{ debugName: "highlighted" }] : /* istanbul ignore next */ []));
750
+ this.highlighted = computed(() => this.rootContext?.activeIndex() === this.listItem.index() || this.isFocused(), ...(ngDevMode ? [{ debugName: "highlighted" }] : /* istanbul ignore next */ []));
621
751
  this.effectiveDisabled = computed(() => this.disabled() || (this.rootContext?.disabled() ?? false), ...(ngDevMode ? [{ debugName: "effectiveDisabled" }] : /* istanbul ignore next */ []));
752
+ effect(() => {
753
+ this.listItem.setMetadata({
754
+ type: 'regular-item',
755
+ disabled: this.effectiveDisabled(),
756
+ label: this.label()
757
+ });
758
+ });
622
759
  }
623
760
  onFocus() {
624
- if (!this.effectiveDisabled()) {
761
+ if (!this.rootContext?.disabled()) {
625
762
  this.isFocused.set(true);
626
763
  }
764
+ this.setActiveIndex();
627
765
  }
628
766
  onBlur() {
629
767
  this.isFocused.set(false);
768
+ this.clearActiveIndex();
630
769
  }
631
770
  onPointerMove(event) {
632
771
  if (event.defaultPrevented || event.pointerType !== 'mouse' || this.effectiveDisabled()) {
@@ -635,6 +774,7 @@ class RdxMenuItem {
635
774
  if (this.rootContext && !this.rootContext.highlightItemOnHover()) {
636
775
  return;
637
776
  }
777
+ this.setActiveIndex();
638
778
  if (this.elementRef.nativeElement.ownerDocument.activeElement !== this.elementRef.nativeElement) {
639
779
  this.elementRef.nativeElement.focus({ preventScroll: true });
640
780
  }
@@ -646,6 +786,8 @@ class RdxMenuItem {
646
786
  // Clear highlight when the pointer leaves: move focus back to the popup. A subsequent
647
787
  // pointermove on a sibling item re-focuses it, so moving between items still works.
648
788
  if (this.elementRef.nativeElement.ownerDocument.activeElement === this.elementRef.nativeElement) {
789
+ this.isFocused.set(false);
790
+ this.clearActiveIndex();
649
791
  this.elementRef.nativeElement.closest('[rdxMenuPopup]')?.focus({ preventScroll: true });
650
792
  }
651
793
  }
@@ -671,17 +813,32 @@ class RdxMenuItem {
671
813
  if (this.closeOnClick())
672
814
  this.rootContext?.closeEntireMenu();
673
815
  }
816
+ setActiveIndex() {
817
+ if (!this.rootContext || this.rootContext.disabled()) {
818
+ return;
819
+ }
820
+ const index = this.listItem.index();
821
+ if (index !== -1) {
822
+ this.rootContext.setActiveIndex(index);
823
+ }
824
+ }
825
+ clearActiveIndex() {
826
+ if (this.rootContext?.activeIndex() === this.listItem.index()) {
827
+ this.rootContext.setActiveIndex(null);
828
+ }
829
+ }
674
830
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxMenuItem, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
675
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxMenuItem, isStandalone: true, selector: "[rdxMenuItem]", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, closeOnClick: { classPropertyName: "closeOnClick", publicName: "closeOnClick", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSelect: "onSelect" }, host: { attributes: { "role": "menuitem", "tabindex": "-1" }, listeners: { "focus": "onFocus()", "blur": "onBlur()", "pointermove": "onPointerMove($event)", "pointerleave": "onPointerLeave($event)", "mouseup": "onMouseUp($event)", "click": "onItemClick()", "keydown.enter": "onActivate($event)", "keydown.space": "onActivate($event)" }, properties: { "attr.data-disabled": "effectiveDisabled() ? \"\" : undefined", "attr.aria-disabled": "effectiveDisabled() ? true : undefined", "attr.data-highlighted": "highlighted() ? \"\" : undefined", "attr.data-label": "label() ?? undefined" } }, exportAs: ["rdxMenuItem"], ngImport: i0 }); }
831
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxMenuItem, isStandalone: true, selector: "[rdxMenuItem]", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, closeOnClick: { classPropertyName: "closeOnClick", publicName: "closeOnClick", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSelect: "onSelect" }, host: { attributes: { "role": "menuitem" }, listeners: { "focus": "onFocus()", "blur": "onBlur()", "pointermove": "onPointerMove($event)", "pointerleave": "onPointerLeave($event)", "mouseup": "onMouseUp($event)", "click": "onItemClick()", "keydown.enter": "onActivate($event)", "keydown.space": "onActivate($event)" }, properties: { "attr.tabindex": "rootContext?.isOpen() && highlighted() ? 0 : -1", "attr.data-disabled": "effectiveDisabled() ? \"\" : undefined", "attr.aria-disabled": "effectiveDisabled() ? true : undefined", "attr.data-highlighted": "highlighted() ? \"\" : undefined", "attr.data-label": "label() ?? undefined" } }, exportAs: ["rdxMenuItem"], hostDirectives: [{ directive: i1$1.RdxCompositeListItem }], ngImport: i0 }); }
676
832
  }
677
833
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxMenuItem, decorators: [{
678
834
  type: Directive,
679
835
  args: [{
680
836
  selector: '[rdxMenuItem]',
681
837
  exportAs: 'rdxMenuItem',
838
+ hostDirectives: [RdxCompositeListItem],
682
839
  host: {
683
840
  role: 'menuitem',
684
- tabindex: '-1',
841
+ '[attr.tabindex]': 'rootContext?.isOpen() && highlighted() ? 0 : -1',
685
842
  '[attr.data-disabled]': 'effectiveDisabled() ? "" : undefined',
686
843
  '[attr.aria-disabled]': 'effectiveDisabled() ? true : undefined',
687
844
  '[attr.data-highlighted]': 'highlighted() ? "" : undefined',
@@ -696,7 +853,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
696
853
  '(keydown.space)': 'onActivate($event)'
697
854
  }
698
855
  }]
699
- }], propDecorators: { disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], closeOnClick: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnClick", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], onSelect: [{ type: i0.Output, args: ["onSelect"] }] } });
856
+ }], ctorParameters: () => [], propDecorators: { disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], closeOnClick: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnClick", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], onSelect: [{ type: i0.Output, args: ["onSelect"] }] } });
700
857
 
701
858
  /**
702
859
  * A menu item that renders as a link.
@@ -704,6 +861,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
704
861
  class RdxMenuLinkItem {
705
862
  constructor() {
706
863
  this.rootContext = injectRdxMenuRootContext(true);
864
+ this.listItem = inject(RdxCompositeListItem, { self: true });
707
865
  this.elementRef = inject(ElementRef);
708
866
  this.isFocused = signal(false, ...(ngDevMode ? [{ debugName: "isFocused" }] : /* istanbul ignore next */ []));
709
867
  /** Whether this item is disabled. */
@@ -714,16 +872,25 @@ class RdxMenuLinkItem {
714
872
  this.label = input(undefined, ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
715
873
  /** Emits when the item is selected. */
716
874
  this.onSelect = output();
717
- this.highlighted = computed(() => this.isFocused(), ...(ngDevMode ? [{ debugName: "highlighted" }] : /* istanbul ignore next */ []));
875
+ this.highlighted = computed(() => this.rootContext?.activeIndex() === this.listItem.index() || this.isFocused(), ...(ngDevMode ? [{ debugName: "highlighted" }] : /* istanbul ignore next */ []));
718
876
  this.effectiveDisabled = computed(() => this.disabled() || (this.rootContext?.disabled() ?? false), ...(ngDevMode ? [{ debugName: "effectiveDisabled" }] : /* istanbul ignore next */ []));
877
+ effect(() => {
878
+ this.listItem.setMetadata({
879
+ type: 'link-item',
880
+ disabled: this.effectiveDisabled(),
881
+ label: this.label()
882
+ });
883
+ });
719
884
  }
720
885
  onFocus() {
721
- if (!this.effectiveDisabled()) {
886
+ if (!this.rootContext?.disabled()) {
722
887
  this.isFocused.set(true);
723
888
  }
889
+ this.setActiveIndex();
724
890
  }
725
891
  onBlur() {
726
892
  this.isFocused.set(false);
893
+ this.clearActiveIndex();
727
894
  }
728
895
  onPointerMove(event) {
729
896
  if (event.defaultPrevented || event.pointerType !== 'mouse' || this.effectiveDisabled()) {
@@ -732,6 +899,7 @@ class RdxMenuLinkItem {
732
899
  if (this.rootContext && !this.rootContext.highlightItemOnHover()) {
733
900
  return;
734
901
  }
902
+ this.setActiveIndex();
735
903
  if (this.elementRef.nativeElement.ownerDocument.activeElement !== this.elementRef.nativeElement) {
736
904
  this.elementRef.nativeElement.focus({ preventScroll: true });
737
905
  }
@@ -741,6 +909,8 @@ class RdxMenuLinkItem {
741
909
  return;
742
910
  }
743
911
  if (this.elementRef.nativeElement.ownerDocument.activeElement === this.elementRef.nativeElement) {
912
+ this.isFocused.set(false);
913
+ this.clearActiveIndex();
744
914
  this.elementRef.nativeElement.closest('[rdxMenuPopup]')?.focus({ preventScroll: true });
745
915
  }
746
916
  }
@@ -761,25 +931,38 @@ class RdxMenuLinkItem {
761
931
  this.elementRef.nativeElement.click();
762
932
  }
763
933
  onActivate(event) {
934
+ event.preventDefault();
764
935
  if (this.effectiveDisabled()) {
765
- event.preventDefault();
766
936
  return;
767
937
  }
768
- this.onSelect.emit();
769
- if (this.closeOnClick())
770
- this.rootContext?.closeEntireMenu();
938
+ this.elementRef.nativeElement.click();
939
+ }
940
+ setActiveIndex() {
941
+ if (!this.rootContext || this.rootContext.disabled()) {
942
+ return;
943
+ }
944
+ const index = this.listItem.index();
945
+ if (index !== -1) {
946
+ this.rootContext.setActiveIndex(index);
947
+ }
948
+ }
949
+ clearActiveIndex() {
950
+ if (this.rootContext?.activeIndex() === this.listItem.index()) {
951
+ this.rootContext.setActiveIndex(null);
952
+ }
771
953
  }
772
954
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxMenuLinkItem, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
773
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxMenuLinkItem, isStandalone: true, selector: "a[rdxMenuLinkItem]", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, closeOnClick: { classPropertyName: "closeOnClick", publicName: "closeOnClick", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSelect: "onSelect" }, host: { attributes: { "role": "menuitem", "tabindex": "-1" }, listeners: { "focus": "onFocus()", "blur": "onBlur()", "pointermove": "onPointerMove($event)", "pointerleave": "onPointerLeave($event)", "mouseup": "onMouseUp($event)", "click": "onItemClick($event)", "keydown.enter": "onActivate($event)" }, properties: { "attr.data-disabled": "effectiveDisabled() ? \"\" : undefined", "attr.aria-disabled": "effectiveDisabled() ? true : undefined", "attr.data-highlighted": "highlighted() ? \"\" : undefined", "attr.data-label": "label() ?? undefined" } }, exportAs: ["rdxMenuLinkItem"], ngImport: i0 }); }
955
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxMenuLinkItem, isStandalone: true, selector: "a[rdxMenuLinkItem]", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, closeOnClick: { classPropertyName: "closeOnClick", publicName: "closeOnClick", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSelect: "onSelect" }, host: { attributes: { "role": "menuitem" }, listeners: { "focus": "onFocus()", "blur": "onBlur()", "pointermove": "onPointerMove($event)", "pointerleave": "onPointerLeave($event)", "mouseup": "onMouseUp($event)", "click": "onItemClick($event)", "keydown.enter": "onActivate($event)", "keydown.space": "onActivate($event)" }, properties: { "attr.tabindex": "rootContext?.isOpen() && highlighted() ? 0 : -1", "attr.data-disabled": "effectiveDisabled() ? \"\" : undefined", "attr.aria-disabled": "effectiveDisabled() ? true : undefined", "attr.data-highlighted": "highlighted() ? \"\" : undefined", "attr.data-label": "label() ?? undefined" } }, exportAs: ["rdxMenuLinkItem"], hostDirectives: [{ directive: i1$1.RdxCompositeListItem }], ngImport: i0 }); }
774
956
  }
775
957
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxMenuLinkItem, decorators: [{
776
958
  type: Directive,
777
959
  args: [{
778
960
  selector: 'a[rdxMenuLinkItem]',
779
961
  exportAs: 'rdxMenuLinkItem',
962
+ hostDirectives: [RdxCompositeListItem],
780
963
  host: {
781
964
  role: 'menuitem',
782
- tabindex: '-1',
965
+ '[attr.tabindex]': 'rootContext?.isOpen() && highlighted() ? 0 : -1',
783
966
  '[attr.data-disabled]': 'effectiveDisabled() ? "" : undefined',
784
967
  '[attr.aria-disabled]': 'effectiveDisabled() ? true : undefined',
785
968
  '[attr.data-highlighted]': 'highlighted() ? "" : undefined',
@@ -790,22 +973,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
790
973
  '(pointerleave)': 'onPointerLeave($event)',
791
974
  '(mouseup)': 'onMouseUp($event)',
792
975
  '(click)': 'onItemClick($event)',
793
- '(keydown.enter)': 'onActivate($event)'
976
+ '(keydown.enter)': 'onActivate($event)',
977
+ '(keydown.space)': 'onActivate($event)'
794
978
  }
795
979
  }]
796
- }], propDecorators: { disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], closeOnClick: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnClick", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], onSelect: [{ type: i0.Output, args: ["onSelect"] }] } });
797
-
798
- /** Selector for focusable menu items within a popup. */
799
- const RDX_MENU_ITEM_SELECTOR = [
800
- '[rdxMenuItem]:not([data-disabled])',
801
- '[rdxMenuCheckboxItem]:not([data-disabled])',
802
- '[rdxMenuRadioItem]:not([data-disabled])',
803
- '[rdxMenuLinkItem]:not([data-disabled])',
804
- '[rdxMenuSubTrigger]:not([data-disabled])'
805
- ].join(',');
806
- function getFocusableMenuItems(popup) {
807
- return Array.from(popup.querySelectorAll(RDX_MENU_ITEM_SELECTOR)).filter((item) => item.closest('[rdxMenuPopup]') === popup);
808
- }
980
+ }], ctorParameters: () => [], propDecorators: { disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], closeOnClick: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnClick", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], onSelect: [{ type: i0.Output, args: ["onSelect"] }] } });
809
981
 
810
982
  /**
811
983
  * A container for the menu contents.
@@ -817,6 +989,7 @@ class RdxMenuPopup {
817
989
  this.registration = inject(RDX_FLOATING_REGISTRATION, { optional: true });
818
990
  this.focusManager = inject(RdxFloatingFocusManager);
819
991
  this.focusScope = inject(RdxFocusScope);
992
+ this.compositeList = inject(RdxCompositeList, { self: true });
820
993
  this.wrapper = inject(RdxPopperContentWrapper, { optional: true });
821
994
  this.elementRef = inject(ElementRef);
822
995
  this.search = '';
@@ -931,10 +1104,11 @@ class RdxMenuPopup {
931
1104
  }
932
1105
  }
933
1106
  handleKeydown(event) {
934
- const el = this.elementRef.nativeElement;
935
- const items = getFocusableMenuItems(el);
936
- const current = el.ownerDocument.activeElement;
937
- const currentIndex = items.indexOf(current);
1107
+ if (this.rootContext.disabled()) {
1108
+ return;
1109
+ }
1110
+ const items = this.menuItems();
1111
+ const currentIndex = this.currentItemIndex(items);
938
1112
  switch (event.key) {
939
1113
  case 'ArrowDown': {
940
1114
  event.preventDefault();
@@ -945,7 +1119,7 @@ class RdxMenuPopup {
945
1119
  ? items[0]
946
1120
  : items[items.length - 1]
947
1121
  : items[currentIndex + 1];
948
- next?.focus({ preventScroll: true });
1122
+ this.focusMenuItem(next);
949
1123
  break;
950
1124
  }
951
1125
  case 'ArrowUp': {
@@ -957,19 +1131,19 @@ class RdxMenuPopup {
957
1131
  ? items[items.length - 1]
958
1132
  : items[0]
959
1133
  : items[currentIndex - 1];
960
- prev?.focus({ preventScroll: true });
1134
+ this.focusMenuItem(prev);
961
1135
  break;
962
1136
  }
963
1137
  case 'Home': {
964
1138
  event.preventDefault();
965
1139
  event.stopPropagation();
966
- items[0]?.focus({ preventScroll: true });
1140
+ this.focusMenuItem(items[0]);
967
1141
  break;
968
1142
  }
969
1143
  case 'End': {
970
1144
  event.preventDefault();
971
1145
  event.stopPropagation();
972
- items[items.length - 1]?.focus({ preventScroll: true });
1146
+ this.focusMenuItem(items[items.length - 1]);
973
1147
  break;
974
1148
  }
975
1149
  case 'ArrowLeft': {
@@ -1032,11 +1206,8 @@ class RdxMenuPopup {
1032
1206
  const query = this.search.length > 1 && [...this.search].every((c) => c === char) ? char : this.search;
1033
1207
  const startIndex = currentIndex >= 0 ? currentIndex + 1 : 0;
1034
1208
  const rotated = [...items.slice(startIndex), ...items.slice(0, startIndex)];
1035
- const match = rotated.find((item) => {
1036
- const text = (item.dataset['label'] ?? item.textContent?.trim() ?? '').toLowerCase();
1037
- return text.startsWith(query);
1038
- });
1039
- match?.focus({ preventScroll: true });
1209
+ const match = rotated.find((item) => item.label.startsWith(query));
1210
+ this.focusMenuItem(match);
1040
1211
  }
1041
1212
  break;
1042
1213
  }
@@ -1056,14 +1227,34 @@ class RdxMenuPopup {
1056
1227
  if (activeElement && popup.contains(activeElement)) {
1057
1228
  return;
1058
1229
  }
1059
- const items = getFocusableMenuItems(popup);
1230
+ const items = this.menuItems();
1060
1231
  if (items.length === 0) {
1061
1232
  if (attempt < maxAttempts) {
1062
1233
  this.scheduleSubmenuKeyboardFocus(attempt + 1);
1063
1234
  }
1064
1235
  return;
1065
1236
  }
1066
- items[0]?.focus({ preventScroll: true });
1237
+ this.focusMenuItem(items[0]);
1238
+ }
1239
+ menuItems() {
1240
+ const compositeItems = getCompositeMenuItems(this.compositeList);
1241
+ return compositeItems.length > 0 ? compositeItems : getDomMenuItems(this.elementRef.nativeElement);
1242
+ }
1243
+ currentItemIndex(items) {
1244
+ const current = this.elementRef.nativeElement.ownerDocument.activeElement;
1245
+ const focusedIndex = items.findIndex((item) => item.element === current);
1246
+ if (focusedIndex !== -1) {
1247
+ return focusedIndex;
1248
+ }
1249
+ const activeIndex = this.rootContext.activeIndex();
1250
+ return items.findIndex((item) => item.index === activeIndex);
1251
+ }
1252
+ focusMenuItem(item) {
1253
+ if (!item) {
1254
+ return;
1255
+ }
1256
+ this.rootContext.setActiveIndex(item.index);
1257
+ item.element.focus({ preventScroll: true });
1067
1258
  }
1068
1259
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxMenuPopup, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1069
1260
  static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxMenuPopup, isStandalone: true, selector: "[rdxMenuPopup]", outputs: { escapeKeyDown: "escapeKeyDown", pointerDownOutside: "pointerDownOutside", focusOutside: "focusOutside", interactOutside: "interactOutside", openAutoFocus: "openAutoFocus", closeAutoFocus: "closeAutoFocus" }, host: { attributes: { "role": "menu", "tabindex": "-1" }, listeners: { "keydown": "handleKeydown($event)", "rdx-menu-close-parent": "handleCloseParent($event)" }, properties: { "attr.aria-orientation": "rootContext.orientation()", "attr.data-closed": "rootContext.isOpen() ? undefined : \"\"", "attr.data-open": "rootContext.isOpen() ? \"\" : undefined", "attr.data-state": "rootContext.isOpen() ? \"open\" : \"closed\"", "attr.data-starting-style": "rootContext.transitionStatus() === \"starting\" ? \"\" : undefined", "attr.data-ending-style": "rootContext.transitionStatus() === \"ending\" ? \"\" : undefined", "attr.data-align": "align()", "attr.data-side": "side()" } }, providers: [
@@ -1115,14 +1306,14 @@ class RdxMenuPopup {
1115
1306
  closeInteractionType: () => rootContext.closeInteractionType()
1116
1307
  };
1117
1308
  })
1118
- ], exportAs: ["rdxMenuPopup"], hostDirectives: [{ directive: i1.RdxPopperContent }, { directive: i2.RdxFloatingNodeRegistration }, { directive: i3.RdxFloatingFocusManager }], ngImport: i0 }); }
1309
+ ], exportAs: ["rdxMenuPopup"], hostDirectives: [{ directive: i1.RdxPopperContent }, { directive: i2.RdxFloatingNodeRegistration }, { directive: i3.RdxFloatingFocusManager }, { directive: i1$1.RdxCompositeList }], ngImport: i0 }); }
1119
1310
  }
1120
1311
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxMenuPopup, decorators: [{
1121
1312
  type: Directive,
1122
1313
  args: [{
1123
1314
  selector: '[rdxMenuPopup]',
1124
1315
  exportAs: 'rdxMenuPopup',
1125
- hostDirectives: [RdxPopperContent, RdxFloatingNodeRegistration, RdxFloatingFocusManager],
1316
+ hostDirectives: [RdxPopperContent, RdxFloatingNodeRegistration, RdxFloatingFocusManager, RdxCompositeList],
1126
1317
  providers: [
1127
1318
  provideFloatingFocusManagerConfig(() => {
1128
1319
  const rootContext = injectRdxMenuRootContext();
@@ -1201,7 +1392,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
1201
1392
  */
1202
1393
  class RdxMenuPortal {
1203
1394
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxMenuPortal, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1204
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxMenuPortal, isStandalone: true, selector: "ng-template[rdxMenuPortal]", providers: [provideRdxPresenceContext(() => ({ present: injectRdxMenuRootContext().present }))], exportAs: ["rdxMenuPortal"], hostDirectives: [{ directive: i1$1.RdxPortalPresence, inputs: ["container", "container"] }], ngImport: i0 }); }
1395
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxMenuPortal, isStandalone: true, selector: "ng-template[rdxMenuPortal]", providers: [provideRdxPresenceContext(() => ({ present: injectRdxMenuRootContext().present }))], exportAs: ["rdxMenuPortal"], hostDirectives: [{ directive: i1$2.RdxPortalPresence, inputs: ["container", "container"] }], ngImport: i0 }); }
1205
1396
  }
1206
1397
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxMenuPortal, decorators: [{
1207
1398
  type: Directive,
@@ -1399,6 +1590,7 @@ class RdxMenuRadioItem {
1399
1590
  constructor() {
1400
1591
  this.rootContext = injectRdxMenuRootContext(true);
1401
1592
  this.radioGroupContext = injectRdxMenuRadioGroupContext();
1593
+ this.listItem = inject(RdxCompositeListItem, { self: true });
1402
1594
  this.elementRef = inject(ElementRef);
1403
1595
  this.isFocused = signal(false, ...(ngDevMode ? [{ debugName: "isFocused" }] : /* istanbul ignore next */ []));
1404
1596
  /** The value of this radio item. */
@@ -1412,17 +1604,26 @@ class RdxMenuRadioItem {
1412
1604
  /** Emits when this item is selected. */
1413
1605
  this.onSelect = output();
1414
1606
  this.checked = computed(() => this.radioGroupContext.value() === this.value(), ...(ngDevMode ? [{ debugName: "checked" }] : /* istanbul ignore next */ []));
1415
- this.highlighted = computed(() => this.isFocused(), ...(ngDevMode ? [{ debugName: "highlighted" }] : /* istanbul ignore next */ []));
1607
+ this.highlighted = computed(() => this.rootContext?.activeIndex() === this.listItem.index() || this.isFocused(), ...(ngDevMode ? [{ debugName: "highlighted" }] : /* istanbul ignore next */ []));
1416
1608
  this.effectiveDisabled = computed(() => this.disabled() || this.radioGroupContext.disabled() || (this.rootContext?.disabled() ?? false), ...(ngDevMode ? [{ debugName: "effectiveDisabled" }] : /* istanbul ignore next */ []));
1417
1609
  this.getCheckedState = getCheckedState;
1610
+ effect(() => {
1611
+ this.listItem.setMetadata({
1612
+ type: 'radio-item',
1613
+ disabled: this.effectiveDisabled(),
1614
+ label: this.label()
1615
+ });
1616
+ });
1418
1617
  }
1419
1618
  onFocus() {
1420
- if (!this.effectiveDisabled()) {
1619
+ if (!this.rootContext?.disabled()) {
1421
1620
  this.isFocused.set(true);
1422
1621
  }
1622
+ this.setActiveIndex();
1423
1623
  }
1424
1624
  onBlur() {
1425
1625
  this.isFocused.set(false);
1626
+ this.clearActiveIndex();
1426
1627
  }
1427
1628
  onPointerMove(event) {
1428
1629
  if (event.defaultPrevented || event.pointerType !== 'mouse' || this.effectiveDisabled()) {
@@ -1431,6 +1632,7 @@ class RdxMenuRadioItem {
1431
1632
  if (this.rootContext && !this.rootContext.highlightItemOnHover()) {
1432
1633
  return;
1433
1634
  }
1635
+ this.setActiveIndex();
1434
1636
  if (this.elementRef.nativeElement.ownerDocument.activeElement !== this.elementRef.nativeElement) {
1435
1637
  this.elementRef.nativeElement.focus({ preventScroll: true });
1436
1638
  }
@@ -1440,6 +1642,8 @@ class RdxMenuRadioItem {
1440
1642
  return;
1441
1643
  }
1442
1644
  if (this.elementRef.nativeElement.ownerDocument.activeElement === this.elementRef.nativeElement) {
1645
+ this.isFocused.set(false);
1646
+ this.clearActiveIndex();
1443
1647
  this.elementRef.nativeElement.closest('[rdxMenuPopup]')?.focus({ preventScroll: true });
1444
1648
  }
1445
1649
  }
@@ -1470,8 +1674,22 @@ class RdxMenuRadioItem {
1470
1674
  if (this.closeOnClick())
1471
1675
  this.rootContext?.closeEntireMenu();
1472
1676
  }
1677
+ setActiveIndex() {
1678
+ if (!this.rootContext || this.rootContext.disabled()) {
1679
+ return;
1680
+ }
1681
+ const index = this.listItem.index();
1682
+ if (index !== -1) {
1683
+ this.rootContext.setActiveIndex(index);
1684
+ }
1685
+ }
1686
+ clearActiveIndex() {
1687
+ if (this.rootContext?.activeIndex() === this.listItem.index()) {
1688
+ this.rootContext.setActiveIndex(null);
1689
+ }
1690
+ }
1473
1691
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxMenuRadioItem, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1474
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxMenuRadioItem, isStandalone: true, selector: "[rdxMenuRadioItem]", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, closeOnClick: { classPropertyName: "closeOnClick", publicName: "closeOnClick", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSelect: "onSelect" }, host: { attributes: { "role": "menuitemradio", "tabindex": "-1" }, listeners: { "focus": "onFocus()", "blur": "onBlur()", "pointermove": "onPointerMove($event)", "pointerleave": "onPointerLeave($event)", "mouseup": "onMouseUp($event)", "click": "onItemClick()", "keydown.enter": "onActivate($event)", "keydown.space": "onActivate($event)" }, properties: { "attr.aria-checked": "checked()", "attr.data-state": "getCheckedState(checked())", "attr.data-disabled": "effectiveDisabled() ? \"\" : undefined", "attr.aria-disabled": "effectiveDisabled() ? true : undefined", "attr.data-highlighted": "highlighted() ? \"\" : undefined", "attr.data-label": "label() ?? undefined" } }, providers: [provideRdxMenuRadioItemContext(radioItemContextFactory)], exportAs: ["rdxMenuRadioItem"], ngImport: i0 }); }
1692
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxMenuRadioItem, isStandalone: true, selector: "[rdxMenuRadioItem]", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, closeOnClick: { classPropertyName: "closeOnClick", publicName: "closeOnClick", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSelect: "onSelect" }, host: { attributes: { "role": "menuitemradio" }, listeners: { "focus": "onFocus()", "blur": "onBlur()", "pointermove": "onPointerMove($event)", "pointerleave": "onPointerLeave($event)", "mouseup": "onMouseUp($event)", "click": "onItemClick()", "keydown.enter": "onActivate($event)", "keydown.space": "onActivate($event)" }, properties: { "attr.tabindex": "rootContext?.isOpen() && highlighted() ? 0 : -1", "attr.aria-checked": "checked()", "attr.data-state": "getCheckedState(checked())", "attr.data-disabled": "effectiveDisabled() ? \"\" : undefined", "attr.aria-disabled": "effectiveDisabled() ? true : undefined", "attr.data-highlighted": "highlighted() ? \"\" : undefined", "attr.data-label": "label() ?? undefined" } }, providers: [provideRdxMenuRadioItemContext(radioItemContextFactory)], exportAs: ["rdxMenuRadioItem"], hostDirectives: [{ directive: i1$1.RdxCompositeListItem }], ngImport: i0 }); }
1475
1693
  }
1476
1694
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxMenuRadioItem, decorators: [{
1477
1695
  type: Directive,
@@ -1479,9 +1697,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
1479
1697
  selector: '[rdxMenuRadioItem]',
1480
1698
  exportAs: 'rdxMenuRadioItem',
1481
1699
  providers: [provideRdxMenuRadioItemContext(radioItemContextFactory)],
1700
+ hostDirectives: [RdxCompositeListItem],
1482
1701
  host: {
1483
1702
  role: 'menuitemradio',
1484
- tabindex: '-1',
1703
+ '[attr.tabindex]': 'rootContext?.isOpen() && highlighted() ? 0 : -1',
1485
1704
  '[attr.aria-checked]': 'checked()',
1486
1705
  '[attr.data-state]': 'getCheckedState(checked())',
1487
1706
  '[attr.data-disabled]': 'effectiveDisabled() ? "" : undefined',
@@ -1498,7 +1717,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
1498
1717
  '(keydown.space)': 'onActivate($event)'
1499
1718
  }
1500
1719
  }]
1501
- }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], closeOnClick: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnClick", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], onSelect: [{ type: i0.Output, args: ["onSelect"] }] } });
1720
+ }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], closeOnClick: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnClick", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], onSelect: [{ type: i0.Output, args: ["onSelect"] }] } });
1502
1721
 
1503
1722
  /**
1504
1723
  * Renders when the parent radio item is selected.
@@ -1892,6 +2111,8 @@ class RdxMenuSubTrigger {
1892
2111
  constructor() {
1893
2112
  this.submenuContext = injectRdxMenuRootContext();
1894
2113
  this.submenuRoot = inject(RdxMenuRoot);
2114
+ this.parentMenuRoot = this.submenuRoot.parentRoot;
2115
+ this.listItem = inject(RdxCompositeListItem, { self: true });
1895
2116
  this.elementRef = inject(ElementRef);
1896
2117
  this.destroyRef = inject(DestroyRef);
1897
2118
  this.isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
@@ -1913,8 +2134,10 @@ class RdxMenuSubTrigger {
1913
2134
  this.closeDelay = input(0, { ...(ngDevMode ? { debugName: "closeDelay" } : /* istanbul ignore next */ {}), transform: numberOrUndefined$1 });
1914
2135
  /** Explicit typeahead label. When set, overrides textContent for character search. */
1915
2136
  this.label = input(undefined, ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
1916
- /** Highlighted when focused OR while the submenu is open. */
1917
- this.highlighted = computed(() => this.isFocused() || this.submenuContext.isOpen(), ...(ngDevMode ? [{ debugName: "highlighted" }] : /* istanbul ignore next */ []));
2137
+ /** Highlighted when active in the parent menu or while the submenu is open. */
2138
+ this.highlighted = computed(() => this.parentMenuRoot?.activeIndex() === this.listItem.index() ||
2139
+ this.isFocused() ||
2140
+ this.submenuContext.isOpen(), ...(ngDevMode ? [{ debugName: "highlighted" }] : /* istanbul ignore next */ []));
1918
2141
  this.effectiveDisabled = computed(() => this.disabled() || this.submenuContext.disabled(), ...(ngDevMode ? [{ debugName: "effectiveDisabled" }] : /* istanbul ignore next */ []));
1919
2142
  this.nativeButtonState = computed(() => this.nativeButton() || this.elementRef.nativeElement.tagName === 'BUTTON', ...(ngDevMode ? [{ debugName: "nativeButtonState" }] : /* istanbul ignore next */ []));
1920
2143
  this.submenuContext.markAsSubmenu();
@@ -1927,6 +2150,13 @@ class RdxMenuSubTrigger {
1927
2150
  submenuRootsByTrigger.delete(el);
1928
2151
  });
1929
2152
  });
2153
+ effect(() => {
2154
+ this.listItem.setMetadata({
2155
+ type: 'submenu-trigger',
2156
+ disabled: this.effectiveDisabled(),
2157
+ label: this.label()
2158
+ });
2159
+ });
1930
2160
  // While this submenu is open by hover, it owns the decision to close itself: a document
1931
2161
  // `mousemove` handler keeps it open while the cursor traverses the safe polygon toward the
1932
2162
  // popup, and a pointer-events tunnel stops siblings from stealing it mid-traversal.
@@ -1988,13 +2218,16 @@ class RdxMenuSubTrigger {
1988
2218
  }
1989
2219
  }
1990
2220
  onFocus() {
1991
- if (!this.effectiveDisabled()) {
1992
- this.clearSiblingHighlights();
1993
- this.isFocused.set(true);
2221
+ if (this.submenuContext.disabled()) {
2222
+ return;
1994
2223
  }
2224
+ this.clearSiblingHighlights();
2225
+ this.isFocused.set(true);
2226
+ this.setParentActiveIndex();
1995
2227
  }
1996
2228
  onBlur() {
1997
2229
  this.isFocused.set(false);
2230
+ this.clearParentActiveIndex();
1998
2231
  }
1999
2232
  onClick(event) {
2000
2233
  if (this.effectiveDisabled())
@@ -2077,6 +2310,7 @@ class RdxMenuSubTrigger {
2077
2310
  return;
2078
2311
  this.lastPointer = { x: event.clientX, y: event.clientY };
2079
2312
  this.clearSiblingHighlights();
2313
+ this.setParentActiveIndex();
2080
2314
  const el = this.elementRef.nativeElement;
2081
2315
  if (this.submenuContext.highlightItemOnHover() && el.ownerDocument.activeElement !== el) {
2082
2316
  el.focus({ preventScroll: true });
@@ -2095,6 +2329,7 @@ class RdxMenuSubTrigger {
2095
2329
  }
2096
2330
  clearHighlight() {
2097
2331
  this.isFocused.set(false);
2332
+ this.clearParentActiveIndex();
2098
2333
  }
2099
2334
  closeSiblingSubmenus() {
2100
2335
  const currentTrigger = this.elementRef.nativeElement;
@@ -2154,23 +2389,34 @@ class RdxMenuSubTrigger {
2154
2389
  setTimeout(run);
2155
2390
  }
2156
2391
  }
2392
+ setParentActiveIndex() {
2393
+ const index = this.listItem.index();
2394
+ if (index === -1 || !this.parentMenuRoot || this.parentMenuRoot.effectiveDisabled()) {
2395
+ return;
2396
+ }
2397
+ this.parentMenuRoot.setActiveIndex(index);
2398
+ }
2399
+ clearParentActiveIndex() {
2400
+ if (this.parentMenuRoot?.activeIndex() === this.listItem.index()) {
2401
+ this.parentMenuRoot.setActiveIndex(null);
2402
+ }
2403
+ }
2157
2404
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxMenuSubTrigger, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
2158
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxMenuSubTrigger, isStandalone: true, selector: "[rdxMenuSubTrigger]", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, nativeButton: { classPropertyName: "nativeButton", publicName: "nativeButton", isSignal: true, isRequired: false, transformFunction: null }, openOnHover: { classPropertyName: "openOnHover", publicName: "openOnHover", 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 }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "menuitem", "tabindex": "-1" }, listeners: { "focus": "onFocus()", "blur": "onBlur()", "click": "onClick($event)", "keydown.enter": "onEnter($event)", "keydown.arrowleft": "onArrowLeft($event)", "keydown.arrowright": "onArrowRight($event)", "pointermove": "onPointerMove($event)", "pointerleave": "onPointerLeave()", "rdx-menu-subtrigger-clear-highlight": "clearHighlight()" }, properties: { "attr.type": "nativeButtonState() ? \"button\" : undefined", "attr.aria-haspopup": "\"menu\"", "attr.aria-expanded": "submenuContext.isOpen()", "attr.aria-disabled": "effectiveDisabled() ? true : undefined", "attr.disabled": "nativeButtonState() && effectiveDisabled() ? \"\" : undefined", "attr.data-state": "submenuContext.isOpen() ? \"open\" : \"closed\"", "attr.data-popup-open": "submenuContext.isOpen() ? \"\" : undefined", "attr.data-highlighted": "highlighted() ? \"\" : undefined", "attr.data-disabled": "effectiveDisabled() ? \"\" : undefined", "attr.data-label": "label() ?? undefined" } }, exportAs: ["rdxMenuSubTrigger"], hostDirectives: [{ directive: i1.RdxPopperAnchor }], ngImport: i0 }); }
2405
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxMenuSubTrigger, isStandalone: true, selector: "[rdxMenuSubTrigger]", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, nativeButton: { classPropertyName: "nativeButton", publicName: "nativeButton", isSignal: true, isRequired: false, transformFunction: null }, openOnHover: { classPropertyName: "openOnHover", publicName: "openOnHover", 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 }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "menuitem" }, listeners: { "focus": "onFocus()", "blur": "onBlur()", "click": "onClick($event)", "keydown.enter": "onEnter($event)", "keydown.space": "onEnter($event)", "keydown.arrowleft": "onArrowLeft($event)", "keydown.arrowright": "onArrowRight($event)", "pointermove": "onPointerMove($event)", "pointerleave": "onPointerLeave()", "rdx-menu-subtrigger-clear-highlight": "clearHighlight()" }, properties: { "attr.type": "nativeButtonState() ? \"button\" : undefined", "attr.tabindex": "parentMenuRoot?.open() && highlighted() ? 0 : -1", "attr.aria-haspopup": "\"menu\"", "attr.aria-expanded": "submenuContext.isOpen()", "attr.aria-disabled": "effectiveDisabled() ? true : undefined", "attr.data-state": "submenuContext.isOpen() ? \"open\" : \"closed\"", "attr.data-popup-open": "submenuContext.isOpen() ? \"\" : undefined", "attr.data-highlighted": "highlighted() ? \"\" : undefined", "attr.data-disabled": "effectiveDisabled() ? \"\" : undefined", "attr.data-label": "label() ?? undefined" } }, exportAs: ["rdxMenuSubTrigger"], hostDirectives: [{ directive: i1.RdxPopperAnchor }, { directive: i1$1.RdxCompositeListItem }], ngImport: i0 }); }
2159
2406
  }
2160
2407
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxMenuSubTrigger, decorators: [{
2161
2408
  type: Directive,
2162
2409
  args: [{
2163
2410
  selector: '[rdxMenuSubTrigger]',
2164
2411
  exportAs: 'rdxMenuSubTrigger',
2165
- hostDirectives: [RdxPopperAnchor],
2412
+ hostDirectives: [RdxPopperAnchor, RdxCompositeListItem],
2166
2413
  host: {
2167
2414
  '[attr.type]': 'nativeButtonState() ? "button" : undefined',
2168
2415
  role: 'menuitem',
2169
- tabindex: '-1',
2416
+ '[attr.tabindex]': 'parentMenuRoot?.open() && highlighted() ? 0 : -1',
2170
2417
  '[attr.aria-haspopup]': '"menu"',
2171
2418
  '[attr.aria-expanded]': 'submenuContext.isOpen()',
2172
2419
  '[attr.aria-disabled]': 'effectiveDisabled() ? true : undefined',
2173
- '[attr.disabled]': 'nativeButtonState() && effectiveDisabled() ? "" : undefined',
2174
2420
  '[attr.data-state]': 'submenuContext.isOpen() ? "open" : "closed"',
2175
2421
  '[attr.data-popup-open]': 'submenuContext.isOpen() ? "" : undefined',
2176
2422
  '[attr.data-highlighted]': 'highlighted() ? "" : undefined',
@@ -2180,6 +2426,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
2180
2426
  '(blur)': 'onBlur()',
2181
2427
  '(click)': 'onClick($event)',
2182
2428
  '(keydown.enter)': 'onEnter($event)',
2429
+ '(keydown.space)': 'onEnter($event)',
2183
2430
  '(keydown.arrowleft)': 'onArrowLeft($event)',
2184
2431
  '(keydown.arrowright)': 'onArrowRight($event)',
2185
2432
  '(pointermove)': 'onPointerMove($event)',
@@ -2203,6 +2450,7 @@ class RdxMenuTrigger {
2203
2450
  this.openedByHover = false;
2204
2451
  this.ignoreNextClick = null;
2205
2452
  this.handleDocumentMouseUp = (event) => {
2453
+ clearTimeout(this.allowMouseUpTriggerTimer);
2206
2454
  this.allowMouseUpTriggerTimer = undefined;
2207
2455
  this.rootContext.setAllowMouseUpTrigger(false);
2208
2456
  const trigger = this.elementRef.nativeElement;
@@ -2492,7 +2740,7 @@ class RdxMenuTrigger {
2492
2740
  }
2493
2741
  }
2494
2742
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxMenuTrigger, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
2495
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxMenuTrigger, isStandalone: true, selector: "[rdxMenuTrigger]", inputs: { nativeButton: { classPropertyName: "nativeButton", publicName: "nativeButton", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, openOnHover: { classPropertyName: "openOnHover", publicName: "openOnHover", 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 } }, host: { listeners: { "pointerdown": "handlePointerDown($event)", "mousedown": "handleMouseDown($event)", "click": "handleClick($event)", "pointerenter": "handlePointerEnter($event)", "pointermove": "handlePointerMove($event)", "pointerleave": "handlePointerLeave($event)", "keydown.arrowdown": "handleArrowDown($event)", "keydown.arrowup": "handleArrowUp($event)", "keydown.arrowleft": "handleArrowLeft($event)", "keydown.arrowright": "handleArrowRight($event)", "keydown.home": "handleHome($event)", "keydown.end": "handleEnd($event)", "keydown.escape": "handleEscape($event)", "keydown.enter": "handleKeyboardToggle($event)", "keydown.space": "handleKeyboardToggle($event)" }, properties: { "attr.type": "nativeButtonState() ? \"button\" : undefined", "attr.role": "rootContext.hasTriggerInteractionHandler() ? \"menuitem\" : nativeButtonState() ? undefined : \"button\"", "attr.tabindex": "rootContext.hasTriggerInteractionHandler() ? \"-1\" : undefined", "attr.aria-haspopup": "\"menu\"", "attr.aria-expanded": "triggerInteraction.ariaExpanded()", "attr.aria-disabled": "isDisabled() ? true : undefined", "attr.disabled": "nativeButtonState() && isDisabled() ? \"\" : undefined", "attr.data-state": "triggerInteraction.dataState()", "attr.data-disabled": "isDisabled() ? \"\" : undefined", "attr.data-popup-open": "triggerInteraction.dataPopupOpen()", "style.pointer-events": "rootContext.isOpen() && rootContext.modal() ? \"auto\" : undefined" } }, exportAs: ["rdxMenuTrigger"], hostDirectives: [{ directive: i1.RdxPopperAnchor }], ngImport: i0 }); }
2743
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxMenuTrigger, isStandalone: true, selector: "[rdxMenuTrigger]", inputs: { nativeButton: { classPropertyName: "nativeButton", publicName: "nativeButton", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, openOnHover: { classPropertyName: "openOnHover", publicName: "openOnHover", 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 } }, host: { listeners: { "pointerdown": "handlePointerDown($event)", "mousedown": "handleMouseDown($event)", "click": "handleClick($event)", "pointerenter": "handlePointerEnter($event)", "pointermove": "handlePointerMove($event)", "pointerleave": "handlePointerLeave($event)", "keydown.arrowdown": "handleArrowDown($event)", "keydown.arrowup": "handleArrowUp($event)", "keydown.arrowleft": "handleArrowLeft($event)", "keydown.arrowright": "handleArrowRight($event)", "keydown.home": "handleHome($event)", "keydown.end": "handleEnd($event)", "keydown.escape": "handleEscape($event)", "keydown.enter": "handleKeyboardToggle($event)", "keydown.space": "handleKeyboardToggle($event)" }, properties: { "attr.type": "nativeButtonState() ? \"button\" : undefined", "attr.role": "rootContext.hasTriggerInteractionHandler() ? \"menuitem\" : nativeButtonState() ? undefined : \"button\"", "attr.aria-haspopup": "\"menu\"", "attr.aria-expanded": "triggerInteraction.ariaExpanded()", "attr.aria-disabled": "isDisabled() ? true : undefined", "attr.disabled": "nativeButtonState() && isDisabled() ? \"\" : undefined", "attr.data-state": "triggerInteraction.dataState()", "attr.data-disabled": "isDisabled() ? \"\" : undefined", "attr.data-popup-open": "triggerInteraction.dataPopupOpen()", "style.pointer-events": "rootContext.isOpen() && rootContext.modal() ? \"auto\" : undefined" } }, exportAs: ["rdxMenuTrigger"], hostDirectives: [{ directive: i1.RdxPopperAnchor }], ngImport: i0 }); }
2496
2744
  }
2497
2745
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxMenuTrigger, decorators: [{
2498
2746
  type: Directive,
@@ -2503,7 +2751,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
2503
2751
  host: {
2504
2752
  '[attr.type]': 'nativeButtonState() ? "button" : undefined',
2505
2753
  '[attr.role]': 'rootContext.hasTriggerInteractionHandler() ? "menuitem" : nativeButtonState() ? undefined : "button"',
2506
- '[attr.tabindex]': 'rootContext.hasTriggerInteractionHandler() ? "-1" : undefined',
2507
2754
  '[attr.aria-haspopup]': '"menu"',
2508
2755
  '[attr.aria-expanded]': 'triggerInteraction.ariaExpanded()',
2509
2756
  '[attr.aria-disabled]': 'isDisabled() ? true : undefined',