@radix-ng/primitives 1.0.0-beta.3 → 1.0.0-beta.4

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 (97) hide show
  1. package/README.md +1 -1
  2. package/fesm2022/radix-ng-primitives-accordion.mjs +5 -3
  3. package/fesm2022/radix-ng-primitives-accordion.mjs.map +1 -1
  4. package/fesm2022/radix-ng-primitives-alert-dialog.mjs +3 -2
  5. package/fesm2022/radix-ng-primitives-alert-dialog.mjs.map +1 -1
  6. package/fesm2022/radix-ng-primitives-autocomplete.mjs +617 -659
  7. package/fesm2022/radix-ng-primitives-autocomplete.mjs.map +1 -1
  8. package/fesm2022/radix-ng-primitives-calendar.mjs +5 -3
  9. package/fesm2022/radix-ng-primitives-calendar.mjs.map +1 -1
  10. package/fesm2022/radix-ng-primitives-combobox.mjs +1305 -572
  11. package/fesm2022/radix-ng-primitives-combobox.mjs.map +1 -1
  12. package/fesm2022/radix-ng-primitives-config.mjs +13 -4
  13. package/fesm2022/radix-ng-primitives-config.mjs.map +1 -1
  14. package/fesm2022/radix-ng-primitives-context-menu.mjs +51 -10
  15. package/fesm2022/radix-ng-primitives-context-menu.mjs.map +1 -1
  16. package/fesm2022/radix-ng-primitives-core.mjs +1345 -64
  17. package/fesm2022/radix-ng-primitives-core.mjs.map +1 -1
  18. package/fesm2022/radix-ng-primitives-date-field.mjs +5 -3
  19. package/fesm2022/radix-ng-primitives-date-field.mjs.map +1 -1
  20. package/fesm2022/radix-ng-primitives-dialog.mjs +240 -112
  21. package/fesm2022/radix-ng-primitives-dialog.mjs.map +1 -1
  22. package/fesm2022/radix-ng-primitives-direction-provider.mjs +70 -0
  23. package/fesm2022/radix-ng-primitives-direction-provider.mjs.map +1 -0
  24. package/fesm2022/radix-ng-primitives-dismissable-layer.mjs +519 -184
  25. package/fesm2022/radix-ng-primitives-dismissable-layer.mjs.map +1 -1
  26. package/fesm2022/radix-ng-primitives-drawer.mjs +3 -3
  27. package/fesm2022/radix-ng-primitives-drawer.mjs.map +1 -1
  28. package/fesm2022/radix-ng-primitives-field.mjs +3 -2
  29. package/fesm2022/radix-ng-primitives-field.mjs.map +1 -1
  30. package/fesm2022/radix-ng-primitives-floating-focus-manager.mjs +517 -0
  31. package/fesm2022/radix-ng-primitives-floating-focus-manager.mjs.map +1 -0
  32. package/fesm2022/radix-ng-primitives-focus-scope.mjs +296 -70
  33. package/fesm2022/radix-ng-primitives-focus-scope.mjs.map +1 -1
  34. package/fesm2022/radix-ng-primitives-menu.mjs +861 -286
  35. package/fesm2022/radix-ng-primitives-menu.mjs.map +1 -1
  36. package/fesm2022/radix-ng-primitives-menubar.mjs +32 -4
  37. package/fesm2022/radix-ng-primitives-menubar.mjs.map +1 -1
  38. package/fesm2022/radix-ng-primitives-navigation-menu.mjs +144 -159
  39. package/fesm2022/radix-ng-primitives-navigation-menu.mjs.map +1 -1
  40. package/fesm2022/radix-ng-primitives-popover.mjs +220 -205
  41. package/fesm2022/radix-ng-primitives-popover.mjs.map +1 -1
  42. package/fesm2022/radix-ng-primitives-popper.mjs +94 -51
  43. package/fesm2022/radix-ng-primitives-popper.mjs.map +1 -1
  44. package/fesm2022/radix-ng-primitives-presence.mjs +1 -1
  45. package/fesm2022/radix-ng-primitives-presence.mjs.map +1 -1
  46. package/fesm2022/radix-ng-primitives-preview-card.mjs +141 -173
  47. package/fesm2022/radix-ng-primitives-preview-card.mjs.map +1 -1
  48. package/fesm2022/radix-ng-primitives-roving-focus.mjs +4 -2
  49. package/fesm2022/radix-ng-primitives-roving-focus.mjs.map +1 -1
  50. package/fesm2022/radix-ng-primitives-scroll-area.mjs +5 -4
  51. package/fesm2022/radix-ng-primitives-scroll-area.mjs.map +1 -1
  52. package/fesm2022/radix-ng-primitives-select.mjs +211 -156
  53. package/fesm2022/radix-ng-primitives-select.mjs.map +1 -1
  54. package/fesm2022/radix-ng-primitives-slider.mjs +5 -3
  55. package/fesm2022/radix-ng-primitives-slider.mjs.map +1 -1
  56. package/fesm2022/radix-ng-primitives-stepper.mjs +5 -3
  57. package/fesm2022/radix-ng-primitives-stepper.mjs.map +1 -1
  58. package/fesm2022/radix-ng-primitives-time-field.mjs +5 -3
  59. package/fesm2022/radix-ng-primitives-time-field.mjs.map +1 -1
  60. package/fesm2022/radix-ng-primitives-toast.mjs +15 -36
  61. package/fesm2022/radix-ng-primitives-toast.mjs.map +1 -1
  62. package/fesm2022/radix-ng-primitives-toggle-group.mjs +5 -3
  63. package/fesm2022/radix-ng-primitives-toggle-group.mjs.map +1 -1
  64. package/fesm2022/radix-ng-primitives-toolbar.mjs +5 -3
  65. package/fesm2022/radix-ng-primitives-toolbar.mjs.map +1 -1
  66. package/fesm2022/radix-ng-primitives-tooltip.mjs +73 -110
  67. package/fesm2022/radix-ng-primitives-tooltip.mjs.map +1 -1
  68. package/package.json +10 -1
  69. package/types/radix-ng-primitives-accordion.d.ts +4 -3
  70. package/types/radix-ng-primitives-autocomplete.d.ts +217 -152
  71. package/types/radix-ng-primitives-calendar.d.ts +5 -3
  72. package/types/radix-ng-primitives-combobox.d.ts +672 -283
  73. package/types/radix-ng-primitives-config.d.ts +1 -1
  74. package/types/radix-ng-primitives-context-menu.d.ts +15 -5
  75. package/types/radix-ng-primitives-core.d.ts +762 -14
  76. package/types/radix-ng-primitives-date-field.d.ts +3 -2
  77. package/types/radix-ng-primitives-dialog.d.ts +77 -32
  78. package/types/radix-ng-primitives-direction-provider.d.ts +41 -0
  79. package/types/radix-ng-primitives-dismissable-layer.d.ts +147 -99
  80. package/types/radix-ng-primitives-field.d.ts +1 -0
  81. package/types/radix-ng-primitives-floating-focus-manager.d.ts +175 -0
  82. package/types/radix-ng-primitives-focus-scope.d.ts +132 -1
  83. package/types/radix-ng-primitives-menu.d.ts +186 -103
  84. package/types/radix-ng-primitives-navigation-menu.d.ts +37 -75
  85. package/types/radix-ng-primitives-popover.d.ts +59 -92
  86. package/types/radix-ng-primitives-popper.d.ts +39 -9
  87. package/types/radix-ng-primitives-preview-card.d.ts +39 -72
  88. package/types/radix-ng-primitives-roving-focus.d.ts +7 -6
  89. package/types/radix-ng-primitives-scroll-area.d.ts +2 -2
  90. package/types/radix-ng-primitives-select.d.ts +145 -108
  91. package/types/radix-ng-primitives-slider.d.ts +5 -4
  92. package/types/radix-ng-primitives-stepper.d.ts +4 -3
  93. package/types/radix-ng-primitives-time-field.d.ts +3 -2
  94. package/types/radix-ng-primitives-toast.d.ts +7 -7
  95. package/types/radix-ng-primitives-toggle-group.d.ts +5 -4
  96. package/types/radix-ng-primitives-toolbar.d.ts +3 -2
  97. package/types/radix-ng-primitives-tooltip.d.ts +24 -67
@@ -1,16 +1,16 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, computed, Directive, input, signal, TemplateRef, booleanAttribute, effect, untracked, ElementRef, output, DestroyRef, isDevMode, numberAttribute, model, ViewContainerRef, Renderer2, NgModule } from '@angular/core';
2
+ import { inject, computed, Directive, input, signal, TemplateRef, booleanAttribute, effect, untracked, ElementRef, output, DestroyRef, isDevMode, model, numberAttribute, ViewContainerRef, Renderer2, NgModule } from '@angular/core';
3
3
  import * as i1 from '@radix-ng/primitives/popper';
4
- import { RdxPopperContentWrapper, RdxPopperArrow, RdxPopperContent, provideRdxPopperContentConfig, RdxPopper } from '@radix-ng/primitives/popper';
5
- import { createContext, ENTER, SPACE, ARROW_DOWN, ARROW_UP, HOME, END, useGraceArea, useTransitionStatus, injectDocument, ARROW_LEFT, ARROW_RIGHT, getMaxTransitionDuration } from '@radix-ng/primitives/core';
4
+ import { RdxPopperContentWrapper, RdxPopperArrow, RdxPopperContent, provideRdxPopperContentWrapper, provideRdxPopperContentConfig, RdxPopper } from '@radix-ng/primitives/popper';
5
+ import * as i2 from '@radix-ng/primitives/core';
6
+ import { createContext, ENTER, SPACE, RDX_FLOATING_ROOT_CONTEXT, RDX_FLOATING_REGISTRATION, ARROW_DOWN, ARROW_UP, HOME, END, rdxDevError, useGraceArea, createFloatingRootContext, useTransitionStatus, provideFloatingTree, provideFloatingRootContext, RdxFloatingNodeRegistration, createCancelableChangeEventDetails, injectDocument, ARROW_LEFT, ARROW_RIGHT, getMaxTransitionDuration } from '@radix-ng/primitives/core';
6
7
  import * as i1$1 from '@radix-ng/primitives/roving-focus';
7
8
  import { RdxRovingFocusGroupDirective, RdxRovingFocusItemDirective } from '@radix-ng/primitives/roving-focus';
8
- import { outputFromObservable, outputToObservable } from '@angular/core/rxjs-interop';
9
- import * as i2 from '@radix-ng/primitives/dismissable-layer';
10
- import { RdxDismissableLayer, RdxDismissableLayersContextToken } from '@radix-ng/primitives/dismissable-layer';
9
+ import { RdxDismiss } from '@radix-ng/primitives/dismissable-layer';
11
10
  import * as i1$2 from '@radix-ng/primitives/portal';
12
11
  import { RdxPortalPresence } from '@radix-ng/primitives/portal';
13
12
  import { provideRdxPresenceContext } from '@radix-ng/primitives/presence';
13
+ import { injectDirection } from '@radix-ng/primitives/direction-provider';
14
14
 
15
15
  const [injectNavigationMenuRootContext, provideNavigationMenuRootContext] = createContext('RdxNavigationMenuRootContext', 'components/navigation-menu');
16
16
 
@@ -317,7 +317,7 @@ class RdxNavigationMenuList {
317
317
  if (event.pointerType === 'touch') {
318
318
  return;
319
319
  }
320
- this.rootContext.closeOnHover();
320
+ this.rootContext.closeOnHover(event);
321
321
  }
322
322
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuList, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
323
323
  static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxNavigationMenuList, isStandalone: true, selector: "[rdxNavigationMenuList]", host: { attributes: { "role": "menubar" }, listeners: { "pointerleave": "onPointerLeave($event)" }, properties: { "attr.data-orientation": "rootContext.orientation()" } }, hostDirectives: [{ directive: i1$1.RdxRovingFocusGroupDirective }], ngImport: i0 }); }
@@ -341,9 +341,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
341
341
  class RdxNavigationMenuPopup {
342
342
  constructor() {
343
343
  this.rootContext = injectNavigationMenuRootContext();
344
- this.dismissableLayer = inject(RdxDismissableLayer);
344
+ this.floatingContext = inject(RDX_FLOATING_ROOT_CONTEXT);
345
+ this.registration = inject(RDX_FLOATING_REGISTRATION, { optional: true });
345
346
  this.wrapper = inject(RdxPopperContentWrapper, { optional: true });
346
- this.layersContext = inject(RdxDismissableLayersContextToken);
347
347
  this.elementRef = inject(ElementRef);
348
348
  this.side = computed(() => this.wrapper?.placedSide(), ...(ngDevMode ? [{ debugName: "side" }] : /* istanbul ignore next */ []));
349
349
  this.align = computed(() => this.wrapper?.placedAlign(), ...(ngDevMode ? [{ debugName: "align" }] : /* istanbul ignore next */ []));
@@ -352,59 +352,46 @@ class RdxNavigationMenuPopup {
352
352
  const value = this.rootContext.value() ?? this.rootContext.previousValue();
353
353
  return value ? this.rootContext.triggerId(value) : undefined;
354
354
  }, ...(ngDevMode ? [{ debugName: "labelledBy" }] : /* istanbul ignore next */ []));
355
- this.dismissReason = 'none';
356
- this.dismissEvent = new Event('navigation-menu.dismiss');
357
355
  /**
358
356
  * Event handler called when the escape key is down. Can be prevented.
359
357
  */
360
- this.escapeKeyDown = outputFromObservable(outputToObservable(this.dismissableLayer.escapeKeyDown));
358
+ this.escapeKeyDown = output();
361
359
  /**
362
360
  * Event handler called when a pointerdown event happens outside the popup. Can be prevented.
363
361
  */
364
- this.pointerDownOutside = outputFromObservable(outputToObservable(this.dismissableLayer.pointerDownOutside));
362
+ this.pointerDownOutside = output();
365
363
  /**
366
364
  * Event handler called when focus moves outside the popup. Can be prevented.
367
365
  */
368
- this.focusOutside = outputFromObservable(outputToObservable(this.dismissableLayer.focusOutside));
366
+ this.focusOutside = output();
369
367
  const destroyRef = inject(DestroyRef);
370
368
  const unregisterTransitionElement = this.rootContext.registerTransitionElement(this.elementRef.nativeElement);
371
369
  destroyRef.onDestroy(unregisterTransitionElement);
372
- // Register the triggers as dismissable-layer branches so a pointer-down or (async) focus move
373
- // onto a trigger counts as "inside" otherwise focusing a sibling trigger to switch items,
374
- // or returning focus to the trigger, would dismiss the menu. See dismissable-layer gotcha.
375
- effect(() => {
376
- const triggers = this.rootContext.triggers();
377
- untracked(() => this.layersContext.branches.update((branches) => {
378
- const next = new Set(branches);
379
- triggers.forEach((trigger) => next.add(trigger));
380
- return [...next];
381
- }));
382
- });
383
- destroyRef.onDestroy(() => {
384
- const triggers = this.rootContext.triggers();
385
- this.layersContext.branches.update((branches) => branches.filter((el) => !triggers.includes(el)));
386
- });
387
- this.dismissableLayer.escapeKeyDown.subscribe((event) => {
388
- this.dismissReason = 'escape-key';
389
- this.dismissEvent = event;
390
- });
391
- this.dismissableLayer.pointerDownOutside.subscribe((event) => {
392
- this.dismissReason = 'outside-press';
393
- this.dismissEvent = event;
394
- });
395
- this.dismissableLayer.focusOutside.subscribe((event) => {
396
- this.dismissReason = 'focus-out';
397
- this.dismissEvent = event;
398
- });
399
- this.dismissableLayer.dismiss.subscribe(() => {
400
- const reason = this.dismissReason;
401
- const event = this.dismissEvent;
402
- this.dismissReason = 'none';
403
- this.dismissEvent = new Event('navigation-menu.dismiss');
404
- this.rootContext.close(reason, event);
405
- // Return focus to the trigger after an Escape dismissal.
406
- if (reason === 'escape-key') {
407
- this.rootContext.trigger()?.focus();
370
+ // The popup is this layer's floating element (the inside surface for containment checks). The
371
+ // triggers are registered as "inside" on the shared root context (in `registerTrigger`), so a
372
+ // press / focus on a sibling trigger to switch items — or back on the active trigger never
373
+ // counts as an outside dismissal. This replaces the legacy dismissable-layer `branches` registry.
374
+ this.floatingContext.setFloatingElement(this.elementRef.nativeElement);
375
+ // Dismissal (ADR 0015): Escape, an outside press, or focus moving outside closes the menu.
376
+ // Navigation Menu registers its FloatingNode on the root, matching Base UI's
377
+ // NavigationMenuRoot → FloatingNode wiring. This lets nested roots participate in parent/child
378
+ // ownership even though each root has one shared popup.
379
+ // It does not trap focus, so it closes on focus-out like a non-modal menu.
380
+ new RdxDismiss(this.floatingContext, () => this.registration?.node() ?? null, {
381
+ escapeKey: () => true,
382
+ outsidePress: () => true,
383
+ outsidePressEvent: () => 'intentional',
384
+ focusOutside: () => true,
385
+ onEscapeKeyDown: (event) => this.escapeKeyDown.emit(event),
386
+ onPointerDownOutside: (event) => this.pointerDownOutside.emit(event),
387
+ onFocusOutside: (event) => this.focusOutside.emit(event),
388
+ onDismiss: (reason, event) => {
389
+ const navReason = reason === 'escape-key' ? 'escape-key' : reason === 'focus-outside' ? 'focus-out' : 'outside-press';
390
+ this.rootContext.close(navReason, event);
391
+ // Return focus to the trigger after an Escape dismissal.
392
+ if (reason === 'escape-key') {
393
+ this.rootContext.trigger()?.focus();
394
+ }
408
395
  }
409
396
  });
410
397
  }
@@ -412,12 +399,12 @@ class RdxNavigationMenuPopup {
412
399
  if (event.pointerType === 'touch') {
413
400
  return;
414
401
  }
415
- this.rootContext.closeOnHover();
402
+ this.rootContext.closeOnHover(event);
416
403
  }
417
404
  /**
418
405
  * Keyboard navigation inside the open panel: Down/Up move between the panel's focusable items in
419
406
  * DOM order, Home/End jump to the first/last, and Up from the first item returns focus to the
420
- * trigger. (Tab keeps working natively; Escape is handled by the dismissable layer.)
407
+ * trigger. (Tab keeps working natively; Escape is handled by the dismissal capability.)
421
408
  */
422
409
  onKeydown(event) {
423
410
  if (event.key !== ARROW_DOWN && event.key !== ARROW_UP && event.key !== HOME && event.key !== END) {
@@ -457,13 +444,13 @@ class RdxNavigationMenuPopup {
457
444
  }
458
445
  }
459
446
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuPopup, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
460
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxNavigationMenuPopup, isStandalone: true, selector: "[rdxNavigationMenuPopup]", outputs: { escapeKeyDown: "escapeKeyDown", pointerDownOutside: "pointerDownOutside", focusOutside: "focusOutside" }, host: { attributes: { "role": "menu", "tabindex": "-1" }, listeners: { "pointerenter": "rootContext.cancelHoverClose()", "pointerleave": "onPointerLeave($event)", "keydown": "onKeydown($event)" }, properties: { "attr.aria-labelledby": "labelledBy()", "attr.data-open": "rootContext.isOpen() ? \"\" : undefined", "attr.data-closed": "rootContext.isOpen() ? undefined : \"\"", "attr.data-starting-style": "rootContext.transitionStatus() === \"starting\" ? \"\" : undefined", "attr.data-ending-style": "rootContext.transitionStatus() === \"ending\" ? \"\" : undefined", "attr.data-instant": "rootContext.instant() ? \"\" : undefined", "attr.data-state": "rootContext.isOpen() ? \"open\" : \"closed\"", "attr.data-side": "side()", "attr.data-align": "align()" } }, hostDirectives: [{ directive: i1.RdxPopperContent }, { directive: i2.RdxDismissableLayer }], ngImport: i0 }); }
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: { "role": "menu", "tabindex": "-1" }, listeners: { "pointerenter": "rootContext.cancelHoverClose()", "pointerleave": "onPointerLeave($event)", "keydown": "onKeydown($event)" }, properties: { "attr.aria-labelledby": "labelledBy()", "attr.data-open": "rootContext.isOpen() ? \"\" : undefined", "attr.data-closed": "rootContext.isOpen() ? undefined : \"\"", "attr.data-starting-style": "rootContext.transitionStatus() === \"starting\" ? \"\" : undefined", "attr.data-ending-style": "rootContext.transitionStatus() === \"ending\" ? \"\" : undefined", "attr.data-instant": "rootContext.instant() ? \"\" : undefined", "attr.data-state": "rootContext.isOpen() ? \"open\" : \"closed\"", "attr.data-side": "side()", "attr.data-align": "align()" } }, hostDirectives: [{ directive: i1.RdxPopperContent }], ngImport: i0 }); }
461
448
  }
462
449
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuPopup, decorators: [{
463
450
  type: Directive,
464
451
  args: [{
465
452
  selector: '[rdxNavigationMenuPopup]',
466
- hostDirectives: [RdxPopperContent, RdxDismissableLayer],
453
+ hostDirectives: [RdxPopperContent],
467
454
  host: {
468
455
  role: 'menu',
469
456
  tabindex: '-1',
@@ -495,7 +482,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
495
482
  */
496
483
  class RdxNavigationMenuPortal {
497
484
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuPortal, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
498
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxNavigationMenuPortal, isStandalone: true, selector: "ng-template[rdxNavigationMenuPortal]", providers: [provideRdxPresenceContext(() => ({ present: injectNavigationMenuRootContext().isOpen }))], exportAs: ["rdxNavigationMenuPortal"], hostDirectives: [{ directive: i1$2.RdxPortalPresence, inputs: ["container", "container"] }], ngImport: i0 }); }
485
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxNavigationMenuPortal, isStandalone: true, selector: "ng-template[rdxNavigationMenuPortal]", providers: [provideRdxPresenceContext(() => ({ present: injectNavigationMenuRootContext().present }))], exportAs: ["rdxNavigationMenuPortal"], hostDirectives: [{ directive: i1$2.RdxPortalPresence, inputs: ["container", "container"] }], ngImport: i0 }); }
499
486
  }
500
487
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuPortal, decorators: [{
501
488
  type: Directive,
@@ -503,7 +490,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
503
490
  selector: 'ng-template[rdxNavigationMenuPortal]',
504
491
  exportAs: 'rdxNavigationMenuPortal',
505
492
  hostDirectives: [{ directive: RdxPortalPresence, inputs: ['container'] }],
506
- providers: [provideRdxPresenceContext(() => ({ present: injectNavigationMenuRootContext().isOpen }))]
493
+ providers: [provideRdxPresenceContext(() => ({ present: injectNavigationMenuRootContext().present }))]
507
494
  }]
508
495
  }] });
509
496
  /**
@@ -514,10 +501,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
514
501
  class RdxNavigationMenuPortalMisuseGuard {
515
502
  constructor() {
516
503
  if (isDevMode()) {
517
- throw new Error('[rdxNavigationMenuPortal] is now a structural directive. ' +
504
+ rdxDevError('navigation-menu/portal-on-element', '`rdxNavigationMenuPortal` is now a structural directive. ' +
518
505
  'Use `*rdxNavigationMenuPortal` on the positioner element or ' +
519
- '`<ng-template rdxNavigationMenuPortal>`. rdxNavigationMenuPortalPresence has been removed. ' +
520
- 'See https://radix-ng.com/components/navigation-menu.md');
506
+ '`<ng-template rdxNavigationMenuPortal>`. rdxNavigationMenuPortalPresence has been removed.', 'components/navigation-menu');
521
507
  }
522
508
  }
523
509
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuPortalMisuseGuard, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
@@ -532,123 +518,46 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
532
518
 
533
519
  /**
534
520
  * Positions the shared popup against the active trigger.
521
+ *
522
+ * A "thin" positioner (ADR 0012): it inherits the popper positioning surface (inputs, `placed`
523
+ * output, unified vars + placement attrs) from {@link RdxPopperContentWrapper} and adds the
524
+ * navigation-menu defaults, the open/closed/instant state attributes, and the grace-area hover
525
+ * bridge. It exposes no legacy `--radix-*` aliases.
535
526
  */
536
- class RdxNavigationMenuPositioner {
527
+ class RdxNavigationMenuPositioner extends RdxPopperContentWrapper {
537
528
  constructor() {
529
+ super();
538
530
  this.rootContext = injectNavigationMenuRootContext();
539
- this.wrapper = inject(RdxPopperContentWrapper);
540
- this.elementRef = inject(ElementRef);
531
+ this.containerRef = inject(ElementRef);
541
532
  this.triggerEl = signal(null, ...(ngDevMode ? [{ debugName: "triggerEl" }] : /* istanbul ignore next */ []));
542
- this.containerEl = signal(this.elementRef.nativeElement, ...(ngDevMode ? [{ debugName: "containerEl" }] : /* istanbul ignore next */ []));
533
+ this.containerEl = signal(this.containerRef.nativeElement, ...(ngDevMode ? [{ debugName: "containerEl" }] : /* istanbul ignore next */ []));
543
534
  this.graceArea = useGraceArea(this.triggerEl, this.containerEl);
544
- /**
545
- * An element to position the popup against. Defaults to the active trigger.
546
- */
547
- this.anchor = input(...(ngDevMode ? [undefined, { debugName: "anchor" }] : /* istanbul ignore next */ []));
548
- /**
549
- * The preferred side of the trigger to render against when open.
550
- */
551
- this.side = input('bottom', ...(ngDevMode ? [{ debugName: "side" }] : /* istanbul ignore next */ []));
552
- /**
553
- * Distance between the trigger and the popup in pixels.
554
- */
555
- this.sideOffset = input(0, { ...(ngDevMode ? { debugName: "sideOffset" } : /* istanbul ignore next */ {}), transform: numberAttribute });
556
- /**
557
- * How to align the popup relative to the specified side.
558
- */
559
- this.align = input('center', ...(ngDevMode ? [{ debugName: "align" }] : /* istanbul ignore next */ []));
560
- /**
561
- * An offset in pixels from the `start` or `end` alignment options.
562
- */
563
- this.alignOffset = input(0, { ...(ngDevMode ? { debugName: "alignOffset" } : /* istanbul ignore next */ {}), transform: numberAttribute });
564
- /**
565
- * Minimum distance to maintain between the arrow and the edges of the popup.
566
- */
567
- this.arrowPadding = input(5, { ...(ngDevMode ? { debugName: "arrowPadding" } : /* istanbul ignore next */ {}), transform: numberAttribute });
568
- /**
569
- * Whether to override side and alignment preferences to prevent collisions.
570
- */
571
- this.avoidCollisions = input(true, { ...(ngDevMode ? { debugName: "avoidCollisions" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
572
- /**
573
- * The element used as the collision boundary.
574
- */
575
- this.collisionBoundary = input(...(ngDevMode ? [undefined, { debugName: "collisionBoundary" }] : /* istanbul ignore next */ []));
576
- /**
577
- * Distance in pixels from the boundary edges where collision detection should occur.
578
- */
579
- this.collisionPadding = input(5, ...(ngDevMode ? [{ debugName: "collisionPadding" }] : /* istanbul ignore next */ []));
580
- /**
581
- * The sticky behavior on the alignment axis.
582
- */
583
- this.sticky = input('partial', ...(ngDevMode ? [{ debugName: "sticky" }] : /* istanbul ignore next */ []));
584
- /**
585
- * Whether to hide the popup when the trigger becomes fully occluded.
586
- */
587
- this.hideWhenDetached = input(false, { ...(ngDevMode ? { debugName: "hideWhenDetached" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
588
- /**
589
- * The CSS position strategy used by Floating UI.
590
- */
591
- this.positionStrategy = input('fixed', ...(ngDevMode ? [{ debugName: "positionStrategy" }] : /* istanbul ignore next */ []));
592
- /**
593
- * Whether to update position on every animation frame.
594
- */
595
- this.updatePositionStrategy = input('always', ...(ngDevMode ? [{ debugName: "updatePositionStrategy" }] : /* istanbul ignore next */ []));
596
535
  effect(() => this.triggerEl.set(this.rootContext.trigger() ?? null));
597
536
  // Keep the menu open while the pointer travels from the trigger to the popup; close once it
598
537
  // leaves the grace area between them.
599
538
  this.graceArea.onPointerExit(() => this.rootContext.closeOnHover());
600
539
  }
601
540
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuPositioner, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
602
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxNavigationMenuPositioner, isStandalone: true, selector: "[rdxNavigationMenuPositioner]", inputs: { anchor: { classPropertyName: "anchor", publicName: "anchor", isSignal: true, isRequired: false, transformFunction: null }, side: { classPropertyName: "side", publicName: "side", isSignal: true, isRequired: false, transformFunction: null }, sideOffset: { classPropertyName: "sideOffset", publicName: "sideOffset", isSignal: true, isRequired: false, transformFunction: null }, align: { classPropertyName: "align", publicName: "align", isSignal: true, isRequired: false, transformFunction: null }, alignOffset: { classPropertyName: "alignOffset", publicName: "alignOffset", isSignal: true, isRequired: false, transformFunction: null }, arrowPadding: { classPropertyName: "arrowPadding", publicName: "arrowPadding", isSignal: true, isRequired: false, transformFunction: null }, avoidCollisions: { classPropertyName: "avoidCollisions", publicName: "avoidCollisions", isSignal: true, isRequired: false, transformFunction: null }, collisionBoundary: { classPropertyName: "collisionBoundary", publicName: "collisionBoundary", isSignal: true, isRequired: false, transformFunction: null }, collisionPadding: { classPropertyName: "collisionPadding", publicName: "collisionPadding", isSignal: true, isRequired: false, transformFunction: null }, sticky: { classPropertyName: "sticky", publicName: "sticky", isSignal: true, isRequired: false, transformFunction: null }, hideWhenDetached: { classPropertyName: "hideWhenDetached", publicName: "hideWhenDetached", isSignal: true, isRequired: false, transformFunction: null }, positionStrategy: { classPropertyName: "positionStrategy", publicName: "positionStrategy", isSignal: true, isRequired: false, transformFunction: null }, updatePositionStrategy: { classPropertyName: "updatePositionStrategy", publicName: "updatePositionStrategy", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.data-open": "rootContext.isOpen() ? \"\" : undefined", "attr.data-closed": "rootContext.isOpen() ? undefined : \"\"", "attr.data-anchor-hidden": "wrapper.anchorHidden() ? \"\" : undefined", "attr.data-align": "wrapper.placedAlign()", "attr.data-side": "wrapper.placedSide()", "attr.data-instant": "rootContext.instant() ? \"\" : undefined", "style": "{\n '--anchor-width': 'var(--radix-popper-anchor-width)',\n '--anchor-height': 'var(--radix-popper-anchor-height)',\n '--available-width': 'var(--radix-popper-available-width)',\n '--available-height': 'var(--radix-popper-available-height)',\n '--positioner-width': 'var(--radix-popper-content-wrapper-width)',\n '--positioner-height': 'var(--radix-popper-content-wrapper-height)',\n '--transform-origin': 'var(--radix-popper-transform-origin)'\n }" } }, providers: [
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: [
542
+ ...provideRdxPopperContentWrapper(RdxNavigationMenuPositioner),
603
543
  provideRdxPopperContentConfig({ arrowPadding: 5, collisionPadding: 5, updatePositionStrategy: 'always' })
604
- ], hostDirectives: [{ directive: i1.RdxPopperContentWrapper, inputs: ["anchor", "anchor", "side", "side", "sideOffset", "sideOffset", "align", "align", "alignOffset", "alignOffset", "arrowPadding", "arrowPadding", "avoidCollisions", "avoidCollisions", "collisionBoundary", "collisionBoundary", "collisionPadding", "collisionPadding", "sticky", "sticky", "hideWhenDetached", "hideWhenDetached", "positionStrategy", "positionStrategy", "updatePositionStrategy", "updatePositionStrategy"] }], ngImport: i0 }); }
544
+ ], usesInheritance: true, ngImport: i0 }); }
605
545
  }
606
546
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuPositioner, decorators: [{
607
547
  type: Directive,
608
548
  args: [{
609
549
  selector: '[rdxNavigationMenuPositioner]',
610
550
  providers: [
551
+ ...provideRdxPopperContentWrapper(RdxNavigationMenuPositioner),
611
552
  provideRdxPopperContentConfig({ arrowPadding: 5, collisionPadding: 5, updatePositionStrategy: 'always' })
612
553
  ],
613
- hostDirectives: [
614
- {
615
- directive: RdxPopperContentWrapper,
616
- inputs: [
617
- 'anchor',
618
- 'side',
619
- 'sideOffset',
620
- 'align',
621
- 'alignOffset',
622
- 'arrowPadding',
623
- 'avoidCollisions',
624
- 'collisionBoundary',
625
- 'collisionPadding',
626
- 'sticky',
627
- 'hideWhenDetached',
628
- 'positionStrategy',
629
- 'updatePositionStrategy'
630
- ]
631
- }
632
- ],
633
554
  host: {
634
555
  '[attr.data-open]': 'rootContext.isOpen() ? "" : undefined',
635
556
  '[attr.data-closed]': 'rootContext.isOpen() ? undefined : ""',
636
- '[attr.data-anchor-hidden]': 'wrapper.anchorHidden() ? "" : undefined',
637
- '[attr.data-align]': 'wrapper.placedAlign()',
638
- '[attr.data-side]': 'wrapper.placedSide()',
639
- '[attr.data-instant]': 'rootContext.instant() ? "" : undefined',
640
- '[style]': `{
641
- '--anchor-width': 'var(--radix-popper-anchor-width)',
642
- '--anchor-height': 'var(--radix-popper-anchor-height)',
643
- '--available-width': 'var(--radix-popper-available-width)',
644
- '--available-height': 'var(--radix-popper-available-height)',
645
- '--positioner-width': 'var(--radix-popper-content-wrapper-width)',
646
- '--positioner-height': 'var(--radix-popper-content-wrapper-height)',
647
- '--transform-origin': 'var(--radix-popper-transform-origin)'
648
- }`
557
+ '[attr.data-instant]': 'rootContext.instant() ? "" : undefined'
649
558
  }
650
559
  }]
651
- }], ctorParameters: () => [], propDecorators: { anchor: [{ type: i0.Input, args: [{ isSignal: true, alias: "anchor", required: false }] }], side: [{ type: i0.Input, args: [{ isSignal: true, alias: "side", required: false }] }], sideOffset: [{ type: i0.Input, args: [{ isSignal: true, alias: "sideOffset", required: false }] }], align: [{ type: i0.Input, args: [{ isSignal: true, alias: "align", required: false }] }], alignOffset: [{ type: i0.Input, args: [{ isSignal: true, alias: "alignOffset", required: false }] }], arrowPadding: [{ type: i0.Input, args: [{ isSignal: true, alias: "arrowPadding", required: false }] }], avoidCollisions: [{ type: i0.Input, args: [{ isSignal: true, alias: "avoidCollisions", required: false }] }], collisionBoundary: [{ type: i0.Input, args: [{ isSignal: true, alias: "collisionBoundary", required: false }] }], collisionPadding: [{ type: i0.Input, args: [{ isSignal: true, alias: "collisionPadding", required: false }] }], sticky: [{ type: i0.Input, args: [{ isSignal: true, alias: "sticky", required: false }] }], hideWhenDetached: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideWhenDetached", required: false }] }], positionStrategy: [{ type: i0.Input, args: [{ isSignal: true, alias: "positionStrategy", required: false }] }], updatePositionStrategy: [{ type: i0.Input, args: [{ isSignal: true, alias: "updatePositionStrategy", required: false }] }] } });
560
+ }], ctorParameters: () => [] });
652
561
 
653
562
  const context = () => contextFor(inject(RdxNavigationMenuRoot));
654
563
  /**
@@ -663,6 +572,12 @@ class RdxNavigationMenuRoot {
663
572
  this.popper = inject(RdxPopper);
664
573
  this.destroyRef = inject(DestroyRef);
665
574
  this.parentRoot = inject(RdxNavigationMenuRoot, { optional: true, skipSelf: true });
575
+ this.registration = inject(RDX_FLOATING_REGISTRATION, { optional: true });
576
+ /** Per-popup floating root context (ADR 0015) — `open` / `triggers` / reference for the dismissal engine. */
577
+ this.floatingContext = createFloatingRootContext({
578
+ ownerDocument: inject(ElementRef).nativeElement.ownerDocument,
579
+ open: () => this.isOpen()
580
+ });
666
581
  /** Whether this root is nested inside another navigation menu's content. */
667
582
  this.nested = !!this.parentRoot;
668
583
  this.baseId = `rdx-nav-menu-${generateId()}`;
@@ -681,7 +596,8 @@ class RdxNavigationMenuRoot {
681
596
  /**
682
597
  * The reading direction of the navigation menu.
683
598
  */
684
- this.dir = input('ltr', ...(ngDevMode ? [{ debugName: "dir" }] : /* istanbul ignore next */ []));
599
+ this.dirInput = input(undefined, { ...(ngDevMode ? { debugName: "dirInput" } : /* istanbul ignore next */ {}), alias: 'dir' });
600
+ this.dir = injectDirection(this.dirInput);
685
601
  /**
686
602
  * Whether keyboard navigation loops from the last item back to the first and vice versa.
687
603
  */
@@ -715,6 +631,8 @@ class RdxNavigationMenuRoot {
715
631
  this.transitionStatus = this.transition.status;
716
632
  this.previousValue = signal(null, ...(ngDevMode ? [{ debugName: "previousValue" }] : /* istanbul ignore next */ []));
717
633
  this.isOpen = computed(() => this.value() !== null, ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
634
+ this.preventUnmountOnClose = signal(false, ...(ngDevMode ? [{ debugName: "preventUnmountOnClose" }] : /* istanbul ignore next */ []));
635
+ this.present = computed(() => this.isOpen() || this.preventUnmountOnClose(), ...(ngDevMode ? [{ debugName: "present" }] : /* istanbul ignore next */ []));
718
636
  this.trigger = signal(undefined, ...(ngDevMode ? [{ debugName: "trigger" }] : /* istanbul ignore next */ []));
719
637
  this.triggers = signal([], ...(ngDevMode ? [{ debugName: "triggers" }] : /* istanbul ignore next */ []));
720
638
  this.contents = signal(new Map(), ...(ngDevMode ? [{ debugName: "contents" }] : /* istanbul ignore next */ []));
@@ -741,6 +659,9 @@ class RdxNavigationMenuRoot {
741
659
  });
742
660
  // Anchor the shared popper to the active trigger.
743
661
  effect(() => this.popper.anchorOverride.set(this.trigger()));
662
+ // Keep the dismissal reference in sync with the active trigger (the anchor) so a press / focus on
663
+ // it counts as "inside" (ADR 0015). The full trigger registry is maintained in `registerTrigger`.
664
+ effect(() => this.floatingContext.setReferenceElement(this.trigger() ?? null));
744
665
  this.destroyRef.onDestroy(() => {
745
666
  this.clearHoverTimers();
746
667
  if (this.instantFrame !== undefined) {
@@ -762,6 +683,11 @@ class RdxNavigationMenuRoot {
762
683
  const previousTrigger = this.trigger();
763
684
  const nextTrigger = value ? this.registeredTriggers.get(value) : undefined;
764
685
  const changedTriggerWhileOpen = previous !== null && value !== null && previousTrigger !== nextTrigger;
686
+ const change = this.createOpenChangeEvent(value, reason, event, nextTrigger ?? previousTrigger);
687
+ this.onOpenChange.emit(change.payload);
688
+ if (change.eventDetails.isCanceled()) {
689
+ return;
690
+ }
765
691
  this.instant.set(changedTriggerWhileOpen || reason === 'trigger-focus');
766
692
  if (changedTriggerWhileOpen) {
767
693
  this.scheduleInstantReset();
@@ -773,9 +699,9 @@ class RdxNavigationMenuRoot {
773
699
  this.trigger.set(nextTrigger);
774
700
  }
775
701
  this.previousValue.set(previous);
702
+ this.preventUnmountOnClose.set(value === null ? change.shouldPreventUnmountOnClose() : false);
776
703
  this.value.set(value);
777
704
  this.onValueChange.emit(value);
778
- this.onOpenChange.emit({ value, open: value !== null, reason, event });
779
705
  }
780
706
  open(value, trigger, reason = 'none', event) {
781
707
  this.clearHoverTimers();
@@ -812,7 +738,11 @@ class RdxNavigationMenuRoot {
812
738
  }
813
739
  this.openTimer = setTimeout(() => this.open(value, trigger, 'trigger-hover', event), this.delay());
814
740
  }
815
- closeOnHover() {
741
+ closeOnHover(event) {
742
+ if (event && this.isInsideOpenChild(event.relatedTarget)) {
743
+ this.cancelHoverClose();
744
+ return;
745
+ }
816
746
  this.clearOpenTimer();
817
747
  this.clearCloseTimer();
818
748
  this.closeTimer = setTimeout(() => this.close('list-leave', new Event('navigation-menu.hover-close')), this.closeDelay());
@@ -826,6 +756,9 @@ class RdxNavigationMenuRoot {
826
756
  registerTrigger(value, trigger) {
827
757
  this.registeredTriggers.set(value, trigger);
828
758
  this.triggers.update((triggers) => (triggers.includes(trigger) ? triggers : [...triggers, trigger]));
759
+ // Mark every trigger as "inside" the floating layer (ADR 0015): a press / focus on a sibling
760
+ // trigger (to switch items) or back on the active trigger must not count as an outside dismissal.
761
+ this.floatingContext.triggers.add(trigger);
829
762
  if (this.value() === value) {
830
763
  this.trigger.set(trigger);
831
764
  }
@@ -834,6 +767,7 @@ class RdxNavigationMenuRoot {
834
767
  this.registeredTriggers.delete(value);
835
768
  }
836
769
  this.triggers.update((triggers) => triggers.filter((candidate) => candidate !== trigger));
770
+ this.floatingContext.triggers.delete(trigger);
837
771
  if (this.destroyRef.destroyed || this.value() !== value) {
838
772
  return;
839
773
  }
@@ -872,6 +806,9 @@ class RdxNavigationMenuRoot {
872
806
  this.viewportTriggerChange.add(onTriggerChange);
873
807
  return () => this.viewportTriggerChange.delete(onTriggerChange);
874
808
  }
809
+ createOpenChangeEvent(value, reason, event, trigger) {
810
+ return createNavigationMenuOpenChangeEvent(value, reason, event, trigger);
811
+ }
875
812
  scheduleInstantReset() {
876
813
  if (this.instantFrame !== undefined) {
877
814
  cancelAnimationFrame(this.instantFrame);
@@ -899,16 +836,48 @@ class RdxNavigationMenuRoot {
899
836
  this.closeTimer = undefined;
900
837
  }
901
838
  }
839
+ isInsideOpenChild(target) {
840
+ if (!(target instanceof Node)) {
841
+ return false;
842
+ }
843
+ const node = this.registration?.node();
844
+ if (!node) {
845
+ return false;
846
+ }
847
+ return node.tree.children(node, { onlyOpen: true }).some((child) => {
848
+ const context = child.context;
849
+ const floating = context?.floatingElement;
850
+ return !!floating && floating.contains(target);
851
+ });
852
+ }
902
853
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuRoot, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
903
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxNavigationMenuRoot, isStandalone: true, selector: "[rdxNavigationMenuRoot]", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, defaultValue: { classPropertyName: "defaultValue", publicName: "defaultValue", isSignal: true, isRequired: false, transformFunction: null }, orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, dir: { classPropertyName: "dir", publicName: "dir", isSignal: true, isRequired: false, transformFunction: null }, loop: { classPropertyName: "loop", publicName: "loop", isSignal: true, isRequired: false, transformFunction: null }, delay: { classPropertyName: "delay", publicName: "delay", isSignal: true, isRequired: false, transformFunction: null }, closeDelay: { classPropertyName: "closeDelay", publicName: "closeDelay", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", onValueChange: "onValueChange", onOpenChange: "onOpenChange", onOpenChangeComplete: "onOpenChangeComplete" }, host: { attributes: { "role": "navigation", "aria-label": "Main" }, properties: { "attr.data-orientation": "orientation()", "attr.dir": "dir()" } }, providers: [provideNavigationMenuRootContext(context)], exportAs: ["rdxNavigationMenuRoot"], hostDirectives: [{ directive: i1.RdxPopper }], ngImport: i0 }); }
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 }, loop: { classPropertyName: "loop", publicName: "loop", isSignal: true, isRequired: false, transformFunction: null }, delay: { classPropertyName: "delay", publicName: "delay", isSignal: true, isRequired: false, transformFunction: null }, closeDelay: { classPropertyName: "closeDelay", publicName: "closeDelay", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", onValueChange: "onValueChange", onOpenChange: "onOpenChange", onOpenChangeComplete: "onOpenChangeComplete" }, host: { attributes: { "role": "navigation", "aria-label": "Main" }, properties: { "attr.data-orientation": "orientation()", "attr.dir": "dir()" } }, providers: [
855
+ provideNavigationMenuRootContext(context),
856
+ // Base UI wraps every NavigationMenu.Root in a FloatingNode and only creates FloatingTree at the
857
+ // top boundary. `provideFloatingTree()` is inherit-or-create, so nested navigation menus join the
858
+ // parent's tree while the top-level menu starts the coordination store.
859
+ provideFloatingTree(),
860
+ // New floating foundation (ADR 0015/0017) — dismissal reads this shared root context, while the
861
+ // root-level node above gives nested navigation menus real parent/child ownership.
862
+ provideFloatingRootContext(() => inject(RdxNavigationMenuRoot).floatingContext)
863
+ ], exportAs: ["rdxNavigationMenuRoot"], hostDirectives: [{ directive: i1.RdxPopper }, { directive: i2.RdxFloatingNodeRegistration }], ngImport: i0 }); }
904
864
  }
905
865
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxNavigationMenuRoot, decorators: [{
906
866
  type: Directive,
907
867
  args: [{
908
868
  selector: '[rdxNavigationMenuRoot]',
909
869
  exportAs: 'rdxNavigationMenuRoot',
910
- providers: [provideNavigationMenuRootContext(context)],
911
- hostDirectives: [RdxPopper],
870
+ providers: [
871
+ provideNavigationMenuRootContext(context),
872
+ // Base UI wraps every NavigationMenu.Root in a FloatingNode and only creates FloatingTree at the
873
+ // top boundary. `provideFloatingTree()` is inherit-or-create, so nested navigation menus join the
874
+ // parent's tree while the top-level menu starts the coordination store.
875
+ provideFloatingTree(),
876
+ // New floating foundation (ADR 0015/0017) — dismissal reads this shared root context, while the
877
+ // root-level node above gives nested navigation menus real parent/child ownership.
878
+ provideFloatingRootContext(() => inject(RdxNavigationMenuRoot).floatingContext)
879
+ ],
880
+ hostDirectives: [RdxPopper, RdxFloatingNodeRegistration],
912
881
  host: {
913
882
  role: 'navigation',
914
883
  'aria-label': 'Main',
@@ -916,7 +885,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
916
885
  '[attr.dir]': 'dir()'
917
886
  }
918
887
  }]
919
- }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], defaultValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultValue", required: false }] }], orientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: false }] }], dir: [{ type: i0.Input, args: [{ isSignal: true, alias: "dir", required: false }] }], loop: [{ type: i0.Input, args: [{ isSignal: true, alias: "loop", required: false }] }], delay: [{ type: i0.Input, args: [{ isSignal: true, alias: "delay", required: false }] }], closeDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeDelay", required: false }] }], onValueChange: [{ type: i0.Output, args: ["onValueChange"] }], onOpenChange: [{ type: i0.Output, args: ["onOpenChange"] }], onOpenChangeComplete: [{ type: i0.Output, args: ["onOpenChangeComplete"] }] } });
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 }] }], loop: [{ type: i0.Input, args: [{ isSignal: true, alias: "loop", required: false }] }], delay: [{ type: i0.Input, args: [{ isSignal: true, alias: "delay", required: false }] }], closeDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeDelay", required: false }] }], onValueChange: [{ type: i0.Output, args: ["onValueChange"] }], onOpenChange: [{ type: i0.Output, args: ["onOpenChange"] }], onOpenChangeComplete: [{ type: i0.Output, args: ["onOpenChangeComplete"] }] } });
920
889
  function contextFor(root) {
921
890
  return {
922
891
  nested: root.nested,
@@ -927,6 +896,7 @@ function contextFor(root) {
927
896
  value: root.value,
928
897
  previousValue: root.previousValue.asReadonly(),
929
898
  isOpen: root.isOpen,
899
+ present: root.present,
930
900
  instant: root.instant.asReadonly(),
931
901
  transitionStatus: root.transitionStatus,
932
902
  trigger: root.trigger.asReadonly(),
@@ -939,7 +909,7 @@ function contextFor(root) {
939
909
  close: (reason, event) => root.close(reason, event),
940
910
  toggle: (value, trigger, event) => root.toggle(value, trigger, event),
941
911
  openOnHover: (value, trigger, event) => root.openOnHover(value, trigger, event),
942
- closeOnHover: () => root.closeOnHover(),
912
+ closeOnHover: (event) => root.closeOnHover(event),
943
913
  cancelHoverOpen: () => root.cancelHoverOpen(),
944
914
  cancelHoverClose: () => root.cancelHoverClose(),
945
915
  registerTrigger: (value, trigger) => root.registerTrigger(value, trigger),
@@ -948,6 +918,21 @@ function contextFor(root) {
948
918
  registerViewport: (onTriggerChange) => root.registerViewport(onTriggerChange)
949
919
  };
950
920
  }
921
+ function createNavigationMenuOpenChangeEvent(value, reason, event, trigger) {
922
+ const change = createCancelableChangeEventDetails(reason, event, trigger);
923
+ return {
924
+ payload: {
925
+ value,
926
+ open: value !== null,
927
+ reason,
928
+ event: change.eventDetails.event,
929
+ trigger,
930
+ eventDetails: change.eventDetails
931
+ },
932
+ eventDetails: change.eventDetails,
933
+ shouldPreventUnmountOnClose: change.shouldPreventUnmountOnClose
934
+ };
935
+ }
951
936
 
952
937
  /**
953
938
  * A button that opens its item's content in the shared popup.