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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) 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-checkbox.mjs +33 -18
  11. package/fesm2022/radix-ng-primitives-checkbox.mjs.map +1 -1
  12. package/fesm2022/radix-ng-primitives-combobox.mjs +1305 -572
  13. package/fesm2022/radix-ng-primitives-combobox.mjs.map +1 -1
  14. package/fesm2022/radix-ng-primitives-config.mjs +13 -4
  15. package/fesm2022/radix-ng-primitives-config.mjs.map +1 -1
  16. package/fesm2022/radix-ng-primitives-context-menu.mjs +51 -10
  17. package/fesm2022/radix-ng-primitives-context-menu.mjs.map +1 -1
  18. package/fesm2022/radix-ng-primitives-core.mjs +1352 -64
  19. package/fesm2022/radix-ng-primitives-core.mjs.map +1 -1
  20. package/fesm2022/radix-ng-primitives-date-field.mjs +5 -3
  21. package/fesm2022/radix-ng-primitives-date-field.mjs.map +1 -1
  22. package/fesm2022/radix-ng-primitives-dialog.mjs +290 -120
  23. package/fesm2022/radix-ng-primitives-dialog.mjs.map +1 -1
  24. package/fesm2022/radix-ng-primitives-direction-provider.mjs +70 -0
  25. package/fesm2022/radix-ng-primitives-direction-provider.mjs.map +1 -0
  26. package/fesm2022/radix-ng-primitives-dismissable-layer.mjs +519 -184
  27. package/fesm2022/radix-ng-primitives-dismissable-layer.mjs.map +1 -1
  28. package/fesm2022/radix-ng-primitives-drawer.mjs +3 -3
  29. package/fesm2022/radix-ng-primitives-drawer.mjs.map +1 -1
  30. package/fesm2022/radix-ng-primitives-editable.mjs +12 -7
  31. package/fesm2022/radix-ng-primitives-editable.mjs.map +1 -1
  32. package/fesm2022/radix-ng-primitives-field.mjs +3 -2
  33. package/fesm2022/radix-ng-primitives-field.mjs.map +1 -1
  34. package/fesm2022/radix-ng-primitives-floating-focus-manager.mjs +803 -0
  35. package/fesm2022/radix-ng-primitives-floating-focus-manager.mjs.map +1 -0
  36. package/fesm2022/radix-ng-primitives-focus-scope.mjs +305 -70
  37. package/fesm2022/radix-ng-primitives-focus-scope.mjs.map +1 -1
  38. package/fesm2022/radix-ng-primitives-menu.mjs +893 -289
  39. package/fesm2022/radix-ng-primitives-menu.mjs.map +1 -1
  40. package/fesm2022/radix-ng-primitives-menubar.mjs +32 -4
  41. package/fesm2022/radix-ng-primitives-menubar.mjs.map +1 -1
  42. package/fesm2022/radix-ng-primitives-navigation-menu.mjs +144 -159
  43. package/fesm2022/radix-ng-primitives-navigation-menu.mjs.map +1 -1
  44. package/fesm2022/radix-ng-primitives-number-field.mjs +7 -2
  45. package/fesm2022/radix-ng-primitives-number-field.mjs.map +1 -1
  46. package/fesm2022/radix-ng-primitives-popover.mjs +284 -212
  47. package/fesm2022/radix-ng-primitives-popover.mjs.map +1 -1
  48. package/fesm2022/radix-ng-primitives-popper.mjs +94 -51
  49. package/fesm2022/radix-ng-primitives-popper.mjs.map +1 -1
  50. package/fesm2022/radix-ng-primitives-presence.mjs +1 -1
  51. package/fesm2022/radix-ng-primitives-presence.mjs.map +1 -1
  52. package/fesm2022/radix-ng-primitives-preview-card.mjs +141 -173
  53. package/fesm2022/radix-ng-primitives-preview-card.mjs.map +1 -1
  54. package/fesm2022/radix-ng-primitives-radio.mjs +19 -14
  55. package/fesm2022/radix-ng-primitives-radio.mjs.map +1 -1
  56. package/fesm2022/radix-ng-primitives-roving-focus.mjs +4 -2
  57. package/fesm2022/radix-ng-primitives-roving-focus.mjs.map +1 -1
  58. package/fesm2022/radix-ng-primitives-scroll-area.mjs +5 -4
  59. package/fesm2022/radix-ng-primitives-scroll-area.mjs.map +1 -1
  60. package/fesm2022/radix-ng-primitives-select.mjs +241 -164
  61. package/fesm2022/radix-ng-primitives-select.mjs.map +1 -1
  62. package/fesm2022/radix-ng-primitives-slider.mjs +262 -29
  63. package/fesm2022/radix-ng-primitives-slider.mjs.map +1 -1
  64. package/fesm2022/radix-ng-primitives-stepper.mjs +16 -10
  65. package/fesm2022/radix-ng-primitives-stepper.mjs.map +1 -1
  66. package/fesm2022/radix-ng-primitives-switch.mjs +10 -5
  67. package/fesm2022/radix-ng-primitives-switch.mjs.map +1 -1
  68. package/fesm2022/radix-ng-primitives-tabs.mjs +15 -10
  69. package/fesm2022/radix-ng-primitives-tabs.mjs.map +1 -1
  70. package/fesm2022/radix-ng-primitives-time-field.mjs +5 -3
  71. package/fesm2022/radix-ng-primitives-time-field.mjs.map +1 -1
  72. package/fesm2022/radix-ng-primitives-toast.mjs +15 -36
  73. package/fesm2022/radix-ng-primitives-toast.mjs.map +1 -1
  74. package/fesm2022/radix-ng-primitives-toggle-group.mjs +14 -7
  75. package/fesm2022/radix-ng-primitives-toggle-group.mjs.map +1 -1
  76. package/fesm2022/radix-ng-primitives-toggle.mjs +12 -6
  77. package/fesm2022/radix-ng-primitives-toggle.mjs.map +1 -1
  78. package/fesm2022/radix-ng-primitives-toolbar.mjs +5 -3
  79. package/fesm2022/radix-ng-primitives-toolbar.mjs.map +1 -1
  80. package/fesm2022/radix-ng-primitives-tooltip.mjs +251 -143
  81. package/fesm2022/radix-ng-primitives-tooltip.mjs.map +1 -1
  82. package/package.json +10 -1
  83. package/types/radix-ng-primitives-accordion.d.ts +4 -3
  84. package/types/radix-ng-primitives-autocomplete.d.ts +217 -152
  85. package/types/radix-ng-primitives-calendar.d.ts +5 -3
  86. package/types/radix-ng-primitives-checkbox.d.ts +27 -15
  87. package/types/radix-ng-primitives-combobox.d.ts +672 -283
  88. package/types/radix-ng-primitives-config.d.ts +1 -1
  89. package/types/radix-ng-primitives-context-menu.d.ts +15 -5
  90. package/types/radix-ng-primitives-core.d.ts +764 -14
  91. package/types/radix-ng-primitives-date-field.d.ts +3 -2
  92. package/types/radix-ng-primitives-dialog.d.ts +88 -32
  93. package/types/radix-ng-primitives-direction-provider.d.ts +41 -0
  94. package/types/radix-ng-primitives-dismissable-layer.d.ts +147 -99
  95. package/types/radix-ng-primitives-editable.d.ts +11 -5
  96. package/types/radix-ng-primitives-field.d.ts +1 -0
  97. package/types/radix-ng-primitives-floating-focus-manager.d.ts +272 -0
  98. package/types/radix-ng-primitives-focus-scope.d.ts +132 -1
  99. package/types/radix-ng-primitives-menu.d.ts +192 -103
  100. package/types/radix-ng-primitives-navigation-menu.d.ts +37 -75
  101. package/types/radix-ng-primitives-number-field.d.ts +8 -3
  102. package/types/radix-ng-primitives-popover.d.ts +71 -92
  103. package/types/radix-ng-primitives-popper.d.ts +39 -9
  104. package/types/radix-ng-primitives-preview-card.d.ts +39 -72
  105. package/types/radix-ng-primitives-radio.d.ts +13 -6
  106. package/types/radix-ng-primitives-roving-focus.d.ts +7 -6
  107. package/types/radix-ng-primitives-scroll-area.d.ts +2 -2
  108. package/types/radix-ng-primitives-select.d.ts +142 -109
  109. package/types/radix-ng-primitives-slider.d.ts +64 -12
  110. package/types/radix-ng-primitives-stepper.d.ts +15 -7
  111. package/types/radix-ng-primitives-switch.d.ts +10 -4
  112. package/types/radix-ng-primitives-tabs.d.ts +12 -6
  113. package/types/radix-ng-primitives-time-field.d.ts +3 -2
  114. package/types/radix-ng-primitives-toast.d.ts +7 -7
  115. package/types/radix-ng-primitives-toggle-group.d.ts +15 -8
  116. package/types/radix-ng-primitives-toggle.d.ts +10 -3
  117. package/types/radix-ng-primitives-toolbar.d.ts +3 -2
  118. package/types/radix-ng-primitives-tooltip.d.ts +61 -80
@@ -1,15 +1,15 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, model, input, booleanAttribute, output, computed, signal, untracked, effect, Directive, InjectionToken, ElementRef, Injector, DestroyRef, afterNextRender, linkedSignal, isDevMode, numberAttribute, forwardRef } from '@angular/core';
3
- import { isEqual, getActiveElement, createContext, useTransitionStatus, isNullish, injectId, useListHighlight, useScrollLock, handleAndDispatchCustomEvent } from '@radix-ng/primitives/core';
2
+ import { inject, model, signal, computed, ElementRef, input, booleanAttribute, output, untracked, effect, Directive, InjectionToken, Injector, DestroyRef, afterNextRender, linkedSignal, isDevMode, forwardRef } from '@angular/core';
3
+ import * as i3 from '@radix-ng/primitives/core';
4
+ import { isEqual, getActiveElement, createContext, injectId, createFloatingRootContext, useTransitionStatus, isNullish, createCancelableChangeEventDetails, provideFloatingTree, provideFloatingRootContext, RDX_FLOATING_ROOT_CONTEXT, RDX_FLOATING_REGISTRATION, useListHighlight, useAnchoredScrollLock, RdxFloatingNodeRegistration, handleAndDispatchCustomEvent, rdxDevError, setupInternalBackdrop } from '@radix-ng/primitives/core';
5
+ import { injectDirection } from '@radix-ng/primitives/direction-provider';
6
+ import * as i2 from '@radix-ng/primitives/floating-focus-manager';
7
+ import { getInteractionTypeFromEvent, provideFloatingFocusManagerConfig, RdxFloatingFocusManager, createRdxTriggerInteraction } from '@radix-ng/primitives/floating-focus-manager';
4
8
  import * as i1 from '@radix-ng/primitives/popper';
5
- import { RdxPopper, RdxPopperContent, RdxPopperContentWrapper, RdxPopperAnchor } from '@radix-ng/primitives/popper';
9
+ import { RdxPopper, RdxPopperContent, RdxPopperContentWrapper, legacyPopperVars, provideRdxPopperContentWrapper, provideRdxPopperContentConfig, RdxPopperAnchor } from '@radix-ng/primitives/popper';
6
10
  import * as i4 from '@radix-ng/primitives/collection';
7
11
  import { RdxCollectionProvider, RdxCollectionItem } from '@radix-ng/primitives/collection';
8
- import { outputFromObservable, outputToObservable } from '@angular/core/rxjs-interop';
9
- import * as i3 from '@radix-ng/primitives/dismissable-layer';
10
- import { RdxDismissableLayer, provideRdxDismissableLayerConfig } from '@radix-ng/primitives/dismissable-layer';
11
- import * as i2 from '@radix-ng/primitives/focus-scope';
12
- import { RdxFocusScope } from '@radix-ng/primitives/focus-scope';
12
+ import { RdxDismiss } from '@radix-ng/primitives/dismissable-layer';
13
13
  import * as i1$1 from '@radix-ng/primitives/portal';
14
14
  import { RdxPortalPresence } from '@radix-ng/primitives/portal';
15
15
  import { provideRdxPresenceContext } from '@radix-ng/primitives/presence';
@@ -58,13 +58,17 @@ const context$2 = () => {
58
58
  triggerElement: context.triggerElement,
59
59
  valueElement: context.valueElement,
60
60
  triggerPointerDownPosRef: context.triggerPointerDownPosRef,
61
- contentId: '',
61
+ contentId: context.contentId,
62
62
  dir: context.dir,
63
63
  value: context.value,
64
64
  multiple: context.multiple,
65
65
  isItemEqualToValue: context.isItemEqualToValue,
66
66
  itemToStringLabel: context.itemToStringLabel,
67
67
  open: context.open,
68
+ openedByTouch: context.openedByTouch,
69
+ openMethod: context.openMethod,
70
+ openInteractionType: context.openInteractionType,
71
+ closeInteractionType: context.closeInteractionType,
68
72
  disabled: context.disabled,
69
73
  modal: context.modal,
70
74
  isEmptyModelValue: context.isEmptyModelValue,
@@ -84,32 +88,49 @@ const context$2 = () => {
84
88
  context.optionsSet().delete(existingOption);
85
89
  }
86
90
  },
87
- onValueChange: context.handleValueChange,
91
+ onValueChange: (value, reason, event) => context.setValue(value, reason, event),
88
92
  onTriggerChange: (node) => {
89
93
  context.triggerElement.set(node.nativeElement);
90
94
  },
91
95
  onValueElementChange: (node) => {
92
96
  context.valueElement.set(node);
93
97
  },
94
- onOpenChange: (value) => {
95
- context.open.set(value);
96
- }
98
+ onOpenChange: (value, reason, event) => context.setOpen(value, reason, event)
97
99
  };
98
100
  };
99
101
  const [injectSelectRootContext, provideSelectRootContext] = createContext('RdxSelectRootContext', 'components/select');
100
102
  class RdxSelectRoot {
101
103
  constructor() {
104
+ this.contentId = injectId('rdx-select-content-');
102
105
  this.open = model(false, ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
106
+ /** Whether the current open was initiated by **touch** (ADR 0016 §3 — gates the anchored scroll lock). */
107
+ this.openedByTouch = signal(false, ...(ngDevMode ? [{ debugName: "openedByTouch" }] : /* istanbul ignore next */ []));
108
+ /** How the select was opened. Base UI names this state `openMethod`. */
109
+ this.openInteractionType = signal(null, ...(ngDevMode ? [{ debugName: "openInteractionType" }] : /* istanbul ignore next */ []));
110
+ /** How the select was closed. */
111
+ this.closeInteractionType = signal(null, ...(ngDevMode ? [{ debugName: "closeInteractionType" }] : /* istanbul ignore next */ []));
112
+ /** Public Base UI-aligned alias for the open interaction type. */
113
+ this.openMethod = computed(() => this.openInteractionType(), ...(ngDevMode ? [{ debugName: "openMethod" }] : /* istanbul ignore next */ []));
114
+ /** Per-popup floating root context (ADR 0015) — `open` / `triggers` / reference for the dismissal engine. */
115
+ this.floatingContext = createFloatingRootContext({
116
+ ownerDocument: inject(ElementRef).nativeElement.ownerDocument,
117
+ open: () => this.open()
118
+ });
103
119
  this.value = model(...(ngDevMode ? [undefined, { debugName: "value" }] : /* istanbul ignore next */ []));
104
120
  this.multiple = input(false, { ...(ngDevMode ? { debugName: "multiple" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
105
121
  this.disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
106
122
  /** Whether the popup is modal: locks page scroll and makes outside content inert while open. */
107
123
  this.modal = input(true, { ...(ngDevMode ? { debugName: "modal" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
108
- this.dir = input('ltr', ...(ngDevMode ? [{ debugName: "dir" }] : /* istanbul ignore next */ []));
124
+ this.dirInput = input(undefined, { ...(ngDevMode ? { debugName: "dirInput" } : /* istanbul ignore next */ {}), alias: 'dir' });
125
+ this.dir = injectDirection(this.dirInput);
109
126
  /** How item values are compared for equality — a function `(a, b) => boolean` or an object key. */
110
127
  this.isItemEqualToValue = input(...(ngDevMode ? [undefined, { debugName: "isItemEqualToValue" }] : /* istanbul ignore next */ []));
111
128
  /** Converts a value to its display label (used by `RdxSelectValue`). */
112
129
  this.itemToStringLabel = input(...(ngDevMode ? [undefined, { debugName: "itemToStringLabel" }] : /* istanbul ignore next */ []));
130
+ /** Emits before an open-state change is committed; call `eventDetails.cancel()` to veto it. */
131
+ this.onOpenChange = output();
132
+ /** Emits before a value change is committed; call `eventDetails.cancel()` to veto it. */
133
+ this.onValueChange = output();
113
134
  /** Emits after the open/close transition (including any exit animation) finishes. */
114
135
  this.onOpenChangeComplete = output();
115
136
  this.transition = useTransitionStatus((open) => this.onOpenChangeComplete.emit(open));
@@ -150,34 +171,89 @@ class RdxSelectRoot {
150
171
  previousOpen = open;
151
172
  untracked(() => this.transition.start(open));
152
173
  });
174
+ // A fresh open starts non-touch; the trigger flips it on a touch open. Reset whenever it closes.
175
+ effect(() => {
176
+ if (!this.open()) {
177
+ untracked(() => this.openedByTouch.set(false));
178
+ }
179
+ });
180
+ // Bridge the trigger into the floating context: the dismissal capability treats a press on the
181
+ // trigger (the reference) as "inside", and uses it for positioning containment (ADR 0015).
182
+ effect((onCleanup) => {
183
+ const trigger = this.triggerElement();
184
+ this.floatingContext.setReferenceElement(trigger);
185
+ if (trigger) {
186
+ this.floatingContext.triggers.add(trigger);
187
+ onCleanup(() => this.floatingContext.triggers.delete(trigger));
188
+ }
189
+ });
153
190
  }
154
191
  getOption(value) {
155
192
  return Array.from(this.optionsSet()).find((option) => valueComparator(value, option.value, this.isItemEqualToValue()));
156
193
  }
157
- handleValueChange(value) {
194
+ setValue(value, reason = 'none', event = new Event('select.value-change')) {
195
+ const nextValue = this.multiple()
196
+ ? (() => {
197
+ const current = this.value();
198
+ const array = Array.isArray(current) ? [...current] : [];
199
+ const index = array.findIndex((i) => compare(i, value, this.isItemEqualToValue()));
200
+ index === -1 ? array.push(value) : array.splice(index, 1);
201
+ return [...array];
202
+ })()
203
+ : value;
204
+ const { eventDetails } = createCancelableChangeEventDetails(reason, event, this.triggerElement() ?? undefined);
205
+ this.onValueChange.emit({ value: nextValue, eventDetails });
206
+ if (eventDetails.isCanceled()) {
207
+ return false;
208
+ }
158
209
  if (this.multiple()) {
159
- const current = this.value();
160
- const array = Array.isArray(current) ? [...current] : [];
161
- const index = array.findIndex((i) => compare(i, value, this.isItemEqualToValue()));
162
- index === -1 ? array.push(value) : array.splice(index, 1);
163
- this.value.set([...array]);
210
+ this.value.set(nextValue);
164
211
  }
165
212
  else {
166
- this.value.set(value);
213
+ this.value.set(nextValue);
167
214
  }
215
+ return true;
216
+ }
217
+ setOpen(open, reason = 'none', event) {
218
+ const resolvedEvent = event ?? new Event('select.open-change');
219
+ const interactionType = getInteractionTypeFromEvent(event);
220
+ const { eventDetails } = createCancelableChangeEventDetails(reason, resolvedEvent, this.triggerElement() ?? undefined);
221
+ this.onOpenChange.emit({ open, eventDetails });
222
+ if (eventDetails.isCanceled()) {
223
+ return false;
224
+ }
225
+ this.open.set(open);
226
+ if (open) {
227
+ this.openedByTouch.set(event?.pointerType === 'touch');
228
+ this.openInteractionType.set(interactionType);
229
+ }
230
+ else {
231
+ this.closeInteractionType.set(interactionType);
232
+ }
233
+ return true;
168
234
  }
169
235
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectRoot, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
170
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxSelectRoot, isStandalone: true, selector: "[rdxSelectRoot]", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, modal: { classPropertyName: "modal", publicName: "modal", isSignal: true, isRequired: false, transformFunction: null }, dir: { classPropertyName: "dir", publicName: "dir", isSignal: true, isRequired: false, transformFunction: null }, isItemEqualToValue: { classPropertyName: "isItemEqualToValue", publicName: "isItemEqualToValue", isSignal: true, isRequired: false, transformFunction: null }, itemToStringLabel: { classPropertyName: "itemToStringLabel", publicName: "itemToStringLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange", value: "valueChange", onOpenChangeComplete: "onOpenChangeComplete" }, providers: [provideSelectRootContext(context$2)], exportAs: ["rdxSelectRoot"], hostDirectives: [{ directive: i1.RdxPopper }], ngImport: i0 }); }
236
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxSelectRoot, isStandalone: true, selector: "[rdxSelectRoot]", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, modal: { classPropertyName: "modal", publicName: "modal", isSignal: true, isRequired: false, transformFunction: null }, dirInput: { classPropertyName: "dirInput", publicName: "dir", isSignal: true, isRequired: false, transformFunction: null }, isItemEqualToValue: { classPropertyName: "isItemEqualToValue", publicName: "isItemEqualToValue", isSignal: true, isRequired: false, transformFunction: null }, itemToStringLabel: { classPropertyName: "itemToStringLabel", publicName: "itemToStringLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange", value: "valueChange", onOpenChange: "onOpenChange", onValueChange: "onValueChange", onOpenChangeComplete: "onOpenChangeComplete" }, providers: [
237
+ provideSelectRootContext(context$2),
238
+ // New floating foundation (ADR 0015/0017) — the dismissal capability reads this shared context.
239
+ provideFloatingTree(),
240
+ provideFloatingRootContext(() => inject(RdxSelectRoot).floatingContext)
241
+ ], exportAs: ["rdxSelectRoot"], hostDirectives: [{ directive: i1.RdxPopper }], ngImport: i0 }); }
171
242
  }
172
243
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectRoot, decorators: [{
173
244
  type: Directive,
174
245
  args: [{
175
246
  selector: '[rdxSelectRoot]',
176
247
  exportAs: 'rdxSelectRoot',
177
- providers: [provideSelectRootContext(context$2)],
248
+ providers: [
249
+ provideSelectRootContext(context$2),
250
+ // New floating foundation (ADR 0015/0017) — the dismissal capability reads this shared context.
251
+ provideFloatingTree(),
252
+ provideFloatingRootContext(() => inject(RdxSelectRoot).floatingContext)
253
+ ],
178
254
  hostDirectives: [RdxPopper]
179
255
  }]
180
- }], ctorParameters: () => [], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], modal: [{ type: i0.Input, args: [{ isSignal: true, alias: "modal", required: false }] }], dir: [{ type: i0.Input, args: [{ isSignal: true, alias: "dir", required: false }] }], isItemEqualToValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "isItemEqualToValue", required: false }] }], itemToStringLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemToStringLabel", required: false }] }], onOpenChangeComplete: [{ type: i0.Output, args: ["onOpenChangeComplete"] }] } });
256
+ }], ctorParameters: () => [], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], modal: [{ type: i0.Input, args: [{ isSignal: true, alias: "modal", required: false }] }], dirInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "dir", required: false }] }], isItemEqualToValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "isItemEqualToValue", required: false }] }], itemToStringLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemToStringLabel", required: false }] }], onOpenChange: [{ type: i0.Output, args: ["onOpenChange"] }], onValueChange: [{ type: i0.Output, args: ["onValueChange"] }], onOpenChangeComplete: [{ type: i0.Output, args: ["onOpenChangeComplete"] }] } });
181
257
 
182
258
  /**
183
259
  * An overlay rendered beneath the popup in `modal` mode. Place it inside the portal/presence; style
@@ -312,7 +388,8 @@ const RDX_SELECT_POSITIONER_TOKEN = new InjectionToken('RDX_SELECT_POSITIONER_TO
312
388
  */
313
389
  class RdxSelectPopup {
314
390
  constructor() {
315
- this.dismissableLayer = inject(RdxDismissableLayer);
391
+ this.floatingContext = inject(RDX_FLOATING_ROOT_CONTEXT);
392
+ this.registration = inject(RDX_FLOATING_REGISTRATION, { optional: true });
316
393
  this.currentElement = inject(ElementRef);
317
394
  this.collection = inject(RdxCollectionProvider);
318
395
  this.injector = inject(Injector);
@@ -347,12 +424,12 @@ class RdxSelectPopup {
347
424
  * Event handler called when the escape key is down.
348
425
  * Can be prevented.
349
426
  */
350
- this.escapeKeyDown = outputFromObservable(outputToObservable(this.dismissableLayer.escapeKeyDown));
427
+ this.escapeKeyDown = output();
351
428
  /**
352
- * Event handler called when a `pointerdown` event happens outside of the `DismissableLayer`.
429
+ * Event handler called when a `pointerdown` event happens outside of the popup.
353
430
  * Can be prevented.
354
431
  */
355
- this.pointerDownOutside = outputFromObservable(outputToObservable(this.dismissableLayer.pointerDownOutside));
432
+ this.pointerDownOutside = output();
356
433
  this.content = signal(null, ...(ngDevMode ? [{ debugName: "content" }] : /* istanbul ignore next */ []));
357
434
  /**
358
435
  * The positioner — now an **ancestor** element — provides {@link RDX_SELECT_POSITIONER_TOKEN}
@@ -383,27 +460,34 @@ class RdxSelectPopup {
383
460
  item.element.scrollIntoView?.({ block: 'nearest' });
384
461
  }
385
462
  });
386
- // Lock page scroll while a modal popup is open (content mounts only while open).
387
- useScrollLock(this.rootContext.modal);
463
+ // Activation policy (ADR 0016 §2 + §3). Lock page scroll while the popup is OPEN and either modal
464
+ // **or** item-aligned — Base UI `(alignItemWithTriggerActive || modal) && open` (AC #3): an
465
+ // item-aligned select overlays the trigger, so the page must not scroll behind it even when
466
+ // `modal === false`. The gate keys on `open` (not mounted) so it releases at close-start. A
467
+ // **touch** open never uses item-aligned mode (the positioner falls back), so the lock there is
468
+ // driven by `modal` alone and the anchored helper only engages when the popup is viewport-width (§3).
469
+ const itemAlignedActive = computed(() => this.positioner?.alignItemWithTriggerActive?.() ?? false, ...(ngDevMode ? [{ debugName: "itemAlignedActive" }] : /* istanbul ignore next */ []));
470
+ useAnchoredScrollLock(computed(() => (itemAlignedActive() || this.rootContext.modal()) && this.rootContext.open()), {
471
+ touchOpen: () => this.rootContext.openedByTouch(),
472
+ element: () => this.currentElement.nativeElement
473
+ });
388
474
  // The popup's animation determines when the open/close transition (onOpenChangeComplete) is done.
389
475
  const unregisterTransition = this.rootContext.registerTransitionElement(this.currentElement.nativeElement);
390
476
  inject(DestroyRef).onDestroy(unregisterTransition);
391
- this.dismissableLayer.focusOutside.subscribe((e) => e.preventDefault());
392
- this.dismissableLayer.dismiss.subscribe(() => this.rootContext.onOpenChange(false));
393
- const focusScope = inject(RdxFocusScope);
477
+ // The popup (listbox) is this layer's floating element — the inside surface for containment.
478
+ this.floatingContext.setFloatingElement(this.currentElement.nativeElement);
479
+ // Dismissal (ADR 0015): Escape or an outside press closes the select. Focus-out does NOT close it
480
+ // — the listbox holds focus while open (items are navigated virtually), so a focus-out is not a
481
+ // dismissal (the legacy preventDefaulted it too).
482
+ new RdxDismiss(this.floatingContext, () => this.registration?.node() ?? null, {
483
+ escapeKey: () => true,
484
+ outsidePress: () => true,
485
+ focusOutside: () => false,
486
+ onEscapeKeyDown: (event) => this.escapeKeyDown.emit(event),
487
+ onPointerDownOutside: (event) => this.pointerDownOutside.emit(event),
488
+ onDismiss: (reason, event) => this.rootContext.onOpenChange(false, reason === 'escape-key' ? 'escape-key' : reason === 'focus-outside' ? 'focus-out' : 'outside-press', event)
489
+ });
394
490
  afterNextRender(() => {
395
- focusScope.unmountAutoFocus.subscribe((event) => {
396
- if (event.defaultPrevented)
397
- return;
398
- this.rootContext.triggerElement()?.focus({ preventScroll: true });
399
- event.preventDefault();
400
- });
401
- // Focus the popup itself (not an item) — the listbox is the focus owner; items are
402
- // navigated virtually via aria-activedescendant.
403
- focusScope.mountAutoFocus.subscribe((event) => {
404
- event.preventDefault();
405
- this.content()?.focus({ preventScroll: true });
406
- });
407
491
  // The popup is now the listbox host itself (no longer the positioner's first child).
408
492
  this.content.set(this.currentElement.nativeElement);
409
493
  });
@@ -427,8 +511,9 @@ class RdxSelectPopup {
427
511
  }
428
512
  else {
429
513
  // otherwise, if the event was outside the content, close.
430
- if (!this.content()?.contains(event.target))
431
- this.rootContext.onOpenChange(false);
514
+ if (!this.content()?.contains(event.target)) {
515
+ this.rootContext.onOpenChange(false, 'outside-press', event);
516
+ }
432
517
  }
433
518
  document.removeEventListener('pointermove', handlePointerMove);
434
519
  this.rootContext.triggerPointerDownPosRef.set(null);
@@ -447,6 +532,21 @@ class RdxSelectPopup {
447
532
  });
448
533
  });
449
534
  });
535
+ effect((onCleanup) => {
536
+ if (!itemAlignedActive() || !this.rootContext.open()) {
537
+ return;
538
+ }
539
+ const popup = this.content();
540
+ const view = popup?.ownerDocument.defaultView;
541
+ if (!view) {
542
+ return;
543
+ }
544
+ const handleResize = (event) => {
545
+ this.rootContext.onOpenChange(false, 'window-resize', event);
546
+ };
547
+ view.addEventListener('resize', handleResize);
548
+ onCleanup(() => view.removeEventListener('resize', handleResize));
549
+ });
450
550
  }
451
551
  /** Highlights the selected item (or the first enabled one) when the popup opens. */
452
552
  highlightSelectedItem() {
@@ -481,9 +581,10 @@ class RdxSelectPopup {
481
581
  event.preventDefault();
482
582
  const item = this.highlight.highlightedItem();
483
583
  if (item && !item.disabled()) {
484
- this.rootContext.onValueChange(item.value());
485
- if (!this.rootContext.multiple())
486
- this.rootContext.onOpenChange(false);
584
+ this.rootContext.onValueChange(item.value(), 'item-press', event);
585
+ if (!this.rootContext.multiple()) {
586
+ this.rootContext.onOpenChange(false, 'item-press', event);
587
+ }
487
588
  }
488
589
  return;
489
590
  }
@@ -509,23 +610,46 @@ class RdxSelectPopup {
509
610
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectPopup, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
510
611
  static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxSelectPopup, isStandalone: true, selector: "[rdxSelectPopup]", outputs: { escapeKeyDown: "escapeKeyDown", pointerDownOutside: "pointerDownOutside" }, host: { attributes: { "role": "listbox", "tabindex": "-1" }, listeners: { "keydown": "handleKeyDown($event)" }, properties: { "id": "rootContext.contentId", "attr.aria-activedescendant": "highlight.activeId()", "attr.aria-multiselectable": "rootContext.multiple() ? \"true\" : undefined", "attr.data-state": "rootContext.open() ? \"open\" : \"closed\"", "attr.data-open": "rootContext.open() ? \"\" : undefined", "attr.data-closed": "rootContext.open() ? undefined : \"\"", "attr.data-starting-style": "rootContext.transitionStatus() === \"starting\" ? \"\" : undefined", "attr.data-ending-style": "rootContext.transitionStatus() === \"ending\" ? \"\" : undefined", "dir": "rootContext.dir()", "style": "{\n display: 'flex',\n flexDirection: 'column',\n outline: 'none'\n }" } }, providers: [
511
612
  provideSelectPopupContext(context$1),
512
- provideRdxDismissableLayerConfig(() => {
613
+ provideFloatingFocusManagerConfig(() => {
614
+ const rootContext = injectSelectRootContext();
615
+ const host = inject(ElementRef).nativeElement;
513
616
  return {
514
- disableOutsidePointerEvents: injectSelectRootContext().modal
617
+ modal: () => false,
618
+ enabled: () => rootContext.open() || rootContext.transitionStatus() === 'ending',
619
+ closeOnFocusOut: () => false,
620
+ // The listbox owns DOM focus; items are navigated virtually through aria-activedescendant.
621
+ initialFocus: () => host,
622
+ restoreFocus: () => true,
623
+ openInteractionType: () => rootContext.openInteractionType(),
624
+ closeInteractionType: () => rootContext.closeInteractionType()
515
625
  };
516
626
  })
517
- ], hostDirectives: [{ directive: i1.RdxPopperContent }, { directive: i2.RdxFocusScope }, { directive: i3.RdxDismissableLayer }, { directive: i4.RdxCollectionProvider }], ngImport: i0 }); }
627
+ ], hostDirectives: [{ directive: i1.RdxPopperContent }, { directive: i2.RdxFloatingFocusManager, inputs: ["returnFocus", "finalFocus"] }, { directive: i3.RdxFloatingNodeRegistration }, { directive: i4.RdxCollectionProvider }], ngImport: i0 }); }
518
628
  }
519
629
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectPopup, decorators: [{
520
630
  type: Directive,
521
631
  args: [{
522
632
  selector: '[rdxSelectPopup]',
523
- hostDirectives: [RdxPopperContent, RdxFocusScope, RdxDismissableLayer, RdxCollectionProvider],
633
+ hostDirectives: [
634
+ RdxPopperContent,
635
+ { directive: RdxFloatingFocusManager, inputs: ['returnFocus: finalFocus'] },
636
+ RdxFloatingNodeRegistration,
637
+ RdxCollectionProvider
638
+ ],
524
639
  providers: [
525
640
  provideSelectPopupContext(context$1),
526
- provideRdxDismissableLayerConfig(() => {
641
+ provideFloatingFocusManagerConfig(() => {
642
+ const rootContext = injectSelectRootContext();
643
+ const host = inject(ElementRef).nativeElement;
527
644
  return {
528
- disableOutsidePointerEvents: injectSelectRootContext().modal
645
+ modal: () => false,
646
+ enabled: () => rootContext.open() || rootContext.transitionStatus() === 'ending',
647
+ closeOnFocusOut: () => false,
648
+ // The listbox owns DOM focus; items are navigated virtually through aria-activedescendant.
649
+ initialFocus: () => host,
650
+ restoreFocus: () => true,
651
+ openInteractionType: () => rootContext.openInteractionType(),
652
+ closeInteractionType: () => rootContext.closeInteractionType()
529
653
  };
530
654
  })
531
655
  ],
@@ -594,9 +718,9 @@ class RdxSelectItem {
594
718
  handleAndDispatchCustomEvent(this.SELECT_SELECT, async (event) => {
595
719
  if (event.defaultPrevented)
596
720
  return;
597
- this.rootContext.onValueChange(this.value());
721
+ this.rootContext.onValueChange(this.value(), 'item-press', event.detail.originalEvent);
598
722
  if (!this.rootContext.multiple()) {
599
- this.rootContext.onOpenChange(false);
723
+ this.rootContext.onOpenChange(false, 'item-press', event.detail.originalEvent);
600
724
  }
601
725
  }, eventDetail);
602
726
  }
@@ -773,9 +897,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
773
897
  class RdxSelectPortalMisuseGuard {
774
898
  constructor() {
775
899
  if (isDevMode()) {
776
- throw new Error('[rdxSelectPortal] is now a structural directive. ' +
900
+ rdxDevError('select/portal-on-element', '`rdxSelectPortal` is now a structural directive. ' +
777
901
  'Use `*rdxSelectPortal` on the popup element or `<ng-template rdxSelectPortal>`. ' +
778
- 'rdxSelectPortalPresence has been removed. See https://radix-ng.com/components/select.md');
902
+ 'rdxSelectPortalPresence has been removed.', 'components/select');
779
903
  }
780
904
  }
781
905
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectPortalMisuseGuard, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
@@ -788,112 +912,53 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
788
912
  }]
789
913
  }], ctorParameters: () => [] });
790
914
 
791
- class RdxSelectPositioner {
915
+ /**
916
+ * Positions the select popup against the trigger using the popper engine.
917
+ *
918
+ * A "thin" positioner (ADR 0012): it inherits the full popper positioning surface — the inputs
919
+ * (`side`, `sideOffset`, `align`, …), the `placed` output, and the host bindings — from
920
+ * {@link RdxPopperContentWrapper}, and declares select's Base UI-aligned defaults via the config
921
+ * provider. It also satisfies {@link RdxPositionerImpl} (via the inherited `placed`) so the popup can
922
+ * resolve it through {@link RDX_SELECT_POSITIONER_TOKEN}, the same as the item-aligned positioner.
923
+ */
924
+ class RdxSelectPositioner extends RdxPopperContentWrapper {
792
925
  constructor() {
793
- /**
794
- * The preferred side of the anchor to render against when open.
795
- * Will be reversed when collisions occur and avoidCollisions is enabled.
796
- */
797
- this.side = input('bottom', ...(ngDevMode ? [{ debugName: "side" }] : /* istanbul ignore next */ []));
798
- /**
799
- * The distance in pixels from the anchor.
800
- */
801
- this.sideOffset = input(0, { ...(ngDevMode ? { debugName: "sideOffset" } : /* istanbul ignore next */ {}), transform: numberAttribute });
802
- /**
803
- * The preferred alignment against the anchor. May change when collisions occur.
804
- */
805
- this.align = input('start', ...(ngDevMode ? [{ debugName: "align" }] : /* istanbul ignore next */ []));
806
- /**
807
- * An offset in pixels from the `start` or `end` alignment options.
808
- */
809
- this.alignOffset = input(0, { ...(ngDevMode ? { debugName: "alignOffset" } : /* istanbul ignore next */ {}), transform: numberAttribute });
810
- /**
811
- * The padding between the arrow and the edges of the content.
812
- * If your content has border-radius, this will prevent it from overflowing the corners.
813
- */
814
- this.arrowPadding = input(0, { ...(ngDevMode ? { debugName: "arrowPadding" } : /* istanbul ignore next */ {}), transform: numberAttribute });
815
- /**
816
- * When `true`, overrides the `side` and `align` preferences to prevent collisions with boundary edges.
817
- */
818
- this.avoidCollisions = input(true, { ...(ngDevMode ? { debugName: "avoidCollisions" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
819
- /**
820
- * The element used as the collision boundary.
821
- * By default this is the viewport, though you can provide additional element(s) to be included in this check.
822
- */
823
- this.collisionBoundary = input(...(ngDevMode ? [undefined, { debugName: "collisionBoundary" }] : /* istanbul ignore next */ []));
824
- /**
825
- * The distance in pixels from the boundary edges where collision detection should occur.
826
- * Accepts a number (same for all sides), or a partial padding object, for example: `{ top: 20, left: 20 }`.
827
- */
828
- this.collisionPadding = input(0, ...(ngDevMode ? [{ debugName: "collisionPadding" }] : /* istanbul ignore next */ []));
829
- /**
830
- * The sticky behavior on the `align` axis.
831
- * - `partial` will keep the content in the boundary as long as the trigger is at least partially in the boundary
832
- * - `always` will keep the content in the boundary regardless.
833
- */
834
- this.sticky = input('partial', ...(ngDevMode ? [{ debugName: "sticky" }] : /* istanbul ignore next */ []));
835
- /**
836
- * Whether to hide the content when the trigger becomes fully occluded.
837
- */
838
- this.hideWhenDetached = input(false, { ...(ngDevMode ? { debugName: "hideWhenDetached" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
839
- /**
840
- * Whether to update the position of the floating element on every animation frame if required.
841
- */
842
- this.updatePositionStrategy = input('always', ...(ngDevMode ? [{ debugName: "updatePositionStrategy" }] : /* istanbul ignore next */ []));
843
- /**
844
- * Emits when the element is placed.
845
- */
846
- this.placed = outputFromObservable(outputToObservable(inject(RdxPopperContentWrapper).placed));
926
+ super();
927
+ this.positionerStyle = { boxSizing: 'border-box', ...legacyPopperVars('select') };
928
+ const rootContext = injectSelectRootContext();
929
+ const injector = inject(Injector);
930
+ const host = inject(ElementRef).nativeElement;
931
+ // Modal select isolates the background with an internal backdrop (finding #1 / Base UI) instead of
932
+ // a global pointer lock; the trigger stays clickable through a cutout (toggle-close).
933
+ afterNextRender(() => setupInternalBackdrop(host, injector, {
934
+ isOpen: () => rootContext.open(),
935
+ shouldRender: () => rootContext.modal(),
936
+ cutout: () => rootContext.triggerElement()
937
+ }));
847
938
  }
848
939
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectPositioner, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
849
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxSelectPositioner, isStandalone: true, selector: "[rdxSelectPositioner]", inputs: { 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 }, updatePositionStrategy: { classPropertyName: "updatePositionStrategy", publicName: "updatePositionStrategy", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { placed: "placed" }, host: { properties: { "style": "{\n 'boxSizing': 'border-box',\n '--radix-select-content-transform-origin': 'var(--radix-popper-transform-origin)',\n '--radix-select-content-available-width': 'var(--radix-popper-available-width)',\n '--radix-select-content-available-height': 'var(--radix-popper-available-height)',\n '--radix-select-trigger-width': 'var(--radix-popper-anchor-width)',\n '--radix-select-trigger-height': 'var(--radix-popper-anchor-height)',\n }" } }, providers: [
850
- {
851
- provide: RDX_SELECT_POSITIONER_TOKEN,
852
- useExisting: forwardRef(() => RdxSelectPositioner)
853
- }
854
- ], hostDirectives: [{ directive: i1.RdxPopperContentWrapper, inputs: ["side", "side", "sideOffset", "sideOffset", "align", "align", "alignOffset", "alignOffset", "arrowPadding", "arrowPadding", "avoidCollisions", "avoidCollisions", "collisionBoundary", "collisionBoundary", "collisionPadding", "collisionPadding", "sticky", "sticky", "hideWhenDetached", "hideWhenDetached", "updatePositionStrategy", "updatePositionStrategy"] }], ngImport: i0 }); }
940
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxSelectPositioner, isStandalone: true, selector: "[rdxSelectPositioner]", host: { properties: { "style": "positionerStyle" } }, providers: [
941
+ ...provideRdxPopperContentWrapper(RdxSelectPositioner),
942
+ provideRdxPopperContentConfig({ align: 'start', updatePositionStrategy: 'always' }),
943
+ { provide: RDX_SELECT_POSITIONER_TOKEN, useExisting: forwardRef(() => RdxSelectPositioner) }
944
+ ], usesInheritance: true, ngImport: i0 }); }
855
945
  }
856
946
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectPositioner, decorators: [{
857
947
  type: Directive,
858
948
  args: [{
859
949
  selector: '[rdxSelectPositioner]',
860
950
  providers: [
861
- {
862
- provide: RDX_SELECT_POSITIONER_TOKEN,
863
- useExisting: forwardRef(() => RdxSelectPositioner)
864
- }
865
- ],
866
- hostDirectives: [
867
- {
868
- directive: RdxPopperContentWrapper,
869
- inputs: [
870
- 'side',
871
- 'sideOffset',
872
- 'align',
873
- 'alignOffset',
874
- 'arrowPadding',
875
- 'avoidCollisions',
876
- 'collisionBoundary',
877
- 'collisionPadding',
878
- 'sticky',
879
- 'hideWhenDetached',
880
- 'updatePositionStrategy'
881
- ]
882
- }
951
+ ...provideRdxPopperContentWrapper(RdxSelectPositioner),
952
+ provideRdxPopperContentConfig({ align: 'start', updatePositionStrategy: 'always' }),
953
+ { provide: RDX_SELECT_POSITIONER_TOKEN, useExisting: forwardRef(() => RdxSelectPositioner) }
883
954
  ],
884
955
  host: {
885
- // re-namespace exposed content custom properties
886
- '[style]': `{
887
- 'boxSizing': 'border-box',
888
- '--radix-select-content-transform-origin': 'var(--radix-popper-transform-origin)',
889
- '--radix-select-content-available-width': 'var(--radix-popper-available-width)',
890
- '--radix-select-content-available-height': 'var(--radix-popper-available-height)',
891
- '--radix-select-trigger-width': 'var(--radix-popper-anchor-width)',
892
- '--radix-select-trigger-height': 'var(--radix-popper-anchor-height)',
893
- }`
956
+ // The unified vars + placement attrs come from the wrapper (ADR 0012); only `box-sizing` and
957
+ // the deprecated `--radix-select-*` aliases (still consumed by demos) remain here.
958
+ '[style]': 'positionerStyle'
894
959
  }
895
960
  }]
896
- }], propDecorators: { 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 }] }], updatePositionStrategy: [{ type: i0.Input, args: [{ isSignal: true, alias: "updatePositionStrategy", required: false }] }], placed: [{ type: i0.Output, args: ["placed"] }] } });
961
+ }], ctorParameters: () => [] });
897
962
 
898
963
  /**
899
964
  * A visual divider between groups of items.
@@ -926,6 +991,13 @@ class RdxSelectTrigger {
926
991
  this.id = input(injectId('rdx-select-trigger-'), ...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
927
992
  this.disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
928
993
  this.isDisabled = computed(() => this.rootContext.disabled() || this.disabled() || Boolean(this.fieldRootContext?.disabledState()), ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
994
+ this.triggerInteraction = createRdxTriggerInteraction({
995
+ trigger: () => this.elementRef.nativeElement,
996
+ activeTrigger: () => this.rootContext.triggerElement(),
997
+ open: () => this.rootContext.open(),
998
+ disabled: () => this.isDisabled(),
999
+ contentId: () => this.rootContext.contentId
1000
+ });
929
1001
  this.invalidState = computed(() => Boolean(this.fieldRootContext?.invalidState()), ...(ngDevMode ? [{ debugName: "invalidState" }] : /* istanbul ignore next */ []));
930
1002
  this.requiredState = computed(() => Boolean(this.fieldRootContext?.requiredState()), ...(ngDevMode ? [{ debugName: "requiredState" }] : /* istanbul ignore next */ []));
931
1003
  this.filledState = computed(() => !this.rootContext.isEmptyModelValue() || Boolean(this.fieldRootContext?.filledState()), ...(ngDevMode ? [{ debugName: "filledState" }] : /* istanbul ignore next */ []));
@@ -947,18 +1019,20 @@ class RdxSelectTrigger {
947
1019
  this.fieldRootContext?.setFilled(!this.rootContext.isEmptyModelValue());
948
1020
  });
949
1021
  }
950
- handleOpen() {
1022
+ handleOpen(reason, event) {
951
1023
  if (!this.isDisabled()) {
952
- this.rootContext.onOpenChange(true);
1024
+ return this.rootContext.onOpenChange(true, reason, event);
953
1025
  }
1026
+ return false;
954
1027
  }
955
1028
  handlePointerOpen(event) {
956
1029
  const pointerEvent = event;
957
- this.handleOpen();
958
- this.rootContext.triggerPointerDownPosRef.set({
959
- x: Math.round(pointerEvent.pageX),
960
- y: Math.round(pointerEvent.pageY)
961
- });
1030
+ if (this.handleOpen('trigger-press', event)) {
1031
+ this.rootContext.triggerPointerDownPosRef.set({
1032
+ x: Math.round(pointerEvent.pageX),
1033
+ y: Math.round(pointerEvent.pageY)
1034
+ });
1035
+ }
962
1036
  }
963
1037
  onClickHandler(event) {
964
1038
  // Whilst browsers generally have no issue focusing the trigger when clicking
@@ -970,6 +1044,7 @@ class RdxSelectTrigger {
970
1044
  }
971
1045
  onPointerDown(event) {
972
1046
  const pointerEvent = event;
1047
+ this.triggerInteraction.recordPointerDown(pointerEvent);
973
1048
  if (pointerEvent.pointerType === 'touch')
974
1049
  return event.preventDefault();
975
1050
  // prevent implicit pointer capture
@@ -996,7 +1071,8 @@ class RdxSelectTrigger {
996
1071
  onKeydown(event) {
997
1072
  const keyEvent = event;
998
1073
  if (OPEN_KEYS.includes(keyEvent.key)) {
999
- this.handleOpen();
1074
+ const reason = keyEvent.key === 'ArrowUp' || keyEvent.key === 'ArrowDown' ? 'list-navigation' : 'trigger-press';
1075
+ this.handleOpen(reason, event);
1000
1076
  event.preventDefault();
1001
1077
  }
1002
1078
  }
@@ -1008,7 +1084,7 @@ class RdxSelectTrigger {
1008
1084
  this.fieldRootContext?.setTouched(true);
1009
1085
  }
1010
1086
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectTrigger, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1011
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxSelectTrigger, isStandalone: true, selector: "button[rdxSelectTrigger]", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "combobox", "type": "button" }, listeners: { "click": "onClickHandler($event)", "pointerdown": "onPointerDown($event)", "pointerup": "onPointerUp($event)", "keydown": "onKeydown($event)", "focus": "onFocus()", "blur": "onBlur()" }, properties: { "attr.id": "id()", "attr.aria-describedby": "describedBy()", "attr.aria-invalid": "invalidState() ? \"true\" : undefined", "attr.aria-required": "requiredState() ? \"true\" : undefined", "attr.disabled": "isDisabled() ? \"\" : undefined", "dir": "rootContext.dir()", "attr.data-state": "rootContext.open() ? \"open\" : \"closed\"", "attr.data-popup-open": "dataAttr(rootContext.open())", "attr.data-placeholder": "dataAttr(rootContext.isEmptyModelValue())", "attr.data-disabled": "dataAttr(isDisabled())", "attr.data-invalid": "dataAttr(invalidState())", "attr.data-valid": "dataAttr(!invalidState())", "attr.data-required": "dataAttr(requiredState())", "attr.data-filled": "dataAttr(filledState())", "attr.data-focused": "dataAttr(focusedState())" } }, hostDirectives: [{ directive: i1.RdxPopperAnchor }], ngImport: i0 }); }
1087
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxSelectTrigger, isStandalone: true, selector: "button[rdxSelectTrigger]", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "combobox", "type": "button" }, listeners: { "click": "onClickHandler($event)", "pointerdown": "onPointerDown($event)", "pointerup": "onPointerUp($event)", "keydown": "onKeydown($event)", "focus": "onFocus()", "blur": "onBlur()" }, properties: { "attr.id": "id()", "attr.aria-describedby": "describedBy()", "attr.aria-controls": "triggerInteraction.ariaControls()", "attr.aria-invalid": "invalidState() ? \"true\" : undefined", "attr.aria-required": "requiredState() ? \"true\" : undefined", "attr.disabled": "isDisabled() ? \"\" : undefined", "dir": "rootContext.dir()", "attr.data-state": "triggerInteraction.dataState()", "attr.data-popup-open": "triggerInteraction.dataPopupOpen()", "attr.data-placeholder": "dataAttr(rootContext.isEmptyModelValue())", "attr.data-disabled": "dataAttr(isDisabled())", "attr.data-invalid": "dataAttr(invalidState())", "attr.data-valid": "dataAttr(!invalidState())", "attr.data-required": "dataAttr(requiredState())", "attr.data-filled": "dataAttr(filledState())", "attr.data-focused": "dataAttr(focusedState())" } }, hostDirectives: [{ directive: i1.RdxPopperAnchor }], ngImport: i0 }); }
1012
1088
  }
1013
1089
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSelectTrigger, decorators: [{
1014
1090
  type: Directive,
@@ -1020,12 +1096,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
1020
1096
  type: 'button',
1021
1097
  '[attr.id]': 'id()',
1022
1098
  '[attr.aria-describedby]': 'describedBy()',
1099
+ '[attr.aria-controls]': 'triggerInteraction.ariaControls()',
1023
1100
  '[attr.aria-invalid]': 'invalidState() ? "true" : undefined',
1024
1101
  '[attr.aria-required]': 'requiredState() ? "true" : undefined',
1025
1102
  '[attr.disabled]': 'isDisabled() ? "" : undefined',
1026
1103
  '[dir]': 'rootContext.dir()',
1027
- '[attr.data-state]': 'rootContext.open() ? "open" : "closed"',
1028
- '[attr.data-popup-open]': 'dataAttr(rootContext.open())',
1104
+ '[attr.data-state]': 'triggerInteraction.dataState()',
1105
+ '[attr.data-popup-open]': 'triggerInteraction.dataPopupOpen()',
1029
1106
  '[attr.data-placeholder]': 'dataAttr(rootContext.isEmptyModelValue())',
1030
1107
  '[attr.data-disabled]': 'dataAttr(isDisabled())',
1031
1108
  '[attr.data-invalid]': 'dataAttr(invalidState())',