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