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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/composite/README.md +3 -0
  2. package/fesm2022/radix-ng-primitives-accordion.mjs +12 -36
  3. package/fesm2022/radix-ng-primitives-accordion.mjs.map +1 -1
  4. package/fesm2022/radix-ng-primitives-checkbox.mjs +33 -18
  5. package/fesm2022/radix-ng-primitives-checkbox.mjs.map +1 -1
  6. package/fesm2022/radix-ng-primitives-composite.mjs +515 -0
  7. package/fesm2022/radix-ng-primitives-composite.mjs.map +1 -0
  8. package/fesm2022/radix-ng-primitives-core.mjs +7 -0
  9. package/fesm2022/radix-ng-primitives-core.mjs.map +1 -1
  10. package/fesm2022/radix-ng-primitives-dialog.mjs +54 -12
  11. package/fesm2022/radix-ng-primitives-dialog.mjs.map +1 -1
  12. package/fesm2022/radix-ng-primitives-drawer.mjs +442 -2
  13. package/fesm2022/radix-ng-primitives-drawer.mjs.map +1 -1
  14. package/fesm2022/radix-ng-primitives-editable.mjs +12 -7
  15. package/fesm2022/radix-ng-primitives-editable.mjs.map +1 -1
  16. package/fesm2022/radix-ng-primitives-floating-focus-manager.mjs +294 -8
  17. package/fesm2022/radix-ng-primitives-floating-focus-manager.mjs.map +1 -1
  18. package/fesm2022/radix-ng-primitives-focus-scope.mjs +9 -0
  19. package/fesm2022/radix-ng-primitives-focus-scope.mjs.map +1 -1
  20. package/fesm2022/radix-ng-primitives-menu.mjs +71 -20
  21. package/fesm2022/radix-ng-primitives-menu.mjs.map +1 -1
  22. package/fesm2022/radix-ng-primitives-menubar.mjs +68 -36
  23. package/fesm2022/radix-ng-primitives-menubar.mjs.map +1 -1
  24. package/fesm2022/radix-ng-primitives-navigation-menu.mjs +281 -88
  25. package/fesm2022/radix-ng-primitives-navigation-menu.mjs.map +1 -1
  26. package/fesm2022/radix-ng-primitives-number-field.mjs +7 -2
  27. package/fesm2022/radix-ng-primitives-number-field.mjs.map +1 -1
  28. package/fesm2022/radix-ng-primitives-popover.mjs +117 -35
  29. package/fesm2022/radix-ng-primitives-popover.mjs.map +1 -1
  30. package/fesm2022/radix-ng-primitives-popper.mjs +73 -65
  31. package/fesm2022/radix-ng-primitives-popper.mjs.map +1 -1
  32. package/fesm2022/radix-ng-primitives-radio.mjs +77 -36
  33. package/fesm2022/radix-ng-primitives-radio.mjs.map +1 -1
  34. package/fesm2022/radix-ng-primitives-roving-focus.mjs +40 -8
  35. package/fesm2022/radix-ng-primitives-roving-focus.mjs.map +1 -1
  36. package/fesm2022/radix-ng-primitives-scroll-area.mjs +56 -25
  37. package/fesm2022/radix-ng-primitives-scroll-area.mjs.map +1 -1
  38. package/fesm2022/radix-ng-primitives-select.mjs +62 -37
  39. package/fesm2022/radix-ng-primitives-select.mjs.map +1 -1
  40. package/fesm2022/radix-ng-primitives-slider.mjs +259 -28
  41. package/fesm2022/radix-ng-primitives-slider.mjs.map +1 -1
  42. package/fesm2022/radix-ng-primitives-stepper.mjs +11 -7
  43. package/fesm2022/radix-ng-primitives-stepper.mjs.map +1 -1
  44. package/fesm2022/radix-ng-primitives-switch.mjs +10 -5
  45. package/fesm2022/radix-ng-primitives-switch.mjs.map +1 -1
  46. package/fesm2022/radix-ng-primitives-tabs.mjs +64 -30
  47. package/fesm2022/radix-ng-primitives-tabs.mjs.map +1 -1
  48. package/fesm2022/radix-ng-primitives-toggle-group.mjs +69 -19
  49. package/fesm2022/radix-ng-primitives-toggle-group.mjs.map +1 -1
  50. package/fesm2022/radix-ng-primitives-toggle.mjs +37 -13
  51. package/fesm2022/radix-ng-primitives-toggle.mjs.map +1 -1
  52. package/fesm2022/radix-ng-primitives-toolbar.mjs +50 -24
  53. package/fesm2022/radix-ng-primitives-toolbar.mjs.map +1 -1
  54. package/fesm2022/radix-ng-primitives-tooltip.mjs +180 -35
  55. package/fesm2022/radix-ng-primitives-tooltip.mjs.map +1 -1
  56. package/navigation-menu/README.md +5 -2
  57. package/package.json +5 -1
  58. package/types/radix-ng-primitives-accordion.d.ts +9 -13
  59. package/types/radix-ng-primitives-checkbox.d.ts +27 -15
  60. package/types/radix-ng-primitives-composite.d.ts +152 -0
  61. package/types/radix-ng-primitives-core.d.ts +2 -0
  62. package/types/radix-ng-primitives-dialog.d.ts +13 -2
  63. package/types/radix-ng-primitives-drawer.d.ts +40 -2
  64. package/types/radix-ng-primitives-editable.d.ts +11 -5
  65. package/types/radix-ng-primitives-floating-focus-manager.d.ts +113 -16
  66. package/types/radix-ng-primitives-menu.d.ts +13 -5
  67. package/types/radix-ng-primitives-menubar.d.ts +10 -5
  68. package/types/radix-ng-primitives-navigation-menu.d.ts +65 -33
  69. package/types/radix-ng-primitives-number-field.d.ts +8 -3
  70. package/types/radix-ng-primitives-popover.d.ts +26 -10
  71. package/types/radix-ng-primitives-popper.d.ts +1 -0
  72. package/types/radix-ng-primitives-radio.d.ts +22 -13
  73. package/types/radix-ng-primitives-roving-focus.d.ts +15 -1
  74. package/types/radix-ng-primitives-scroll-area.d.ts +4 -1
  75. package/types/radix-ng-primitives-select.d.ts +16 -20
  76. package/types/radix-ng-primitives-slider.d.ts +60 -9
  77. package/types/radix-ng-primitives-stepper.d.ts +11 -4
  78. package/types/radix-ng-primitives-switch.d.ts +10 -4
  79. package/types/radix-ng-primitives-tabs.d.ts +20 -11
  80. package/types/radix-ng-primitives-toggle-group.d.ts +34 -17
  81. package/types/radix-ng-primitives-toggle.d.ts +14 -7
  82. package/types/radix-ng-primitives-toolbar.d.ts +22 -14
  83. package/types/radix-ng-primitives-tooltip.d.ts +38 -14
@@ -4,10 +4,10 @@ import * as i1 from '@radix-ng/primitives/popper';
4
4
  import { RdxPopper, RdxPopperContentWrapper, RdxPopperArrow, RdxPopperContent, legacyPopperVars, provideRdxPopperContentWrapper, provideRdxPopperContentConfig, RdxPopperAnchor } from '@radix-ng/primitives/popper';
5
5
  import * as i2 from '@radix-ng/primitives/core';
6
6
  import { createContext, createFloatingRootContext, useTransitionStatus, injectId, createCancelableChangeEventDetails, provideFloatingTree, provideFloatingRootContext, RDX_FLOATING_ROOT_CONTEXT, RDX_FLOATING_REGISTRATION, useAnchoredScrollLock, RdxFloatingNodeRegistration, rdxDevError, useGraceArea } from '@radix-ng/primitives/core';
7
+ import * as i3 from '@radix-ng/primitives/floating-focus-manager';
8
+ import { getInteractionTypeFromEvent, RdxFloatingFocusManager, provideFloatingFocusManagerConfig, createRdxTriggerInteraction, useTriggerFocusGuards } from '@radix-ng/primitives/floating-focus-manager';
7
9
  import { outputFromObservable, outputToObservable } from '@angular/core/rxjs-interop';
8
10
  import { RdxDismiss } from '@radix-ng/primitives/dismissable-layer';
9
- import * as i3 from '@radix-ng/primitives/floating-focus-manager';
10
- import { RdxFloatingFocusManager, provideFloatingFocusManagerConfig } from '@radix-ng/primitives/floating-focus-manager';
11
11
  import { RdxFocusScope } from '@radix-ng/primitives/focus-scope';
12
12
  import * as i1$1 from '@radix-ng/primitives/portal';
13
13
  import { RdxPortalPresence } from '@radix-ng/primitives/portal';
@@ -65,6 +65,7 @@ class RdxPopoverRoot {
65
65
  */
66
66
  this.handle = input(...(ngDevMode ? [undefined, { debugName: "handle" }] : /* istanbul ignore next */ []));
67
67
  this.contentId = injectId('rdx-popover-content-');
68
+ this.beforeContentFocusGuard = signal(null, ...(ngDevMode ? [{ debugName: "beforeContentFocusGuard" }] : /* istanbul ignore next */ []));
68
69
  this.descriptionId = signal(undefined, ...(ngDevMode ? [{ debugName: "descriptionId" }] : /* istanbul ignore next */ []));
69
70
  this.titleId = signal(undefined, ...(ngDevMode ? [{ debugName: "titleId" }] : /* istanbul ignore next */ []));
70
71
  this.trigger = signal(undefined, ...(ngDevMode ? [{ debugName: "trigger" }] : /* istanbul ignore next */ []));
@@ -73,8 +74,11 @@ class RdxPopoverRoot {
73
74
  this.isPointerDownOnTrigger = signal(false, ...(ngDevMode ? [{ debugName: "isPointerDownOnTrigger" }] : /* istanbul ignore next */ []));
74
75
  /** Whether the current open was initiated by touch (ADR 0016 §3 — gates the anchored scroll lock). */
75
76
  this.openedByTouch = signal(false, ...(ngDevMode ? [{ debugName: "openedByTouch" }] : /* istanbul ignore next */ []));
77
+ this.openInteractionType = signal(null, ...(ngDevMode ? [{ debugName: "openInteractionType" }] : /* istanbul ignore next */ []));
78
+ this.closeInteractionType = signal(null, ...(ngDevMode ? [{ debugName: "closeInteractionType" }] : /* istanbul ignore next */ []));
76
79
  this.popupCloseCount = signal(0, ...(ngDevMode ? [{ debugName: "popupCloseCount" }] : /* istanbul ignore next */ []));
77
80
  this.preventUnmountOnClose = signal(false, ...(ngDevMode ? [{ debugName: "preventUnmountOnClose" }] : /* istanbul ignore next */ []));
81
+ this.pendingTriggerOpenInteractionType = signal(null, ...(ngDevMode ? [{ debugName: "pendingTriggerOpenInteractionType" }] : /* istanbul ignore next */ []));
78
82
  this.onOpenChange = output();
79
83
  this.onOpenChangeComplete = output();
80
84
  this.registeredTriggers = new Map();
@@ -132,7 +136,7 @@ class RdxPopoverRoot {
132
136
  }
133
137
  });
134
138
  }
135
- show(trigger = this.trigger(), payload, triggerId, reason = 'none', event = new Event('popover.open-change'), fromHover = false) {
139
+ show(trigger = this.trigger(), payload, triggerId, reason = 'none', event, fromHover = false) {
136
140
  this.clearHoverTimers();
137
141
  const previousTrigger = this.trigger();
138
142
  const changedTriggerWhileOpen = this.open() && previousTrigger !== trigger;
@@ -146,11 +150,15 @@ class RdxPopoverRoot {
146
150
  this.payload.set(payload);
147
151
  return;
148
152
  }
149
- const change = this.createOpenChangeEvent(true, reason, event, trigger, triggerId ?? this.triggerId());
153
+ const resolvedEvent = event ?? new Event('popover.open-change');
154
+ const change = this.createOpenChangeEvent(true, reason, resolvedEvent, trigger, triggerId ?? this.triggerId());
150
155
  this.onOpenChange.emit(change.payload);
151
156
  if (change.eventDetails.isCanceled()) {
152
157
  return;
153
158
  }
159
+ const interactionType = this.consumeOpenInteractionType(event);
160
+ this.openInteractionType.set(interactionType);
161
+ this.openedByTouch.set(interactionType === 'touch');
154
162
  this.isHoverActive.set(fromHover);
155
163
  this.openChangeReason.set(reason);
156
164
  this.instant.set(changedTriggerWhileOpen || reason === 'trigger-focus');
@@ -171,16 +179,19 @@ class RdxPopoverRoot {
171
179
  this.open.set(true);
172
180
  this.floatingContext.events.emit('openchange', { open: true, reason, event: change.eventDetails.event });
173
181
  }
174
- close(reason = 'none', event = new Event('popover.open-change')) {
182
+ close(reason = 'none', event) {
175
183
  this.clearHoverTimers();
176
184
  if (!this.open()) {
177
185
  return;
178
186
  }
179
- const change = this.createOpenChangeEvent(false, reason, event, this.trigger(), this.triggerId());
187
+ const resolvedEvent = event ?? new Event('popover.open-change');
188
+ const change = this.createOpenChangeEvent(false, reason, resolvedEvent, this.trigger(), this.triggerId());
180
189
  this.onOpenChange.emit(change.payload);
181
190
  if (change.eventDetails.isCanceled()) {
182
191
  return;
183
192
  }
193
+ this.pendingTriggerOpenInteractionType.set(null);
194
+ this.closeInteractionType.set(getInteractionTypeFromEvent(event));
184
195
  this.isHoverActive.set(false);
185
196
  this.openedByTouch.set(false);
186
197
  this.instant.set(reason !== 'none' && reason !== 'trigger-hover');
@@ -224,6 +235,9 @@ class RdxPopoverRoot {
224
235
  this.hoverDelay = delay;
225
236
  this.hoverCloseDelay = closeDelay;
226
237
  }
238
+ setTriggerOpenInteractionType(type) {
239
+ this.pendingTriggerOpenInteractionType.set(type);
240
+ }
227
241
  registerTrigger(id, trigger, payload) {
228
242
  this.registeredTriggers.set(id, { element: trigger, payload });
229
243
  this.triggers.update((triggers) => (triggers.includes(trigger) ? triggers : [...triggers, trigger]));
@@ -263,6 +277,9 @@ class RdxPopoverRoot {
263
277
  this.viewportTriggerChange.add(onTriggerChange);
264
278
  return () => this.viewportTriggerChange.delete(onTriggerChange);
265
279
  }
280
+ setBeforeContentFocusGuard(element) {
281
+ this.beforeContentFocusGuard.set(element);
282
+ }
266
283
  registerTransitionElement(element) {
267
284
  return this.transition.registerElement(element);
268
285
  }
@@ -330,6 +347,11 @@ class RdxPopoverRoot {
330
347
  }
331
348
  });
332
349
  }
350
+ consumeOpenInteractionType(event) {
351
+ const pending = this.pendingTriggerOpenInteractionType();
352
+ this.pendingTriggerOpenInteractionType.set(null);
353
+ return pending ?? getInteractionTypeFromEvent(event);
354
+ }
333
355
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxPopoverRoot, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
334
356
  static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxPopoverRoot, isStandalone: true, selector: "[rdxPopoverRoot]", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, defaultOpen: { classPropertyName: "defaultOpen", publicName: "defaultOpen", isSignal: true, isRequired: false, transformFunction: null }, triggerId: { classPropertyName: "triggerId", publicName: "triggerId", isSignal: true, isRequired: false, transformFunction: null }, defaultTriggerId: { classPropertyName: "defaultTriggerId", publicName: "defaultTriggerId", isSignal: true, isRequired: false, transformFunction: null }, modal: { classPropertyName: "modal", publicName: "modal", isSignal: true, isRequired: false, transformFunction: null }, handle: { classPropertyName: "handle", publicName: "handle", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange", triggerId: "triggerIdChange", onOpenChange: "onOpenChange", onOpenChangeComplete: "onOpenChangeComplete" }, providers: [
335
357
  provideRdxPopoverRootContext(context),
@@ -371,6 +393,8 @@ function contextFor(root) {
371
393
  openChangeReason: root.openChangeReason.asReadonly(),
372
394
  isPointerDownOnTrigger: root.isPointerDownOnTrigger.asReadonly(),
373
395
  openedByTouch: root.openedByTouch.asReadonly(),
396
+ openInteractionType: root.openInteractionType.asReadonly(),
397
+ closeInteractionType: root.closeInteractionType.asReadonly(),
374
398
  close: (reason, event) => root.close(reason, event),
375
399
  cancelHoverClose: () => root.cancelHoverClose(),
376
400
  cancelHoverOpen: () => root.cancelHoverOpen(),
@@ -382,6 +406,7 @@ function contextFor(root) {
382
406
  setTitleId: (id) => root.titleId.set(id),
383
407
  setPointerDownOnTrigger: (pointerDown) => root.isPointerDownOnTrigger.set(pointerDown),
384
408
  setOpenedByTouch: (value) => root.openedByTouch.set(value),
409
+ setTriggerOpenInteractionType: (type) => root.setTriggerOpenInteractionType(type),
385
410
  setHoverDelays: (delay, closeDelay) => root.setHoverDelays(delay, closeDelay),
386
411
  registerPopupClose: () => {
387
412
  root.popupCloseCount.update((count) => count + 1);
@@ -389,7 +414,9 @@ function contextFor(root) {
389
414
  },
390
415
  registerTransitionElement: (element) => root.registerTransitionElement(element),
391
416
  transitionStatus: root.transitionStatus,
417
+ beforeContentFocusGuard: root.beforeContentFocusGuard.asReadonly(),
392
418
  registerViewport: (onTriggerChange) => root.registerViewport(onTriggerChange),
419
+ setBeforeContentFocusGuard: (element) => root.setBeforeContentFocusGuard(element),
393
420
  toggle: (triggerId, trigger, payload, event) => root.toggle(triggerId, trigger, payload, event)
394
421
  };
395
422
  }
@@ -429,29 +456,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
429
456
  class RdxPopoverBackdrop {
430
457
  constructor() {
431
458
  this.rootContext = injectRdxPopoverRootContext();
459
+ const host = inject(ElementRef).nativeElement;
460
+ host.style.setProperty('-webkit-user-select', 'none');
432
461
  // Register the backdrop as owned DOM footprint for primitive-specific checks. The focus manager's
433
462
  // marker keep-set stays narrow and does not keep sibling backdrop roots.
434
463
  const floatingContext = inject(RDX_FLOATING_ROOT_CONTEXT, { optional: true });
435
464
  if (floatingContext) {
436
- const host = inject(ElementRef).nativeElement;
437
465
  floatingContext.addFloatingElement(host);
438
466
  inject(DestroyRef).onDestroy(() => floatingContext.removeFloatingElement(host));
439
467
  }
440
468
  }
441
469
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxPopoverBackdrop, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
442
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxPopoverBackdrop, isStandalone: true, selector: "[rdxPopoverBackdrop]", host: { properties: { "attr.data-closed": "rootContext.isOpen() ? undefined : \"\"", "attr.data-ending-style": "rootContext.transitionStatus() === \"ending\" ? \"\" : undefined", "attr.data-instant": "rootContext.instant() ? \"\" : undefined", "attr.data-open": "rootContext.isOpen() ? \"\" : undefined", "attr.data-starting-style": "rootContext.transitionStatus() === \"starting\" ? \"\" : undefined", "attr.data-state": "rootContext.isOpen() ? \"open\" : \"closed\"" } }, ngImport: i0 }); }
470
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxPopoverBackdrop, isStandalone: true, selector: "[rdxPopoverBackdrop]", host: { attributes: { "role": "presentation" }, properties: { "attr.data-closed": "rootContext.isOpen() ? undefined : \"\"", "attr.data-ending-style": "rootContext.transitionStatus() === \"ending\" ? \"\" : undefined", "attr.data-instant": "rootContext.instant() ? \"\" : undefined", "attr.data-open": "rootContext.isOpen() ? \"\" : undefined", "attr.data-starting-style": "rootContext.transitionStatus() === \"starting\" ? \"\" : undefined", "attr.data-state": "rootContext.isOpen() ? \"open\" : \"closed\"", "style.pointer-events": "rootContext.openChangeReason() === \"trigger-hover\" ? \"none\" : null", "style.user-select": "\"none\"" } }, ngImport: i0 }); }
443
471
  }
444
472
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxPopoverBackdrop, decorators: [{
445
473
  type: Directive,
446
474
  args: [{
447
475
  selector: '[rdxPopoverBackdrop]',
448
476
  host: {
477
+ role: 'presentation',
449
478
  '[attr.data-closed]': 'rootContext.isOpen() ? undefined : ""',
450
479
  '[attr.data-ending-style]': 'rootContext.transitionStatus() === "ending" ? "" : undefined',
451
480
  '[attr.data-instant]': 'rootContext.instant() ? "" : undefined',
452
481
  '[attr.data-open]': 'rootContext.isOpen() ? "" : undefined',
453
482
  '[attr.data-starting-style]': 'rootContext.transitionStatus() === "starting" ? "" : undefined',
454
- '[attr.data-state]': 'rootContext.isOpen() ? "open" : "closed"'
483
+ '[attr.data-state]': 'rootContext.isOpen() ? "open" : "closed"',
484
+ '[style.pointer-events]': 'rootContext.openChangeReason() === "trigger-hover" ? "none" : null',
485
+ '[style.user-select]': '"none"'
455
486
  }
456
487
  }]
457
488
  }], ctorParameters: () => [] });
@@ -469,8 +500,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
469
500
  * - No `disablePointerDismissal` — outside-press + focus-out always close.
470
501
  *
471
502
  * Note: a positioned popover does **not** auto-focus into the popup on open (pre-existing — the legacy
472
- * behaved the same; verified). The trap holds focus once it is inside. Auto-focus-on-open + redirecting a
473
- * Tab from the trigger into the popup needs the deferred portal-focus bridge / guards (ADR 0017 §6a).
503
+ * behaved the same; verified). The trap holds focus once it is inside; portal tab-order handoff is owned
504
+ * by `RdxFloatingFocusManager` and anchored to the active trigger below.
474
505
  */
475
506
  class RdxPopoverPopup {
476
507
  constructor() {
@@ -547,20 +578,29 @@ class RdxPopoverPopup {
547
578
  (rootContext.modal() === true && rootContext.hasPopupClose()),
548
579
  // Full modal blocks outside pointer interaction; `trap-focus` only traps focus.
549
580
  inert: () => rootContext.modal() === true && rootContext.hasPopupClose(),
581
+ restoreFocus: () => 'popup',
582
+ previousFocusableElement: () => rootContext.trigger() ?? null,
583
+ beforeContentFocusGuardRef: () => (element) => rootContext.setBeforeContentFocusGuard(element),
550
584
  // Active for the whole MOUNTED lifetime (Base UI `disabled={!mounted}`, not `open`) for
551
585
  // trap/return-focus — including an explicit `preventUnmountOnClose()` cycle after the
552
586
  // exit transition. Marker + isolation are additionally gated on `open` inside the
553
587
  // manager. Still suppressed while hover-opened.
554
- enabled: () => rootContext.present() && !rootContext.isHoverActive()
588
+ enabled: () => rootContext.present() && !rootContext.isHoverActive(),
589
+ openInteractionType: () => rootContext.openInteractionType(),
590
+ closeInteractionType: () => rootContext.closeInteractionType()
555
591
  };
556
592
  })
557
- ], hostDirectives: [{ directive: i1.RdxPopperContent }, { directive: i2.RdxFloatingNodeRegistration }, { directive: i3.RdxFloatingFocusManager }], ngImport: i0 }); }
593
+ ], hostDirectives: [{ directive: i1.RdxPopperContent }, { directive: i2.RdxFloatingNodeRegistration }, { directive: i3.RdxFloatingFocusManager, inputs: ["initialFocus", "initialFocus", "returnFocus", "finalFocus"] }], ngImport: i0 }); }
558
594
  }
559
595
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxPopoverPopup, decorators: [{
560
596
  type: Directive,
561
597
  args: [{
562
598
  selector: '[rdxPopoverPopup]',
563
- hostDirectives: [RdxPopperContent, RdxFloatingNodeRegistration, RdxFloatingFocusManager],
599
+ hostDirectives: [
600
+ RdxPopperContent,
601
+ RdxFloatingNodeRegistration,
602
+ { directive: RdxFloatingFocusManager, inputs: ['initialFocus', 'returnFocus: finalFocus'] }
603
+ ],
564
604
  providers: [
565
605
  provideFloatingFocusManagerConfig(() => {
566
606
  const rootContext = injectRdxPopoverRootContext();
@@ -569,11 +609,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
569
609
  (rootContext.modal() === true && rootContext.hasPopupClose()),
570
610
  // Full modal blocks outside pointer interaction; `trap-focus` only traps focus.
571
611
  inert: () => rootContext.modal() === true && rootContext.hasPopupClose(),
612
+ restoreFocus: () => 'popup',
613
+ previousFocusableElement: () => rootContext.trigger() ?? null,
614
+ beforeContentFocusGuardRef: () => (element) => rootContext.setBeforeContentFocusGuard(element),
572
615
  // Active for the whole MOUNTED lifetime (Base UI `disabled={!mounted}`, not `open`) for
573
616
  // trap/return-focus — including an explicit `preventUnmountOnClose()` cycle after the
574
617
  // exit transition. Marker + isolation are additionally gated on `open` inside the
575
618
  // manager. Still suppressed while hover-opened.
576
- enabled: () => rootContext.present() && !rootContext.isHoverActive()
619
+ enabled: () => rootContext.present() && !rootContext.isHoverActive(),
620
+ openInteractionType: () => rootContext.openInteractionType(),
621
+ closeInteractionType: () => rootContext.closeInteractionType()
577
622
  };
578
623
  })
579
624
  ],
@@ -626,22 +671,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
626
671
  class RdxPopoverDescription {
627
672
  constructor() {
628
673
  this.rootContext = injectRdxPopoverRootContext();
629
- this.id = injectId('rdx-popover-description-');
630
- this.rootContext.setDescriptionId(this.id);
674
+ this.generatedId = injectId('rdx-popover-description-');
675
+ this.idInput = input(undefined, { ...(ngDevMode ? { debugName: "idInput" } : /* istanbul ignore next */ {}), alias: 'id' });
676
+ this.id = computed(() => this.idInput() ?? this.generatedId, ...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
677
+ effect((onCleanup) => {
678
+ const id = this.id();
679
+ this.rootContext.setDescriptionId(id);
680
+ onCleanup(() => this.rootContext.setDescriptionId(undefined));
681
+ });
631
682
  inject(DestroyRef).onDestroy(() => this.rootContext.setDescriptionId(undefined));
632
683
  }
633
684
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxPopoverDescription, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
634
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxPopoverDescription, isStandalone: true, selector: "[rdxPopoverDescription]", host: { properties: { "id": "id" } }, ngImport: i0 }); }
685
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxPopoverDescription, isStandalone: true, selector: "[rdxPopoverDescription]", inputs: { idInput: { classPropertyName: "idInput", publicName: "id", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "id": "id()" } }, ngImport: i0 }); }
635
686
  }
636
687
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxPopoverDescription, decorators: [{
637
688
  type: Directive,
638
689
  args: [{
639
690
  selector: '[rdxPopoverDescription]',
640
691
  host: {
641
- '[id]': 'id'
692
+ '[id]': 'id()'
642
693
  }
643
694
  }]
644
- }], ctorParameters: () => [] });
695
+ }], ctorParameters: () => [], propDecorators: { idInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }] } });
645
696
 
646
697
  /**
647
698
  * Structural directive that teleports the popover content into a container (default `document.body`)
@@ -741,22 +792,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
741
792
  class RdxPopoverTitle {
742
793
  constructor() {
743
794
  this.rootContext = injectRdxPopoverRootContext();
744
- this.id = injectId('rdx-popover-title-');
745
- this.rootContext.setTitleId(this.id);
795
+ this.generatedId = injectId('rdx-popover-title-');
796
+ this.idInput = input(undefined, { ...(ngDevMode ? { debugName: "idInput" } : /* istanbul ignore next */ {}), alias: 'id' });
797
+ this.id = computed(() => this.idInput() ?? this.generatedId, ...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
798
+ effect((onCleanup) => {
799
+ const id = this.id();
800
+ this.rootContext.setTitleId(id);
801
+ onCleanup(() => this.rootContext.setTitleId(undefined));
802
+ });
746
803
  inject(DestroyRef).onDestroy(() => this.rootContext.setTitleId(undefined));
747
804
  }
748
805
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxPopoverTitle, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
749
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxPopoverTitle, isStandalone: true, selector: "[rdxPopoverTitle]", host: { properties: { "id": "id" } }, ngImport: i0 }); }
806
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxPopoverTitle, isStandalone: true, selector: "[rdxPopoverTitle]", inputs: { idInput: { classPropertyName: "idInput", publicName: "id", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "id": "id()" } }, ngImport: i0 }); }
750
807
  }
751
808
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxPopoverTitle, decorators: [{
752
809
  type: Directive,
753
810
  args: [{
754
811
  selector: '[rdxPopoverTitle]',
755
812
  host: {
756
- '[id]': 'id'
813
+ '[id]': 'id()'
757
814
  }
758
815
  }]
759
- }], ctorParameters: () => [] });
816
+ }], ctorParameters: () => [], propDecorators: { idInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }] } });
760
817
 
761
818
  /**
762
819
  * A button that opens the popover.
@@ -765,8 +822,6 @@ class RdxPopoverTrigger {
765
822
  constructor() {
766
823
  this.parentRootContext = injectRdxPopoverRootContext(true);
767
824
  this.elementRef = inject(ElementRef);
768
- /** Pointer type of the most recent `pointerdown`, used to detect a touch open (ADR 0016 §3). */
769
- this.lastPointerType = '';
770
825
  /**
771
826
  * Associates this trigger with a detached popover root.
772
827
  */
@@ -798,8 +853,20 @@ class RdxPopoverTrigger {
798
853
  this.triggerId = computed(() => this.id() ?? this.generatedId, ...(ngDevMode ? [{ debugName: "triggerId" }] : /* istanbul ignore next */ []));
799
854
  this.rootContext = computed(() => this.handle()?.context() ?? this.parentRootContext, ...(ngDevMode ? [{ debugName: "rootContext" }] : /* istanbul ignore next */ []));
800
855
  this.isOpen = computed(() => this.rootContext()?.isOpen() === true && this.rootContext()?.trigger() === this.elementRef.nativeElement, ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
801
- this.isPressed = computed(() => this.isOpen() && this.rootContext()?.openChangeReason() === 'trigger-press', ...(ngDevMode ? [{ debugName: "isPressed" }] : /* istanbul ignore next */ []));
856
+ this.triggerInteraction = createRdxTriggerInteraction({
857
+ trigger: () => this.elementRef.nativeElement,
858
+ activeTrigger: () => this.rootContext()?.trigger(),
859
+ open: () => this.rootContext()?.isOpen() ?? false,
860
+ disabled: () => this.disabled(),
861
+ contentId: () => this.rootContext()?.contentId
862
+ });
863
+ this.isPressed = computed(() => this.triggerInteraction.isActive() && this.rootContext()?.openChangeReason() === 'trigger-press', ...(ngDevMode ? [{ debugName: "isPressed" }] : /* istanbul ignore next */ []));
802
864
  this.generatedId = injectId('rdx-popover-trigger-');
865
+ effect(() => {
866
+ if (!this.handle() && !this.parentRootContext) {
867
+ rdxDevError('popover/trigger-missing-root', '`rdxPopoverTrigger` must be used inside `rdxPopoverRoot` or receive a `handle` connected to a root.', 'components/popover');
868
+ }
869
+ });
803
870
  effect((onCleanup) => {
804
871
  const handle = this.handle();
805
872
  if (handle) {
@@ -809,6 +876,19 @@ class RdxPopoverTrigger {
809
876
  onCleanup(untracked(() => this.parentRootContext.registerTrigger(this.triggerId(), this.elementRef.nativeElement, () => this.payload())));
810
877
  }
811
878
  });
879
+ useTriggerFocusGuards({
880
+ trigger: () => this.elementRef.nativeElement,
881
+ close: (event) => this.rootContext()?.close('focus-out', event),
882
+ beforeContentFocusGuard: () => this.rootContext()?.beforeContentFocusGuard(),
883
+ contentId: () => this.rootContext()?.contentId,
884
+ enabled: () => this.triggerInteraction.isActive(),
885
+ popupElement: () => {
886
+ const contentId = this.rootContext()?.contentId;
887
+ return contentId
888
+ ? this.elementRef.nativeElement.ownerDocument.getElementById(contentId)
889
+ : null;
890
+ }
891
+ });
812
892
  }
813
893
  handleClick(event) {
814
894
  if (this.disabled()) {
@@ -816,7 +896,9 @@ class RdxPopoverTrigger {
816
896
  }
817
897
  // Record whether this open is a touch tap (ADR 0016 §3). `detail === 0` is a keyboard-activated
818
898
  // click (no preceding pointerdown), which must read non-touch regardless of the last pointer type.
819
- this.rootContext()?.setOpenedByTouch(event.detail !== 0 && this.lastPointerType === 'touch');
899
+ const interactionType = this.triggerInteraction.clickInteractionType(event);
900
+ this.rootContext()?.setOpenedByTouch(interactionType === 'touch');
901
+ this.rootContext()?.setTriggerOpenInteractionType(interactionType);
820
902
  this.rootContext()?.setPointerDownOnTrigger(false);
821
903
  if (this.handle()) {
822
904
  this.handle().toggle(this.triggerId(), event);
@@ -840,14 +922,14 @@ class RdxPopoverTrigger {
840
922
  this.rootContext()?.cancelHoverOpen();
841
923
  }
842
924
  handlePointerDown(event) {
843
- this.lastPointerType = event.pointerType;
925
+ this.triggerInteraction.recordPointerDown(event);
844
926
  this.rootContext()?.setPointerDownOnTrigger(true);
845
927
  }
846
928
  handlePointerUp() {
847
929
  this.rootContext()?.setPointerDownOnTrigger(false);
848
930
  }
849
931
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxPopoverTrigger, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
850
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxPopoverTrigger, isStandalone: true, selector: "button[rdxPopoverTrigger]", inputs: { handle: { classPropertyName: "handle", publicName: "handle", isSignal: true, isRequired: false, transformFunction: null }, payload: { classPropertyName: "payload", publicName: "payload", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", 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: { attributes: { "type": "button" }, listeners: { "click": "handleClick($event)", "pointerenter": "handlePointerEnter($event)", "pointerleave": "handlePointerLeave($event)", "pointerdown": "handlePointerDown($event)", "pointerup": "handlePointerUp()", "pointercancel": "handlePointerUp()" }, properties: { "attr.aria-controls": "rootContext()?.contentId", "attr.aria-expanded": "isOpen()", "attr.aria-haspopup": "\"dialog\"", "attr.data-state": "isOpen() ? \"open\" : \"closed\"", "attr.data-popup-open": "isOpen() ? \"\" : undefined", "attr.data-pressed": "isPressed() ? \"\" : undefined", "attr.disabled": "disabled() ? \"\" : undefined", "id": "triggerId()" } }, hostDirectives: [{ directive: i1.RdxPopperAnchor }], ngImport: i0 }); }
932
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxPopoverTrigger, isStandalone: true, selector: "button[rdxPopoverTrigger]", inputs: { handle: { classPropertyName: "handle", publicName: "handle", isSignal: true, isRequired: false, transformFunction: null }, payload: { classPropertyName: "payload", publicName: "payload", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", 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: { attributes: { "type": "button" }, listeners: { "click": "handleClick($event)", "pointerenter": "handlePointerEnter($event)", "pointerleave": "handlePointerLeave($event)", "pointerdown": "handlePointerDown($event)", "pointerup": "handlePointerUp()", "pointercancel": "handlePointerUp()" }, properties: { "attr.aria-controls": "rootContext()?.contentId", "attr.aria-expanded": "triggerInteraction.ariaExpanded()", "attr.aria-haspopup": "\"dialog\"", "attr.data-state": "triggerInteraction.dataState()", "attr.data-popup-open": "triggerInteraction.dataPopupOpen()", "attr.data-pressed": "isPressed() ? \"\" : undefined", "attr.disabled": "triggerInteraction.disabled() ? \"\" : undefined", "id": "triggerId()" } }, hostDirectives: [{ directive: i1.RdxPopperAnchor }], ngImport: i0 }); }
851
933
  }
852
934
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxPopoverTrigger, decorators: [{
853
935
  type: Directive,
@@ -857,12 +939,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
857
939
  host: {
858
940
  type: 'button',
859
941
  '[attr.aria-controls]': 'rootContext()?.contentId',
860
- '[attr.aria-expanded]': 'isOpen()',
942
+ '[attr.aria-expanded]': 'triggerInteraction.ariaExpanded()',
861
943
  '[attr.aria-haspopup]': '"dialog"',
862
- '[attr.data-state]': 'isOpen() ? "open" : "closed"',
863
- '[attr.data-popup-open]': 'isOpen() ? "" : undefined',
944
+ '[attr.data-state]': 'triggerInteraction.dataState()',
945
+ '[attr.data-popup-open]': 'triggerInteraction.dataPopupOpen()',
864
946
  '[attr.data-pressed]': 'isPressed() ? "" : undefined',
865
- '[attr.disabled]': 'disabled() ? "" : undefined',
947
+ '[attr.disabled]': 'triggerInteraction.disabled() ? "" : undefined',
866
948
  '[id]': 'triggerId()',
867
949
  '(click)': 'handleClick($event)',
868
950
  '(pointerenter)': 'handlePointerEnter($event)',