@radix-ng/primitives 1.0.0-beta.2 → 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.
- package/LICENSE +1 -1
- package/README.md +76 -6
- package/fesm2022/radix-ng-primitives-accordion.mjs +5 -3
- package/fesm2022/radix-ng-primitives-accordion.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-alert-dialog.mjs +31 -24
- package/fesm2022/radix-ng-primitives-alert-dialog.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-autocomplete.mjs +1744 -0
- package/fesm2022/radix-ng-primitives-autocomplete.mjs.map +1 -0
- package/fesm2022/radix-ng-primitives-calendar.mjs +5 -3
- package/fesm2022/radix-ng-primitives-calendar.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-combobox.mjs +1399 -606
- package/fesm2022/radix-ng-primitives-combobox.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-config.mjs +13 -4
- package/fesm2022/radix-ng-primitives-config.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-context-menu.mjs +51 -10
- package/fesm2022/radix-ng-primitives-context-menu.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-core.mjs +1345 -64
- package/fesm2022/radix-ng-primitives-core.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-date-field.mjs +5 -3
- package/fesm2022/radix-ng-primitives-date-field.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-dialog.mjs +271 -145
- package/fesm2022/radix-ng-primitives-dialog.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-direction-provider.mjs +70 -0
- package/fesm2022/radix-ng-primitives-direction-provider.mjs.map +1 -0
- package/fesm2022/radix-ng-primitives-dismissable-layer.mjs +519 -184
- package/fesm2022/radix-ng-primitives-dismissable-layer.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-drawer.mjs +154 -64
- package/fesm2022/radix-ng-primitives-drawer.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-field.mjs +3 -2
- package/fesm2022/radix-ng-primitives-field.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-floating-focus-manager.mjs +517 -0
- package/fesm2022/radix-ng-primitives-floating-focus-manager.mjs.map +1 -0
- package/fesm2022/radix-ng-primitives-focus-scope.mjs +296 -70
- package/fesm2022/radix-ng-primitives-focus-scope.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-menu.mjs +894 -299
- package/fesm2022/radix-ng-primitives-menu.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-menubar.mjs +32 -4
- package/fesm2022/radix-ng-primitives-menubar.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-navigation-menu.mjs +176 -207
- package/fesm2022/radix-ng-primitives-navigation-menu.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-popover.mjs +250 -250
- package/fesm2022/radix-ng-primitives-popover.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-popper.mjs +94 -45
- package/fesm2022/radix-ng-primitives-popper.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-portal.mjs +107 -17
- package/fesm2022/radix-ng-primitives-portal.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-presence.mjs +262 -79
- package/fesm2022/radix-ng-primitives-presence.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-preview-card.mjs +172 -218
- package/fesm2022/radix-ng-primitives-preview-card.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-roving-focus.mjs +4 -2
- package/fesm2022/radix-ng-primitives-roving-focus.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-scroll-area.mjs +5 -4
- package/fesm2022/radix-ng-primitives-scroll-area.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-select.mjs +303 -234
- package/fesm2022/radix-ng-primitives-select.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-slider.mjs +5 -3
- package/fesm2022/radix-ng-primitives-slider.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-stepper.mjs +5 -3
- package/fesm2022/radix-ng-primitives-stepper.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-time-field.mjs +5 -3
- package/fesm2022/radix-ng-primitives-time-field.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-toast.mjs +15 -36
- package/fesm2022/radix-ng-primitives-toast.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-toggle-group.mjs +5 -3
- package/fesm2022/radix-ng-primitives-toggle-group.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-toolbar.mjs +5 -3
- package/fesm2022/radix-ng-primitives-toolbar.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-tooltip.mjs +105 -145
- package/fesm2022/radix-ng-primitives-tooltip.mjs.map +1 -1
- package/package.json +14 -1
- package/types/radix-ng-primitives-accordion.d.ts +4 -3
- package/types/radix-ng-primitives-alert-dialog.d.ts +17 -11
- package/types/radix-ng-primitives-autocomplete.d.ts +661 -0
- package/types/radix-ng-primitives-calendar.d.ts +5 -3
- package/types/radix-ng-primitives-combobox.d.ts +727 -293
- package/types/radix-ng-primitives-config.d.ts +1 -1
- package/types/radix-ng-primitives-context-menu.d.ts +15 -5
- package/types/radix-ng-primitives-core.d.ts +762 -14
- package/types/radix-ng-primitives-date-field.d.ts +3 -2
- package/types/radix-ng-primitives-dialog.d.ts +107 -55
- package/types/radix-ng-primitives-direction-provider.d.ts +41 -0
- package/types/radix-ng-primitives-dismissable-layer.d.ts +147 -99
- package/types/radix-ng-primitives-drawer.d.ts +49 -22
- package/types/radix-ng-primitives-field.d.ts +1 -0
- package/types/radix-ng-primitives-floating-focus-manager.d.ts +175 -0
- package/types/radix-ng-primitives-focus-scope.d.ts +132 -1
- package/types/radix-ng-primitives-menu.d.ts +204 -112
- package/types/radix-ng-primitives-navigation-menu.d.ts +61 -101
- package/types/radix-ng-primitives-popover.d.ts +82 -115
- package/types/radix-ng-primitives-popper.d.ts +46 -10
- package/types/radix-ng-primitives-portal.d.ts +53 -8
- package/types/radix-ng-primitives-presence.d.ts +98 -17
- package/types/radix-ng-primitives-preview-card.d.ts +63 -95
- package/types/radix-ng-primitives-roving-focus.d.ts +7 -6
- package/types/radix-ng-primitives-scroll-area.d.ts +2 -2
- package/types/radix-ng-primitives-select.d.ts +192 -158
- package/types/radix-ng-primitives-slider.d.ts +5 -4
- package/types/radix-ng-primitives-stepper.d.ts +4 -3
- package/types/radix-ng-primitives-time-field.d.ts +3 -2
- package/types/radix-ng-primitives-toast.d.ts +7 -7
- package/types/radix-ng-primitives-toggle-group.d.ts +5 -4
- package/types/radix-ng-primitives-toolbar.d.ts +3 -2
- package/types/radix-ng-primitives-tooltip.d.ts +48 -84
|
@@ -1,43 +1,57 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, model, input, booleanAttribute, output, signal, computed, effect, untracked, Directive,
|
|
2
|
+
import { inject, ElementRef, model, input, booleanAttribute, output, signal, computed, effect, untracked, Directive, DestroyRef, isDevMode, Injector, afterNextRender, numberAttribute, PLATFORM_ID, NgModule } from '@angular/core';
|
|
3
3
|
import * as i1 from '@radix-ng/primitives/popper';
|
|
4
|
-
import { RdxPopper, RdxPopperContentWrapper, RdxPopperArrow, RdxPopperContent, provideRdxPopperContentConfig, RdxPopperAnchor } from '@radix-ng/primitives/popper';
|
|
5
|
-
import
|
|
4
|
+
import { RdxPopper, RdxPopperContentWrapper, RdxPopperArrow, RdxPopperContent, legacyPopperVars, provideRdxPopperContentWrapper, provideRdxPopperContentConfig, RdxPopperAnchor } from '@radix-ng/primitives/popper';
|
|
5
|
+
import * as i2 from '@radix-ng/primitives/core';
|
|
6
|
+
import { createContext, createFloatingRootContext, useTransitionStatus, createCancelableChangeEventDetails, provideFloatingTree, provideFloatingRootContext, injectId, RDX_FLOATING_ROOT_CONTEXT, RDX_FLOATING_REGISTRATION, useAnchoredScrollLock, RdxFloatingNodeRegistration, rdxDevError, setupInternalBackdrop, getMaxTransitionDuration } from '@radix-ng/primitives/core';
|
|
7
|
+
import { injectDirection } from '@radix-ng/primitives/direction-provider';
|
|
8
|
+
import * as i3 from '@radix-ng/primitives/floating-focus-manager';
|
|
9
|
+
import { getInteractionTypeFromEvent, RdxFloatingFocusManager, provideFloatingFocusManagerConfig } from '@radix-ng/primitives/floating-focus-manager';
|
|
6
10
|
import { outputFromObservable, outputToObservable } from '@angular/core/rxjs-interop';
|
|
7
|
-
import
|
|
8
|
-
import {
|
|
9
|
-
import * as i3 from '@radix-ng/primitives/focus-scope';
|
|
10
|
-
import { RdxFocusScope, provideRdxFocusScopeConfig } from '@radix-ng/primitives/focus-scope';
|
|
11
|
+
import { RdxDismiss } from '@radix-ng/primitives/dismissable-layer';
|
|
12
|
+
import { RdxFocusScope } from '@radix-ng/primitives/focus-scope';
|
|
11
13
|
import * as i1$1 from '@radix-ng/primitives/portal';
|
|
12
|
-
import {
|
|
14
|
+
import { RdxPortalPresence } from '@radix-ng/primitives/portal';
|
|
15
|
+
import { provideRdxPresenceContext } from '@radix-ng/primitives/presence';
|
|
13
16
|
import { isPlatformBrowser } from '@angular/common';
|
|
14
17
|
|
|
15
18
|
const [injectRdxMenuRootContext, provideRdxMenuRootContext] = createContext('RdxMenuRootContext', 'components/menu');
|
|
16
19
|
function buildContext(instance) {
|
|
17
20
|
return {
|
|
18
21
|
isOpen: instance.open,
|
|
19
|
-
|
|
20
|
-
|
|
22
|
+
present: instance.present,
|
|
23
|
+
disabled: instance.effectiveDisabled,
|
|
24
|
+
modal: instance.effectiveModal,
|
|
21
25
|
loopFocus: instance.loopFocus,
|
|
22
26
|
highlightItemOnHover: instance.highlightItemOnHover,
|
|
23
27
|
orientation: instance.orientation,
|
|
28
|
+
dir: instance.dir,
|
|
24
29
|
closeParentOnEsc: instance.closeParentOnEsc,
|
|
25
30
|
autoFocus: instance.autoFocus.asReadonly(),
|
|
26
31
|
isSubmenu: instance.isSubmenu.asReadonly(),
|
|
32
|
+
parentType: instance.parentType,
|
|
33
|
+
lastOpenChangeReason: instance.lastOpenChangeReason.asReadonly(),
|
|
34
|
+
allowMouseUpTrigger: instance.allowMouseUpTrigger,
|
|
35
|
+
openedByTouch: instance.openedByTouch.asReadonly(),
|
|
36
|
+
openInteractionType: instance.openInteractionType.asReadonly(),
|
|
37
|
+
closeInteractionType: instance.closeInteractionType.asReadonly(),
|
|
27
38
|
hasTriggerInteractionHandler: instance.hasTriggerInteractionHandler.asReadonly(),
|
|
28
39
|
trigger: instance.trigger.asReadonly(),
|
|
29
40
|
popupElement: instance.popupElement.asReadonly(),
|
|
30
41
|
transitionStatus: instance.transitionStatus,
|
|
31
|
-
close: () => instance.close(),
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
42
|
+
close: (reason, event) => instance.close(reason, event),
|
|
43
|
+
closeEntireMenu: (reason, event) => instance.closeEntireMenu(reason, event),
|
|
44
|
+
toggle: (reason, event) => instance.toggle(reason, event),
|
|
45
|
+
show: (autoFocus, reason, event) => instance.show(autoFocus, reason, event),
|
|
46
|
+
showWithoutAutoFocus: (reason, event) => instance.show(false, reason, event),
|
|
35
47
|
registerTrigger: (el) => instance.registerTrigger(el),
|
|
36
48
|
registerPopup: (el) => instance.registerPopup(el),
|
|
37
49
|
registerTransitionElement: (el) => instance.registerTransitionElement(el),
|
|
38
50
|
registerPopupArrowNavigationHandler: (handler) => instance.registerPopupArrowNavigationHandler(handler),
|
|
39
51
|
registerTriggerInteractionHandler: (handler) => instance.registerTriggerInteractionHandler(handler),
|
|
40
52
|
markAsSubmenu: () => instance.markAsSubmenu(),
|
|
53
|
+
markAsContextMenu: () => instance.markAsContextMenu(),
|
|
54
|
+
setAllowMouseUpTrigger: (value) => instance.setAllowMouseUpTrigger(value),
|
|
41
55
|
closeParent: () => instance.closeParent(),
|
|
42
56
|
handlePopupArrowNavigation: (offset) => instance.handlePopupArrowNavigation(offset),
|
|
43
57
|
handleTriggerInteraction: (interaction) => instance.handleTriggerInteraction(interaction)
|
|
@@ -50,6 +64,17 @@ const contextFactory = () => buildContext(inject(RdxMenuRoot));
|
|
|
50
64
|
class RdxMenuRoot {
|
|
51
65
|
constructor() {
|
|
52
66
|
this.popper = inject(RdxPopper);
|
|
67
|
+
this.parentRoot = inject(RdxMenuRoot, { optional: true, skipSelf: true });
|
|
68
|
+
this.providedDirection = injectDirection();
|
|
69
|
+
/**
|
|
70
|
+
* The shared per-popup floating context (ADR 0015 §1) — `open` mirrors this menu's open state, the
|
|
71
|
+
* trigger registry is bridged from {@link registerTrigger}, and the reference / floating elements are
|
|
72
|
+
* set by the trigger / popup. The new dismissal engine reads this once the popup migrates.
|
|
73
|
+
*/
|
|
74
|
+
this.floatingContext = createFloatingRootContext({
|
|
75
|
+
ownerDocument: inject(ElementRef).nativeElement.ownerDocument,
|
|
76
|
+
open: () => this.open()
|
|
77
|
+
});
|
|
53
78
|
/** Shared open/close transition state machine (completes on the real animationend). */
|
|
54
79
|
this.transition = useTransitionStatus((open) => this.onOpenChangeComplete.emit(open));
|
|
55
80
|
this.hasAppliedDefaultOpen = false;
|
|
@@ -59,14 +84,19 @@ class RdxMenuRoot {
|
|
|
59
84
|
this.defaultOpen = input(false, { ...(ngDevMode ? { debugName: "defaultOpen" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
60
85
|
/** Whether interactions with the menu are disabled. */
|
|
61
86
|
this.disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
62
|
-
/**
|
|
63
|
-
|
|
87
|
+
/**
|
|
88
|
+
* Whether the menu should block outside interactions and page scrolling.
|
|
89
|
+
* Nested menus are always non-modal.
|
|
90
|
+
*/
|
|
91
|
+
this.modal = input(true, { ...(ngDevMode ? { debugName: "modal" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
64
92
|
/** Whether keyboard navigation wraps at list boundaries. */
|
|
65
93
|
this.loopFocus = input(true, { ...(ngDevMode ? { debugName: "loopFocus" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
66
94
|
/** Whether moving the pointer over items should highlight them. */
|
|
67
95
|
this.highlightItemOnHover = input(true, { ...(ngDevMode ? { debugName: "highlightItemOnHover" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
68
96
|
/** The menu orientation. */
|
|
69
97
|
this.orientation = input('vertical', ...(ngDevMode ? [{ debugName: "orientation" }] : /* istanbul ignore next */ []));
|
|
98
|
+
/** Text direction for submenu arrow-key behavior. Inherited by nested submenu roots. */
|
|
99
|
+
this.dirInput = input(undefined, { ...(ngDevMode ? { debugName: "dirInput" } : /* istanbul ignore next */ {}), alias: 'dir' });
|
|
70
100
|
/** Whether pressing Escape inside a submenu closes the whole menu chain. */
|
|
71
101
|
this.closeParentOnEsc = input(false, { ...(ngDevMode ? { debugName: "closeParentOnEsc" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
72
102
|
/** Emits when the open state changes. */
|
|
@@ -79,8 +109,42 @@ class RdxMenuRoot {
|
|
|
79
109
|
/** Whether the popup grabs focus when it opens. Set false for menubar hover-switching. */
|
|
80
110
|
this.autoFocus = signal('first', ...(ngDevMode ? [{ debugName: "autoFocus" }] : /* istanbul ignore next */ []));
|
|
81
111
|
this.isSubmenu = signal(false, ...(ngDevMode ? [{ debugName: "isSubmenu" }] : /* istanbul ignore next */ []));
|
|
112
|
+
/** Set by `RdxContextMenuRoot` (it composes this root) — distinguishes a context menu from a dropdown. */
|
|
113
|
+
this.isContextMenu = signal(false, ...(ngDevMode ? [{ debugName: "isContextMenu" }] : /* istanbul ignore next */ []));
|
|
82
114
|
this.hasTriggerInteractionHandler = signal(false, ...(ngDevMode ? [{ debugName: "hasTriggerInteractionHandler" }] : /* istanbul ignore next */ []));
|
|
115
|
+
this.preventUnmountOnClose = signal(false, ...(ngDevMode ? [{ debugName: "preventUnmountOnClose" }] : /* istanbul ignore next */ []));
|
|
116
|
+
/**
|
|
117
|
+
* What kind of parent this menu has (Base UI `MenuParent.type`). A submenu wins over everything (its
|
|
118
|
+
* parent is a menu); otherwise a context-menu marker, then a menubar (detected by the trigger
|
|
119
|
+
* interaction handler the menubar registers), else a standalone dropdown.
|
|
120
|
+
*/
|
|
121
|
+
this.parentType = computed(() => {
|
|
122
|
+
if (this.isSubmenu()) {
|
|
123
|
+
return 'menu';
|
|
124
|
+
}
|
|
125
|
+
if (this.isContextMenu()) {
|
|
126
|
+
return 'context-menu';
|
|
127
|
+
}
|
|
128
|
+
if (this.hasTriggerInteractionHandler()) {
|
|
129
|
+
return 'menubar';
|
|
130
|
+
}
|
|
131
|
+
return undefined;
|
|
132
|
+
}, ...(ngDevMode ? [{ debugName: "parentType" }] : /* istanbul ignore next */ []));
|
|
133
|
+
/** The reason for the most recent open-change (Base UI open-change `reason`), for the per-kind policy. */
|
|
134
|
+
this.lastOpenChangeReason = signal('none', ...(ngDevMode ? [{ debugName: "lastOpenChangeReason" }] : /* istanbul ignore next */ []));
|
|
135
|
+
this.localAllowMouseUpTrigger = signal(false, ...(ngDevMode ? [{ debugName: "localAllowMouseUpTrigger" }] : /* istanbul ignore next */ []));
|
|
136
|
+
this.allowMouseUpTrigger = computed(() => this.parentRoot?.allowMouseUpTrigger() ?? this.localAllowMouseUpTrigger(), ...(ngDevMode ? [{ debugName: "allowMouseUpTrigger" }] : /* istanbul ignore next */ []));
|
|
137
|
+
/** Whether the current open was initiated by **touch** (ADR 0016 §3 — gates the anchored scroll lock). */
|
|
138
|
+
this.openedByTouch = signal(false, ...(ngDevMode ? [{ debugName: "openedByTouch" }] : /* istanbul ignore next */ []));
|
|
139
|
+
this.openInteractionType = signal(null, ...(ngDevMode ? [{ debugName: "openInteractionType" }] : /* istanbul ignore next */ []));
|
|
140
|
+
this.closeInteractionType = signal(null, ...(ngDevMode ? [{ debugName: "closeInteractionType" }] : /* istanbul ignore next */ []));
|
|
141
|
+
this.effectiveDisabled = computed(() => this.disabled() || (this.parentRoot?.effectiveDisabled() ?? false), ...(ngDevMode ? [{ debugName: "effectiveDisabled" }] : /* istanbul ignore next */ []));
|
|
142
|
+
this.dir = computed(() => {
|
|
143
|
+
return this.dirInput() ?? this.parentRoot?.dir() ?? this.providedDirection();
|
|
144
|
+
}, ...(ngDevMode ? [{ debugName: "dir" }] : /* istanbul ignore next */ []));
|
|
145
|
+
this.effectiveModal = computed(() => this.modal() && !this.isSubmenu(), ...(ngDevMode ? [{ debugName: "effectiveModal" }] : /* istanbul ignore next */ []));
|
|
83
146
|
this.state = computed(() => (this.open() ? 'open' : 'closed'), ...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
|
|
147
|
+
this.present = computed(() => this.open() || this.preventUnmountOnClose(), ...(ngDevMode ? [{ debugName: "present" }] : /* istanbul ignore next */ []));
|
|
84
148
|
effect(() => {
|
|
85
149
|
const defaultOpen = this.defaultOpen();
|
|
86
150
|
if (!this.hasAppliedDefaultOpen && defaultOpen) {
|
|
@@ -89,6 +153,14 @@ class RdxMenuRoot {
|
|
|
89
153
|
}
|
|
90
154
|
});
|
|
91
155
|
effect(() => this.popper.anchorOverride.set(this.trigger()));
|
|
156
|
+
// Keep the dismissal reference (the active trigger) in sync so an outside-press / focus on the
|
|
157
|
+
// trigger counts as "inside" and never dismisses (ADR 0015).
|
|
158
|
+
effect(() => this.floatingContext.setReferenceElement(this.trigger() ?? null));
|
|
159
|
+
effect(() => {
|
|
160
|
+
if (this.open() && this.preventUnmountOnClose()) {
|
|
161
|
+
this.preventUnmountOnClose.set(false);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
92
164
|
let previousOpen = this.open();
|
|
93
165
|
effect(() => {
|
|
94
166
|
const open = this.open();
|
|
@@ -98,37 +170,79 @@ class RdxMenuRoot {
|
|
|
98
170
|
}
|
|
99
171
|
});
|
|
100
172
|
}
|
|
101
|
-
show(autoFocus = 'first') {
|
|
102
|
-
if (this.
|
|
173
|
+
show(autoFocus = 'first', reason = 'none', event) {
|
|
174
|
+
if (this.effectiveDisabled()) {
|
|
103
175
|
return;
|
|
104
176
|
}
|
|
105
177
|
this.autoFocus.set(autoFocus === true ? 'first' : autoFocus);
|
|
106
178
|
if (!this.open()) {
|
|
179
|
+
const change = this.createOpenChangeEvent(true, reason, event);
|
|
180
|
+
this.onOpenChange.emit(change.payload);
|
|
181
|
+
if (change.eventDetails.isCanceled()) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
this.lastOpenChangeReason.set(reason);
|
|
185
|
+
// Record whether this open came from touch (ADR 0016 §3). Hover / mouse / keyboard all resolve
|
|
186
|
+
// to false (no `'touch'` pointer type), so only a genuine touch open gates the anchored lock.
|
|
187
|
+
this.openedByTouch.set(event?.pointerType === 'touch');
|
|
188
|
+
this.openInteractionType.set(getInteractionTypeFromEvent(event));
|
|
189
|
+
this.preventUnmountOnClose.set(false);
|
|
107
190
|
this.open.set(true);
|
|
108
|
-
|
|
191
|
+
// Publish reason + native event on the per-popup floating channel (Base UI open-change) so the
|
|
192
|
+
// dismissal / future focus policy can read why the menu opened (e.g. hover vs press).
|
|
193
|
+
this.floatingContext.events.emit('openchange', { open: true, reason, event: change.eventDetails.event });
|
|
109
194
|
}
|
|
110
195
|
}
|
|
111
|
-
close() {
|
|
196
|
+
close(reason = 'none', event) {
|
|
112
197
|
if (this.open()) {
|
|
198
|
+
const change = this.createOpenChangeEvent(false, reason, event);
|
|
199
|
+
this.onOpenChange.emit(change.payload);
|
|
200
|
+
if (change.eventDetails.isCanceled()) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
this.setAllowMouseUpTrigger(false);
|
|
204
|
+
this.lastOpenChangeReason.set(reason);
|
|
205
|
+
this.closeInteractionType.set(getInteractionTypeFromEvent(event));
|
|
206
|
+
this.preventUnmountOnClose.set(change.shouldPreventUnmountOnClose());
|
|
113
207
|
this.open.set(false);
|
|
114
|
-
this.
|
|
208
|
+
this.floatingContext.events.emit('openchange', { open: false, reason, event: change.eventDetails.event });
|
|
115
209
|
}
|
|
116
210
|
}
|
|
117
|
-
toggle() {
|
|
118
|
-
if (this.
|
|
211
|
+
toggle(reason = 'trigger-press', event) {
|
|
212
|
+
if (this.effectiveDisabled()) {
|
|
119
213
|
return;
|
|
120
214
|
}
|
|
121
215
|
if (this.open()) {
|
|
122
|
-
this.close();
|
|
216
|
+
this.close(reason, event);
|
|
123
217
|
}
|
|
124
218
|
else {
|
|
125
|
-
this.show();
|
|
219
|
+
this.show('first', reason, event);
|
|
126
220
|
}
|
|
127
221
|
}
|
|
222
|
+
markAsContextMenu() {
|
|
223
|
+
this.isContextMenu.set(true);
|
|
224
|
+
}
|
|
225
|
+
setAllowMouseUpTrigger(value) {
|
|
226
|
+
if (this.parentRoot) {
|
|
227
|
+
this.parentRoot.setAllowMouseUpTrigger(value);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
this.localAllowMouseUpTrigger.set(value);
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Close this menu **and every ancestor menu** in the chain. Selecting an item dismisses the whole
|
|
234
|
+
* menu, not just the innermost submenu (a submenu's `close()` would leave its parents open).
|
|
235
|
+
*/
|
|
236
|
+
closeEntireMenu(reason = 'none', event) {
|
|
237
|
+
this.close(reason, event);
|
|
238
|
+
this.parentRoot?.closeEntireMenu(reason, event);
|
|
239
|
+
}
|
|
128
240
|
registerTrigger(el) {
|
|
129
241
|
this.registeredTrigger = el;
|
|
130
242
|
this.trigger.set(el);
|
|
243
|
+
this.floatingContext.triggers.add(el);
|
|
131
244
|
return () => {
|
|
245
|
+
this.floatingContext.triggers.delete(el);
|
|
132
246
|
if (this.registeredTrigger === el) {
|
|
133
247
|
this.registeredTrigger = undefined;
|
|
134
248
|
this.trigger.set(undefined);
|
|
@@ -176,18 +290,44 @@ class RdxMenuRoot {
|
|
|
176
290
|
closeParent() {
|
|
177
291
|
this.trigger()?.dispatchEvent(new CustomEvent('rdx-menu-close-parent', { bubbles: true }));
|
|
178
292
|
}
|
|
293
|
+
createOpenChangeEvent(open, reason, event) {
|
|
294
|
+
const change = createCancelableChangeEventDetails(reason, event ?? new Event('menu.open-change'), this.trigger());
|
|
295
|
+
return {
|
|
296
|
+
eventDetails: change.eventDetails,
|
|
297
|
+
shouldPreventUnmountOnClose: change.shouldPreventUnmountOnClose,
|
|
298
|
+
payload: {
|
|
299
|
+
open,
|
|
300
|
+
trigger: change.eventDetails.trigger,
|
|
301
|
+
reason: change.eventDetails.reason,
|
|
302
|
+
event: change.eventDetails.event,
|
|
303
|
+
eventDetails: change.eventDetails
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
}
|
|
179
307
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxMenuRoot, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
180
|
-
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 },
|
|
308
|
+
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: [
|
|
309
|
+
provideRdxMenuRootContext(contextFactory),
|
|
310
|
+
// New floating foundation (ADR 0015/0017). Inherit-or-create the tree so a submenu shares its
|
|
311
|
+
// parent menu's tree; the per-popup root context bridges open / triggers / reference.
|
|
312
|
+
provideFloatingTree(),
|
|
313
|
+
provideFloatingRootContext(() => inject(RdxMenuRoot).floatingContext)
|
|
314
|
+
], exportAs: ["rdxMenuRoot"], hostDirectives: [{ directive: i1.RdxPopper }], ngImport: i0 }); }
|
|
181
315
|
}
|
|
182
316
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxMenuRoot, decorators: [{
|
|
183
317
|
type: Directive,
|
|
184
318
|
args: [{
|
|
185
319
|
selector: '[rdxMenuRoot],[rdxMenuSubmenuRoot]',
|
|
186
320
|
exportAs: 'rdxMenuRoot',
|
|
187
|
-
providers: [
|
|
321
|
+
providers: [
|
|
322
|
+
provideRdxMenuRootContext(contextFactory),
|
|
323
|
+
// New floating foundation (ADR 0015/0017). Inherit-or-create the tree so a submenu shares its
|
|
324
|
+
// parent menu's tree; the per-popup root context bridges open / triggers / reference.
|
|
325
|
+
provideFloatingTree(),
|
|
326
|
+
provideFloatingRootContext(() => inject(RdxMenuRoot).floatingContext)
|
|
327
|
+
],
|
|
188
328
|
hostDirectives: [RdxPopper]
|
|
189
329
|
}]
|
|
190
|
-
}], ctorParameters: () => [], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], defaultOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultOpen", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], modal: [{ type: i0.Input, args: [{ isSignal: true, alias: "modal", required: false }] }], loopFocus: [{ type: i0.Input, args: [{ isSignal: true, alias: "loopFocus", required: false }] }], highlightItemOnHover: [{ type: i0.Input, args: [{ isSignal: true, alias: "highlightItemOnHover", required: false }] }], orientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: false }] }], closeParentOnEsc: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeParentOnEsc", required: false }] }], onOpenChange: [{ type: i0.Output, args: ["onOpenChange"] }], onOpenChangeComplete: [{ type: i0.Output, args: ["onOpenChangeComplete"] }] } });
|
|
330
|
+
}], ctorParameters: () => [], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], defaultOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultOpen", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], modal: [{ type: i0.Input, args: [{ isSignal: true, alias: "modal", required: false }] }], loopFocus: [{ type: i0.Input, args: [{ isSignal: true, alias: "loopFocus", required: false }] }], highlightItemOnHover: [{ type: i0.Input, args: [{ isSignal: true, alias: "highlightItemOnHover", required: false }] }], orientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: false }] }], dirInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "dir", required: false }] }], closeParentOnEsc: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeParentOnEsc", required: false }] }], onOpenChange: [{ type: i0.Output, args: ["onOpenChange"] }], onOpenChangeComplete: [{ type: i0.Output, args: ["onOpenChangeComplete"] }] } });
|
|
191
331
|
|
|
192
332
|
/**
|
|
193
333
|
* An optional visual arrow connecting the popup to its trigger.
|
|
@@ -280,12 +420,13 @@ class RdxMenuCheckboxItem {
|
|
|
280
420
|
/** Emits when the checked state changes. */
|
|
281
421
|
this.onCheckedChange = output();
|
|
282
422
|
this.highlighted = computed(() => this.isFocused(), ...(ngDevMode ? [{ debugName: "highlighted" }] : /* istanbul ignore next */ []));
|
|
423
|
+
this.effectiveDisabled = computed(() => this.disabled() || (this.rootContext?.disabled() ?? false), ...(ngDevMode ? [{ debugName: "effectiveDisabled" }] : /* istanbul ignore next */ []));
|
|
283
424
|
// Expose helpers for host bindings
|
|
284
425
|
this.isIndeterminate = isIndeterminate;
|
|
285
426
|
this.getCheckedState = getCheckedState;
|
|
286
427
|
}
|
|
287
428
|
onFocus() {
|
|
288
|
-
if (!this.
|
|
429
|
+
if (!this.effectiveDisabled()) {
|
|
289
430
|
this.isFocused.set(true);
|
|
290
431
|
}
|
|
291
432
|
}
|
|
@@ -293,13 +434,13 @@ class RdxMenuCheckboxItem {
|
|
|
293
434
|
this.isFocused.set(false);
|
|
294
435
|
}
|
|
295
436
|
onPointerMove(event) {
|
|
296
|
-
if (event.defaultPrevented || event.pointerType !== 'mouse' || this.
|
|
437
|
+
if (event.defaultPrevented || event.pointerType !== 'mouse' || this.effectiveDisabled()) {
|
|
297
438
|
return;
|
|
298
439
|
}
|
|
299
440
|
if (this.rootContext && !this.rootContext.highlightItemOnHover()) {
|
|
300
441
|
return;
|
|
301
442
|
}
|
|
302
|
-
if (
|
|
443
|
+
if (this.elementRef.nativeElement.ownerDocument.activeElement !== this.elementRef.nativeElement) {
|
|
303
444
|
this.elementRef.nativeElement.focus({ preventScroll: true });
|
|
304
445
|
}
|
|
305
446
|
}
|
|
@@ -307,24 +448,31 @@ class RdxMenuCheckboxItem {
|
|
|
307
448
|
if (event.pointerType !== 'mouse') {
|
|
308
449
|
return;
|
|
309
450
|
}
|
|
310
|
-
if (
|
|
451
|
+
if (this.elementRef.nativeElement.ownerDocument.activeElement === this.elementRef.nativeElement) {
|
|
311
452
|
this.elementRef.nativeElement.closest('[rdxMenuPopup]')?.focus({ preventScroll: true });
|
|
312
453
|
}
|
|
313
454
|
}
|
|
314
455
|
onItemClick() {
|
|
315
|
-
if (this.
|
|
456
|
+
if (this.effectiveDisabled())
|
|
316
457
|
return;
|
|
317
458
|
this.toggleChecked();
|
|
318
459
|
if (this.closeOnClick())
|
|
319
|
-
this.rootContext?.
|
|
460
|
+
this.rootContext?.closeEntireMenu();
|
|
461
|
+
}
|
|
462
|
+
onMouseUp(event) {
|
|
463
|
+
if (this.effectiveDisabled() || event.button !== 0 || !this.rootContext?.allowMouseUpTrigger()) {
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
this.rootContext.setAllowMouseUpTrigger(false);
|
|
467
|
+
this.elementRef.nativeElement.click();
|
|
320
468
|
}
|
|
321
469
|
onActivate(event) {
|
|
322
|
-
if (this.
|
|
470
|
+
if (this.effectiveDisabled())
|
|
323
471
|
return;
|
|
324
472
|
event.preventDefault();
|
|
325
473
|
this.toggleChecked();
|
|
326
474
|
if (this.closeOnClick())
|
|
327
|
-
this.rootContext?.
|
|
475
|
+
this.rootContext?.closeEntireMenu();
|
|
328
476
|
}
|
|
329
477
|
toggleChecked() {
|
|
330
478
|
const next = isIndeterminate(this.checked()) ? true : !this.checked();
|
|
@@ -332,7 +480,7 @@ class RdxMenuCheckboxItem {
|
|
|
332
480
|
this.onCheckedChange.emit(next);
|
|
333
481
|
}
|
|
334
482
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxMenuCheckboxItem, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
335
|
-
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)", "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": "
|
|
483
|
+
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 }); }
|
|
336
484
|
}
|
|
337
485
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxMenuCheckboxItem, decorators: [{
|
|
338
486
|
type: Directive,
|
|
@@ -345,14 +493,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
345
493
|
tabindex: '-1',
|
|
346
494
|
'[attr.aria-checked]': 'isIndeterminate(checked()) ? "mixed" : checked()',
|
|
347
495
|
'[attr.data-state]': 'getCheckedState(checked())',
|
|
348
|
-
'[attr.data-disabled]': '
|
|
349
|
-
'[attr.aria-disabled]': '
|
|
496
|
+
'[attr.data-disabled]': 'effectiveDisabled() ? "" : undefined',
|
|
497
|
+
'[attr.aria-disabled]': 'effectiveDisabled() ? true : undefined',
|
|
350
498
|
'[attr.data-highlighted]': 'highlighted() ? "" : undefined',
|
|
351
499
|
'[attr.data-label]': 'label() ?? undefined',
|
|
352
500
|
'(focus)': 'onFocus()',
|
|
353
501
|
'(blur)': 'onBlur()',
|
|
354
502
|
'(pointermove)': 'onPointerMove($event)',
|
|
355
503
|
'(pointerleave)': 'onPointerLeave($event)',
|
|
504
|
+
'(mouseup)': 'onMouseUp($event)',
|
|
356
505
|
'(click)': 'onItemClick()',
|
|
357
506
|
'(keydown.enter)': 'onActivate($event)',
|
|
358
507
|
'(keydown.space)': 'onActivate($event)'
|
|
@@ -389,20 +538,31 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
389
538
|
}]
|
|
390
539
|
}], propDecorators: { keepMounted: [{ type: i0.Input, args: [{ isSignal: true, alias: "keepMounted", required: false }] }] } });
|
|
391
540
|
|
|
541
|
+
const [injectRdxMenuGroupContext, provideRdxMenuGroupContext] = createContext('RdxMenuGroupContext', 'components/menu');
|
|
542
|
+
|
|
543
|
+
const groupContextFactory$1 = () => {
|
|
544
|
+
const instance = inject(RdxMenuGroup);
|
|
545
|
+
return { labelId: instance.labelId };
|
|
546
|
+
};
|
|
392
547
|
/**
|
|
393
548
|
* Groups related menu items together.
|
|
394
549
|
*/
|
|
395
550
|
class RdxMenuGroup {
|
|
551
|
+
constructor() {
|
|
552
|
+
this.labelId = signal(undefined, ...(ngDevMode ? [{ debugName: "labelId" }] : /* istanbul ignore next */ []));
|
|
553
|
+
}
|
|
396
554
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxMenuGroup, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
397
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxMenuGroup, isStandalone: true, selector: "[rdxMenuGroup]", host: { attributes: { "role": "group" } }, exportAs: ["rdxMenuGroup"], ngImport: i0 }); }
|
|
555
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxMenuGroup, isStandalone: true, selector: "[rdxMenuGroup]", host: { attributes: { "role": "group" }, properties: { "attr.aria-labelledby": "labelId()" } }, providers: [provideRdxMenuGroupContext(groupContextFactory$1)], exportAs: ["rdxMenuGroup"], ngImport: i0 }); }
|
|
398
556
|
}
|
|
399
557
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxMenuGroup, decorators: [{
|
|
400
558
|
type: Directive,
|
|
401
559
|
args: [{
|
|
402
560
|
selector: '[rdxMenuGroup]',
|
|
403
561
|
exportAs: 'rdxMenuGroup',
|
|
562
|
+
providers: [provideRdxMenuGroupContext(groupContextFactory$1)],
|
|
404
563
|
host: {
|
|
405
|
-
role: 'group'
|
|
564
|
+
role: 'group',
|
|
565
|
+
'[attr.aria-labelledby]': 'labelId()'
|
|
406
566
|
}
|
|
407
567
|
}]
|
|
408
568
|
}] });
|
|
@@ -411,16 +571,29 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
411
571
|
* A label for a menu group.
|
|
412
572
|
*/
|
|
413
573
|
class RdxMenuGroupLabel {
|
|
574
|
+
constructor() {
|
|
575
|
+
this.groupContext = injectRdxMenuGroupContext();
|
|
576
|
+
this.id = injectId('rdx-menu-group-label-');
|
|
577
|
+
this.groupContext.labelId.set(this.id);
|
|
578
|
+
inject(DestroyRef).onDestroy(() => {
|
|
579
|
+
if (this.groupContext.labelId() === this.id) {
|
|
580
|
+
this.groupContext.labelId.set(undefined);
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
}
|
|
414
584
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxMenuGroupLabel, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
415
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxMenuGroupLabel, isStandalone: true, selector: "[rdxMenuGroupLabel]", exportAs: ["rdxMenuGroupLabel"], ngImport: i0 }); }
|
|
585
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxMenuGroupLabel, isStandalone: true, selector: "[rdxMenuGroupLabel]", host: { properties: { "attr.id": "id" } }, exportAs: ["rdxMenuGroupLabel"], ngImport: i0 }); }
|
|
416
586
|
}
|
|
417
587
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxMenuGroupLabel, decorators: [{
|
|
418
588
|
type: Directive,
|
|
419
589
|
args: [{
|
|
420
590
|
selector: '[rdxMenuGroupLabel]',
|
|
421
|
-
exportAs: 'rdxMenuGroupLabel'
|
|
591
|
+
exportAs: 'rdxMenuGroupLabel',
|
|
592
|
+
host: {
|
|
593
|
+
'[attr.id]': 'id'
|
|
594
|
+
}
|
|
422
595
|
}]
|
|
423
|
-
}] });
|
|
596
|
+
}], ctorParameters: () => [] });
|
|
424
597
|
|
|
425
598
|
/**
|
|
426
599
|
* An individual menu item.
|
|
@@ -439,9 +612,10 @@ class RdxMenuItem {
|
|
|
439
612
|
/** Emits when the item is selected. */
|
|
440
613
|
this.onSelect = output();
|
|
441
614
|
this.highlighted = computed(() => this.isFocused(), ...(ngDevMode ? [{ debugName: "highlighted" }] : /* istanbul ignore next */ []));
|
|
615
|
+
this.effectiveDisabled = computed(() => this.disabled() || (this.rootContext?.disabled() ?? false), ...(ngDevMode ? [{ debugName: "effectiveDisabled" }] : /* istanbul ignore next */ []));
|
|
442
616
|
}
|
|
443
617
|
onFocus() {
|
|
444
|
-
if (!this.
|
|
618
|
+
if (!this.effectiveDisabled()) {
|
|
445
619
|
this.isFocused.set(true);
|
|
446
620
|
}
|
|
447
621
|
}
|
|
@@ -449,13 +623,13 @@ class RdxMenuItem {
|
|
|
449
623
|
this.isFocused.set(false);
|
|
450
624
|
}
|
|
451
625
|
onPointerMove(event) {
|
|
452
|
-
if (event.defaultPrevented || event.pointerType !== 'mouse' || this.
|
|
626
|
+
if (event.defaultPrevented || event.pointerType !== 'mouse' || this.effectiveDisabled()) {
|
|
453
627
|
return;
|
|
454
628
|
}
|
|
455
629
|
if (this.rootContext && !this.rootContext.highlightItemOnHover()) {
|
|
456
630
|
return;
|
|
457
631
|
}
|
|
458
|
-
if (
|
|
632
|
+
if (this.elementRef.nativeElement.ownerDocument.activeElement !== this.elementRef.nativeElement) {
|
|
459
633
|
this.elementRef.nativeElement.focus({ preventScroll: true });
|
|
460
634
|
}
|
|
461
635
|
}
|
|
@@ -465,27 +639,34 @@ class RdxMenuItem {
|
|
|
465
639
|
}
|
|
466
640
|
// Clear highlight when the pointer leaves: move focus back to the popup. A subsequent
|
|
467
641
|
// pointermove on a sibling item re-focuses it, so moving between items still works.
|
|
468
|
-
if (
|
|
642
|
+
if (this.elementRef.nativeElement.ownerDocument.activeElement === this.elementRef.nativeElement) {
|
|
469
643
|
this.elementRef.nativeElement.closest('[rdxMenuPopup]')?.focus({ preventScroll: true });
|
|
470
644
|
}
|
|
471
645
|
}
|
|
472
646
|
onItemClick() {
|
|
473
|
-
if (this.
|
|
647
|
+
if (this.effectiveDisabled())
|
|
474
648
|
return;
|
|
475
649
|
this.onSelect.emit();
|
|
476
650
|
if (this.closeOnClick())
|
|
477
|
-
this.rootContext?.
|
|
651
|
+
this.rootContext?.closeEntireMenu();
|
|
652
|
+
}
|
|
653
|
+
onMouseUp(event) {
|
|
654
|
+
if (this.effectiveDisabled() || event.button !== 0 || !this.rootContext?.allowMouseUpTrigger()) {
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
this.rootContext.setAllowMouseUpTrigger(false);
|
|
658
|
+
this.elementRef.nativeElement.click();
|
|
478
659
|
}
|
|
479
660
|
onActivate(event) {
|
|
480
|
-
if (this.
|
|
661
|
+
if (this.effectiveDisabled())
|
|
481
662
|
return;
|
|
482
663
|
event.preventDefault();
|
|
483
664
|
this.onSelect.emit();
|
|
484
665
|
if (this.closeOnClick())
|
|
485
|
-
this.rootContext?.
|
|
666
|
+
this.rootContext?.closeEntireMenu();
|
|
486
667
|
}
|
|
487
668
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxMenuItem, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
488
|
-
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)", "click": "onItemClick()", "keydown.enter": "onActivate($event)", "keydown.space": "onActivate($event)" }, properties: { "attr.data-disabled": "
|
|
669
|
+
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 }); }
|
|
489
670
|
}
|
|
490
671
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxMenuItem, decorators: [{
|
|
491
672
|
type: Directive,
|
|
@@ -495,14 +676,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
495
676
|
host: {
|
|
496
677
|
role: 'menuitem',
|
|
497
678
|
tabindex: '-1',
|
|
498
|
-
'[attr.data-disabled]': '
|
|
499
|
-
'[attr.aria-disabled]': '
|
|
679
|
+
'[attr.data-disabled]': 'effectiveDisabled() ? "" : undefined',
|
|
680
|
+
'[attr.aria-disabled]': 'effectiveDisabled() ? true : undefined',
|
|
500
681
|
'[attr.data-highlighted]': 'highlighted() ? "" : undefined',
|
|
501
682
|
'[attr.data-label]': 'label() ?? undefined',
|
|
502
683
|
'(focus)': 'onFocus()',
|
|
503
684
|
'(blur)': 'onBlur()',
|
|
504
685
|
'(pointermove)': 'onPointerMove($event)',
|
|
505
686
|
'(pointerleave)': 'onPointerLeave($event)',
|
|
687
|
+
'(mouseup)': 'onMouseUp($event)',
|
|
506
688
|
'(click)': 'onItemClick()',
|
|
507
689
|
'(keydown.enter)': 'onActivate($event)',
|
|
508
690
|
'(keydown.space)': 'onActivate($event)'
|
|
@@ -527,9 +709,10 @@ class RdxMenuLinkItem {
|
|
|
527
709
|
/** Emits when the item is selected. */
|
|
528
710
|
this.onSelect = output();
|
|
529
711
|
this.highlighted = computed(() => this.isFocused(), ...(ngDevMode ? [{ debugName: "highlighted" }] : /* istanbul ignore next */ []));
|
|
712
|
+
this.effectiveDisabled = computed(() => this.disabled() || (this.rootContext?.disabled() ?? false), ...(ngDevMode ? [{ debugName: "effectiveDisabled" }] : /* istanbul ignore next */ []));
|
|
530
713
|
}
|
|
531
714
|
onFocus() {
|
|
532
|
-
if (!this.
|
|
715
|
+
if (!this.effectiveDisabled()) {
|
|
533
716
|
this.isFocused.set(true);
|
|
534
717
|
}
|
|
535
718
|
}
|
|
@@ -537,13 +720,13 @@ class RdxMenuLinkItem {
|
|
|
537
720
|
this.isFocused.set(false);
|
|
538
721
|
}
|
|
539
722
|
onPointerMove(event) {
|
|
540
|
-
if (event.defaultPrevented || event.pointerType !== 'mouse' || this.
|
|
723
|
+
if (event.defaultPrevented || event.pointerType !== 'mouse' || this.effectiveDisabled()) {
|
|
541
724
|
return;
|
|
542
725
|
}
|
|
543
726
|
if (this.rootContext && !this.rootContext.highlightItemOnHover()) {
|
|
544
727
|
return;
|
|
545
728
|
}
|
|
546
|
-
if (
|
|
729
|
+
if (this.elementRef.nativeElement.ownerDocument.activeElement !== this.elementRef.nativeElement) {
|
|
547
730
|
this.elementRef.nativeElement.focus({ preventScroll: true });
|
|
548
731
|
}
|
|
549
732
|
}
|
|
@@ -551,30 +734,37 @@ class RdxMenuLinkItem {
|
|
|
551
734
|
if (event.pointerType !== 'mouse') {
|
|
552
735
|
return;
|
|
553
736
|
}
|
|
554
|
-
if (
|
|
737
|
+
if (this.elementRef.nativeElement.ownerDocument.activeElement === this.elementRef.nativeElement) {
|
|
555
738
|
this.elementRef.nativeElement.closest('[rdxMenuPopup]')?.focus({ preventScroll: true });
|
|
556
739
|
}
|
|
557
740
|
}
|
|
558
741
|
onItemClick(event) {
|
|
559
|
-
if (this.
|
|
742
|
+
if (this.effectiveDisabled()) {
|
|
560
743
|
event.preventDefault();
|
|
561
744
|
return;
|
|
562
745
|
}
|
|
563
746
|
this.onSelect.emit();
|
|
564
747
|
if (this.closeOnClick())
|
|
565
|
-
this.rootContext?.
|
|
748
|
+
this.rootContext?.closeEntireMenu();
|
|
749
|
+
}
|
|
750
|
+
onMouseUp(event) {
|
|
751
|
+
if (this.effectiveDisabled() || event.button !== 0 || !this.rootContext?.allowMouseUpTrigger()) {
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
this.rootContext.setAllowMouseUpTrigger(false);
|
|
755
|
+
this.elementRef.nativeElement.click();
|
|
566
756
|
}
|
|
567
757
|
onActivate(event) {
|
|
568
|
-
if (this.
|
|
758
|
+
if (this.effectiveDisabled()) {
|
|
569
759
|
event.preventDefault();
|
|
570
760
|
return;
|
|
571
761
|
}
|
|
572
762
|
this.onSelect.emit();
|
|
573
763
|
if (this.closeOnClick())
|
|
574
|
-
this.rootContext?.
|
|
764
|
+
this.rootContext?.closeEntireMenu();
|
|
575
765
|
}
|
|
576
766
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxMenuLinkItem, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
577
|
-
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)", "click": "onItemClick($event)", "keydown.enter": "onActivate($event)" }, properties: { "attr.data-disabled": "
|
|
767
|
+
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 }); }
|
|
578
768
|
}
|
|
579
769
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxMenuLinkItem, decorators: [{
|
|
580
770
|
type: Directive,
|
|
@@ -584,62 +774,64 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
584
774
|
host: {
|
|
585
775
|
role: 'menuitem',
|
|
586
776
|
tabindex: '-1',
|
|
587
|
-
'[attr.data-disabled]': '
|
|
588
|
-
'[attr.aria-disabled]': '
|
|
777
|
+
'[attr.data-disabled]': 'effectiveDisabled() ? "" : undefined',
|
|
778
|
+
'[attr.aria-disabled]': 'effectiveDisabled() ? true : undefined',
|
|
589
779
|
'[attr.data-highlighted]': 'highlighted() ? "" : undefined',
|
|
590
780
|
'[attr.data-label]': 'label() ?? undefined',
|
|
591
781
|
'(focus)': 'onFocus()',
|
|
592
782
|
'(blur)': 'onBlur()',
|
|
593
783
|
'(pointermove)': 'onPointerMove($event)',
|
|
594
784
|
'(pointerleave)': 'onPointerLeave($event)',
|
|
785
|
+
'(mouseup)': 'onMouseUp($event)',
|
|
595
786
|
'(click)': 'onItemClick($event)',
|
|
596
787
|
'(keydown.enter)': 'onActivate($event)'
|
|
597
788
|
}
|
|
598
789
|
}]
|
|
599
790
|
}], 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"] }] } });
|
|
600
791
|
|
|
601
|
-
/** Selector for focusable menu items within
|
|
602
|
-
const
|
|
792
|
+
/** Selector for focusable menu items within a popup. */
|
|
793
|
+
const RDX_MENU_ITEM_SELECTOR = [
|
|
603
794
|
'[rdxMenuItem]:not([data-disabled])',
|
|
604
795
|
'[rdxMenuCheckboxItem]:not([data-disabled])',
|
|
605
796
|
'[rdxMenuRadioItem]:not([data-disabled])',
|
|
606
797
|
'[rdxMenuLinkItem]:not([data-disabled])',
|
|
607
798
|
'[rdxMenuSubTrigger]:not([data-disabled])'
|
|
608
799
|
].join(',');
|
|
609
|
-
function
|
|
610
|
-
|
|
611
|
-
return Array.from(popup.querySelectorAll(ITEM_SELECTOR)).filter((item) => item.closest('[rdxMenuPopup]') === popup);
|
|
800
|
+
function getFocusableMenuItems(popup) {
|
|
801
|
+
return Array.from(popup.querySelectorAll(RDX_MENU_ITEM_SELECTOR)).filter((item) => item.closest('[rdxMenuPopup]') === popup);
|
|
612
802
|
}
|
|
803
|
+
|
|
613
804
|
/**
|
|
614
805
|
* A container for the menu contents.
|
|
615
806
|
*/
|
|
616
807
|
class RdxMenuPopup {
|
|
617
808
|
constructor() {
|
|
618
809
|
this.rootContext = injectRdxMenuRootContext();
|
|
619
|
-
this.
|
|
810
|
+
this.floatingContext = inject(RDX_FLOATING_ROOT_CONTEXT);
|
|
811
|
+
this.registration = inject(RDX_FLOATING_REGISTRATION, { optional: true });
|
|
812
|
+
this.focusManager = inject(RdxFloatingFocusManager);
|
|
620
813
|
this.focusScope = inject(RdxFocusScope);
|
|
621
814
|
this.wrapper = inject(RdxPopperContentWrapper, { optional: true });
|
|
622
815
|
this.elementRef = inject(ElementRef);
|
|
623
|
-
this.dismissableLayersContext = inject(RdxDismissableLayersContextToken);
|
|
624
816
|
this.search = '';
|
|
625
817
|
this.align = computed(() => this.wrapper?.placedAlign(), ...(ngDevMode ? [{ debugName: "align" }] : /* istanbul ignore next */ []));
|
|
626
818
|
this.side = computed(() => this.wrapper?.placedSide(), ...(ngDevMode ? [{ debugName: "side" }] : /* istanbul ignore next */ []));
|
|
627
819
|
/**
|
|
628
820
|
* Event handler called when the escape key is pressed. Can be prevented.
|
|
629
821
|
*/
|
|
630
|
-
this.escapeKeyDown =
|
|
822
|
+
this.escapeKeyDown = output();
|
|
631
823
|
/**
|
|
632
824
|
* Event handler called when a pointerdown event happens outside of the popup. Can be prevented.
|
|
633
825
|
*/
|
|
634
|
-
this.pointerDownOutside =
|
|
826
|
+
this.pointerDownOutside = output();
|
|
635
827
|
/**
|
|
636
828
|
* Event handler called when focus moves outside of the popup. Can be prevented.
|
|
637
829
|
*/
|
|
638
|
-
this.focusOutside =
|
|
830
|
+
this.focusOutside = output();
|
|
639
831
|
/**
|
|
640
832
|
* Event handler called when an interaction happens outside of the popup. Can be prevented.
|
|
641
833
|
*/
|
|
642
|
-
this.interactOutside =
|
|
834
|
+
this.interactOutside = output();
|
|
643
835
|
/**
|
|
644
836
|
* Event handler called before focus moves into the popup. Can be prevented.
|
|
645
837
|
*/
|
|
@@ -648,6 +840,27 @@ class RdxMenuPopup {
|
|
|
648
840
|
* Event handler called before focus returns after the popup is removed. Can be prevented.
|
|
649
841
|
*/
|
|
650
842
|
this.closeAutoFocus = outputFromObservable(outputToObservable(this.focusScope.unmountAutoFocus));
|
|
843
|
+
// Page scroll lock (Base UI `MenuPositioner`): only while **open** and **modal**, and a hover-open
|
|
844
|
+
// dropdown / context menu does NOT lock (a menubar menu always does when modal). A submenu never
|
|
845
|
+
// locks — its `modal` is already effectively false. For a **touch** open the anchored helper only
|
|
846
|
+
// locks when the popup is effectively viewport-width (ADR 0016 §3), so a small menu stays
|
|
847
|
+
// swipe-to-dismissable on mobile.
|
|
848
|
+
useAnchoredScrollLock(computed(() => {
|
|
849
|
+
if (!this.rootContext.isOpen() || !this.rootContext.modal()) {
|
|
850
|
+
return false;
|
|
851
|
+
}
|
|
852
|
+
if (this.rootContext.parentType() === 'menubar') {
|
|
853
|
+
return true;
|
|
854
|
+
}
|
|
855
|
+
return this.rootContext.lastOpenChangeReason() !== 'trigger-hover';
|
|
856
|
+
}), {
|
|
857
|
+
touchOpen: () => this.rootContext.openedByTouch(),
|
|
858
|
+
element: () => this.elementRef.nativeElement
|
|
859
|
+
});
|
|
860
|
+
// The popup is this layer's floating element (the inside-surface for containment checks). A
|
|
861
|
+
// submenu is a child node in the shared tree, so the capability's logical containment treats an
|
|
862
|
+
// open submenu as "inside" its parent automatically — replacing the legacy `branches` registry.
|
|
863
|
+
this.floatingContext.setFloatingElement(this.elementRef.nativeElement);
|
|
651
864
|
const unregister = this.rootContext.registerTransitionElement(this.elementRef.nativeElement);
|
|
652
865
|
const unregisterPopup = this.rootContext.registerPopup(this.elementRef.nativeElement);
|
|
653
866
|
inject(DestroyRef).onDestroy(() => {
|
|
@@ -655,34 +868,52 @@ class RdxMenuPopup {
|
|
|
655
868
|
unregisterPopup();
|
|
656
869
|
clearTimeout(this.searchTimer);
|
|
657
870
|
});
|
|
658
|
-
|
|
659
|
-
|
|
871
|
+
// Base UI moves focus into a keyboard-opened submenu via its list-navigation layer, not via the
|
|
872
|
+
// focus manager (`initialFocus={false}` for submenus). In Angular, the popup itself is the first
|
|
873
|
+
// point where the submenu DOM definitely exists, so complete the keyboard handoff here.
|
|
874
|
+
effect(() => {
|
|
875
|
+
if (!this.rootContext.isOpen() ||
|
|
876
|
+
this.rootContext.parentType() !== 'menu' ||
|
|
877
|
+
this.rootContext.openInteractionType() !== 'keyboard') {
|
|
660
878
|
return;
|
|
661
879
|
}
|
|
662
|
-
|
|
663
|
-
this.dismissableLayersContext.branches.update((branches) => [...branches, element]);
|
|
664
|
-
onCleanup(() => {
|
|
665
|
-
this.dismissableLayersContext.branches.update((branches) => branches.filter((branch) => branch !== element));
|
|
666
|
-
});
|
|
880
|
+
this.scheduleSubmenuKeyboardFocus();
|
|
667
881
|
});
|
|
668
|
-
|
|
669
|
-
|
|
882
|
+
// Dismissal (ADR 0015): Escape, an outside press, or focus moving outside closes the menu.
|
|
883
|
+
// Escape is owned by the capability (a document-level listener — it works regardless of where
|
|
884
|
+
// focus currently sits, matching Base UI `useDismiss`). Deepest-first: a non-bubbling layer
|
|
885
|
+
// yields to an open child, so Escape closes only the innermost menu — unless `closeParentOnEsc`
|
|
886
|
+
// makes it bubble up the whole chain.
|
|
887
|
+
new RdxDismiss(this.floatingContext, () => this.registration?.node() ?? null, {
|
|
888
|
+
// A disabled menu does not dismiss (Base UI `useDismiss({ enabled: !disabled })`): if an open
|
|
889
|
+
// menu becomes disabled it stays put rather than closing on Escape / outside-press / focus-out.
|
|
890
|
+
enabled: () => !this.rootContext.disabled(),
|
|
891
|
+
escapeKey: () => true,
|
|
892
|
+
escapeKeyBubbles: () => this.rootContext.closeParentOnEsc(),
|
|
893
|
+
outsidePress: () => true,
|
|
894
|
+
focusOutside: () => false,
|
|
895
|
+
onEscapeKeyDown: (event) => this.escapeKeyDown.emit(event),
|
|
896
|
+
onPointerDownOutside: (event) => {
|
|
897
|
+
this.pointerDownOutside.emit(event);
|
|
898
|
+
this.interactOutside.emit(event);
|
|
899
|
+
},
|
|
900
|
+
onDismiss: (reason, event) => {
|
|
901
|
+
// Forward the dismissal reason + native event into the menu's open-change channel.
|
|
902
|
+
const menuReason = reason === 'escape-key' ? 'escape-key' : 'outside-press';
|
|
903
|
+
this.rootContext.close(menuReason, event);
|
|
904
|
+
// Escape should restore focus synchronously to the trigger / submenu trigger so the
|
|
905
|
+
// menu chain remains keyboard-stable even before the scope's queued unmount return-focus.
|
|
906
|
+
if (reason === 'escape-key') {
|
|
907
|
+
this.rootContext.trigger()?.focus({ preventScroll: true });
|
|
908
|
+
}
|
|
909
|
+
}
|
|
670
910
|
});
|
|
671
|
-
//
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
if (
|
|
676
|
-
|
|
677
|
-
// `'popup'` focuses the container without highlighting an item (pointer opening).
|
|
678
|
-
if (autoFocus === 'popup') {
|
|
679
|
-
this.elementRef.nativeElement.focus({ preventScroll: true });
|
|
680
|
-
return;
|
|
681
|
-
}
|
|
682
|
-
const items = getFocusableItems(this.elementRef.nativeElement);
|
|
683
|
-
const item = autoFocus === 'last' ? items[items.length - 1] : items[0];
|
|
684
|
-
item?.focus({ preventScroll: true });
|
|
685
|
-
});
|
|
911
|
+
// Focus-out close is owned by the floating focus manager, matching Base UI's MenuPopup.
|
|
912
|
+
this.focusManager.focusOut.subscribe((event) => {
|
|
913
|
+
this.focusOutside.emit(event);
|
|
914
|
+
this.interactOutside.emit(event);
|
|
915
|
+
if (!event.defaultPrevented) {
|
|
916
|
+
this.rootContext.close('focus-out', event);
|
|
686
917
|
}
|
|
687
918
|
});
|
|
688
919
|
}
|
|
@@ -695,8 +926,8 @@ class RdxMenuPopup {
|
|
|
695
926
|
}
|
|
696
927
|
handleKeydown(event) {
|
|
697
928
|
const el = this.elementRef.nativeElement;
|
|
698
|
-
const items =
|
|
699
|
-
const current =
|
|
929
|
+
const items = getFocusableMenuItems(el);
|
|
930
|
+
const current = el.ownerDocument.activeElement;
|
|
700
931
|
const currentIndex = items.indexOf(current);
|
|
701
932
|
switch (event.key) {
|
|
702
933
|
case 'ArrowDown': {
|
|
@@ -744,6 +975,13 @@ class RdxMenuPopup {
|
|
|
744
975
|
}
|
|
745
976
|
break;
|
|
746
977
|
}
|
|
978
|
+
if (this.rootContext.dir() === 'rtl') {
|
|
979
|
+
if (this.rootContext.handlePopupArrowNavigation(-1)) {
|
|
980
|
+
event.preventDefault();
|
|
981
|
+
event.stopPropagation();
|
|
982
|
+
}
|
|
983
|
+
break;
|
|
984
|
+
}
|
|
747
985
|
// Close this popup and return focus to the trigger (used by submenus).
|
|
748
986
|
event.preventDefault();
|
|
749
987
|
event.stopPropagation();
|
|
@@ -752,22 +990,23 @@ class RdxMenuPopup {
|
|
|
752
990
|
break;
|
|
753
991
|
}
|
|
754
992
|
case 'ArrowRight': {
|
|
755
|
-
|
|
993
|
+
const trigger = this.rootContext.trigger();
|
|
994
|
+
if (trigger?.hasAttribute('rdxMenuSubTrigger') && this.rootContext.dir() === 'rtl') {
|
|
756
995
|
event.preventDefault();
|
|
757
996
|
event.stopPropagation();
|
|
997
|
+
this.rootContext.close();
|
|
998
|
+
trigger.focus({ preventScroll: true });
|
|
999
|
+
break;
|
|
758
1000
|
}
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
event.preventDefault();
|
|
763
|
-
event.stopPropagation();
|
|
764
|
-
this.rootContext.close();
|
|
765
|
-
if (this.rootContext.isSubmenu() && this.rootContext.closeParentOnEsc()) {
|
|
766
|
-
this.rootContext.closeParent();
|
|
1001
|
+
if (this.rootContext.handlePopupArrowNavigation(1)) {
|
|
1002
|
+
event.preventDefault();
|
|
1003
|
+
event.stopPropagation();
|
|
767
1004
|
}
|
|
768
|
-
this.rootContext.trigger()?.focus({ preventScroll: true });
|
|
769
1005
|
break;
|
|
770
1006
|
}
|
|
1007
|
+
// Escape is owned by the dismissal capability (a document-level listener that works
|
|
1008
|
+
// regardless of focus position); it closes the menu, restores focus to the trigger, and
|
|
1009
|
+
// cascades up the chain when `closeParentOnEsc` is set.
|
|
771
1010
|
case 'Tab': {
|
|
772
1011
|
// Close on tab to allow natural tab navigation
|
|
773
1012
|
this.rootContext.close();
|
|
@@ -797,35 +1036,130 @@ class RdxMenuPopup {
|
|
|
797
1036
|
}
|
|
798
1037
|
}
|
|
799
1038
|
}
|
|
1039
|
+
scheduleSubmenuKeyboardFocus(attempt = 0) {
|
|
1040
|
+
const view = this.elementRef.nativeElement.ownerDocument.defaultView ?? globalThis;
|
|
1041
|
+
view.requestAnimationFrame(() => this.applySubmenuKeyboardFocus(attempt));
|
|
1042
|
+
}
|
|
1043
|
+
applySubmenuKeyboardFocus(attempt) {
|
|
1044
|
+
const maxAttempts = 10;
|
|
1045
|
+
const popup = this.elementRef.nativeElement;
|
|
1046
|
+
if (!this.rootContext.isOpen() || this.rootContext.parentType() !== 'menu') {
|
|
1047
|
+
return;
|
|
1048
|
+
}
|
|
1049
|
+
const activeElement = popup.ownerDocument.activeElement;
|
|
1050
|
+
if (activeElement && popup.contains(activeElement)) {
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1053
|
+
const items = getFocusableMenuItems(popup);
|
|
1054
|
+
if (items.length === 0) {
|
|
1055
|
+
if (attempt < maxAttempts) {
|
|
1056
|
+
this.scheduleSubmenuKeyboardFocus(attempt + 1);
|
|
1057
|
+
}
|
|
1058
|
+
return;
|
|
1059
|
+
}
|
|
1060
|
+
items[0]?.focus({ preventScroll: true });
|
|
1061
|
+
}
|
|
800
1062
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxMenuPopup, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
801
1063
|
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: [
|
|
802
|
-
|
|
1064
|
+
provideFloatingFocusManagerConfig(() => {
|
|
803
1065
|
const rootContext = injectRdxMenuRootContext();
|
|
1066
|
+
const popup = inject(ElementRef).nativeElement;
|
|
804
1067
|
return {
|
|
805
|
-
|
|
1068
|
+
// Only a (modal) **context menu** traps focus — Base UI's `FloatingFocusManager modal` is
|
|
1069
|
+
// true for `context-menu` alone. Other menus stay non-modal and close on focus-out.
|
|
1070
|
+
modal: () => rootContext.parentType() === 'context-menu',
|
|
1071
|
+
// The manager follows mounted/open lifecycle, not menu disabled state. Dismissal remains
|
|
1072
|
+
// disabled separately below; focus/marker policy should not disappear if a menu becomes
|
|
1073
|
+
// disabled while open, or if `preventUnmountOnClose()` keeps it mounted after close.
|
|
1074
|
+
enabled: () => rootContext.present(),
|
|
1075
|
+
// Base UI's submenu policy: a submenu mount does not steal focus from its trigger.
|
|
1076
|
+
// Root menus still choose first / last item vs popup container from the menu's own
|
|
1077
|
+
// open policy (`autoFocus`), but the decision now lives in the focus manager instead
|
|
1078
|
+
// of a separate popup effect.
|
|
1079
|
+
initialFocus: () => {
|
|
1080
|
+
if (!rootContext.isOpen() || rootContext.parentType() === 'menu') {
|
|
1081
|
+
return false;
|
|
1082
|
+
}
|
|
1083
|
+
const autoFocus = rootContext.autoFocus();
|
|
1084
|
+
if (autoFocus === false) {
|
|
1085
|
+
return false;
|
|
1086
|
+
}
|
|
1087
|
+
if (autoFocus === 'popup') {
|
|
1088
|
+
return popup;
|
|
1089
|
+
}
|
|
1090
|
+
return () => {
|
|
1091
|
+
const items = getFocusableMenuItems(popup);
|
|
1092
|
+
return autoFocus === 'last' ? (items.at(-1) ?? popup) : (items[0] ?? popup);
|
|
1093
|
+
};
|
|
1094
|
+
},
|
|
1095
|
+
returnFocus: () => {
|
|
1096
|
+
const parentType = rootContext.parentType();
|
|
1097
|
+
if (rootContext.trigger() || parentType === undefined || parentType === 'context-menu') {
|
|
1098
|
+
return true;
|
|
1099
|
+
}
|
|
1100
|
+
if (parentType === 'menubar' && rootContext.lastOpenChangeReason() !== 'outside-press') {
|
|
1101
|
+
return true;
|
|
1102
|
+
}
|
|
1103
|
+
return false;
|
|
1104
|
+
},
|
|
1105
|
+
openInteractionType: () => rootContext.openInteractionType(),
|
|
1106
|
+
closeInteractionType: () => rootContext.closeInteractionType()
|
|
806
1107
|
};
|
|
807
|
-
})
|
|
808
|
-
|
|
809
|
-
trapped: signal(false)
|
|
810
|
-
}))
|
|
811
|
-
], exportAs: ["rdxMenuPopup"], hostDirectives: [{ directive: i1.RdxPopperContent }, { directive: i2.RdxDismissableLayer }, { directive: i3.RdxFocusScope }], ngImport: i0 }); }
|
|
1108
|
+
})
|
|
1109
|
+
], exportAs: ["rdxMenuPopup"], hostDirectives: [{ directive: i1.RdxPopperContent }, { directive: i2.RdxFloatingNodeRegistration }, { directive: i3.RdxFloatingFocusManager }], ngImport: i0 }); }
|
|
812
1110
|
}
|
|
813
1111
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxMenuPopup, decorators: [{
|
|
814
1112
|
type: Directive,
|
|
815
1113
|
args: [{
|
|
816
1114
|
selector: '[rdxMenuPopup]',
|
|
817
1115
|
exportAs: 'rdxMenuPopup',
|
|
818
|
-
hostDirectives: [RdxPopperContent,
|
|
1116
|
+
hostDirectives: [RdxPopperContent, RdxFloatingNodeRegistration, RdxFloatingFocusManager],
|
|
819
1117
|
providers: [
|
|
820
|
-
|
|
1118
|
+
provideFloatingFocusManagerConfig(() => {
|
|
821
1119
|
const rootContext = injectRdxMenuRootContext();
|
|
1120
|
+
const popup = inject(ElementRef).nativeElement;
|
|
822
1121
|
return {
|
|
823
|
-
|
|
1122
|
+
// Only a (modal) **context menu** traps focus — Base UI's `FloatingFocusManager modal` is
|
|
1123
|
+
// true for `context-menu` alone. Other menus stay non-modal and close on focus-out.
|
|
1124
|
+
modal: () => rootContext.parentType() === 'context-menu',
|
|
1125
|
+
// The manager follows mounted/open lifecycle, not menu disabled state. Dismissal remains
|
|
1126
|
+
// disabled separately below; focus/marker policy should not disappear if a menu becomes
|
|
1127
|
+
// disabled while open, or if `preventUnmountOnClose()` keeps it mounted after close.
|
|
1128
|
+
enabled: () => rootContext.present(),
|
|
1129
|
+
// Base UI's submenu policy: a submenu mount does not steal focus from its trigger.
|
|
1130
|
+
// Root menus still choose first / last item vs popup container from the menu's own
|
|
1131
|
+
// open policy (`autoFocus`), but the decision now lives in the focus manager instead
|
|
1132
|
+
// of a separate popup effect.
|
|
1133
|
+
initialFocus: () => {
|
|
1134
|
+
if (!rootContext.isOpen() || rootContext.parentType() === 'menu') {
|
|
1135
|
+
return false;
|
|
1136
|
+
}
|
|
1137
|
+
const autoFocus = rootContext.autoFocus();
|
|
1138
|
+
if (autoFocus === false) {
|
|
1139
|
+
return false;
|
|
1140
|
+
}
|
|
1141
|
+
if (autoFocus === 'popup') {
|
|
1142
|
+
return popup;
|
|
1143
|
+
}
|
|
1144
|
+
return () => {
|
|
1145
|
+
const items = getFocusableMenuItems(popup);
|
|
1146
|
+
return autoFocus === 'last' ? (items.at(-1) ?? popup) : (items[0] ?? popup);
|
|
1147
|
+
};
|
|
1148
|
+
},
|
|
1149
|
+
returnFocus: () => {
|
|
1150
|
+
const parentType = rootContext.parentType();
|
|
1151
|
+
if (rootContext.trigger() || parentType === undefined || parentType === 'context-menu') {
|
|
1152
|
+
return true;
|
|
1153
|
+
}
|
|
1154
|
+
if (parentType === 'menubar' && rootContext.lastOpenChangeReason() !== 'outside-press') {
|
|
1155
|
+
return true;
|
|
1156
|
+
}
|
|
1157
|
+
return false;
|
|
1158
|
+
},
|
|
1159
|
+
openInteractionType: () => rootContext.openInteractionType(),
|
|
1160
|
+
closeInteractionType: () => rootContext.closeInteractionType()
|
|
824
1161
|
};
|
|
825
|
-
})
|
|
826
|
-
provideRdxFocusScopeConfig(() => ({
|
|
827
|
-
trapped: signal(false)
|
|
828
|
-
}))
|
|
1162
|
+
})
|
|
829
1163
|
],
|
|
830
1164
|
host: {
|
|
831
1165
|
role: 'menu',
|
|
@@ -845,101 +1179,110 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
845
1179
|
}], ctorParameters: () => [], propDecorators: { escapeKeyDown: [{ type: i0.Output, args: ["escapeKeyDown"] }], pointerDownOutside: [{ type: i0.Output, args: ["pointerDownOutside"] }], focusOutside: [{ type: i0.Output, args: ["focusOutside"] }], interactOutside: [{ type: i0.Output, args: ["interactOutside"] }], openAutoFocus: [{ type: i0.Output, args: ["openAutoFocus"] }], closeAutoFocus: [{ type: i0.Output, args: ["closeAutoFocus"] }] } });
|
|
846
1180
|
|
|
847
1181
|
/**
|
|
848
|
-
*
|
|
849
|
-
*
|
|
1182
|
+
* Structural directive that teleports the menu popup into a container (default `document.body`) while
|
|
1183
|
+
* the menu is open, and keeps it mounted until any CSS exit `@keyframes` finishes.
|
|
1184
|
+
*
|
|
1185
|
+
* This replaces the consumer-owned `@if (root.open())` mount: it adds both teleporting *and*
|
|
1186
|
+
* exit-animation support. Apply it with the `*` microsyntax on the positioner —
|
|
1187
|
+
* `<div *rdxMenuPortal rdxMenuPositioner>` — or as an explicit `<ng-template rdxMenuPortal>`. For a
|
|
1188
|
+
* custom container, or a backdrop alongside the positioner (multi-root), use the explicit form.
|
|
850
1189
|
*/
|
|
851
1190
|
class RdxMenuPortal {
|
|
852
|
-
constructor() {
|
|
853
|
-
/**
|
|
854
|
-
* Optional container to portal the content into. Defaults to `document.body`.
|
|
855
|
-
*/
|
|
856
|
-
this.container = input(...(ngDevMode ? [undefined, { debugName: "container" }] : /* istanbul ignore next */ []));
|
|
857
|
-
}
|
|
858
1191
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxMenuPortal, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
859
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "
|
|
1192
|
+
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 }); }
|
|
860
1193
|
}
|
|
861
1194
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxMenuPortal, decorators: [{
|
|
862
1195
|
type: Directive,
|
|
863
1196
|
args: [{
|
|
864
|
-
selector: '[rdxMenuPortal]',
|
|
1197
|
+
selector: 'ng-template[rdxMenuPortal]',
|
|
865
1198
|
exportAs: 'rdxMenuPortal',
|
|
866
|
-
hostDirectives: [
|
|
867
|
-
|
|
868
|
-
directive: RdxPortal,
|
|
869
|
-
inputs: ['container']
|
|
870
|
-
}
|
|
871
|
-
]
|
|
1199
|
+
hostDirectives: [{ directive: RdxPortalPresence, inputs: ['container'] }],
|
|
1200
|
+
providers: [provideRdxPresenceContext(() => ({ present: injectRdxMenuRootContext().present }))]
|
|
872
1201
|
}]
|
|
873
|
-
}]
|
|
1202
|
+
}] });
|
|
1203
|
+
/**
|
|
1204
|
+
* Dev-mode guard: `rdxMenuPortal` is a structural directive. The old `<div rdxMenuPortal>` markup
|
|
1205
|
+
* would silently stop portaling — fail loudly instead.
|
|
1206
|
+
*/
|
|
1207
|
+
class RdxMenuPortalMisuseGuard {
|
|
1208
|
+
constructor() {
|
|
1209
|
+
if (isDevMode()) {
|
|
1210
|
+
rdxDevError('menu/portal-on-element', '`rdxMenuPortal` is a structural directive. ' +
|
|
1211
|
+
'Use `*rdxMenuPortal` on the positioner element or `<ng-template rdxMenuPortal>`.', 'components/menu');
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxMenuPortalMisuseGuard, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1215
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxMenuPortalMisuseGuard, isStandalone: true, selector: "[rdxMenuPortal]:not(ng-template)", ngImport: i0 }); }
|
|
1216
|
+
}
|
|
1217
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxMenuPortalMisuseGuard, decorators: [{
|
|
1218
|
+
type: Directive,
|
|
1219
|
+
args: [{
|
|
1220
|
+
selector: '[rdxMenuPortal]:not(ng-template)'
|
|
1221
|
+
}]
|
|
1222
|
+
}], ctorParameters: () => [] });
|
|
1223
|
+
|
|
1224
|
+
/** Marker attribute on the menu's internal backdrop element. */
|
|
1225
|
+
const MENU_INTERNAL_BACKDROP_ATTR = 'data-rdx-menu-internal-backdrop';
|
|
1226
|
+
/** The element that stays interactive through the backdrop (Base UI `backdropCutout`). */
|
|
1227
|
+
function cutoutElement(rootContext) {
|
|
1228
|
+
const type = rootContext.parentType();
|
|
1229
|
+
const trigger = rootContext.trigger() ?? null;
|
|
1230
|
+
if (type === 'menubar') {
|
|
1231
|
+
// Keep the whole menubar interactive so hover/click switching between its menus still works.
|
|
1232
|
+
return trigger?.closest('[rdxMenubarRoot]') ?? trigger;
|
|
1233
|
+
}
|
|
1234
|
+
if (type === 'context-menu') {
|
|
1235
|
+
return null; // right-click anywhere — no cutout
|
|
1236
|
+
}
|
|
1237
|
+
return trigger; // standalone dropdown — keep its trigger clickable (toggle-close)
|
|
1238
|
+
}
|
|
1239
|
+
/**
|
|
1240
|
+
* The menu's modal **internal backdrop** (finding #1) — a thin wrapper over the shared
|
|
1241
|
+
* {@link setupInternalBackdrop}. Rendered for a modal Menu / Context Menu / Menubar menu (Base UI
|
|
1242
|
+
* `MenuPositioner.tsx`): never for a submenu, and not for a hover-opened dropdown / context menu.
|
|
1243
|
+
*/
|
|
1244
|
+
function setupMenuInternalBackdrop(positioner, rootContext, injector) {
|
|
1245
|
+
setupInternalBackdrop(positioner, injector, {
|
|
1246
|
+
marker: MENU_INTERNAL_BACKDROP_ATTR,
|
|
1247
|
+
isOpen: () => rootContext.isOpen(),
|
|
1248
|
+
cutout: () => cutoutElement(rootContext),
|
|
1249
|
+
shouldRender: () => {
|
|
1250
|
+
const type = rootContext.parentType();
|
|
1251
|
+
if (type === 'menu' || !rootContext.modal()) {
|
|
1252
|
+
return false; // submenus and non-modal menus get no backdrop
|
|
1253
|
+
}
|
|
1254
|
+
if (type === 'menubar') {
|
|
1255
|
+
return true; // a modal menubar menu always gets one (even on hover-switch)
|
|
1256
|
+
}
|
|
1257
|
+
// standalone / context menu: suppressed for a hover-open (Base UI excludes `triggerHover`).
|
|
1258
|
+
return rootContext.lastOpenChangeReason() !== 'trigger-hover';
|
|
1259
|
+
}
|
|
1260
|
+
});
|
|
1261
|
+
}
|
|
874
1262
|
|
|
875
1263
|
/**
|
|
876
1264
|
* Positions the menu against its trigger.
|
|
1265
|
+
*
|
|
1266
|
+
* A "thin" positioner (ADR 0012): it inherits the popper positioning surface (inputs, `placed`
|
|
1267
|
+
* output, unified vars + placement attrs) from {@link RdxPopperContentWrapper} and adds the menu
|
|
1268
|
+
* defaults, the open/closed state attributes, and the deprecated `--radix-menu-*` aliases.
|
|
877
1269
|
*/
|
|
878
|
-
class RdxMenuPositioner {
|
|
1270
|
+
class RdxMenuPositioner extends RdxPopperContentWrapper {
|
|
879
1271
|
constructor() {
|
|
1272
|
+
super();
|
|
880
1273
|
this.rootContext = injectRdxMenuRootContext();
|
|
881
|
-
this.
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
* The preferred side of the trigger to render against when open.
|
|
888
|
-
*/
|
|
889
|
-
this.side = input('bottom', ...(ngDevMode ? [{ debugName: "side" }] : /* istanbul ignore next */ []));
|
|
890
|
-
/**
|
|
891
|
-
* Distance between the trigger and the popup in pixels.
|
|
892
|
-
*/
|
|
893
|
-
this.sideOffset = input(0, { ...(ngDevMode ? { debugName: "sideOffset" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
894
|
-
/**
|
|
895
|
-
* How to align the popup relative to the specified side.
|
|
896
|
-
*/
|
|
897
|
-
this.align = input('start', ...(ngDevMode ? [{ debugName: "align" }] : /* istanbul ignore next */ []));
|
|
898
|
-
/**
|
|
899
|
-
* An offset in pixels from the `start` or `end` alignment options.
|
|
900
|
-
*/
|
|
901
|
-
this.alignOffset = input(0, { ...(ngDevMode ? { debugName: "alignOffset" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
902
|
-
/**
|
|
903
|
-
* Minimum distance to maintain between the arrow and the edges of the popup.
|
|
904
|
-
*/
|
|
905
|
-
this.arrowPadding = input(5, { ...(ngDevMode ? { debugName: "arrowPadding" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
906
|
-
/**
|
|
907
|
-
* Whether to override side and alignment preferences to prevent collisions.
|
|
908
|
-
*/
|
|
909
|
-
this.avoidCollisions = input(true, { ...(ngDevMode ? { debugName: "avoidCollisions" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
910
|
-
/**
|
|
911
|
-
* The element used as the collision boundary.
|
|
912
|
-
*/
|
|
913
|
-
this.collisionBoundary = input(...(ngDevMode ? [undefined, { debugName: "collisionBoundary" }] : /* istanbul ignore next */ []));
|
|
914
|
-
/**
|
|
915
|
-
* Distance in pixels from the boundary edges where collision detection should occur.
|
|
916
|
-
*/
|
|
917
|
-
this.collisionPadding = input(5, ...(ngDevMode ? [{ debugName: "collisionPadding" }] : /* istanbul ignore next */ []));
|
|
918
|
-
/**
|
|
919
|
-
* The sticky behavior on the alignment axis.
|
|
920
|
-
*/
|
|
921
|
-
this.sticky = input('partial', ...(ngDevMode ? [{ debugName: "sticky" }] : /* istanbul ignore next */ []));
|
|
922
|
-
/**
|
|
923
|
-
* Whether to hide the popup when the trigger becomes fully occluded.
|
|
924
|
-
*/
|
|
925
|
-
this.hideWhenDetached = input(false, { ...(ngDevMode ? { debugName: "hideWhenDetached" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
926
|
-
/**
|
|
927
|
-
* The CSS position strategy used by Floating UI.
|
|
928
|
-
*/
|
|
929
|
-
this.positionStrategy = input('fixed', ...(ngDevMode ? [{ debugName: "positionStrategy" }] : /* istanbul ignore next */ []));
|
|
930
|
-
/**
|
|
931
|
-
* Whether to update position on every animation frame.
|
|
932
|
-
*/
|
|
933
|
-
this.updatePositionStrategy = input('always', ...(ngDevMode ? [{ debugName: "updatePositionStrategy" }] : /* istanbul ignore next */ []));
|
|
934
|
-
/**
|
|
935
|
-
* Emits when the popup has been placed.
|
|
936
|
-
*/
|
|
937
|
-
this.placed = outputFromObservable(outputToObservable(inject(RdxPopperContentWrapper).placed));
|
|
1274
|
+
this.legacyVars = legacyPopperVars('menu');
|
|
1275
|
+
const injector = inject(Injector);
|
|
1276
|
+
const host = inject(ElementRef).nativeElement;
|
|
1277
|
+
// After the structural portal has relocated this positioner into the portal container, set up the
|
|
1278
|
+
// modal internal backdrop (finding #1) as a sibling before it.
|
|
1279
|
+
afterNextRender(() => setupMenuInternalBackdrop(host, this.rootContext, injector));
|
|
938
1280
|
}
|
|
939
1281
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxMenuPositioner, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
940
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "
|
|
1282
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxMenuPositioner, isStandalone: true, selector: "[rdxMenuPositioner]", host: { properties: { "attr.data-open": "rootContext.isOpen() ? \"\" : undefined", "attr.data-closed": "rootContext.isOpen() ? undefined : \"\"", "style": "legacyVars" } }, providers: [
|
|
1283
|
+
...provideRdxPopperContentWrapper(RdxMenuPositioner),
|
|
941
1284
|
provideRdxPopperContentConfig({ arrowPadding: 5, collisionPadding: 5, updatePositionStrategy: 'always' })
|
|
942
|
-
], exportAs: ["rdxMenuPositioner"],
|
|
1285
|
+
], exportAs: ["rdxMenuPositioner"], usesInheritance: true, ngImport: i0 }); }
|
|
943
1286
|
}
|
|
944
1287
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxMenuPositioner, decorators: [{
|
|
945
1288
|
type: Directive,
|
|
@@ -947,92 +1290,88 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
947
1290
|
selector: '[rdxMenuPositioner]',
|
|
948
1291
|
exportAs: 'rdxMenuPositioner',
|
|
949
1292
|
providers: [
|
|
1293
|
+
...provideRdxPopperContentWrapper(RdxMenuPositioner),
|
|
950
1294
|
provideRdxPopperContentConfig({ arrowPadding: 5, collisionPadding: 5, updatePositionStrategy: 'always' })
|
|
951
1295
|
],
|
|
952
|
-
hostDirectives: [
|
|
953
|
-
{
|
|
954
|
-
directive: RdxPopperContentWrapper,
|
|
955
|
-
inputs: [
|
|
956
|
-
'anchor',
|
|
957
|
-
'side',
|
|
958
|
-
'sideOffset',
|
|
959
|
-
'align',
|
|
960
|
-
'alignOffset',
|
|
961
|
-
'arrowPadding',
|
|
962
|
-
'avoidCollisions',
|
|
963
|
-
'collisionBoundary',
|
|
964
|
-
'collisionPadding',
|
|
965
|
-
'sticky',
|
|
966
|
-
'hideWhenDetached',
|
|
967
|
-
'positionStrategy',
|
|
968
|
-
'updatePositionStrategy'
|
|
969
|
-
]
|
|
970
|
-
}
|
|
971
|
-
],
|
|
972
1296
|
host: {
|
|
973
1297
|
'[attr.data-open]': 'rootContext.isOpen() ? "" : undefined',
|
|
974
1298
|
'[attr.data-closed]': 'rootContext.isOpen() ? undefined : ""',
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
'[
|
|
978
|
-
'[style]': `{
|
|
979
|
-
'--anchor-width': 'var(--radix-popper-anchor-width)',
|
|
980
|
-
'--anchor-height': 'var(--radix-popper-anchor-height)',
|
|
981
|
-
'--available-width': 'var(--radix-popper-available-width)',
|
|
982
|
-
'--available-height': 'var(--radix-popper-available-height)',
|
|
983
|
-
'--positioner-width': 'var(--radix-popper-content-wrapper-width)',
|
|
984
|
-
'--positioner-height': 'var(--radix-popper-content-wrapper-height)',
|
|
985
|
-
'--transform-origin': 'var(--radix-popper-transform-origin)',
|
|
986
|
-
'--radix-menu-content-transform-origin': 'var(--radix-popper-transform-origin)',
|
|
987
|
-
'--radix-menu-content-available-width': 'var(--radix-popper-available-width)',
|
|
988
|
-
'--radix-menu-content-available-height': 'var(--radix-popper-available-height)',
|
|
989
|
-
'--radix-menu-trigger-width': 'var(--radix-popper-anchor-width)',
|
|
990
|
-
'--radix-menu-trigger-height': 'var(--radix-popper-anchor-height)'
|
|
991
|
-
}`
|
|
1299
|
+
// `data-side`/`data-align`/`data-anchor-hidden` and the unified vars come from the inherited
|
|
1300
|
+
// wrapper (ADR 0012); only the deprecated `--radix-menu-*` aliases remain, for back-compat.
|
|
1301
|
+
'[style]': 'legacyVars'
|
|
992
1302
|
}
|
|
993
1303
|
}]
|
|
994
|
-
}],
|
|
1304
|
+
}], ctorParameters: () => [] });
|
|
995
1305
|
|
|
996
1306
|
const [injectRdxMenuRadioGroupContext, provideRdxMenuRadioGroupContext] = createContext('RdxMenuRadioGroupContext', 'components/menu');
|
|
997
1307
|
const radioGroupContextFactory = () => {
|
|
998
1308
|
const instance = inject(RdxMenuRadioGroup);
|
|
999
1309
|
return {
|
|
1000
1310
|
value: instance.value,
|
|
1311
|
+
disabled: instance.disabled,
|
|
1001
1312
|
selectValue: (v) => instance.selectValue(v)
|
|
1002
1313
|
};
|
|
1003
1314
|
};
|
|
1315
|
+
const groupContextFactory = () => {
|
|
1316
|
+
const instance = inject(RdxMenuRadioGroup);
|
|
1317
|
+
return { labelId: instance.labelId };
|
|
1318
|
+
};
|
|
1004
1319
|
/**
|
|
1005
1320
|
* Groups radio items in a menu.
|
|
1006
1321
|
*/
|
|
1007
1322
|
class RdxMenuRadioGroup {
|
|
1008
1323
|
constructor() {
|
|
1324
|
+
this.hasAppliedDefaultValue = false;
|
|
1009
1325
|
/**
|
|
1010
1326
|
* The currently selected value.
|
|
1011
1327
|
*/
|
|
1012
1328
|
this.value = model(undefined, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
1329
|
+
/** The initially selected value for uncontrolled usage. */
|
|
1330
|
+
this.defaultValue = input(undefined, ...(ngDevMode ? [{ debugName: "defaultValue" }] : /* istanbul ignore next */ []));
|
|
1331
|
+
/** Whether all radio items in the group are disabled. */
|
|
1332
|
+
this.disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
1013
1333
|
/**
|
|
1014
1334
|
* Emits when the selected value changes.
|
|
1015
1335
|
*/
|
|
1016
1336
|
this.onValueChange = output();
|
|
1337
|
+
this.labelId = signal(undefined, ...(ngDevMode ? [{ debugName: "labelId" }] : /* istanbul ignore next */ []));
|
|
1338
|
+
effect(() => {
|
|
1339
|
+
const defaultValue = this.defaultValue();
|
|
1340
|
+
if (!this.hasAppliedDefaultValue && defaultValue !== undefined) {
|
|
1341
|
+
this.hasAppliedDefaultValue = true;
|
|
1342
|
+
this.value.set(defaultValue);
|
|
1343
|
+
}
|
|
1344
|
+
});
|
|
1017
1345
|
}
|
|
1018
1346
|
selectValue(newValue) {
|
|
1347
|
+
if (this.disabled()) {
|
|
1348
|
+
return;
|
|
1349
|
+
}
|
|
1019
1350
|
this.value.set(newValue);
|
|
1020
1351
|
this.onValueChange.emit(newValue);
|
|
1021
1352
|
}
|
|
1022
1353
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxMenuRadioGroup, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1023
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxMenuRadioGroup, isStandalone: true, selector: "[rdxMenuRadioGroup]", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", onValueChange: "onValueChange" }, host: { attributes: { "role": "group" }
|
|
1354
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxMenuRadioGroup, isStandalone: true, selector: "[rdxMenuRadioGroup]", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, defaultValue: { classPropertyName: "defaultValue", publicName: "defaultValue", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", onValueChange: "onValueChange" }, host: { attributes: { "role": "group" }, properties: { "attr.aria-labelledby": "labelId()", "attr.data-disabled": "disabled() ? \"\" : undefined" } }, providers: [
|
|
1355
|
+
provideRdxMenuRadioGroupContext(radioGroupContextFactory),
|
|
1356
|
+
provideRdxMenuGroupContext(groupContextFactory)
|
|
1357
|
+
], exportAs: ["rdxMenuRadioGroup"], ngImport: i0 }); }
|
|
1024
1358
|
}
|
|
1025
1359
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxMenuRadioGroup, decorators: [{
|
|
1026
1360
|
type: Directive,
|
|
1027
1361
|
args: [{
|
|
1028
1362
|
selector: '[rdxMenuRadioGroup]',
|
|
1029
1363
|
exportAs: 'rdxMenuRadioGroup',
|
|
1030
|
-
providers: [
|
|
1364
|
+
providers: [
|
|
1365
|
+
provideRdxMenuRadioGroupContext(radioGroupContextFactory),
|
|
1366
|
+
provideRdxMenuGroupContext(groupContextFactory)
|
|
1367
|
+
],
|
|
1031
1368
|
host: {
|
|
1032
|
-
role: 'group'
|
|
1369
|
+
role: 'group',
|
|
1370
|
+
'[attr.aria-labelledby]': 'labelId()',
|
|
1371
|
+
'[attr.data-disabled]': 'disabled() ? "" : undefined'
|
|
1033
1372
|
}
|
|
1034
1373
|
}]
|
|
1035
|
-
}], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], onValueChange: [{ type: i0.Output, args: ["onValueChange"] }] } });
|
|
1374
|
+
}], 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 }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], onValueChange: [{ type: i0.Output, args: ["onValueChange"] }] } });
|
|
1036
1375
|
|
|
1037
1376
|
const [injectRdxMenuRadioItemContext, provideRdxMenuRadioItemContext] = createContext('RdxMenuRadioItemContext', 'components/menu');
|
|
1038
1377
|
const radioItemContextFactory = () => {
|
|
@@ -1062,10 +1401,11 @@ class RdxMenuRadioItem {
|
|
|
1062
1401
|
this.onSelect = output();
|
|
1063
1402
|
this.checked = computed(() => this.radioGroupContext.value() === this.value(), ...(ngDevMode ? [{ debugName: "checked" }] : /* istanbul ignore next */ []));
|
|
1064
1403
|
this.highlighted = computed(() => this.isFocused(), ...(ngDevMode ? [{ debugName: "highlighted" }] : /* istanbul ignore next */ []));
|
|
1404
|
+
this.effectiveDisabled = computed(() => this.disabled() || this.radioGroupContext.disabled() || (this.rootContext?.disabled() ?? false), ...(ngDevMode ? [{ debugName: "effectiveDisabled" }] : /* istanbul ignore next */ []));
|
|
1065
1405
|
this.getCheckedState = getCheckedState;
|
|
1066
1406
|
}
|
|
1067
1407
|
onFocus() {
|
|
1068
|
-
if (!this.
|
|
1408
|
+
if (!this.effectiveDisabled()) {
|
|
1069
1409
|
this.isFocused.set(true);
|
|
1070
1410
|
}
|
|
1071
1411
|
}
|
|
@@ -1073,13 +1413,13 @@ class RdxMenuRadioItem {
|
|
|
1073
1413
|
this.isFocused.set(false);
|
|
1074
1414
|
}
|
|
1075
1415
|
onPointerMove(event) {
|
|
1076
|
-
if (event.defaultPrevented || event.pointerType !== 'mouse' || this.
|
|
1416
|
+
if (event.defaultPrevented || event.pointerType !== 'mouse' || this.effectiveDisabled()) {
|
|
1077
1417
|
return;
|
|
1078
1418
|
}
|
|
1079
1419
|
if (this.rootContext && !this.rootContext.highlightItemOnHover()) {
|
|
1080
1420
|
return;
|
|
1081
1421
|
}
|
|
1082
|
-
if (
|
|
1422
|
+
if (this.elementRef.nativeElement.ownerDocument.activeElement !== this.elementRef.nativeElement) {
|
|
1083
1423
|
this.elementRef.nativeElement.focus({ preventScroll: true });
|
|
1084
1424
|
}
|
|
1085
1425
|
}
|
|
@@ -1087,18 +1427,25 @@ class RdxMenuRadioItem {
|
|
|
1087
1427
|
if (event.pointerType !== 'mouse') {
|
|
1088
1428
|
return;
|
|
1089
1429
|
}
|
|
1090
|
-
if (
|
|
1430
|
+
if (this.elementRef.nativeElement.ownerDocument.activeElement === this.elementRef.nativeElement) {
|
|
1091
1431
|
this.elementRef.nativeElement.closest('[rdxMenuPopup]')?.focus({ preventScroll: true });
|
|
1092
1432
|
}
|
|
1093
1433
|
}
|
|
1094
1434
|
onItemClick() {
|
|
1095
|
-
if (this.
|
|
1435
|
+
if (this.effectiveDisabled()) {
|
|
1096
1436
|
return;
|
|
1097
1437
|
}
|
|
1098
1438
|
this.selectItem();
|
|
1099
1439
|
}
|
|
1440
|
+
onMouseUp(event) {
|
|
1441
|
+
if (this.effectiveDisabled() || event.button !== 0 || !this.rootContext?.allowMouseUpTrigger()) {
|
|
1442
|
+
return;
|
|
1443
|
+
}
|
|
1444
|
+
this.rootContext.setAllowMouseUpTrigger(false);
|
|
1445
|
+
this.elementRef.nativeElement.click();
|
|
1446
|
+
}
|
|
1100
1447
|
onActivate(event) {
|
|
1101
|
-
if (this.
|
|
1448
|
+
if (this.effectiveDisabled()) {
|
|
1102
1449
|
return;
|
|
1103
1450
|
}
|
|
1104
1451
|
event.preventDefault();
|
|
@@ -1109,10 +1456,10 @@ class RdxMenuRadioItem {
|
|
|
1109
1456
|
this.radioGroupContext.selectValue(v);
|
|
1110
1457
|
this.onSelect.emit(v);
|
|
1111
1458
|
if (this.closeOnClick())
|
|
1112
|
-
this.rootContext?.
|
|
1459
|
+
this.rootContext?.closeEntireMenu();
|
|
1113
1460
|
}
|
|
1114
1461
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxMenuRadioItem, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1115
|
-
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)", "click": "onItemClick()", "keydown.enter": "onActivate($event)", "keydown.space": "onActivate($event)" }, properties: { "attr.aria-checked": "checked()", "attr.data-state": "getCheckedState(checked())", "attr.data-disabled": "
|
|
1462
|
+
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 }); }
|
|
1116
1463
|
}
|
|
1117
1464
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxMenuRadioItem, decorators: [{
|
|
1118
1465
|
type: Directive,
|
|
@@ -1125,14 +1472,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
1125
1472
|
tabindex: '-1',
|
|
1126
1473
|
'[attr.aria-checked]': 'checked()',
|
|
1127
1474
|
'[attr.data-state]': 'getCheckedState(checked())',
|
|
1128
|
-
'[attr.data-disabled]': '
|
|
1129
|
-
'[attr.aria-disabled]': '
|
|
1475
|
+
'[attr.data-disabled]': 'effectiveDisabled() ? "" : undefined',
|
|
1476
|
+
'[attr.aria-disabled]': 'effectiveDisabled() ? true : undefined',
|
|
1130
1477
|
'[attr.data-highlighted]': 'highlighted() ? "" : undefined',
|
|
1131
1478
|
'[attr.data-label]': 'label() ?? undefined',
|
|
1132
1479
|
'(focus)': 'onFocus()',
|
|
1133
1480
|
'(blur)': 'onBlur()',
|
|
1134
1481
|
'(pointermove)': 'onPointerMove($event)',
|
|
1135
1482
|
'(pointerleave)': 'onPointerLeave($event)',
|
|
1483
|
+
'(mouseup)': 'onMouseUp($event)',
|
|
1136
1484
|
'(click)': 'onItemClick()',
|
|
1137
1485
|
'(keydown.enter)': 'onActivate($event)',
|
|
1138
1486
|
'(keydown.space)': 'onActivate($event)'
|
|
@@ -1540,6 +1888,7 @@ class RdxMenuSubTrigger {
|
|
|
1540
1888
|
this.lastPointer = null;
|
|
1541
1889
|
/** Whether the current open was initiated by hover (vs keyboard / click). */
|
|
1542
1890
|
this.openedByHover = false;
|
|
1891
|
+
this.ignoreNextKeyboardClick = false;
|
|
1543
1892
|
/** Whether this trigger (and therefore the submenu) is disabled. */
|
|
1544
1893
|
this.disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
1545
1894
|
/** Whether this trigger should be treated as a native button. Auto-detected for `<button>`. */
|
|
@@ -1554,6 +1903,7 @@ class RdxMenuSubTrigger {
|
|
|
1554
1903
|
this.label = input(undefined, ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
1555
1904
|
/** Highlighted when focused OR while the submenu is open. */
|
|
1556
1905
|
this.highlighted = computed(() => this.isFocused() || this.submenuContext.isOpen(), ...(ngDevMode ? [{ debugName: "highlighted" }] : /* istanbul ignore next */ []));
|
|
1906
|
+
this.effectiveDisabled = computed(() => this.disabled() || this.submenuContext.disabled(), ...(ngDevMode ? [{ debugName: "effectiveDisabled" }] : /* istanbul ignore next */ []));
|
|
1557
1907
|
this.nativeButtonState = computed(() => this.nativeButton() || this.elementRef.nativeElement.tagName === 'BUTTON', ...(ngDevMode ? [{ debugName: "nativeButtonState" }] : /* istanbul ignore next */ []));
|
|
1558
1908
|
this.submenuContext.markAsSubmenu();
|
|
1559
1909
|
effect((onCleanup) => {
|
|
@@ -1582,7 +1932,8 @@ class RdxMenuSubTrigger {
|
|
|
1582
1932
|
return;
|
|
1583
1933
|
}
|
|
1584
1934
|
const reference = this.elementRef.nativeElement;
|
|
1585
|
-
const
|
|
1935
|
+
const ownerDocument = reference.ownerDocument;
|
|
1936
|
+
const scope = reference.closest('[rdxMenuPopup]') ?? ownerDocument.body;
|
|
1586
1937
|
const unregisterOpen = registerOpenSubmenu(reference, popup);
|
|
1587
1938
|
let removeTunnel = applyPointerTunnel(scope, reference, popup);
|
|
1588
1939
|
const { handler, dispose } = createSafePolygonHandler({
|
|
@@ -1600,9 +1951,9 @@ class RdxMenuSubTrigger {
|
|
|
1600
1951
|
removeTunnel = undefined;
|
|
1601
1952
|
}
|
|
1602
1953
|
});
|
|
1603
|
-
|
|
1954
|
+
ownerDocument.addEventListener('mousemove', handler);
|
|
1604
1955
|
onCleanup(() => {
|
|
1605
|
-
|
|
1956
|
+
ownerDocument.removeEventListener('mousemove', handler);
|
|
1606
1957
|
dispose();
|
|
1607
1958
|
removeTunnel?.();
|
|
1608
1959
|
unregisterOpen();
|
|
@@ -1625,7 +1976,7 @@ class RdxMenuSubTrigger {
|
|
|
1625
1976
|
}
|
|
1626
1977
|
}
|
|
1627
1978
|
onFocus() {
|
|
1628
|
-
if (!this.
|
|
1979
|
+
if (!this.effectiveDisabled()) {
|
|
1629
1980
|
this.clearSiblingHighlights();
|
|
1630
1981
|
this.isFocused.set(true);
|
|
1631
1982
|
}
|
|
@@ -1633,18 +1984,71 @@ class RdxMenuSubTrigger {
|
|
|
1633
1984
|
onBlur() {
|
|
1634
1985
|
this.isFocused.set(false);
|
|
1635
1986
|
}
|
|
1636
|
-
onClick() {
|
|
1637
|
-
if (this.
|
|
1987
|
+
onClick(event) {
|
|
1988
|
+
if (this.effectiveDisabled())
|
|
1989
|
+
return;
|
|
1990
|
+
if (this.ignoreNextKeyboardClick && event.detail === 0) {
|
|
1991
|
+
this.ignoreNextKeyboardClick = false;
|
|
1992
|
+
return;
|
|
1993
|
+
}
|
|
1994
|
+
const wasOpen = this.submenuContext.isOpen();
|
|
1995
|
+
// When the submenu opens on hover (default), hover owns its open/close, so a real **mouse** click
|
|
1996
|
+
// is ignored — otherwise it would toggle a just-hover-opened submenu shut (a visible flicker).
|
|
1997
|
+
// Base UI: `ignoreMouse: openOnHover`. A keyboard-activated click (`detail === 0`) still opens.
|
|
1998
|
+
const isMouseClick = event.detail > 0;
|
|
1999
|
+
if (this.openOnHover() && isMouseClick) {
|
|
2000
|
+
return;
|
|
2001
|
+
}
|
|
2002
|
+
this.openedByHover = false;
|
|
2003
|
+
this.clearSiblingHighlights();
|
|
2004
|
+
if (this.submenuContext.isOpen()) {
|
|
2005
|
+
// Toggle (close) only for a click-driven submenu (Base UI `toggle: !openOnHover`).
|
|
2006
|
+
if (!this.openOnHover()) {
|
|
2007
|
+
this.submenuContext.close();
|
|
2008
|
+
}
|
|
2009
|
+
return;
|
|
2010
|
+
}
|
|
2011
|
+
this.closeSiblingSubmenus();
|
|
2012
|
+
this.submenuContext.show('first', 'none', event);
|
|
2013
|
+
if (event.detail === 0 && !wasOpen && this.submenuContext.isOpen()) {
|
|
2014
|
+
this.focusFirstSubmenuItem();
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
onEnter(event) {
|
|
2018
|
+
if (this.effectiveDisabled())
|
|
1638
2019
|
return;
|
|
2020
|
+
event.preventDefault();
|
|
2021
|
+
event.stopPropagation();
|
|
2022
|
+
this.ignoreNextKeyboardClick = true;
|
|
1639
2023
|
this.openedByHover = false;
|
|
1640
2024
|
this.clearSiblingHighlights();
|
|
1641
2025
|
if (!this.submenuContext.isOpen()) {
|
|
1642
2026
|
this.closeSiblingSubmenus();
|
|
2027
|
+
this.submenuContext.show('first', 'none', event);
|
|
1643
2028
|
}
|
|
1644
|
-
this.
|
|
2029
|
+
this.focusFirstSubmenuItem();
|
|
1645
2030
|
}
|
|
1646
2031
|
onArrowRight(event) {
|
|
1647
|
-
if (this.
|
|
2032
|
+
if (this.submenuContext.dir() === 'rtl') {
|
|
2033
|
+
return;
|
|
2034
|
+
}
|
|
2035
|
+
if (this.effectiveDisabled())
|
|
2036
|
+
return;
|
|
2037
|
+
event.preventDefault();
|
|
2038
|
+
event.stopPropagation();
|
|
2039
|
+
this.openedByHover = false;
|
|
2040
|
+
this.clearSiblingHighlights();
|
|
2041
|
+
if (!this.submenuContext.isOpen()) {
|
|
2042
|
+
this.closeSiblingSubmenus();
|
|
2043
|
+
this.submenuContext.show('first', 'none', event);
|
|
2044
|
+
this.focusFirstSubmenuItem();
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
onArrowLeft(event) {
|
|
2048
|
+
if (this.submenuContext.dir() !== 'rtl') {
|
|
2049
|
+
return;
|
|
2050
|
+
}
|
|
2051
|
+
if (this.effectiveDisabled())
|
|
1648
2052
|
return;
|
|
1649
2053
|
event.preventDefault();
|
|
1650
2054
|
event.stopPropagation();
|
|
@@ -1652,23 +2056,25 @@ class RdxMenuSubTrigger {
|
|
|
1652
2056
|
this.clearSiblingHighlights();
|
|
1653
2057
|
if (!this.submenuContext.isOpen()) {
|
|
1654
2058
|
this.closeSiblingSubmenus();
|
|
1655
|
-
this.submenuContext.show();
|
|
2059
|
+
this.submenuContext.show('first', 'none', event);
|
|
2060
|
+
this.focusFirstSubmenuItem();
|
|
1656
2061
|
}
|
|
1657
2062
|
}
|
|
1658
2063
|
onPointerMove(event) {
|
|
1659
|
-
if (event.pointerType !== 'mouse' || this.
|
|
2064
|
+
if (event.pointerType !== 'mouse' || this.effectiveDisabled() || !this.openOnHover())
|
|
1660
2065
|
return;
|
|
1661
2066
|
this.lastPointer = { x: event.clientX, y: event.clientY };
|
|
1662
2067
|
this.clearSiblingHighlights();
|
|
1663
|
-
|
|
1664
|
-
|
|
2068
|
+
const el = this.elementRef.nativeElement;
|
|
2069
|
+
if (this.submenuContext.highlightItemOnHover() && el.ownerDocument.activeElement !== el) {
|
|
2070
|
+
el.focus({ preventScroll: true });
|
|
1665
2071
|
}
|
|
1666
2072
|
if (!this.submenuContext.isOpen()) {
|
|
1667
2073
|
clearTimeout(this.openTimer);
|
|
1668
2074
|
this.closeSiblingSubmenus();
|
|
1669
2075
|
this.openTimer = setTimeout(() => {
|
|
1670
2076
|
this.openedByHover = true;
|
|
1671
|
-
this.submenuContext.show(false);
|
|
2077
|
+
this.submenuContext.show(false, 'trigger-hover');
|
|
1672
2078
|
}, this.delay() ?? 100);
|
|
1673
2079
|
}
|
|
1674
2080
|
}
|
|
@@ -1703,31 +2109,66 @@ class RdxMenuSubTrigger {
|
|
|
1703
2109
|
trigger.dispatchEvent(new CustomEvent('rdx-menu-subtrigger-clear-highlight'));
|
|
1704
2110
|
});
|
|
1705
2111
|
}
|
|
2112
|
+
focusFirstSubmenuItem(attempt = 0) {
|
|
2113
|
+
const maxAttempts = 10;
|
|
2114
|
+
const ownerDocument = this.elementRef.nativeElement.ownerDocument;
|
|
2115
|
+
const run = () => {
|
|
2116
|
+
if (!this.submenuContext.isOpen()) {
|
|
2117
|
+
return;
|
|
2118
|
+
}
|
|
2119
|
+
const popup = this.submenuContext.popupElement();
|
|
2120
|
+
if (!popup) {
|
|
2121
|
+
if (attempt < maxAttempts) {
|
|
2122
|
+
this.focusFirstSubmenuItem(attempt + 1);
|
|
2123
|
+
}
|
|
2124
|
+
return;
|
|
2125
|
+
}
|
|
2126
|
+
const items = getFocusableMenuItems(popup);
|
|
2127
|
+
if (items.length === 0) {
|
|
2128
|
+
if (attempt < maxAttempts) {
|
|
2129
|
+
this.focusFirstSubmenuItem(attempt + 1);
|
|
2130
|
+
}
|
|
2131
|
+
return;
|
|
2132
|
+
}
|
|
2133
|
+
const firstItem = items[0];
|
|
2134
|
+
if (ownerDocument.activeElement !== firstItem) {
|
|
2135
|
+
firstItem?.focus({ preventScroll: true });
|
|
2136
|
+
}
|
|
2137
|
+
};
|
|
2138
|
+
if (this.isBrowser) {
|
|
2139
|
+
requestAnimationFrame(run);
|
|
2140
|
+
}
|
|
2141
|
+
else {
|
|
2142
|
+
setTimeout(run);
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
1706
2145
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxMenuSubTrigger, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1707
|
-
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()", "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": "
|
|
2146
|
+
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 }); }
|
|
1708
2147
|
}
|
|
1709
2148
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxMenuSubTrigger, decorators: [{
|
|
1710
2149
|
type: Directive,
|
|
1711
2150
|
args: [{
|
|
1712
2151
|
selector: '[rdxMenuSubTrigger]',
|
|
1713
2152
|
exportAs: 'rdxMenuSubTrigger',
|
|
1714
|
-
hostDirectives: [RdxPopperAnchor
|
|
2153
|
+
hostDirectives: [RdxPopperAnchor],
|
|
1715
2154
|
host: {
|
|
1716
2155
|
'[attr.type]': 'nativeButtonState() ? "button" : undefined',
|
|
1717
2156
|
role: 'menuitem',
|
|
1718
2157
|
tabindex: '-1',
|
|
1719
2158
|
'[attr.aria-haspopup]': '"menu"',
|
|
1720
2159
|
'[attr.aria-expanded]': 'submenuContext.isOpen()',
|
|
1721
|
-
'[attr.aria-disabled]': '
|
|
1722
|
-
'[attr.disabled]': 'nativeButtonState() &&
|
|
2160
|
+
'[attr.aria-disabled]': 'effectiveDisabled() ? true : undefined',
|
|
2161
|
+
'[attr.disabled]': 'nativeButtonState() && effectiveDisabled() ? "" : undefined',
|
|
1723
2162
|
'[attr.data-state]': 'submenuContext.isOpen() ? "open" : "closed"',
|
|
1724
2163
|
'[attr.data-popup-open]': 'submenuContext.isOpen() ? "" : undefined',
|
|
1725
2164
|
'[attr.data-highlighted]': 'highlighted() ? "" : undefined',
|
|
1726
|
-
'[attr.data-disabled]': '
|
|
2165
|
+
'[attr.data-disabled]': 'effectiveDisabled() ? "" : undefined',
|
|
1727
2166
|
'[attr.data-label]': 'label() ?? undefined',
|
|
1728
2167
|
'(focus)': 'onFocus()',
|
|
1729
2168
|
'(blur)': 'onBlur()',
|
|
1730
|
-
'(click)': 'onClick()',
|
|
2169
|
+
'(click)': 'onClick($event)',
|
|
2170
|
+
'(keydown.enter)': 'onEnter($event)',
|
|
2171
|
+
'(keydown.arrowleft)': 'onArrowLeft($event)',
|
|
1731
2172
|
'(keydown.arrowright)': 'onArrowRight($event)',
|
|
1732
2173
|
'(pointermove)': 'onPointerMove($event)',
|
|
1733
2174
|
'(pointerleave)': 'onPointerLeave()',
|
|
@@ -1745,7 +2186,23 @@ class RdxMenuTrigger {
|
|
|
1745
2186
|
this.rootContext = injectRdxMenuRootContext();
|
|
1746
2187
|
this.elementRef = inject(ElementRef);
|
|
1747
2188
|
this.destroyRef = inject(DestroyRef);
|
|
1748
|
-
this.
|
|
2189
|
+
this.isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
|
|
2190
|
+
this.lastPointer = null;
|
|
2191
|
+
this.openedByHover = false;
|
|
2192
|
+
this.ignoreNextClick = null;
|
|
2193
|
+
this.handleDocumentMouseUp = (event) => {
|
|
2194
|
+
this.allowMouseUpTriggerTimer = undefined;
|
|
2195
|
+
this.rootContext.setAllowMouseUpTrigger(false);
|
|
2196
|
+
const trigger = this.elementRef.nativeElement;
|
|
2197
|
+
const target = event.target;
|
|
2198
|
+
const popup = this.rootContext.popupElement();
|
|
2199
|
+
if (target && (trigger.contains(target) || popup?.contains(target))) {
|
|
2200
|
+
return;
|
|
2201
|
+
}
|
|
2202
|
+
if (this.rootContext.isOpen()) {
|
|
2203
|
+
this.rootContext.close('cancel-open', event);
|
|
2204
|
+
}
|
|
2205
|
+
};
|
|
1749
2206
|
/** Whether this trigger should be treated as a native button. Auto-detected for `<button>`. */
|
|
1750
2207
|
this.nativeButton = input(false, { ...(ngDevMode ? { debugName: "nativeButton" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
1751
2208
|
/** Whether this trigger is disabled. */
|
|
@@ -1753,7 +2210,7 @@ class RdxMenuTrigger {
|
|
|
1753
2210
|
/** Whether hovering the trigger opens the menu. */
|
|
1754
2211
|
this.openOnHover = input(false, { ...(ngDevMode ? { debugName: "openOnHover" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
1755
2212
|
/** Delay before hover opens the menu, in milliseconds. */
|
|
1756
|
-
this.delay = input(
|
|
2213
|
+
this.delay = input(100, { ...(ngDevMode ? { debugName: "delay" } : /* istanbul ignore next */ {}), transform: numberOrUndefined });
|
|
1757
2214
|
/** Delay before hover leave closes the menu, in milliseconds. */
|
|
1758
2215
|
this.closeDelay = input(undefined, { ...(ngDevMode ? { debugName: "closeDelay" } : /* istanbul ignore next */ {}), transform: numberOrUndefined });
|
|
1759
2216
|
this.nativeButtonState = computed(() => this.nativeButton() || this.elementRef.nativeElement.tagName === 'BUTTON', ...(ngDevMode ? [{ debugName: "nativeButtonState" }] : /* istanbul ignore next */ []));
|
|
@@ -1763,33 +2220,97 @@ class RdxMenuTrigger {
|
|
|
1763
2220
|
const unregister = this.rootContext.registerTrigger(el);
|
|
1764
2221
|
onCleanup(unregister);
|
|
1765
2222
|
});
|
|
1766
|
-
// When a coordinator (e.g. the menubar) drives this trigger, hover-switching focuses the
|
|
1767
|
-
// trigger and opens the popup without pulling focus inside it. Register the trigger as a
|
|
1768
|
-
// dismissable-layer branch so that focus/pointer interactions on it are treated as "inside"
|
|
1769
|
-
// and do not dismiss the just-opened popup.
|
|
1770
2223
|
effect((onCleanup) => {
|
|
1771
|
-
|
|
2224
|
+
const open = this.rootContext.isOpen();
|
|
2225
|
+
const popup = this.rootContext.popupElement();
|
|
2226
|
+
if (!open) {
|
|
2227
|
+
this.openedByHover = false;
|
|
2228
|
+
this.lastPointer = null;
|
|
1772
2229
|
return;
|
|
1773
2230
|
}
|
|
1774
|
-
|
|
1775
|
-
|
|
2231
|
+
if (!popup || !this.openedByHover || !this.lastPointer || !this.isBrowser) {
|
|
2232
|
+
return;
|
|
2233
|
+
}
|
|
2234
|
+
const trigger = this.elementRef.nativeElement;
|
|
2235
|
+
const ownerDocument = trigger.ownerDocument;
|
|
2236
|
+
let removeTunnel = applyPointerTunnel(ownerDocument.body, trigger, popup);
|
|
2237
|
+
const { handler, dispose } = createSafePolygonHandler({
|
|
2238
|
+
reference: trigger,
|
|
2239
|
+
floating: popup,
|
|
2240
|
+
side: () => popup.getAttribute('data-side') ?? 'bottom',
|
|
2241
|
+
x: this.lastPointer.x,
|
|
2242
|
+
y: this.lastPointer.y,
|
|
2243
|
+
onClose: () => this.scheduleClose(),
|
|
2244
|
+
cancelClose: () => this.clearCloseTimer(),
|
|
2245
|
+
hasOpenChild: () => hasOpenChildSubmenu(trigger, popup),
|
|
2246
|
+
onLanded: () => {
|
|
2247
|
+
removeTunnel?.();
|
|
2248
|
+
removeTunnel = undefined;
|
|
2249
|
+
}
|
|
2250
|
+
});
|
|
2251
|
+
ownerDocument.addEventListener('mousemove', handler);
|
|
1776
2252
|
onCleanup(() => {
|
|
1777
|
-
|
|
2253
|
+
ownerDocument.removeEventListener('mousemove', handler);
|
|
2254
|
+
dispose();
|
|
2255
|
+
removeTunnel?.();
|
|
2256
|
+
this.clearCloseTimer();
|
|
1778
2257
|
});
|
|
1779
2258
|
});
|
|
2259
|
+
// (A press/focus on the trigger no longer needs a dismissable-layer branch to avoid
|
|
2260
|
+
// self-dismissal: the trigger is registered in the menu's floating context, so the dismissal
|
|
2261
|
+
// capability already treats it as "inside" — ADR 0015 trigger registry replaces the branch.)
|
|
1780
2262
|
this.destroyRef.onDestroy(() => {
|
|
1781
2263
|
this.clearOpenTimer();
|
|
1782
2264
|
this.clearCloseTimer();
|
|
2265
|
+
this.clearMouseUpGuard();
|
|
1783
2266
|
});
|
|
1784
2267
|
}
|
|
1785
|
-
|
|
2268
|
+
handleMouseDown(event) {
|
|
2269
|
+
if (this.isDisabled() || event.button !== 0) {
|
|
2270
|
+
return;
|
|
2271
|
+
}
|
|
2272
|
+
if (this.rootContext.hasTriggerInteractionHandler()) {
|
|
2273
|
+
return;
|
|
2274
|
+
}
|
|
2275
|
+
if (this.openOnHover() &&
|
|
2276
|
+
this.rootContext.isOpen() &&
|
|
2277
|
+
this.rootContext.lastOpenChangeReason() === 'trigger-hover') {
|
|
2278
|
+
return;
|
|
2279
|
+
}
|
|
2280
|
+
const wasOpen = this.rootContext.isOpen();
|
|
2281
|
+
this.clearMouseUpGuard();
|
|
2282
|
+
this.ignoreNextClick = 'mouse';
|
|
2283
|
+
this.openedByHover = false;
|
|
2284
|
+
this.rootContext.toggle('trigger-press', event);
|
|
2285
|
+
if (!wasOpen && this.rootContext.isOpen()) {
|
|
2286
|
+
this.armMouseUpGuard(event.currentTarget);
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
handleClick(event) {
|
|
1786
2290
|
if (this.isDisabled()) {
|
|
1787
2291
|
return;
|
|
1788
2292
|
}
|
|
2293
|
+
const wasOpen = this.rootContext.isOpen();
|
|
1789
2294
|
if (this.rootContext.handleTriggerInteraction({ type: 'click' })) {
|
|
2295
|
+
if (event.detail === 0 && !wasOpen && this.rootContext.isOpen()) {
|
|
2296
|
+
this.restoreKeyboardPopupFocus();
|
|
2297
|
+
}
|
|
2298
|
+
return;
|
|
2299
|
+
}
|
|
2300
|
+
if (this.ignoreNextClick &&
|
|
2301
|
+
((this.ignoreNextClick === 'mouse' && event.detail > 0) ||
|
|
2302
|
+
(this.ignoreNextClick === 'keyboard' && event.detail === 0))) {
|
|
2303
|
+
if (this.ignoreNextClick === 'keyboard') {
|
|
2304
|
+
this.restoreKeyboardPopupFocus();
|
|
2305
|
+
}
|
|
2306
|
+
this.ignoreNextClick = null;
|
|
1790
2307
|
return;
|
|
1791
2308
|
}
|
|
1792
|
-
this.
|
|
2309
|
+
this.openedByHover = false;
|
|
2310
|
+
this.rootContext.toggle('trigger-press', event);
|
|
2311
|
+
if (event.detail === 0 && !wasOpen && this.rootContext.isOpen()) {
|
|
2312
|
+
this.restoreKeyboardPopupFocus();
|
|
2313
|
+
}
|
|
1793
2314
|
}
|
|
1794
2315
|
handleArrowDown(event) {
|
|
1795
2316
|
if (this.rootContext.handleTriggerInteraction({ type: 'arrowdown', event })) {
|
|
@@ -1825,11 +2346,27 @@ class RdxMenuTrigger {
|
|
|
1825
2346
|
this.rootContext.handleTriggerInteraction({ type: 'escape', event });
|
|
1826
2347
|
}
|
|
1827
2348
|
handleKeyboardToggle(event) {
|
|
1828
|
-
|
|
2349
|
+
const wasOpen = this.rootContext.isOpen();
|
|
2350
|
+
const interactionType = event instanceof KeyboardEvent && event.key === ' ' ? 'space' : 'enter';
|
|
2351
|
+
if (this.nativeButtonState() && !this.rootContext.hasTriggerInteractionHandler()) {
|
|
2352
|
+
return;
|
|
2353
|
+
}
|
|
2354
|
+
if (this.rootContext.handleTriggerInteraction({ type: interactionType, event })) {
|
|
2355
|
+
event.preventDefault();
|
|
2356
|
+
this.ignoreNextClick = this.nativeButtonState() ? 'keyboard' : null;
|
|
2357
|
+
this.openedByHover = false;
|
|
2358
|
+
if (!wasOpen && this.rootContext.isOpen()) {
|
|
2359
|
+
this.restoreKeyboardPopupFocus();
|
|
2360
|
+
}
|
|
1829
2361
|
return;
|
|
1830
2362
|
}
|
|
1831
2363
|
event.preventDefault();
|
|
1832
|
-
this.
|
|
2364
|
+
this.ignoreNextClick = this.nativeButtonState() ? 'keyboard' : null;
|
|
2365
|
+
this.openedByHover = false;
|
|
2366
|
+
this.rootContext.toggle('trigger-press', event);
|
|
2367
|
+
if (!wasOpen && this.rootContext.isOpen()) {
|
|
2368
|
+
this.restoreKeyboardPopupFocus();
|
|
2369
|
+
}
|
|
1833
2370
|
}
|
|
1834
2371
|
handlePointerEnter(event) {
|
|
1835
2372
|
if (this.rootContext.handleTriggerInteraction({ type: 'pointerenter', event })) {
|
|
@@ -1840,14 +2377,17 @@ class RdxMenuTrigger {
|
|
|
1840
2377
|
}
|
|
1841
2378
|
this.clearCloseTimer();
|
|
1842
2379
|
this.clearOpenTimer();
|
|
1843
|
-
|
|
2380
|
+
this.lastPointer = { x: event.clientX, y: event.clientY };
|
|
2381
|
+
const delay = this.delay() ?? 100;
|
|
1844
2382
|
if (delay <= 0) {
|
|
1845
|
-
this.
|
|
2383
|
+
this.openedByHover = true;
|
|
2384
|
+
this.rootContext.show('first', 'trigger-hover');
|
|
1846
2385
|
return;
|
|
1847
2386
|
}
|
|
1848
2387
|
this.openTimer = setTimeout(() => {
|
|
1849
2388
|
this.openTimer = undefined;
|
|
1850
|
-
this.
|
|
2389
|
+
this.openedByHover = true;
|
|
2390
|
+
this.rootContext.show('first', 'trigger-hover');
|
|
1851
2391
|
}, delay);
|
|
1852
2392
|
}
|
|
1853
2393
|
handlePointerLeave(event) {
|
|
@@ -1855,12 +2395,19 @@ class RdxMenuTrigger {
|
|
|
1855
2395
|
return;
|
|
1856
2396
|
}
|
|
1857
2397
|
this.clearOpenTimer();
|
|
2398
|
+
this.lastPointer = { x: event.clientX, y: event.clientY };
|
|
2399
|
+
if (!this.rootContext.isOpen() || !this.openedByHover) {
|
|
2400
|
+
this.scheduleClose();
|
|
2401
|
+
}
|
|
2402
|
+
}
|
|
2403
|
+
handlePointerMove(event) {
|
|
2404
|
+
if (event.pointerType !== 'touch' && this.openOnHover()) {
|
|
2405
|
+
this.lastPointer = { x: event.clientX, y: event.clientY };
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
scheduleClose() {
|
|
1858
2409
|
this.clearCloseTimer();
|
|
1859
2410
|
const closeDelay = this.closeDelay() ?? 0;
|
|
1860
|
-
if (closeDelay <= 0) {
|
|
1861
|
-
this.rootContext.close();
|
|
1862
|
-
return;
|
|
1863
|
-
}
|
|
1864
2411
|
this.closeTimer = setTimeout(() => {
|
|
1865
2412
|
this.closeTimer = undefined;
|
|
1866
2413
|
this.rootContext.close();
|
|
@@ -1874,8 +2421,50 @@ class RdxMenuTrigger {
|
|
|
1874
2421
|
clearTimeout(this.closeTimer);
|
|
1875
2422
|
this.closeTimer = undefined;
|
|
1876
2423
|
}
|
|
2424
|
+
armMouseUpGuard(trigger) {
|
|
2425
|
+
this.rootContext.setAllowMouseUpTrigger(false);
|
|
2426
|
+
this.allowMouseUpTriggerTimer = setTimeout(() => {
|
|
2427
|
+
this.allowMouseUpTriggerTimer = undefined;
|
|
2428
|
+
this.rootContext.setAllowMouseUpTrigger(true);
|
|
2429
|
+
}, 200);
|
|
2430
|
+
trigger.ownerDocument.addEventListener('mouseup', this.handleDocumentMouseUp, { once: true });
|
|
2431
|
+
}
|
|
2432
|
+
clearMouseUpGuard() {
|
|
2433
|
+
clearTimeout(this.allowMouseUpTriggerTimer);
|
|
2434
|
+
this.allowMouseUpTriggerTimer = undefined;
|
|
2435
|
+
this.rootContext.setAllowMouseUpTrigger(false);
|
|
2436
|
+
this.elementRef.nativeElement.ownerDocument.removeEventListener('mouseup', this.handleDocumentMouseUp);
|
|
2437
|
+
}
|
|
2438
|
+
restoreKeyboardPopupFocus(attempt = 0) {
|
|
2439
|
+
const maxAttempts = 3;
|
|
2440
|
+
const ownerDocument = this.elementRef.nativeElement.ownerDocument;
|
|
2441
|
+
const run = () => {
|
|
2442
|
+
if (!this.rootContext.isOpen()) {
|
|
2443
|
+
return;
|
|
2444
|
+
}
|
|
2445
|
+
const popup = this.rootContext.popupElement();
|
|
2446
|
+
const activeElement = ownerDocument.activeElement;
|
|
2447
|
+
if (!popup || getFocusableMenuItems(popup).length === 0) {
|
|
2448
|
+
if (attempt < maxAttempts) {
|
|
2449
|
+
this.restoreKeyboardPopupFocus(attempt + 1);
|
|
2450
|
+
}
|
|
2451
|
+
return;
|
|
2452
|
+
}
|
|
2453
|
+
if (activeElement && popup.contains(activeElement)) {
|
|
2454
|
+
return;
|
|
2455
|
+
}
|
|
2456
|
+
const firstItem = getFocusableMenuItems(popup)[0];
|
|
2457
|
+
(firstItem ?? popup).focus({ preventScroll: true });
|
|
2458
|
+
};
|
|
2459
|
+
if (this.isBrowser) {
|
|
2460
|
+
requestAnimationFrame(run);
|
|
2461
|
+
}
|
|
2462
|
+
else {
|
|
2463
|
+
setTimeout(run);
|
|
2464
|
+
}
|
|
2465
|
+
}
|
|
1877
2466
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxMenuTrigger, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1878
|
-
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: { "click": "handleClick()", "pointerenter": "handlePointerEnter($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": "rootContext.isOpen()", "attr.aria-disabled": "isDisabled() ? true : undefined", "attr.disabled": "nativeButtonState() && isDisabled() ? \"\" : undefined", "attr.data-state": "rootContext.isOpen() ? \"open\" : \"closed\"", "attr.data-disabled": "isDisabled() ? \"\" : undefined", "attr.data-popup-open": "rootContext.isOpen() ? \"\" : undefined" } }, exportAs: ["rdxMenuTrigger"], hostDirectives: [{ directive: i1.RdxPopperAnchor }], ngImport: i0 }); }
|
|
2467
|
+
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: { "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": "rootContext.isOpen()", "attr.aria-disabled": "isDisabled() ? true : undefined", "attr.disabled": "nativeButtonState() && isDisabled() ? \"\" : undefined", "attr.data-state": "rootContext.isOpen() ? \"open\" : \"closed\"", "attr.data-disabled": "isDisabled() ? \"\" : undefined", "attr.data-popup-open": "rootContext.isOpen() ? \"\" : undefined", "style.pointer-events": "rootContext.isOpen() && rootContext.modal() ? \"auto\" : undefined" } }, exportAs: ["rdxMenuTrigger"], hostDirectives: [{ directive: i1.RdxPopperAnchor }], ngImport: i0 }); }
|
|
1879
2468
|
}
|
|
1880
2469
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxMenuTrigger, decorators: [{
|
|
1881
2470
|
type: Directive,
|
|
@@ -1894,8 +2483,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
1894
2483
|
'[attr.data-state]': 'rootContext.isOpen() ? "open" : "closed"',
|
|
1895
2484
|
'[attr.data-disabled]': 'isDisabled() ? "" : undefined',
|
|
1896
2485
|
'[attr.data-popup-open]': 'rootContext.isOpen() ? "" : undefined',
|
|
1897
|
-
'
|
|
2486
|
+
'[style.pointer-events]': 'rootContext.isOpen() && rootContext.modal() ? "auto" : undefined',
|
|
2487
|
+
'(mousedown)': 'handleMouseDown($event)',
|
|
2488
|
+
'(click)': 'handleClick($event)',
|
|
1898
2489
|
'(pointerenter)': 'handlePointerEnter($event)',
|
|
2490
|
+
'(pointermove)': 'handlePointerMove($event)',
|
|
1899
2491
|
'(pointerleave)': 'handlePointerLeave($event)',
|
|
1900
2492
|
'(keydown.arrowdown)': 'handleArrowDown($event)',
|
|
1901
2493
|
'(keydown.arrowup)': 'handleArrowUp($event)',
|
|
@@ -1991,6 +2583,7 @@ const menuImports = [
|
|
|
1991
2583
|
RdxMenuTrigger,
|
|
1992
2584
|
RdxMenuSubTrigger,
|
|
1993
2585
|
RdxMenuPortal,
|
|
2586
|
+
RdxMenuPortalMisuseGuard,
|
|
1994
2587
|
RdxMenuPositioner,
|
|
1995
2588
|
RdxMenuPopup,
|
|
1996
2589
|
RdxMenuViewport,
|
|
@@ -2013,6 +2606,7 @@ class RdxMenuModule {
|
|
|
2013
2606
|
RdxMenuTrigger,
|
|
2014
2607
|
RdxMenuSubTrigger,
|
|
2015
2608
|
RdxMenuPortal,
|
|
2609
|
+
RdxMenuPortalMisuseGuard,
|
|
2016
2610
|
RdxMenuPositioner,
|
|
2017
2611
|
RdxMenuPopup,
|
|
2018
2612
|
RdxMenuViewport,
|
|
@@ -2031,6 +2625,7 @@ class RdxMenuModule {
|
|
|
2031
2625
|
RdxMenuTrigger,
|
|
2032
2626
|
RdxMenuSubTrigger,
|
|
2033
2627
|
RdxMenuPortal,
|
|
2628
|
+
RdxMenuPortalMisuseGuard,
|
|
2034
2629
|
RdxMenuPositioner,
|
|
2035
2630
|
RdxMenuPopup,
|
|
2036
2631
|
RdxMenuViewport,
|
|
@@ -2060,5 +2655,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
2060
2655
|
* Generated bundle index. Do not edit.
|
|
2061
2656
|
*/
|
|
2062
2657
|
|
|
2063
|
-
export { RdxMenuArrow, RdxMenuBackdrop, RdxMenuCheckboxItem, RdxMenuCheckboxItemIndicator, RdxMenuGroup, RdxMenuGroupLabel, RdxMenuItem, RdxMenuLinkItem, RdxMenuModule, RdxMenuPopup, RdxMenuPortal, RdxMenuPositioner, RdxMenuRadioGroup, RdxMenuRadioItem, RdxMenuRadioItemIndicator, RdxMenuRoot, RdxMenuSeparator, RdxMenuSubTrigger, RdxMenuTrigger, RdxMenuViewport, getCheckedState, injectRdxMenuCheckboxItemContext, injectRdxMenuRadioGroupContext, injectRdxMenuRadioItemContext, injectRdxMenuRootContext, isIndeterminate, provideRdxMenuCheckboxItemContext, provideRdxMenuRadioGroupContext, provideRdxMenuRadioItemContext, provideRdxMenuRootContext };
|
|
2658
|
+
export { RdxMenuArrow, RdxMenuBackdrop, RdxMenuCheckboxItem, RdxMenuCheckboxItemIndicator, RdxMenuGroup, RdxMenuGroupLabel, RdxMenuItem, RdxMenuLinkItem, RdxMenuModule, RdxMenuPopup, RdxMenuPortal, RdxMenuPortalMisuseGuard, RdxMenuPositioner, RdxMenuRadioGroup, RdxMenuRadioItem, RdxMenuRadioItemIndicator, RdxMenuRoot, RdxMenuSeparator, RdxMenuSubTrigger, RdxMenuTrigger, RdxMenuViewport, getCheckedState, injectRdxMenuCheckboxItemContext, injectRdxMenuGroupContext, injectRdxMenuRadioGroupContext, injectRdxMenuRadioItemContext, injectRdxMenuRootContext, isIndeterminate, provideRdxMenuCheckboxItemContext, provideRdxMenuGroupContext, provideRdxMenuRadioGroupContext, provideRdxMenuRadioItemContext, provideRdxMenuRootContext };
|
|
2064
2659
|
//# sourceMappingURL=radix-ng-primitives-menu.mjs.map
|