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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/README.md +1 -1
  2. package/fesm2022/radix-ng-primitives-accordion.mjs +5 -3
  3. package/fesm2022/radix-ng-primitives-accordion.mjs.map +1 -1
  4. package/fesm2022/radix-ng-primitives-alert-dialog.mjs +3 -2
  5. package/fesm2022/radix-ng-primitives-alert-dialog.mjs.map +1 -1
  6. package/fesm2022/radix-ng-primitives-autocomplete.mjs +617 -659
  7. package/fesm2022/radix-ng-primitives-autocomplete.mjs.map +1 -1
  8. package/fesm2022/radix-ng-primitives-calendar.mjs +5 -3
  9. package/fesm2022/radix-ng-primitives-calendar.mjs.map +1 -1
  10. package/fesm2022/radix-ng-primitives-combobox.mjs +1305 -572
  11. package/fesm2022/radix-ng-primitives-combobox.mjs.map +1 -1
  12. package/fesm2022/radix-ng-primitives-config.mjs +13 -4
  13. package/fesm2022/radix-ng-primitives-config.mjs.map +1 -1
  14. package/fesm2022/radix-ng-primitives-context-menu.mjs +51 -10
  15. package/fesm2022/radix-ng-primitives-context-menu.mjs.map +1 -1
  16. package/fesm2022/radix-ng-primitives-core.mjs +1345 -64
  17. package/fesm2022/radix-ng-primitives-core.mjs.map +1 -1
  18. package/fesm2022/radix-ng-primitives-date-field.mjs +5 -3
  19. package/fesm2022/radix-ng-primitives-date-field.mjs.map +1 -1
  20. package/fesm2022/radix-ng-primitives-dialog.mjs +240 -112
  21. package/fesm2022/radix-ng-primitives-dialog.mjs.map +1 -1
  22. package/fesm2022/radix-ng-primitives-direction-provider.mjs +70 -0
  23. package/fesm2022/radix-ng-primitives-direction-provider.mjs.map +1 -0
  24. package/fesm2022/radix-ng-primitives-dismissable-layer.mjs +519 -184
  25. package/fesm2022/radix-ng-primitives-dismissable-layer.mjs.map +1 -1
  26. package/fesm2022/radix-ng-primitives-drawer.mjs +3 -3
  27. package/fesm2022/radix-ng-primitives-drawer.mjs.map +1 -1
  28. package/fesm2022/radix-ng-primitives-field.mjs +3 -2
  29. package/fesm2022/radix-ng-primitives-field.mjs.map +1 -1
  30. package/fesm2022/radix-ng-primitives-floating-focus-manager.mjs +517 -0
  31. package/fesm2022/radix-ng-primitives-floating-focus-manager.mjs.map +1 -0
  32. package/fesm2022/radix-ng-primitives-focus-scope.mjs +296 -70
  33. package/fesm2022/radix-ng-primitives-focus-scope.mjs.map +1 -1
  34. package/fesm2022/radix-ng-primitives-menu.mjs +861 -286
  35. package/fesm2022/radix-ng-primitives-menu.mjs.map +1 -1
  36. package/fesm2022/radix-ng-primitives-menubar.mjs +32 -4
  37. package/fesm2022/radix-ng-primitives-menubar.mjs.map +1 -1
  38. package/fesm2022/radix-ng-primitives-navigation-menu.mjs +144 -159
  39. package/fesm2022/radix-ng-primitives-navigation-menu.mjs.map +1 -1
  40. package/fesm2022/radix-ng-primitives-popover.mjs +220 -205
  41. package/fesm2022/radix-ng-primitives-popover.mjs.map +1 -1
  42. package/fesm2022/radix-ng-primitives-popper.mjs +94 -51
  43. package/fesm2022/radix-ng-primitives-popper.mjs.map +1 -1
  44. package/fesm2022/radix-ng-primitives-presence.mjs +1 -1
  45. package/fesm2022/radix-ng-primitives-presence.mjs.map +1 -1
  46. package/fesm2022/radix-ng-primitives-preview-card.mjs +141 -173
  47. package/fesm2022/radix-ng-primitives-preview-card.mjs.map +1 -1
  48. package/fesm2022/radix-ng-primitives-roving-focus.mjs +4 -2
  49. package/fesm2022/radix-ng-primitives-roving-focus.mjs.map +1 -1
  50. package/fesm2022/radix-ng-primitives-scroll-area.mjs +5 -4
  51. package/fesm2022/radix-ng-primitives-scroll-area.mjs.map +1 -1
  52. package/fesm2022/radix-ng-primitives-select.mjs +211 -156
  53. package/fesm2022/radix-ng-primitives-select.mjs.map +1 -1
  54. package/fesm2022/radix-ng-primitives-slider.mjs +5 -3
  55. package/fesm2022/radix-ng-primitives-slider.mjs.map +1 -1
  56. package/fesm2022/radix-ng-primitives-stepper.mjs +5 -3
  57. package/fesm2022/radix-ng-primitives-stepper.mjs.map +1 -1
  58. package/fesm2022/radix-ng-primitives-time-field.mjs +5 -3
  59. package/fesm2022/radix-ng-primitives-time-field.mjs.map +1 -1
  60. package/fesm2022/radix-ng-primitives-toast.mjs +15 -36
  61. package/fesm2022/radix-ng-primitives-toast.mjs.map +1 -1
  62. package/fesm2022/radix-ng-primitives-toggle-group.mjs +5 -3
  63. package/fesm2022/radix-ng-primitives-toggle-group.mjs.map +1 -1
  64. package/fesm2022/radix-ng-primitives-toolbar.mjs +5 -3
  65. package/fesm2022/radix-ng-primitives-toolbar.mjs.map +1 -1
  66. package/fesm2022/radix-ng-primitives-tooltip.mjs +73 -110
  67. package/fesm2022/radix-ng-primitives-tooltip.mjs.map +1 -1
  68. package/package.json +10 -1
  69. package/types/radix-ng-primitives-accordion.d.ts +4 -3
  70. package/types/radix-ng-primitives-autocomplete.d.ts +217 -152
  71. package/types/radix-ng-primitives-calendar.d.ts +5 -3
  72. package/types/radix-ng-primitives-combobox.d.ts +672 -283
  73. package/types/radix-ng-primitives-config.d.ts +1 -1
  74. package/types/radix-ng-primitives-context-menu.d.ts +15 -5
  75. package/types/radix-ng-primitives-core.d.ts +762 -14
  76. package/types/radix-ng-primitives-date-field.d.ts +3 -2
  77. package/types/radix-ng-primitives-dialog.d.ts +77 -32
  78. package/types/radix-ng-primitives-direction-provider.d.ts +41 -0
  79. package/types/radix-ng-primitives-dismissable-layer.d.ts +147 -99
  80. package/types/radix-ng-primitives-field.d.ts +1 -0
  81. package/types/radix-ng-primitives-floating-focus-manager.d.ts +175 -0
  82. package/types/radix-ng-primitives-focus-scope.d.ts +132 -1
  83. package/types/radix-ng-primitives-menu.d.ts +186 -103
  84. package/types/radix-ng-primitives-navigation-menu.d.ts +37 -75
  85. package/types/radix-ng-primitives-popover.d.ts +59 -92
  86. package/types/radix-ng-primitives-popper.d.ts +39 -9
  87. package/types/radix-ng-primitives-preview-card.d.ts +39 -72
  88. package/types/radix-ng-primitives-roving-focus.d.ts +7 -6
  89. package/types/radix-ng-primitives-scroll-area.d.ts +2 -2
  90. package/types/radix-ng-primitives-select.d.ts +145 -108
  91. package/types/radix-ng-primitives-slider.d.ts +5 -4
  92. package/types/radix-ng-primitives-stepper.d.ts +4 -3
  93. package/types/radix-ng-primitives-time-field.d.ts +3 -2
  94. package/types/radix-ng-primitives-toast.d.ts +7 -7
  95. package/types/radix-ng-primitives-toggle-group.d.ts +5 -4
  96. package/types/radix-ng-primitives-toolbar.d.ts +3 -2
  97. package/types/radix-ng-primitives-tooltip.d.ts +24 -67
@@ -1,13 +1,14 @@
1
1
  import * as i0 from '@angular/core';
2
- import { booleanAttribute, inject, DestroyRef, signal, model, input, output, computed, effect, untracked, Directive, ElementRef, isDevMode, numberAttribute, afterNextRender, NgModule } from '@angular/core';
2
+ import { booleanAttribute, inject, DestroyRef, ElementRef, signal, model, input, output, computed, effect, untracked, Directive, isDevMode, numberAttribute, afterNextRender, 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 { createContext, useTransitionStatus, injectId, useScrollLock, useGraceArea } from '@radix-ng/primitives/core';
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, injectId, createCancelableChangeEventDetails, provideFloatingTree, provideFloatingRootContext, RDX_FLOATING_ROOT_CONTEXT, RDX_FLOATING_REGISTRATION, useAnchoredScrollLock, RdxFloatingNodeRegistration, rdxDevError, useGraceArea } from '@radix-ng/primitives/core';
6
7
  import { outputFromObservable, outputToObservable } from '@angular/core/rxjs-interop';
7
- import * as i2 from '@radix-ng/primitives/dismissable-layer';
8
- import { RdxDismissableLayer, provideRdxDismissableLayerConfig } from '@radix-ng/primitives/dismissable-layer';
9
- import * as i3 from '@radix-ng/primitives/focus-scope';
10
- import { RdxFocusScope, provideRdxFocusScopeConfig } from '@radix-ng/primitives/focus-scope';
8
+ 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
+ import { RdxFocusScope } from '@radix-ng/primitives/focus-scope';
11
12
  import * as i1$1 from '@radix-ng/primitives/portal';
12
13
  import { RdxPortalPresence } from '@radix-ng/primitives/portal';
13
14
  import { provideRdxPresenceContext } from '@radix-ng/primitives/presence';
@@ -22,6 +23,11 @@ class RdxPopoverRoot {
22
23
  constructor() {
23
24
  this.popper = inject(RdxPopper);
24
25
  this.destroyRef = inject(DestroyRef);
26
+ /** Shared per-popup floating context (ADR 0015 §1): `open`, trigger registry, reference / floating els. */
27
+ this.floatingContext = createFloatingRootContext({
28
+ ownerDocument: inject(ElementRef).nativeElement.ownerDocument,
29
+ open: () => this.open()
30
+ });
25
31
  this.hasAppliedDefaultOpen = false;
26
32
  this.hasAppliedDefaultTriggerId = false;
27
33
  this.hoverDelay = 300;
@@ -65,45 +71,60 @@ class RdxPopoverRoot {
65
71
  this.triggers = signal([], ...(ngDevMode ? [{ debugName: "triggers" }] : /* istanbul ignore next */ []));
66
72
  this.payload = signal(undefined, ...(ngDevMode ? [{ debugName: "payload" }] : /* istanbul ignore next */ []));
67
73
  this.isPointerDownOnTrigger = signal(false, ...(ngDevMode ? [{ debugName: "isPointerDownOnTrigger" }] : /* istanbul ignore next */ []));
74
+ /** Whether the current open was initiated by touch (ADR 0016 §3 — gates the anchored scroll lock). */
75
+ this.openedByTouch = signal(false, ...(ngDevMode ? [{ debugName: "openedByTouch" }] : /* istanbul ignore next */ []));
68
76
  this.popupCloseCount = signal(0, ...(ngDevMode ? [{ debugName: "popupCloseCount" }] : /* istanbul ignore next */ []));
77
+ this.preventUnmountOnClose = signal(false, ...(ngDevMode ? [{ debugName: "preventUnmountOnClose" }] : /* istanbul ignore next */ []));
69
78
  this.onOpenChange = output();
70
79
  this.onOpenChangeComplete = output();
71
80
  this.registeredTriggers = new Map();
72
81
  this.viewportTriggerChange = new Set();
73
- this.state = computed(() => (this.open() ? 'open' : 'closed'), ...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
82
+ this.state = computed(() => (this.open() ? 'open' : 'closed'), { debugName: 'RdxPopoverRoot.state' });
83
+ this.present = computed(() => this.open() || this.preventUnmountOnClose(), {
84
+ debugName: 'RdxPopoverRoot.present'
85
+ });
74
86
  let previousOpen = this.open();
87
+ // Keep the floating context's reference element in sync with the active trigger.
88
+ effect(() => this.floatingContext.setReferenceElement(this.trigger() ?? null));
89
+ effect(() => {
90
+ if (this.open() && this.preventUnmountOnClose()) {
91
+ this.preventUnmountOnClose.set(false);
92
+ }
93
+ }, { debugName: 'RdxPopoverRoot.clearPreventUnmountOnOpen' });
75
94
  effect(() => {
76
95
  const defaultOpen = this.defaultOpen();
77
96
  if (!this.hasAppliedDefaultOpen && defaultOpen) {
78
97
  this.hasAppliedDefaultOpen = true;
79
98
  this.open.set(defaultOpen);
80
99
  }
81
- });
100
+ }, { debugName: 'RdxPopoverRoot.applyDefaultOpen' });
82
101
  effect(() => {
83
102
  const defaultTriggerId = this.defaultTriggerId();
84
103
  if (!this.hasAppliedDefaultTriggerId && defaultTriggerId !== null) {
85
104
  this.hasAppliedDefaultTriggerId = true;
86
105
  this.triggerId.set(defaultTriggerId);
87
106
  }
88
- });
107
+ }, { debugName: 'RdxPopoverRoot.applyDefaultTriggerId' });
89
108
  effect(() => {
90
109
  const triggerId = this.triggerId();
91
110
  untracked(() => this.syncTriggerId(triggerId));
92
- });
111
+ }, { debugName: 'RdxPopoverRoot.syncTriggerId' });
93
112
  effect(() => {
94
113
  const open = this.open();
95
114
  if (open !== previousOpen) {
96
115
  previousOpen = open;
97
116
  untracked(() => this.transition.start(open));
98
117
  }
99
- });
118
+ }, { debugName: 'RdxPopoverRoot.startTransition' });
100
119
  effect((onCleanup) => {
101
120
  const handle = this.handle();
102
121
  if (handle) {
103
122
  onCleanup(untracked(() => handle.registerRoot(contextFor(this))));
104
123
  }
124
+ }, { debugName: 'RdxPopoverRoot.registerHandle' });
125
+ effect(() => this.popper.anchorOverride.set(this.trigger()), {
126
+ debugName: 'RdxPopoverRoot.syncAnchorOverride'
105
127
  });
106
- effect(() => this.popper.anchorOverride.set(this.trigger()));
107
128
  this.destroyRef.onDestroy(() => {
108
129
  this.clearHoverTimers();
109
130
  if (this.instantFrame !== undefined) {
@@ -113,10 +134,25 @@ class RdxPopoverRoot {
113
134
  }
114
135
  show(trigger = this.trigger(), payload, triggerId, reason = 'none', event = new Event('popover.open-change'), fromHover = false) {
115
136
  this.clearHoverTimers();
116
- this.isHoverActive.set(fromHover);
117
- this.openChangeReason.set(reason);
118
137
  const previousTrigger = this.trigger();
119
138
  const changedTriggerWhileOpen = this.open() && previousTrigger !== trigger;
139
+ const changed = !this.open() || previousTrigger !== trigger;
140
+ if (!changed) {
141
+ this.isHoverActive.set(fromHover);
142
+ this.openChangeReason.set(reason);
143
+ if (triggerId !== undefined) {
144
+ this.triggerId.set(triggerId);
145
+ }
146
+ this.payload.set(payload);
147
+ return;
148
+ }
149
+ const change = this.createOpenChangeEvent(true, reason, event, trigger, triggerId ?? this.triggerId());
150
+ this.onOpenChange.emit(change.payload);
151
+ if (change.eventDetails.isCanceled()) {
152
+ return;
153
+ }
154
+ this.isHoverActive.set(fromHover);
155
+ this.openChangeReason.set(reason);
120
156
  this.instant.set(changedTriggerWhileOpen || reason === 'trigger-focus');
121
157
  if (changedTriggerWhileOpen) {
122
158
  this.scheduleInstantReset();
@@ -131,22 +167,27 @@ class RdxPopoverRoot {
131
167
  this.triggerId.set(triggerId);
132
168
  }
133
169
  this.payload.set(payload);
134
- const changed = !this.open() || previousTrigger !== trigger;
170
+ this.preventUnmountOnClose.set(false);
135
171
  this.open.set(true);
136
- if (changed) {
137
- this.emitOpenChange(true, reason, event);
138
- }
172
+ this.floatingContext.events.emit('openchange', { open: true, reason, event: change.eventDetails.event });
139
173
  }
140
174
  close(reason = 'none', event = new Event('popover.open-change')) {
141
175
  this.clearHoverTimers();
142
- this.isHoverActive.set(false);
143
176
  if (!this.open()) {
144
177
  return;
145
178
  }
179
+ const change = this.createOpenChangeEvent(false, reason, event, this.trigger(), this.triggerId());
180
+ this.onOpenChange.emit(change.payload);
181
+ if (change.eventDetails.isCanceled()) {
182
+ return;
183
+ }
184
+ this.isHoverActive.set(false);
185
+ this.openedByTouch.set(false);
146
186
  this.instant.set(reason !== 'none' && reason !== 'trigger-hover');
147
187
  this.openChangeReason.set(reason);
188
+ this.preventUnmountOnClose.set(change.shouldPreventUnmountOnClose());
148
189
  this.open.set(false);
149
- this.emitOpenChange(false, reason, event);
190
+ this.floatingContext.events.emit('openchange', { open: false, reason, event: change.eventDetails.event });
150
191
  }
151
192
  toggle(triggerId, trigger, payload, event) {
152
193
  this.clearHoverTimers();
@@ -186,6 +227,8 @@ class RdxPopoverRoot {
186
227
  registerTrigger(id, trigger, payload) {
187
228
  this.registeredTriggers.set(id, { element: trigger, payload });
188
229
  this.triggers.update((triggers) => (triggers.includes(trigger) ? triggers : [...triggers, trigger]));
230
+ // Bridge into the floating context's trigger registry (new dismissal/focus inside-element checks).
231
+ this.floatingContext.triggers.add(trigger);
189
232
  if (this.triggerId() === id) {
190
233
  this.trigger.set(trigger);
191
234
  this.payload.set(payload());
@@ -199,6 +242,7 @@ class RdxPopoverRoot {
199
242
  this.registeredTriggers.delete(id);
200
243
  }
201
244
  this.triggers.update((triggers) => triggers.filter((candidate) => candidate !== trigger));
245
+ this.floatingContext.triggers.delete(trigger);
202
246
  if (this.destroyRef.destroyed) {
203
247
  return;
204
248
  }
@@ -244,14 +288,20 @@ class RdxPopoverRoot {
244
288
  this.trigger.set(trigger.element);
245
289
  this.payload.set(trigger.payload());
246
290
  }
247
- emitOpenChange(open, reason, event) {
248
- this.onOpenChange.emit({
249
- open,
250
- triggerId: this.triggerId(),
251
- trigger: this.trigger(),
252
- reason,
253
- event
254
- });
291
+ createOpenChangeEvent(open, reason, event, trigger, triggerId) {
292
+ const change = createCancelableChangeEventDetails(reason, event, trigger);
293
+ return {
294
+ eventDetails: change.eventDetails,
295
+ shouldPreventUnmountOnClose: change.shouldPreventUnmountOnClose,
296
+ payload: {
297
+ open,
298
+ triggerId,
299
+ trigger: change.eventDetails.trigger,
300
+ reason: change.eventDetails.reason,
301
+ event: change.eventDetails.event,
302
+ eventDetails: change.eventDetails
303
+ }
304
+ };
255
305
  }
256
306
  clearHoverTimers() {
257
307
  this.clearOpenTimer();
@@ -281,14 +331,26 @@ class RdxPopoverRoot {
281
331
  });
282
332
  }
283
333
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxPopoverRoot, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
284
- 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: [provideRdxPopoverRootContext(context)], exportAs: ["rdxPopoverRoot"], hostDirectives: [{ directive: i1.RdxPopper }], ngImport: i0 }); }
334
+ 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
+ provideRdxPopoverRootContext(context),
336
+ // New floating foundation (ADR 0015/0017 migration). Inherit-or-create tree (nested sharing);
337
+ // the per-popup root context bridges open / triggers / reference.
338
+ provideFloatingTree(),
339
+ provideFloatingRootContext(() => inject(RdxPopoverRoot).floatingContext)
340
+ ], exportAs: ["rdxPopoverRoot"], hostDirectives: [{ directive: i1.RdxPopper }], ngImport: i0 }); }
285
341
  }
286
342
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxPopoverRoot, decorators: [{
287
343
  type: Directive,
288
344
  args: [{
289
345
  selector: '[rdxPopoverRoot]',
290
346
  exportAs: 'rdxPopoverRoot',
291
- providers: [provideRdxPopoverRootContext(context)],
347
+ providers: [
348
+ provideRdxPopoverRootContext(context),
349
+ // New floating foundation (ADR 0015/0017 migration). Inherit-or-create tree (nested sharing);
350
+ // the per-popup root context bridges open / triggers / reference.
351
+ provideFloatingTree(),
352
+ provideFloatingRootContext(() => inject(RdxPopoverRoot).floatingContext)
353
+ ],
292
354
  hostDirectives: [RdxPopper]
293
355
  }]
294
356
  }], 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 }] }], triggerId: [{ type: i0.Input, args: [{ isSignal: true, alias: "triggerId", required: false }] }, { type: i0.Output, args: ["triggerIdChange"] }], defaultTriggerId: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultTriggerId", required: false }] }], modal: [{ type: i0.Input, args: [{ isSignal: true, alias: "modal", required: false }] }], handle: [{ type: i0.Input, args: [{ isSignal: true, alias: "handle", required: false }] }], onOpenChange: [{ type: i0.Output, args: ["onOpenChange"] }], onOpenChangeComplete: [{ type: i0.Output, args: ["onOpenChangeComplete"] }] } });
@@ -297,6 +359,7 @@ function contextFor(root) {
297
359
  contentId: root.contentId,
298
360
  descriptionId: root.descriptionId.asReadonly(),
299
361
  isOpen: root.open,
362
+ present: root.present,
300
363
  modal: root.modal,
301
364
  titleId: root.titleId.asReadonly(),
302
365
  trigger: root.trigger.asReadonly(),
@@ -307,6 +370,7 @@ function contextFor(root) {
307
370
  instant: root.instant.asReadonly(),
308
371
  openChangeReason: root.openChangeReason.asReadonly(),
309
372
  isPointerDownOnTrigger: root.isPointerDownOnTrigger.asReadonly(),
373
+ openedByTouch: root.openedByTouch.asReadonly(),
310
374
  close: (reason, event) => root.close(reason, event),
311
375
  cancelHoverClose: () => root.cancelHoverClose(),
312
376
  cancelHoverOpen: () => root.cancelHoverOpen(),
@@ -317,6 +381,7 @@ function contextFor(root) {
317
381
  setDescriptionId: (id) => root.descriptionId.set(id),
318
382
  setTitleId: (id) => root.titleId.set(id),
319
383
  setPointerDownOnTrigger: (pointerDown) => root.isPointerDownOnTrigger.set(pointerDown),
384
+ setOpenedByTouch: (value) => root.openedByTouch.set(value),
320
385
  setHoverDelays: (delay, closeDelay) => root.setHoverDelays(delay, closeDelay),
321
386
  registerPopupClose: () => {
322
387
  root.popupCloseCount.update((count) => count + 1);
@@ -364,6 +429,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
364
429
  class RdxPopoverBackdrop {
365
430
  constructor() {
366
431
  this.rootContext = injectRdxPopoverRootContext();
432
+ // Register the backdrop as owned DOM footprint for primitive-specific checks. The focus manager's
433
+ // marker keep-set stays narrow and does not keep sibling backdrop roots.
434
+ const floatingContext = inject(RDX_FLOATING_ROOT_CONTEXT, { optional: true });
435
+ if (floatingContext) {
436
+ const host = inject(ElementRef).nativeElement;
437
+ floatingContext.addFloatingElement(host);
438
+ inject(DestroyRef).onDestroy(() => floatingContext.removeFloatingElement(host));
439
+ }
367
440
  }
368
441
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxPopoverBackdrop, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
369
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 }); }
@@ -381,109 +454,126 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
381
454
  '[attr.data-state]': 'rootContext.isOpen() ? "open" : "closed"'
382
455
  }
383
456
  }]
384
- }] });
457
+ }], ctorParameters: () => [] });
385
458
 
386
459
  /**
387
460
  * A container for the popover contents.
461
+ *
462
+ * **ADR 0015/0017 Phase-4 migration** onto the new floating dismissal + focus engine (same pattern as
463
+ * Dialog; browser-verified via `popover.behavior` Playwright). Popover-specific:
464
+ * - **Hover-open disables the manager** (`enabled = isOpen && !isHoverActive`) — Base UI parity
465
+ * (`disabled={!mounted || openReason === triggerHover}`); a hover-opened popover does not trap / mark.
466
+ * (The legacy only suppressed auto-focus while still trapping — that Radix divergence is dropped.)
467
+ * - Trap = `'trap-focus' || (modal === true && hasPopupClose())`; scroll lock + real outside `inert`
468
+ * isolation key off the full modal (`modal === true`).
469
+ * - No `disablePointerDismissal` — outside-press + focus-out always close.
470
+ *
471
+ * 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).
388
474
  */
389
475
  class RdxPopoverPopup {
390
476
  constructor() {
391
477
  this.rootContext = injectRdxPopoverRootContext();
392
- this.dismissableLayer = inject(RdxDismissableLayer);
478
+ this.host = inject(ElementRef).nativeElement;
479
+ this.floatingContext = inject(RDX_FLOATING_ROOT_CONTEXT);
480
+ this.registration = inject(RDX_FLOATING_REGISTRATION, { optional: true });
481
+ this.focusManager = inject(RdxFloatingFocusManager);
393
482
  this.focusScope = inject(RdxFocusScope);
394
483
  this.wrapper = inject(RdxPopperContentWrapper, { optional: true });
395
484
  this.align = computed(() => this.wrapper?.placedAlign(), ...(ngDevMode ? [{ debugName: "align" }] : /* istanbul ignore next */ []));
396
485
  this.side = computed(() => this.wrapper?.placedSide(), ...(ngDevMode ? [{ debugName: "side" }] : /* istanbul ignore next */ []));
397
- this.dismissDetails = {
398
- reason: 'none',
399
- event: new Event('popover.dismiss')
400
- };
401
- /**
402
- * Event handler called when the escape key is down. Can be prevented.
403
- */
404
- this.escapeKeyDown = outputFromObservable(outputToObservable(this.dismissableLayer.escapeKeyDown));
405
- /**
406
- * Event handler called when a pointerdown event happens outside of the popup. Can be prevented.
407
- */
408
- this.pointerDownOutside = outputFromObservable(outputToObservable(this.dismissableLayer.pointerDownOutside));
409
- /**
410
- * Event handler called when focus moves outside of the popup. Can be prevented.
411
- */
412
- this.focusOutside = outputFromObservable(outputToObservable(this.dismissableLayer.focusOutside));
413
- /**
414
- * Event handler called when an interaction happens outside of the popup. Can be prevented.
415
- */
416
- this.interactOutside = outputFromObservable(outputToObservable(this.dismissableLayer.interactOutside));
417
- /**
418
- * Event handler called before focus moves into the popup. Can be prevented.
419
- */
486
+ /** Event handler called when the escape key is down. Can be prevented. */
487
+ this.escapeKeyDown = output();
488
+ /** Event handler called when a pointerdown event happens outside of the popup. Can be prevented. */
489
+ this.pointerDownOutside = output();
490
+ /** Event handler called when focus moves outside of the popup. Can be prevented. */
491
+ this.focusOutside = output();
492
+ /** Event handler called when an interaction (pointer / focus) happens outside of the popup. */
493
+ this.interactOutside = output();
494
+ /** Event handler called before focus moves into the popup. Can be prevented. */
420
495
  this.openAutoFocus = outputFromObservable(outputToObservable(this.focusScope.mountAutoFocus));
421
- /**
422
- * Event handler called before focus returns after the popup is removed. Can be prevented.
423
- */
496
+ /** Event handler called before focus returns after the popup is removed. Can be prevented. */
424
497
  this.closeAutoFocus = outputFromObservable(outputToObservable(this.focusScope.unmountAutoFocus));
425
- useScrollLock(computed(() => this.rootContext.modal() === true));
426
- const unregisterTransitionElement = this.rootContext.registerTransitionElement(inject(ElementRef).nativeElement);
498
+ this.floatingContext.setFloatingElement(this.host);
499
+ // Background pointer/AT isolation for a full modal is the focus manager's `inert` pass (finding
500
+ // #4), not a global body lock; only the page scroll lock stays here. Activation policy (ADR 0016
501
+ // §2): lock only while a `modal === true` popover is OPEN and was **not** hover-opened, gated on
502
+ // `open` (not mounted) so it releases at close-start. For a **touch** open the anchored helper only
503
+ // locks when the popup is effectively viewport-width (a small popover stays swipe-to-dismissable on
504
+ // mobile, §3).
505
+ useAnchoredScrollLock(computed(() => this.rootContext.isOpen() && this.rootContext.modal() === true && !this.rootContext.isHoverActive()), {
506
+ touchOpen: () => this.rootContext.openedByTouch(),
507
+ element: () => this.host
508
+ });
509
+ const unregisterTransitionElement = this.rootContext.registerTransitionElement(this.host);
427
510
  inject(DestroyRef).onDestroy(unregisterTransitionElement);
428
- this.dismissableLayer.pointerDownOutside.subscribe((event) => {
429
- this.dismissDetails = { reason: 'outside-press', event };
430
- if (this.rootContext.triggers().some((trigger) => trigger.contains(event.target))) {
511
+ // A hover-opened popover must not steal focus — suppress the composed focus scope's auto-focus.
512
+ this.focusScope.mountAutoFocus.subscribe((event) => {
513
+ if (this.rootContext.isHoverActive()) {
431
514
  event.preventDefault();
432
515
  }
433
516
  });
434
- this.dismissableLayer.focusOutside.subscribe((event) => {
435
- this.dismissDetails = { reason: 'focus-out', event };
436
- if (this.rootContext.isPointerDownOnTrigger()) {
437
- event.preventDefault();
517
+ // Dismissal: Escape + outside-press always close (no pointer-dismissal opt-out). Focus-out is
518
+ // owned by the focus manager (below), so the capability's own focus-out is disabled.
519
+ new RdxDismiss(this.floatingContext, () => this.registration?.node() ?? null, {
520
+ escapeKey: () => true,
521
+ outsidePress: () => true,
522
+ focusOutside: () => false,
523
+ onEscapeKeyDown: (event) => this.escapeKeyDown.emit(event),
524
+ onPointerDownOutside: (event) => {
525
+ this.pointerDownOutside.emit(event);
526
+ this.interactOutside.emit(event);
527
+ },
528
+ onDismiss: (reason, event) => {
529
+ this.rootContext.close(reason === 'escape-key' ? 'escape-key' : 'outside-press', event);
438
530
  }
439
531
  });
440
- this.dismissableLayer.escapeKeyDown.subscribe((event) => {
441
- this.dismissDetails = { reason: 'escape-key', event };
442
- });
443
- this.focusScope.mountAutoFocus.subscribe((event) => {
444
- if (this.rootContext.isHoverActive()) {
445
- event.preventDefault();
532
+ // Focus-out close (ADR 0017 §3) — re-expose as `focusOutside` (preventable) and close unless vetoed.
533
+ this.focusManager.focusOut.subscribe((event) => {
534
+ this.focusOutside.emit(event);
535
+ this.interactOutside.emit(event);
536
+ if (!event.defaultPrevented) {
537
+ this.rootContext.close('focus-out', event);
446
538
  }
447
539
  });
448
- this.dismissableLayer.dismiss.subscribe(() => {
449
- this.rootContext.close(this.dismissDetails.reason, this.dismissDetails.event);
450
- this.dismissDetails = { reason: 'none', event: new Event('popover.dismiss') };
451
- });
452
540
  }
453
541
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxPopoverPopup, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
454
542
  static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxPopoverPopup, isStandalone: true, selector: "[rdxPopoverPopup]", outputs: { escapeKeyDown: "escapeKeyDown", pointerDownOutside: "pointerDownOutside", focusOutside: "focusOutside", interactOutside: "interactOutside", openAutoFocus: "openAutoFocus", closeAutoFocus: "closeAutoFocus" }, host: { attributes: { "role": "dialog" }, listeners: { "pointerenter": "rootContext.cancelHoverClose()" }, properties: { "attr.aria-describedby": "rootContext.descriptionId()", "attr.aria-labelledby": "rootContext.titleId()", "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\"", "attr.data-align": "align()", "attr.data-side": "side()", "id": "rootContext.contentId" } }, providers: [
455
- provideRdxDismissableLayerConfig(() => {
456
- const rootContext = injectRdxPopoverRootContext();
457
- return {
458
- disableOutsidePointerEvents: computed(() => rootContext.modal() === true)
459
- };
460
- }),
461
- provideRdxFocusScopeConfig(() => {
543
+ provideFloatingFocusManagerConfig(() => {
462
544
  const rootContext = injectRdxPopoverRootContext();
463
545
  return {
464
- trapped: computed(() => rootContext.modal() === 'trap-focus' ||
465
- (rootContext.modal() === true && rootContext.hasPopupClose()))
546
+ modal: () => rootContext.modal() === 'trap-focus' ||
547
+ (rootContext.modal() === true && rootContext.hasPopupClose()),
548
+ // Full modal blocks outside pointer interaction; `trap-focus` only traps focus.
549
+ inert: () => rootContext.modal() === true && rootContext.hasPopupClose(),
550
+ // Active for the whole MOUNTED lifetime (Base UI `disabled={!mounted}`, not `open`) for
551
+ // trap/return-focus — including an explicit `preventUnmountOnClose()` cycle after the
552
+ // exit transition. Marker + isolation are additionally gated on `open` inside the
553
+ // manager. Still suppressed while hover-opened.
554
+ enabled: () => rootContext.present() && !rootContext.isHoverActive()
466
555
  };
467
556
  })
468
- ], hostDirectives: [{ directive: i1.RdxPopperContent }, { directive: i2.RdxDismissableLayer }, { directive: i3.RdxFocusScope }], ngImport: i0 }); }
557
+ ], hostDirectives: [{ directive: i1.RdxPopperContent }, { directive: i2.RdxFloatingNodeRegistration }, { directive: i3.RdxFloatingFocusManager }], ngImport: i0 }); }
469
558
  }
470
559
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxPopoverPopup, decorators: [{
471
560
  type: Directive,
472
561
  args: [{
473
562
  selector: '[rdxPopoverPopup]',
474
- hostDirectives: [RdxPopperContent, RdxDismissableLayer, RdxFocusScope],
563
+ hostDirectives: [RdxPopperContent, RdxFloatingNodeRegistration, RdxFloatingFocusManager],
475
564
  providers: [
476
- provideRdxDismissableLayerConfig(() => {
565
+ provideFloatingFocusManagerConfig(() => {
477
566
  const rootContext = injectRdxPopoverRootContext();
478
567
  return {
479
- disableOutsidePointerEvents: computed(() => rootContext.modal() === true)
480
- };
481
- }),
482
- provideRdxFocusScopeConfig(() => {
483
- const rootContext = injectRdxPopoverRootContext();
484
- return {
485
- trapped: computed(() => rootContext.modal() === 'trap-focus' ||
486
- (rootContext.modal() === true && rootContext.hasPopupClose()))
568
+ modal: () => rootContext.modal() === 'trap-focus' ||
569
+ (rootContext.modal() === true && rootContext.hasPopupClose()),
570
+ // Full modal blocks outside pointer interaction; `trap-focus` only traps focus.
571
+ inert: () => rootContext.modal() === true && rootContext.hasPopupClose(),
572
+ // Active for the whole MOUNTED lifetime (Base UI `disabled={!mounted}`, not `open`) for
573
+ // trap/return-focus — including an explicit `preventUnmountOnClose()` cycle after the
574
+ // exit transition. Marker + isolation are additionally gated on `open` inside the
575
+ // manager. Still suppressed while hover-opened.
576
+ enabled: () => rootContext.present() && !rootContext.isHoverActive()
487
577
  };
488
578
  })
489
579
  ],
@@ -563,7 +653,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
563
653
  */
564
654
  class RdxPopoverPortal {
565
655
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxPopoverPortal, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
566
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxPopoverPortal, isStandalone: true, selector: "ng-template[rdxPopoverPortal]", providers: [provideRdxPresenceContext(() => ({ present: injectRdxPopoverRootContext().isOpen }))], exportAs: ["rdxPopoverPortal"], hostDirectives: [{ directive: i1$1.RdxPortalPresence, inputs: ["container", "container"] }], ngImport: i0 }); }
656
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxPopoverPortal, isStandalone: true, selector: "ng-template[rdxPopoverPortal]", providers: [provideRdxPresenceContext(() => ({ present: injectRdxPopoverRootContext().present }))], exportAs: ["rdxPopoverPortal"], hostDirectives: [{ directive: i1$1.RdxPortalPresence, inputs: ["container", "container"] }], ngImport: i0 }); }
567
657
  }
568
658
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxPopoverPortal, decorators: [{
569
659
  type: Directive,
@@ -571,7 +661,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
571
661
  selector: 'ng-template[rdxPopoverPortal]',
572
662
  exportAs: 'rdxPopoverPortal',
573
663
  hostDirectives: [{ directive: RdxPortalPresence, inputs: ['container'] }],
574
- providers: [provideRdxPresenceContext(() => ({ present: injectRdxPopoverRootContext().isOpen }))]
664
+ providers: [provideRdxPresenceContext(() => ({ present: injectRdxPopoverRootContext().present }))]
575
665
  }]
576
666
  }] });
577
667
  /**
@@ -582,9 +672,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
582
672
  class RdxPopoverPortalMisuseGuard {
583
673
  constructor() {
584
674
  if (isDevMode()) {
585
- throw new Error('[rdxPopoverPortal] is now a structural directive. ' +
675
+ rdxDevError('popover/portal-on-element', '`rdxPopoverPortal` is now a structural directive. ' +
586
676
  'Use `*rdxPopoverPortal` on the positioner element or `<ng-template rdxPopoverPortal>`. ' +
587
- 'rdxPopoverPortalPresence has been removed. See https://radix-ng.com/components/popover.md');
677
+ 'rdxPopoverPortalPresence has been removed.', 'components/popover');
588
678
  }
589
679
  }
590
680
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxPopoverPortalMisuseGuard, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
@@ -599,132 +689,51 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
599
689
 
600
690
  /**
601
691
  * Positions the popover against its trigger.
692
+ *
693
+ * A "thin" positioner (ADR 0012): it inherits the popper positioning surface (inputs, `placed`
694
+ * output, unified vars + placement attrs) from {@link RdxPopperContentWrapper} and adds popover's own
695
+ * concerns — Base UI-aligned defaults via the config provider, the open/closed/instant state
696
+ * attributes, the deprecated `--radix-popover-*` aliases, and the grace-area hover bridge.
602
697
  */
603
- class RdxPopoverPositioner {
698
+ class RdxPopoverPositioner extends RdxPopperContentWrapper {
604
699
  constructor() {
700
+ super();
605
701
  this.rootContext = injectRdxPopoverRootContext();
606
- this.wrapper = inject(RdxPopperContentWrapper);
607
- this.elementRef = inject(ElementRef);
702
+ this.legacyVars = legacyPopperVars('popover');
703
+ this.containerRef = inject(ElementRef);
608
704
  this.triggerEl = signal(null, ...(ngDevMode ? [{ debugName: "triggerEl" }] : /* istanbul ignore next */ []));
609
- this.containerEl = signal(this.elementRef.nativeElement, ...(ngDevMode ? [{ debugName: "containerEl" }] : /* istanbul ignore next */ []));
705
+ this.containerEl = signal(this.containerRef.nativeElement, ...(ngDevMode ? [{ debugName: "containerEl" }] : /* istanbul ignore next */ []));
610
706
  this.graceArea = useGraceArea(this.triggerEl, this.containerEl);
611
- /**
612
- * An element to position the popup against. Defaults to the trigger.
613
- */
614
- this.anchor = input(...(ngDevMode ? [undefined, { debugName: "anchor" }] : /* istanbul ignore next */ []));
615
- /**
616
- * The preferred side of the trigger to render against when open.
617
- */
618
- this.side = input('bottom', ...(ngDevMode ? [{ debugName: "side" }] : /* istanbul ignore next */ []));
619
- /**
620
- * Distance between the trigger and the popup in pixels.
621
- */
622
- this.sideOffset = input(0, { ...(ngDevMode ? { debugName: "sideOffset" } : /* istanbul ignore next */ {}), transform: numberAttribute });
623
- /**
624
- * How to align the popup relative to the specified side.
625
- */
626
- this.align = input('center', ...(ngDevMode ? [{ debugName: "align" }] : /* istanbul ignore next */ []));
627
- /**
628
- * An offset in pixels from the `start` or `end` alignment options.
629
- */
630
- this.alignOffset = input(0, { ...(ngDevMode ? { debugName: "alignOffset" } : /* istanbul ignore next */ {}), transform: numberAttribute });
631
- /**
632
- * Minimum distance to maintain between the arrow and the edges of the popup.
633
- */
634
- this.arrowPadding = input(5, { ...(ngDevMode ? { debugName: "arrowPadding" } : /* istanbul ignore next */ {}), transform: numberAttribute });
635
- /**
636
- * Whether to override side and alignment preferences to prevent collisions.
637
- */
638
- this.avoidCollisions = input(true, { ...(ngDevMode ? { debugName: "avoidCollisions" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
639
- /**
640
- * The element used as the collision boundary.
641
- */
642
- this.collisionBoundary = input(...(ngDevMode ? [undefined, { debugName: "collisionBoundary" }] : /* istanbul ignore next */ []));
643
- /**
644
- * Distance in pixels from the boundary edges where collision detection should occur.
645
- */
646
- this.collisionPadding = input(5, ...(ngDevMode ? [{ debugName: "collisionPadding" }] : /* istanbul ignore next */ []));
647
- /**
648
- * The sticky behavior on the alignment axis.
649
- */
650
- this.sticky = input('partial', ...(ngDevMode ? [{ debugName: "sticky" }] : /* istanbul ignore next */ []));
651
- /**
652
- * Whether to hide the popup when the trigger becomes fully occluded.
653
- */
654
- this.hideWhenDetached = input(false, { ...(ngDevMode ? { debugName: "hideWhenDetached" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
655
- /**
656
- * The CSS position strategy used by Floating UI.
657
- */
658
- this.positionStrategy = input('fixed', ...(ngDevMode ? [{ debugName: "positionStrategy" }] : /* istanbul ignore next */ []));
659
- /**
660
- * Whether to update position on every animation frame.
661
- */
662
- this.updatePositionStrategy = input('always', ...(ngDevMode ? [{ debugName: "updatePositionStrategy" }] : /* istanbul ignore next */ []));
663
- /**
664
- * Emits when the popup has been placed.
665
- */
666
- this.placed = outputFromObservable(outputToObservable(inject(RdxPopperContentWrapper).placed));
667
707
  effect(() => this.triggerEl.set(this.rootContext.trigger() ?? null));
668
708
  this.graceArea.onPointerExit(() => {
669
709
  this.rootContext.closeOnHover();
670
710
  });
671
711
  }
672
712
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxPopoverPositioner, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
673
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxPopoverPositioner, isStandalone: true, selector: "[rdxPopoverPositioner]", inputs: { anchor: { classPropertyName: "anchor", publicName: "anchor", isSignal: true, isRequired: false, transformFunction: null }, side: { classPropertyName: "side", publicName: "side", isSignal: true, isRequired: false, transformFunction: null }, sideOffset: { classPropertyName: "sideOffset", publicName: "sideOffset", isSignal: true, isRequired: false, transformFunction: null }, align: { classPropertyName: "align", publicName: "align", isSignal: true, isRequired: false, transformFunction: null }, alignOffset: { classPropertyName: "alignOffset", publicName: "alignOffset", isSignal: true, isRequired: false, transformFunction: null }, arrowPadding: { classPropertyName: "arrowPadding", publicName: "arrowPadding", isSignal: true, isRequired: false, transformFunction: null }, avoidCollisions: { classPropertyName: "avoidCollisions", publicName: "avoidCollisions", isSignal: true, isRequired: false, transformFunction: null }, collisionBoundary: { classPropertyName: "collisionBoundary", publicName: "collisionBoundary", isSignal: true, isRequired: false, transformFunction: null }, collisionPadding: { classPropertyName: "collisionPadding", publicName: "collisionPadding", isSignal: true, isRequired: false, transformFunction: null }, sticky: { classPropertyName: "sticky", publicName: "sticky", isSignal: true, isRequired: false, transformFunction: null }, hideWhenDetached: { classPropertyName: "hideWhenDetached", publicName: "hideWhenDetached", isSignal: true, isRequired: false, transformFunction: null }, positionStrategy: { classPropertyName: "positionStrategy", publicName: "positionStrategy", isSignal: true, isRequired: false, transformFunction: null }, updatePositionStrategy: { classPropertyName: "updatePositionStrategy", publicName: "updatePositionStrategy", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { placed: "placed" }, host: { properties: { "attr.data-open": "rootContext.isOpen() ? \"\" : undefined", "attr.data-closed": "rootContext.isOpen() ? undefined : \"\"", "attr.data-anchor-hidden": "wrapper.anchorHidden() ? \"\" : undefined", "attr.data-align": "wrapper.placedAlign()", "attr.data-side": "wrapper.placedSide()", "attr.data-instant": "rootContext.instant() ? \"\" : undefined", "style": "{\n '--anchor-width': 'var(--radix-popper-anchor-width)',\n '--anchor-height': 'var(--radix-popper-anchor-height)',\n '--available-width': 'var(--radix-popper-available-width)',\n '--available-height': 'var(--radix-popper-available-height)',\n '--positioner-width': 'var(--radix-popper-content-wrapper-width)',\n '--positioner-height': 'var(--radix-popper-content-wrapper-height)',\n '--transform-origin': 'var(--radix-popper-transform-origin)',\n '--radix-popover-content-transform-origin': 'var(--radix-popper-transform-origin)',\n '--radix-popover-content-available-width': 'var(--radix-popper-available-width)',\n '--radix-popover-content-available-height': 'var(--radix-popper-available-height)',\n '--radix-popover-trigger-width': 'var(--radix-popper-anchor-width)',\n '--radix-popover-trigger-height': 'var(--radix-popper-anchor-height)'\n }" } }, providers: [
713
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxPopoverPositioner, isStandalone: true, selector: "[rdxPopoverPositioner]", host: { properties: { "attr.data-open": "rootContext.isOpen() ? \"\" : undefined", "attr.data-closed": "rootContext.isOpen() ? undefined : \"\"", "attr.data-instant": "rootContext.instant() ? \"\" : undefined", "style": "legacyVars" } }, providers: [
714
+ ...provideRdxPopperContentWrapper(RdxPopoverPositioner),
674
715
  provideRdxPopperContentConfig({ arrowPadding: 5, collisionPadding: 5, updatePositionStrategy: 'always' })
675
- ], hostDirectives: [{ directive: i1.RdxPopperContentWrapper, inputs: ["anchor", "anchor", "side", "side", "sideOffset", "sideOffset", "align", "align", "alignOffset", "alignOffset", "arrowPadding", "arrowPadding", "avoidCollisions", "avoidCollisions", "collisionBoundary", "collisionBoundary", "collisionPadding", "collisionPadding", "sticky", "sticky", "hideWhenDetached", "hideWhenDetached", "positionStrategy", "positionStrategy", "updatePositionStrategy", "updatePositionStrategy"] }], ngImport: i0 }); }
716
+ ], usesInheritance: true, ngImport: i0 }); }
676
717
  }
677
718
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxPopoverPositioner, decorators: [{
678
719
  type: Directive,
679
720
  args: [{
680
721
  selector: '[rdxPopoverPositioner]',
681
722
  providers: [
723
+ ...provideRdxPopperContentWrapper(RdxPopoverPositioner),
682
724
  provideRdxPopperContentConfig({ arrowPadding: 5, collisionPadding: 5, updatePositionStrategy: 'always' })
683
725
  ],
684
- hostDirectives: [
685
- {
686
- directive: RdxPopperContentWrapper,
687
- inputs: [
688
- 'anchor',
689
- 'side',
690
- 'sideOffset',
691
- 'align',
692
- 'alignOffset',
693
- 'arrowPadding',
694
- 'avoidCollisions',
695
- 'collisionBoundary',
696
- 'collisionPadding',
697
- 'sticky',
698
- 'hideWhenDetached',
699
- 'positionStrategy',
700
- 'updatePositionStrategy'
701
- ]
702
- }
703
- ],
704
726
  host: {
705
727
  '[attr.data-open]': 'rootContext.isOpen() ? "" : undefined',
706
728
  '[attr.data-closed]': 'rootContext.isOpen() ? undefined : ""',
707
- '[attr.data-anchor-hidden]': 'wrapper.anchorHidden() ? "" : undefined',
708
- '[attr.data-align]': 'wrapper.placedAlign()',
709
- '[attr.data-side]': 'wrapper.placedSide()',
710
729
  '[attr.data-instant]': 'rootContext.instant() ? "" : undefined',
711
- '[style]': `{
712
- '--anchor-width': 'var(--radix-popper-anchor-width)',
713
- '--anchor-height': 'var(--radix-popper-anchor-height)',
714
- '--available-width': 'var(--radix-popper-available-width)',
715
- '--available-height': 'var(--radix-popper-available-height)',
716
- '--positioner-width': 'var(--radix-popper-content-wrapper-width)',
717
- '--positioner-height': 'var(--radix-popper-content-wrapper-height)',
718
- '--transform-origin': 'var(--radix-popper-transform-origin)',
719
- '--radix-popover-content-transform-origin': 'var(--radix-popper-transform-origin)',
720
- '--radix-popover-content-available-width': 'var(--radix-popper-available-width)',
721
- '--radix-popover-content-available-height': 'var(--radix-popper-available-height)',
722
- '--radix-popover-trigger-width': 'var(--radix-popper-anchor-width)',
723
- '--radix-popover-trigger-height': 'var(--radix-popper-anchor-height)'
724
- }`
730
+ // `data-side`/`data-align`/`data-anchor-hidden` and the unified `--anchor-*`/`--available-*`/
731
+ // `--transform-origin` vars come from the inherited wrapper (ADR 0012); only the deprecated
732
+ // `--radix-popover-*` aliases remain, for one release of back-compat.
733
+ '[style]': 'legacyVars'
725
734
  }
726
735
  }]
727
- }], ctorParameters: () => [], propDecorators: { anchor: [{ type: i0.Input, args: [{ isSignal: true, alias: "anchor", required: false }] }], side: [{ type: i0.Input, args: [{ isSignal: true, alias: "side", required: false }] }], sideOffset: [{ type: i0.Input, args: [{ isSignal: true, alias: "sideOffset", required: false }] }], align: [{ type: i0.Input, args: [{ isSignal: true, alias: "align", required: false }] }], alignOffset: [{ type: i0.Input, args: [{ isSignal: true, alias: "alignOffset", required: false }] }], arrowPadding: [{ type: i0.Input, args: [{ isSignal: true, alias: "arrowPadding", required: false }] }], avoidCollisions: [{ type: i0.Input, args: [{ isSignal: true, alias: "avoidCollisions", required: false }] }], collisionBoundary: [{ type: i0.Input, args: [{ isSignal: true, alias: "collisionBoundary", required: false }] }], collisionPadding: [{ type: i0.Input, args: [{ isSignal: true, alias: "collisionPadding", required: false }] }], sticky: [{ type: i0.Input, args: [{ isSignal: true, alias: "sticky", required: false }] }], hideWhenDetached: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideWhenDetached", required: false }] }], positionStrategy: [{ type: i0.Input, args: [{ isSignal: true, alias: "positionStrategy", required: false }] }], updatePositionStrategy: [{ type: i0.Input, args: [{ isSignal: true, alias: "updatePositionStrategy", required: false }] }], placed: [{ type: i0.Output, args: ["placed"] }] } });
736
+ }], ctorParameters: () => [] });
728
737
 
729
738
  /**
730
739
  * An accessible title for the popover.
@@ -756,6 +765,8 @@ class RdxPopoverTrigger {
756
765
  constructor() {
757
766
  this.parentRootContext = injectRdxPopoverRootContext(true);
758
767
  this.elementRef = inject(ElementRef);
768
+ /** Pointer type of the most recent `pointerdown`, used to detect a touch open (ADR 0016 §3). */
769
+ this.lastPointerType = '';
759
770
  /**
760
771
  * Associates this trigger with a detached popover root.
761
772
  */
@@ -803,6 +814,9 @@ class RdxPopoverTrigger {
803
814
  if (this.disabled()) {
804
815
  return;
805
816
  }
817
+ // Record whether this open is a touch tap (ADR 0016 §3). `detail === 0` is a keyboard-activated
818
+ // 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');
806
820
  this.rootContext()?.setPointerDownOnTrigger(false);
807
821
  if (this.handle()) {
808
822
  this.handle().toggle(this.triggerId(), event);
@@ -825,14 +839,15 @@ class RdxPopoverTrigger {
825
839
  }
826
840
  this.rootContext()?.cancelHoverOpen();
827
841
  }
828
- handlePointerDown() {
842
+ handlePointerDown(event) {
843
+ this.lastPointerType = event.pointerType;
829
844
  this.rootContext()?.setPointerDownOnTrigger(true);
830
845
  }
831
846
  handlePointerUp() {
832
847
  this.rootContext()?.setPointerDownOnTrigger(false);
833
848
  }
834
849
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxPopoverTrigger, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
835
- 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()", "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 }); }
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 }); }
836
851
  }
837
852
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxPopoverTrigger, decorators: [{
838
853
  type: Directive,
@@ -852,7 +867,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
852
867
  '(click)': 'handleClick($event)',
853
868
  '(pointerenter)': 'handlePointerEnter($event)',
854
869
  '(pointerleave)': 'handlePointerLeave($event)',
855
- '(pointerdown)': 'handlePointerDown()',
870
+ '(pointerdown)': 'handlePointerDown($event)',
856
871
  '(pointerup)': 'handlePointerUp()',
857
872
  '(pointercancel)': 'handlePointerUp()'
858
873
  }