@radix-ng/primitives 1.0.9 → 1.1.0
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 +26 -5
- package/fesm2022/radix-ng-primitives-autocomplete.mjs +35 -19
- package/fesm2022/radix-ng-primitives-autocomplete.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-checkbox.mjs +55 -7
- package/fesm2022/radix-ng-primitives-checkbox.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-combobox.mjs +46 -21
- package/fesm2022/radix-ng-primitives-combobox.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-composite.mjs +221 -36
- package/fesm2022/radix-ng-primitives-composite.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-core.mjs +189 -26
- package/fesm2022/radix-ng-primitives-core.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-date-field.mjs +29 -13
- package/fesm2022/radix-ng-primitives-date-field.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-field.mjs +127 -23
- package/fesm2022/radix-ng-primitives-field.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-floating-focus-manager.mjs +9 -0
- package/fesm2022/radix-ng-primitives-floating-focus-manager.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-focus-scope.mjs +241 -95
- package/fesm2022/radix-ng-primitives-focus-scope.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-form.mjs +35 -4
- package/fesm2022/radix-ng-primitives-form.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-input.mjs +87 -15
- package/fesm2022/radix-ng-primitives-input.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-menu.mjs +131 -16
- package/fesm2022/radix-ng-primitives-menu.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-navigation-menu.mjs +17 -3
- package/fesm2022/radix-ng-primitives-navigation-menu.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-popper.mjs +223 -29
- package/fesm2022/radix-ng-primitives-popper.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-portal.mjs +16 -5
- package/fesm2022/radix-ng-primitives-portal.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-scroll-area.mjs +6 -3
- package/fesm2022/radix-ng-primitives-scroll-area.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-select.mjs +49 -17
- package/fesm2022/radix-ng-primitives-select.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-signal-forms.mjs +27 -11
- package/fesm2022/radix-ng-primitives-signal-forms.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-time-field.mjs +24 -10
- package/fesm2022/radix-ng-primitives-time-field.mjs.map +1 -1
- package/package.json +8 -7
- package/types/radix-ng-primitives-autocomplete.d.ts +7 -2
- package/types/radix-ng-primitives-checkbox.d.ts +10 -1
- package/types/radix-ng-primitives-combobox.d.ts +29 -2
- package/types/radix-ng-primitives-composite.d.ts +48 -2
- package/types/radix-ng-primitives-core.d.ts +165 -104
- package/types/radix-ng-primitives-date-field.d.ts +21 -8
- package/types/radix-ng-primitives-field.d.ts +55 -8
- package/types/radix-ng-primitives-floating-focus-manager.d.ts +7 -0
- package/types/radix-ng-primitives-focus-scope.d.ts +61 -44
- package/types/radix-ng-primitives-form.d.ts +9 -0
- package/types/radix-ng-primitives-input.d.ts +10 -2
- package/types/radix-ng-primitives-menu.d.ts +59 -7
- package/types/radix-ng-primitives-navigation-menu.d.ts +2 -2
- package/types/radix-ng-primitives-popover.d.ts +2 -2
- package/types/radix-ng-primitives-popper.d.ts +186 -48
- package/types/radix-ng-primitives-portal.d.ts +18 -8
- package/types/radix-ng-primitives-preview-card.d.ts +2 -2
- package/types/radix-ng-primitives-select.d.ts +17 -3
- package/types/radix-ng-primitives-signal-forms.d.ts +22 -8
- package/types/radix-ng-primitives-time-field.d.ts +21 -10
- package/types/radix-ng-primitives-tooltip.d.ts +2 -2
- package/fesm2022/radix-ng-primitives-focus-guards.mjs +0 -53
- package/fesm2022/radix-ng-primitives-focus-guards.mjs.map +0 -1
- package/focus-guards/README.md +0 -1
- package/types/radix-ng-primitives-focus-guards.d.ts +0 -15
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { InjectionToken, 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, legacyPopperVars, provideRdxPopperContentWrapper, provideRdxPopperContentConfig, RdxPopperAnchor } from '@radix-ng/primitives/popper';
|
|
4
|
+
import { RdxPopper, RdxPopperContentWrapper, RdxPopperArrow, RdxPopperContent, legacyPopperVars, provideRdxPopperContentWrapper, provideRdxPopperContentConfig, DROPDOWN_COLLISION_AVOIDANCE, RdxPopperAnchor } from '@radix-ng/primitives/popper';
|
|
5
5
|
import * as i2 from '@radix-ng/primitives/core';
|
|
6
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
7
|
import { injectDirection } from '@radix-ng/primitives/direction-provider';
|
|
@@ -109,6 +109,7 @@ function buildContext(instance) {
|
|
|
109
109
|
hasTriggerInteractionHandler: instance.hasTriggerInteractionHandler.asReadonly(),
|
|
110
110
|
trigger: instance.trigger.asReadonly(),
|
|
111
111
|
popupElement: instance.popupElement.asReadonly(),
|
|
112
|
+
popupPhysicalSide: instance.popupPhysicalSide,
|
|
112
113
|
beforeContentFocusGuard: instance.beforeContentFocusGuard.asReadonly(),
|
|
113
114
|
transitionStatus: instance.transitionStatus,
|
|
114
115
|
close: (reason, event) => instance.close(reason, event),
|
|
@@ -118,7 +119,7 @@ function buildContext(instance) {
|
|
|
118
119
|
show: (autoFocus, reason, event) => instance.show(autoFocus, reason, event),
|
|
119
120
|
showWithoutAutoFocus: (reason, event) => instance.show(false, reason, event),
|
|
120
121
|
registerTrigger: (el) => instance.registerTrigger(el),
|
|
121
|
-
registerPopup: (el) => instance.registerPopup(el),
|
|
122
|
+
registerPopup: (el, physicalSide) => instance.registerPopup(el, physicalSide),
|
|
122
123
|
setBeforeContentFocusGuard: (element) => instance.setBeforeContentFocusGuard(element),
|
|
123
124
|
registerTransitionElement: (el) => instance.registerTransitionElement(el),
|
|
124
125
|
registerPopupArrowNavigationHandler: (handler) => instance.registerPopupArrowNavigationHandler(handler),
|
|
@@ -185,6 +186,11 @@ class RdxMenuRoot {
|
|
|
185
186
|
...(ngDevMode ? [{ debugName: "trigger" }] : /* istanbul ignore next */ []));
|
|
186
187
|
this.popupElement = signal(undefined, /* @ts-ignore */
|
|
187
188
|
...(ngDevMode ? [{ debugName: "popupElement" }] : /* istanbul ignore next */ []));
|
|
189
|
+
this.popupPhysicalSideSource = signal(undefined, /* @ts-ignore */
|
|
190
|
+
...(ngDevMode ? [{ debugName: "popupPhysicalSideSource" }] : /* istanbul ignore next */ []));
|
|
191
|
+
/** Physical placed side read through the popup's own signal (undefined until the popup is placed). */
|
|
192
|
+
this.popupPhysicalSide = computed(() => this.popupPhysicalSideSource()?.(), /* @ts-ignore */
|
|
193
|
+
...(ngDevMode ? [{ debugName: "popupPhysicalSide" }] : /* istanbul ignore next */ []));
|
|
188
194
|
this.beforeContentFocusGuard = signal(null, /* @ts-ignore */
|
|
189
195
|
...(ngDevMode ? [{ debugName: "beforeContentFocusGuard" }] : /* istanbul ignore next */ []));
|
|
190
196
|
this.transitionStatus = this.transition.status;
|
|
@@ -388,11 +394,13 @@ class RdxMenuRoot {
|
|
|
388
394
|
}
|
|
389
395
|
};
|
|
390
396
|
}
|
|
391
|
-
registerPopup(el) {
|
|
397
|
+
registerPopup(el, physicalSide) {
|
|
392
398
|
this.popupElement.set(el);
|
|
399
|
+
this.popupPhysicalSideSource.set(physicalSide);
|
|
393
400
|
return () => {
|
|
394
401
|
if (this.popupElement() === el) {
|
|
395
402
|
this.popupElement.set(undefined);
|
|
403
|
+
this.popupPhysicalSideSource.set(undefined);
|
|
396
404
|
}
|
|
397
405
|
};
|
|
398
406
|
}
|
|
@@ -1122,7 +1130,9 @@ class RdxMenuPopup {
|
|
|
1122
1130
|
// open submenu as "inside" its parent automatically — replacing the legacy `branches` registry.
|
|
1123
1131
|
this.floatingContext.setFloatingElement(this.elementRef.nativeElement);
|
|
1124
1132
|
const unregister = this.rootContext.registerTransitionElement(this.elementRef.nativeElement);
|
|
1125
|
-
|
|
1133
|
+
// Register the wrapper's PHYSICAL side (not the popup's logical `data-side` echo) so safe-polygon
|
|
1134
|
+
// geometry stays correct when opened with a logical `side="inline-start"` / `inline-end`.
|
|
1135
|
+
const unregisterPopup = this.rootContext.registerPopup(this.elementRef.nativeElement, this.wrapper?.physicalPlacedSide);
|
|
1126
1136
|
inject(DestroyRef).onDestroy(() => {
|
|
1127
1137
|
unregister();
|
|
1128
1138
|
unregisterPopup();
|
|
@@ -1176,6 +1186,43 @@ class RdxMenuPopup {
|
|
|
1176
1186
|
this.rootContext.close('focus-out', event);
|
|
1177
1187
|
}
|
|
1178
1188
|
});
|
|
1189
|
+
// keepMounted return-focus: the composed focus scope returns focus in its *unmount* cleanup,
|
|
1190
|
+
// which never runs while a `keepMounted` popup stays in the DOM (so Escape/close would strand
|
|
1191
|
+
// focus on the now-hidden popup — a keyboard trap). Mirror it on close: when the menu settles
|
|
1192
|
+
// closed but the popup is still mounted, apply the focus manager's resolved return target
|
|
1193
|
+
// (honoring `finalFocus`), falling back to the trigger. The `isConnected` guard scopes this to
|
|
1194
|
+
// the keep-mounted case — a normally-unmounting popup is gone by the deferred frame, its scope
|
|
1195
|
+
// having already returned focus.
|
|
1196
|
+
const settledClosed = computed(() => !this.rootContext.isOpen() && this.rootContext.transitionStatus() !== 'ending', /* @ts-ignore */
|
|
1197
|
+
...(ngDevMode ? [{ debugName: "settledClosed" }] : /* istanbul ignore next */ []));
|
|
1198
|
+
let wasSettledClosed = settledClosed();
|
|
1199
|
+
effect(() => {
|
|
1200
|
+
const closed = settledClosed();
|
|
1201
|
+
const justSettled = closed && !wasSettledClosed;
|
|
1202
|
+
wasSettledClosed = closed;
|
|
1203
|
+
if (!justSettled) {
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
const popup = this.elementRef.nativeElement;
|
|
1207
|
+
const view = popup.ownerDocument.defaultView ?? globalThis;
|
|
1208
|
+
view.requestAnimationFrame(() => {
|
|
1209
|
+
if (!popup.isConnected) {
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
// Return focus only if it was inside the popup, or the browser dumped it to <body>
|
|
1213
|
+
// because the positioner became `hidden`. If the user moved focus to another real
|
|
1214
|
+
// element, leave it.
|
|
1215
|
+
const active = popup.ownerDocument.activeElement;
|
|
1216
|
+
if (active !== popup.ownerDocument.body && !popup.contains(active)) {
|
|
1217
|
+
return;
|
|
1218
|
+
}
|
|
1219
|
+
const target = this.focusManager.resolveReturnTarget();
|
|
1220
|
+
if (target === false) {
|
|
1221
|
+
return; // finalFocus={false} suppresses return
|
|
1222
|
+
}
|
|
1223
|
+
(target ?? this.rootContext.trigger())?.focus({ preventScroll: true });
|
|
1224
|
+
});
|
|
1225
|
+
});
|
|
1179
1226
|
}
|
|
1180
1227
|
handleCloseParent(event) {
|
|
1181
1228
|
event.stopPropagation();
|
|
@@ -1387,14 +1434,21 @@ class RdxMenuPopup {
|
|
|
1387
1434
|
closeInteractionType: () => rootContext.closeInteractionType()
|
|
1388
1435
|
};
|
|
1389
1436
|
})
|
|
1390
|
-
], exportAs: ["rdxMenuPopup"], hostDirectives: [{ directive: i1.RdxPopperContent }, { directive: i2.RdxFloatingNodeRegistration }, { directive: i3.RdxFloatingFocusManager }, { directive: i1$1.RdxCompositeList }], ngImport: i0 }); }
|
|
1437
|
+
], exportAs: ["rdxMenuPopup"], hostDirectives: [{ directive: i1.RdxPopperContent }, { directive: i2.RdxFloatingNodeRegistration }, { directive: i3.RdxFloatingFocusManager, inputs: ["returnFocus", "finalFocus"] }, { directive: i1$1.RdxCompositeList }], ngImport: i0 }); }
|
|
1391
1438
|
}
|
|
1392
1439
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: RdxMenuPopup, decorators: [{
|
|
1393
1440
|
type: Directive,
|
|
1394
1441
|
args: [{
|
|
1395
1442
|
selector: '[rdxMenuPopup]',
|
|
1396
1443
|
exportAs: 'rdxMenuPopup',
|
|
1397
|
-
hostDirectives: [
|
|
1444
|
+
hostDirectives: [
|
|
1445
|
+
RdxPopperContent,
|
|
1446
|
+
RdxFloatingNodeRegistration,
|
|
1447
|
+
// `finalFocus` maps to the focus manager's `returnFocus`, overriding the trigger-return policy
|
|
1448
|
+
// below when set (Base UI `Menu.Popup finalFocus`); same wiring as Popover/Select.
|
|
1449
|
+
{ directive: RdxFloatingFocusManager, inputs: ['returnFocus: finalFocus'] },
|
|
1450
|
+
RdxCompositeList
|
|
1451
|
+
],
|
|
1398
1452
|
providers: [
|
|
1399
1453
|
provideFloatingFocusManagerConfig(() => {
|
|
1400
1454
|
const rootContext = injectRdxMenuRootContext();
|
|
@@ -1469,18 +1523,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
1469
1523
|
* This replaces the consumer-owned `@if (root.open())` mount: it adds both teleporting *and*
|
|
1470
1524
|
* exit-animation support. Apply it with the `*` microsyntax on the positioner —
|
|
1471
1525
|
* `<div *rdxMenuPortal rdxMenuPositioner>` — or as an explicit `<ng-template rdxMenuPortal>`. For a
|
|
1472
|
-
* custom container,
|
|
1526
|
+
* custom container, a backdrop alongside the positioner (multi-root), or `[keepMounted]`, use the
|
|
1527
|
+
* explicit form.
|
|
1528
|
+
*
|
|
1529
|
+
* Set `[keepMounted]="true"` to keep the popup mounted while the menu is closed (the positioner's
|
|
1530
|
+
* `data-closed` state hides it) — inherited from {@link RdxPortalPresence}.
|
|
1473
1531
|
*/
|
|
1474
1532
|
class RdxMenuPortal {
|
|
1475
1533
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: RdxMenuPortal, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1476
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: RdxMenuPortal, isStandalone: true, selector: "ng-template[rdxMenuPortal]", providers: [provideRdxPresenceContext(() => ({ present: injectRdxMenuRootContext().present }))], exportAs: ["rdxMenuPortal"], hostDirectives: [{ directive: i1$2.RdxPortalPresence, inputs: ["container", "container"] }], ngImport: i0 }); }
|
|
1534
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: RdxMenuPortal, isStandalone: true, selector: "ng-template[rdxMenuPortal]", providers: [provideRdxPresenceContext(() => ({ present: injectRdxMenuRootContext().present }))], exportAs: ["rdxMenuPortal"], hostDirectives: [{ directive: i1$2.RdxPortalPresence, inputs: ["container", "container", "keepMounted", "keepMounted"] }], ngImport: i0 }); }
|
|
1477
1535
|
}
|
|
1478
1536
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: RdxMenuPortal, decorators: [{
|
|
1479
1537
|
type: Directive,
|
|
1480
1538
|
args: [{
|
|
1481
1539
|
selector: 'ng-template[rdxMenuPortal]',
|
|
1482
1540
|
exportAs: 'rdxMenuPortal',
|
|
1483
|
-
hostDirectives: [{ directive: RdxPortalPresence, inputs: ['container'] }],
|
|
1541
|
+
hostDirectives: [{ directive: RdxPortalPresence, inputs: ['container', 'keepMounted'] }],
|
|
1484
1542
|
providers: [provideRdxPresenceContext(() => ({ present: injectRdxMenuRootContext().present }))]
|
|
1485
1543
|
}]
|
|
1486
1544
|
}] });
|
|
@@ -1531,6 +1589,16 @@ function setupMenuInternalBackdrop(positioner, rootContext, injector) {
|
|
|
1531
1589
|
isOpen: () => rootContext.isOpen(),
|
|
1532
1590
|
cutout: () => cutoutElement(rootContext),
|
|
1533
1591
|
shouldRender: () => {
|
|
1592
|
+
// The backdrop must exist only while the menu is present (open or running its exit
|
|
1593
|
+
// animation), never while idle-closed. This matters for a `keepMounted` menu: its
|
|
1594
|
+
// positioner stays in the DOM while closed, so without this gate the shared backdrop
|
|
1595
|
+
// helper — which creates the element on `shouldRender()` alone — would build it at page
|
|
1596
|
+
// load and capture a stale cutout rect (the trigger's off-screen position), then never
|
|
1597
|
+
// refresh it. On open the stale backdrop would cover the trigger, and the trigger
|
|
1598
|
+
// `mouseup` would land on the backdrop and immediately close the menu (`cancel-open`).
|
|
1599
|
+
if (!rootContext.present()) {
|
|
1600
|
+
return false;
|
|
1601
|
+
}
|
|
1534
1602
|
const type = rootContext.parentType();
|
|
1535
1603
|
if (type === 'menu' || !rootContext.modal()) {
|
|
1536
1604
|
return false; // submenus and non-modal menus get no backdrop
|
|
@@ -1556,6 +1624,17 @@ class RdxMenuPositioner extends RdxPopperContentWrapper {
|
|
|
1556
1624
|
super();
|
|
1557
1625
|
this.rootContext = injectRdxMenuRootContext();
|
|
1558
1626
|
this.legacyVars = legacyPopperVars('menu');
|
|
1627
|
+
/**
|
|
1628
|
+
* Whether the popup is shown: open, or still running its closing transition. Drives both the native
|
|
1629
|
+
* `hidden` (finding: a closed keep-mounted popup must hide itself) and — via `positioningActive` —
|
|
1630
|
+
* whether the inherited wrapper keeps tracking the anchor, so a closed keep-mounted positioner stops
|
|
1631
|
+
* its `autoUpdate` `requestAnimationFrame` loop (finding: endless rAF while closed).
|
|
1632
|
+
*/
|
|
1633
|
+
this.visible = computed(() => this.rootContext.isOpen() || this.rootContext.transitionStatus() === 'ending', /* @ts-ignore */
|
|
1634
|
+
...(ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []));
|
|
1635
|
+
// Gate the inherited positioning on visibility (see `visible`) — pauses `autoUpdate` while the
|
|
1636
|
+
// menu is closed but kept mounted.
|
|
1637
|
+
this.positioningActive = this.visible;
|
|
1559
1638
|
const injector = inject(Injector);
|
|
1560
1639
|
const host = inject(ElementRef).nativeElement;
|
|
1561
1640
|
// After the structural portal has relocated this positioner into the portal container, set up the
|
|
@@ -1563,9 +1642,16 @@ class RdxMenuPositioner extends RdxPopperContentWrapper {
|
|
|
1563
1642
|
afterNextRender(() => setupMenuInternalBackdrop(host, this.rootContext, injector));
|
|
1564
1643
|
}
|
|
1565
1644
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: RdxMenuPositioner, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1566
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: RdxMenuPositioner, isStandalone: true, selector: "[rdxMenuPositioner]", host: { properties: { "attr.data-open": "rootContext.isOpen() ? \"\" : undefined", "attr.data-closed": "rootContext.isOpen() ? undefined : \"\"", "style": "legacyVars" } }, providers: [
|
|
1645
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: RdxMenuPositioner, isStandalone: true, selector: "[rdxMenuPositioner]", host: { properties: { "attr.data-open": "rootContext.isOpen() ? \"\" : undefined", "attr.data-closed": "rootContext.isOpen() ? undefined : \"\"", "hidden": "!visible()", "style": "legacyVars" } }, providers: [
|
|
1567
1646
|
...provideRdxPopperContentWrapper(RdxMenuPositioner),
|
|
1568
|
-
|
|
1647
|
+
// Base UI applies the DROPDOWN preset to a root menu (and POPUP to submenus, which we don't
|
|
1648
|
+
// differentiate yet — logical-side submenu placement is a separate epic).
|
|
1649
|
+
provideRdxPopperContentConfig({
|
|
1650
|
+
arrowPadding: 5,
|
|
1651
|
+
collisionPadding: 5,
|
|
1652
|
+
collisionAvoidance: DROPDOWN_COLLISION_AVOIDANCE,
|
|
1653
|
+
updatePositionStrategy: 'always'
|
|
1654
|
+
})
|
|
1569
1655
|
], exportAs: ["rdxMenuPositioner"], usesInheritance: true, ngImport: i0 }); }
|
|
1570
1656
|
}
|
|
1571
1657
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: RdxMenuPositioner, decorators: [{
|
|
@@ -1575,11 +1661,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
1575
1661
|
exportAs: 'rdxMenuPositioner',
|
|
1576
1662
|
providers: [
|
|
1577
1663
|
...provideRdxPopperContentWrapper(RdxMenuPositioner),
|
|
1578
|
-
|
|
1664
|
+
// Base UI applies the DROPDOWN preset to a root menu (and POPUP to submenus, which we don't
|
|
1665
|
+
// differentiate yet — logical-side submenu placement is a separate epic).
|
|
1666
|
+
provideRdxPopperContentConfig({
|
|
1667
|
+
arrowPadding: 5,
|
|
1668
|
+
collisionPadding: 5,
|
|
1669
|
+
collisionAvoidance: DROPDOWN_COLLISION_AVOIDANCE,
|
|
1670
|
+
updatePositionStrategy: 'always'
|
|
1671
|
+
})
|
|
1579
1672
|
],
|
|
1580
1673
|
host: {
|
|
1581
1674
|
'[attr.data-open]': 'rootContext.isOpen() ? "" : undefined',
|
|
1582
1675
|
'[attr.data-closed]': 'rootContext.isOpen() ? undefined : ""',
|
|
1676
|
+
// Base UI puts a native `hidden` on a closed positioner (`hidden: !mounted`). It matters for a
|
|
1677
|
+
// `keepMounted` menu: while closed-and-settled the popup stays in the DOM but must be removed from
|
|
1678
|
+
// layout / a11y / tab order. Gated on `visible` (not `present`/`isOpen`) so an exit animation
|
|
1679
|
+
// still plays — the popup only hides once the closing transition ends.
|
|
1680
|
+
'[hidden]': '!visible()',
|
|
1583
1681
|
// `data-side`/`data-align`/`data-anchor-hidden` and the unified vars come from the inherited
|
|
1584
1682
|
// wrapper (ADR 0012); only the deprecated `--radix-menu-*` aliases remain, for back-compat.
|
|
1585
1683
|
'[style]': 'legacyVars'
|
|
@@ -2288,8 +2386,9 @@ class RdxMenuSubTrigger {
|
|
|
2288
2386
|
const { handler, dispose } = createSafePolygonHandler({
|
|
2289
2387
|
reference,
|
|
2290
2388
|
floating: popup,
|
|
2291
|
-
//
|
|
2292
|
-
|
|
2389
|
+
// Physical side from context (live: unresolved at open, flips on collision). NOT the popup's
|
|
2390
|
+
// `data-side`, which is the logical `inline-*` echo the geometry can't interpret.
|
|
2391
|
+
side: () => this.submenuContext.popupPhysicalSide() ?? 'right',
|
|
2293
2392
|
x: this.lastPointer.x,
|
|
2294
2393
|
y: this.lastPointer.y,
|
|
2295
2394
|
onClose: () => this.scheduleClose(),
|
|
@@ -2434,6 +2533,20 @@ class RdxMenuSubTrigger {
|
|
|
2434
2533
|
onPointerLeave() {
|
|
2435
2534
|
clearTimeout(this.openTimer);
|
|
2436
2535
|
}
|
|
2536
|
+
/**
|
|
2537
|
+
* Backup cancellation for a `pointerleave` that Chrome can drop during a fast pointer sweep across
|
|
2538
|
+
* adjacent triggers, which would otherwise leave the delayed hover-open armed and open a stale
|
|
2539
|
+
* submenu for a trigger the pointer has already left (stranding the parent at `pointer-events: none`).
|
|
2540
|
+
* `pointerout` bubbles, so it also fires while crossing between the trigger's own descendants — those
|
|
2541
|
+
* (where `relatedTarget` is still inside the trigger) must not cancel the pending open. Base UI parity
|
|
2542
|
+
* (`guardStaleOpen`); safe on this single, hover-driven trigger.
|
|
2543
|
+
*/
|
|
2544
|
+
onPointerOut(event) {
|
|
2545
|
+
if (this.elementRef.nativeElement.contains(event.relatedTarget)) {
|
|
2546
|
+
return;
|
|
2547
|
+
}
|
|
2548
|
+
clearTimeout(this.openTimer);
|
|
2549
|
+
}
|
|
2437
2550
|
clearHighlight() {
|
|
2438
2551
|
this.isFocused.set(false);
|
|
2439
2552
|
this.clearParentActiveIndex();
|
|
@@ -2509,7 +2622,7 @@ class RdxMenuSubTrigger {
|
|
|
2509
2622
|
}
|
|
2510
2623
|
}
|
|
2511
2624
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: RdxMenuSubTrigger, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
2512
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: RdxMenuSubTrigger, isStandalone: true, selector: "[rdxMenuSubTrigger]", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, nativeButton: { classPropertyName: "nativeButton", publicName: "nativeButton", isSignal: true, isRequired: false, transformFunction: null }, openOnHover: { classPropertyName: "openOnHover", publicName: "openOnHover", isSignal: true, isRequired: false, transformFunction: null }, delay: { classPropertyName: "delay", publicName: "delay", isSignal: true, isRequired: false, transformFunction: null }, closeDelay: { classPropertyName: "closeDelay", publicName: "closeDelay", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "menuitem" }, listeners: { "focus": "onFocus()", "blur": "onBlur()", "click": "onClick($event)", "keydown.enter": "onEnter($event)", "keydown.space": "onEnter($event)", "keydown.arrowleft": "onArrowLeft($event)", "keydown.arrowright": "onArrowRight($event)", "pointermove": "onPointerMove($event)", "pointerleave": "onPointerLeave()", "rdx-menu-subtrigger-clear-highlight": "clearHighlight()" }, properties: { "attr.type": "nativeButtonState() ? \"button\" : undefined", "attr.tabindex": "parentMenuRoot?.open() && highlighted() ? 0 : -1", "attr.aria-haspopup": "\"menu\"", "attr.aria-expanded": "submenuContext.isOpen()", "attr.aria-disabled": "effectiveDisabled() ? true : undefined", "attr.data-popup-open": "submenuContext.isOpen() ? \"\" : undefined", "attr.data-highlighted": "highlighted() ? \"\" : undefined", "attr.data-disabled": "effectiveDisabled() ? \"\" : undefined", "attr.data-label": "label() ?? undefined" } }, exportAs: ["rdxMenuSubTrigger"], hostDirectives: [{ directive: i1.RdxPopperAnchor }, { directive: i1$1.RdxCompositeListItem }], ngImport: i0 }); }
|
|
2625
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: RdxMenuSubTrigger, isStandalone: true, selector: "[rdxMenuSubTrigger]", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, nativeButton: { classPropertyName: "nativeButton", publicName: "nativeButton", isSignal: true, isRequired: false, transformFunction: null }, openOnHover: { classPropertyName: "openOnHover", publicName: "openOnHover", isSignal: true, isRequired: false, transformFunction: null }, delay: { classPropertyName: "delay", publicName: "delay", isSignal: true, isRequired: false, transformFunction: null }, closeDelay: { classPropertyName: "closeDelay", publicName: "closeDelay", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "menuitem" }, listeners: { "focus": "onFocus()", "blur": "onBlur()", "click": "onClick($event)", "keydown.enter": "onEnter($event)", "keydown.space": "onEnter($event)", "keydown.arrowleft": "onArrowLeft($event)", "keydown.arrowright": "onArrowRight($event)", "pointermove": "onPointerMove($event)", "pointerleave": "onPointerLeave()", "pointerout": "onPointerOut($event)", "rdx-menu-subtrigger-clear-highlight": "clearHighlight()" }, properties: { "attr.type": "nativeButtonState() ? \"button\" : undefined", "attr.tabindex": "parentMenuRoot?.open() && highlighted() ? 0 : -1", "attr.aria-haspopup": "\"menu\"", "attr.aria-expanded": "submenuContext.isOpen()", "attr.aria-disabled": "effectiveDisabled() ? true : undefined", "attr.data-popup-open": "submenuContext.isOpen() ? \"\" : undefined", "attr.data-highlighted": "highlighted() ? \"\" : undefined", "attr.data-disabled": "effectiveDisabled() ? \"\" : undefined", "attr.data-label": "label() ?? undefined" } }, exportAs: ["rdxMenuSubTrigger"], hostDirectives: [{ directive: i1.RdxPopperAnchor }, { directive: i1$1.RdxCompositeListItem }], ngImport: i0 }); }
|
|
2513
2626
|
}
|
|
2514
2627
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: RdxMenuSubTrigger, decorators: [{
|
|
2515
2628
|
type: Directive,
|
|
@@ -2537,6 +2650,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
2537
2650
|
'(keydown.arrowright)': 'onArrowRight($event)',
|
|
2538
2651
|
'(pointermove)': 'onPointerMove($event)',
|
|
2539
2652
|
'(pointerleave)': 'onPointerLeave()',
|
|
2653
|
+
'(pointerout)': 'onPointerOut($event)',
|
|
2540
2654
|
'(rdx-menu-subtrigger-clear-highlight)': 'clearHighlight()'
|
|
2541
2655
|
}
|
|
2542
2656
|
}]
|
|
@@ -2614,7 +2728,8 @@ class RdxMenuTrigger {
|
|
|
2614
2728
|
const { handler, dispose } = createSafePolygonHandler({
|
|
2615
2729
|
reference: trigger,
|
|
2616
2730
|
floating: popup,
|
|
2617
|
-
side
|
|
2731
|
+
// Physical side from context — the popup's `data-side` may be a logical `inline-*` echo.
|
|
2732
|
+
side: () => this.rootContext.popupPhysicalSide() ?? 'bottom',
|
|
2618
2733
|
x: this.lastPointer.x,
|
|
2619
2734
|
y: this.lastPointer.y,
|
|
2620
2735
|
onClose: () => this.scheduleClose(),
|