@radix-ng/primitives 1.0.9 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.md +26 -5
  2. package/fesm2022/radix-ng-primitives-autocomplete.mjs +35 -19
  3. package/fesm2022/radix-ng-primitives-autocomplete.mjs.map +1 -1
  4. package/fesm2022/radix-ng-primitives-checkbox.mjs +55 -7
  5. package/fesm2022/radix-ng-primitives-checkbox.mjs.map +1 -1
  6. package/fesm2022/radix-ng-primitives-combobox.mjs +46 -21
  7. package/fesm2022/radix-ng-primitives-combobox.mjs.map +1 -1
  8. package/fesm2022/radix-ng-primitives-composite.mjs +221 -36
  9. package/fesm2022/radix-ng-primitives-composite.mjs.map +1 -1
  10. package/fesm2022/radix-ng-primitives-core.mjs +189 -26
  11. package/fesm2022/radix-ng-primitives-core.mjs.map +1 -1
  12. package/fesm2022/radix-ng-primitives-date-field.mjs +29 -13
  13. package/fesm2022/radix-ng-primitives-date-field.mjs.map +1 -1
  14. package/fesm2022/radix-ng-primitives-field.mjs +127 -23
  15. package/fesm2022/radix-ng-primitives-field.mjs.map +1 -1
  16. package/fesm2022/radix-ng-primitives-floating-focus-manager.mjs +9 -0
  17. package/fesm2022/radix-ng-primitives-floating-focus-manager.mjs.map +1 -1
  18. package/fesm2022/radix-ng-primitives-focus-scope.mjs +241 -95
  19. package/fesm2022/radix-ng-primitives-focus-scope.mjs.map +1 -1
  20. package/fesm2022/radix-ng-primitives-form.mjs +35 -4
  21. package/fesm2022/radix-ng-primitives-form.mjs.map +1 -1
  22. package/fesm2022/radix-ng-primitives-input.mjs +87 -15
  23. package/fesm2022/radix-ng-primitives-input.mjs.map +1 -1
  24. package/fesm2022/radix-ng-primitives-menu.mjs +131 -16
  25. package/fesm2022/radix-ng-primitives-menu.mjs.map +1 -1
  26. package/fesm2022/radix-ng-primitives-navigation-menu.mjs +17 -3
  27. package/fesm2022/radix-ng-primitives-navigation-menu.mjs.map +1 -1
  28. package/fesm2022/radix-ng-primitives-popper.mjs +223 -29
  29. package/fesm2022/radix-ng-primitives-popper.mjs.map +1 -1
  30. package/fesm2022/radix-ng-primitives-portal.mjs +16 -5
  31. package/fesm2022/radix-ng-primitives-portal.mjs.map +1 -1
  32. package/fesm2022/radix-ng-primitives-scroll-area.mjs +6 -3
  33. package/fesm2022/radix-ng-primitives-scroll-area.mjs.map +1 -1
  34. package/fesm2022/radix-ng-primitives-select.mjs +49 -17
  35. package/fesm2022/radix-ng-primitives-select.mjs.map +1 -1
  36. package/fesm2022/radix-ng-primitives-signal-forms.mjs +27 -11
  37. package/fesm2022/radix-ng-primitives-signal-forms.mjs.map +1 -1
  38. package/fesm2022/radix-ng-primitives-time-field.mjs +24 -10
  39. package/fesm2022/radix-ng-primitives-time-field.mjs.map +1 -1
  40. package/package.json +8 -7
  41. package/types/radix-ng-primitives-autocomplete.d.ts +7 -2
  42. package/types/radix-ng-primitives-checkbox.d.ts +10 -1
  43. package/types/radix-ng-primitives-combobox.d.ts +29 -2
  44. package/types/radix-ng-primitives-composite.d.ts +48 -2
  45. package/types/radix-ng-primitives-core.d.ts +165 -104
  46. package/types/radix-ng-primitives-date-field.d.ts +21 -8
  47. package/types/radix-ng-primitives-field.d.ts +55 -8
  48. package/types/radix-ng-primitives-floating-focus-manager.d.ts +7 -0
  49. package/types/radix-ng-primitives-focus-scope.d.ts +61 -44
  50. package/types/radix-ng-primitives-form.d.ts +9 -0
  51. package/types/radix-ng-primitives-input.d.ts +10 -2
  52. package/types/radix-ng-primitives-menu.d.ts +59 -7
  53. package/types/radix-ng-primitives-navigation-menu.d.ts +2 -2
  54. package/types/radix-ng-primitives-popover.d.ts +2 -2
  55. package/types/radix-ng-primitives-popper.d.ts +186 -48
  56. package/types/radix-ng-primitives-portal.d.ts +18 -8
  57. package/types/radix-ng-primitives-preview-card.d.ts +2 -2
  58. package/types/radix-ng-primitives-select.d.ts +17 -3
  59. package/types/radix-ng-primitives-signal-forms.d.ts +22 -8
  60. package/types/radix-ng-primitives-time-field.d.ts +21 -10
  61. package/types/radix-ng-primitives-tooltip.d.ts +2 -2
  62. package/fesm2022/radix-ng-primitives-focus-guards.mjs +0 -53
  63. package/fesm2022/radix-ng-primitives-focus-guards.mjs.map +0 -1
  64. package/focus-guards/README.md +0 -1
  65. package/types/radix-ng-primitives-focus-guards.d.ts +0 -15
@@ -1,8 +1,9 @@
1
1
  import * as _angular_core from '@angular/core';
2
- import { ElementRef, Provider, Type, InjectionToken } from '@angular/core';
2
+ import { ElementRef, Signal, Provider, Type, InjectionToken } from '@angular/core';
3
3
  import { ReferenceElement, Placement, Middleware, VirtualElement } from '@floating-ui/dom';
4
+ import * as _radix_ng_primitives_popper from '@radix-ng/primitives/popper';
4
5
  import * as _radix_ng_primitives_core from '@radix-ng/primitives/core';
5
- import { NumberInput, BooleanInput } from '@radix-ng/primitives/core';
6
+ import { Direction, NumberInput, BooleanInput } from '@radix-ng/primitives/core';
6
7
 
7
8
  declare class RdxPopperAnchor {
8
9
  readonly elementRef: ElementRef<HTMLElement>;
@@ -25,7 +26,8 @@ declare class RdxPopperContent {
25
26
  * simply no-op.
26
27
  */
27
28
  protected readonly popperContentWrapper: {
28
- placedSide: _angular_core.Signal<"top" | "right" | "bottom" | "left" | undefined>;
29
+ placedSide: _angular_core.Signal<_radix_ng_primitives_popper.SideOrLogical | undefined>;
30
+ physicalPlacedSide: _angular_core.Signal<"top" | "right" | "bottom" | "left" | undefined>;
29
31
  placedAlign: _angular_core.Signal<"start" | "center" | "end" | undefined>;
30
32
  arrowX: _angular_core.Signal<number | undefined>;
31
33
  arrowY: _angular_core.Signal<number | undefined>;
@@ -48,6 +50,63 @@ declare const SIDE_OPTIONS: readonly ["top", "right", "bottom", "left"];
48
50
  declare const ALIGN_OPTIONS: readonly ["start", "center", "end"];
49
51
  type Side = (typeof SIDE_OPTIONS)[number];
50
52
  type Align = (typeof ALIGN_OPTIONS)[number];
53
+ /** Logical (writing-direction-relative) inline sides, resolved to a physical {@link Side} by text direction. */
54
+ type LogicalSide = 'inline-start' | 'inline-end';
55
+ /** A physical {@link Side} or a {@link LogicalSide} — accepted by the positioner's `side` input. */
56
+ type SideOrLogical = Side | LogicalSide;
57
+ /**
58
+ * Resolve a logical inline side to a physical one for the current text direction; physical sides pass
59
+ * through unchanged (Base UI `useAnchorPositioning`). `inline-start` is the reading-start edge
60
+ * (`left` in LTR, `right` in RTL); `inline-end` is the reading-end edge.
61
+ */
62
+ declare function resolvePhysicalSide(side: SideOrLogical, isRtl: boolean): Side;
63
+ /**
64
+ * Report a rendered physical side back in the same "kind" the consumer requested (Base UI
65
+ * `getLogicalSide`): when a logical side was requested, a `left`/`right` rendered side is mapped back
66
+ * to `inline-start`/`inline-end` for the direction (so a post-collision flip surfaces logically); a
67
+ * physical request, or a `top`/`bottom` rendered side, passes through unchanged.
68
+ */
69
+ declare function toLogicalSide(requestedSide: SideOrLogical, renderedSide: Side, isRtl: boolean): SideOrLogical;
70
+ /**
71
+ * How the popper avoids collisions with the boundary edges (Base UI `collisionAvoidance`).
72
+ *
73
+ * - `side` — behavior on the **preferred placement axis** (`top`/`bottom` or `left`/`right`):
74
+ * `'flip'` swaps to the opposite side when it doesn't fit, `'shift'` keeps the side and nudges the
75
+ * popup within the boundary, `'none'` leaves side-axis overflow uncorrected.
76
+ * - `align` — behavior on the **alignment axis** (`start`/`center`/`end`): `'flip'` swaps `start`/`end`,
77
+ * `'shift'` nudges the popup along the alignment axis, `'none'` leaves it uncorrected.
78
+ * - `fallbackAxisSide` — when neither preferred side fits, whether to fall back to the perpendicular
79
+ * axis and which logical side to prefer (`'start'` / `'end'`), or `'none'` to stay on the axis.
80
+ *
81
+ * Any omitted field falls back to the wrapper default (`side: 'flip'`, `align: 'flip'`,
82
+ * `fallbackAxisSide: 'end'`), matching Base UI.
83
+ */
84
+ interface RdxCollisionAvoidance {
85
+ side?: 'flip' | 'shift' | 'none';
86
+ align?: 'flip' | 'shift' | 'none';
87
+ fallbackAxisSide?: 'start' | 'end' | 'none';
88
+ }
89
+ /** Fully-resolved {@link RdxCollisionAvoidance} — every field present (no wrapper-default fallback left). */
90
+ type ResolvedCollisionAvoidance = Required<RdxCollisionAvoidance>;
91
+ /**
92
+ * Function form of `sideOffset` / `alignOffset` (Base UI `OffsetFunction`). Receives the resolved
93
+ * placement and the measured anchor / positioner dimensions, and returns the offset in pixels — e.g.
94
+ * `({ anchor }) => anchor.width` to offset by the trigger's own width. `side` is the side the popup is
95
+ * placed against — reported logically (`inline-start` / `inline-end`) when a logical side was
96
+ * requested, otherwise physical (Base UI parity).
97
+ */
98
+ type OffsetFunction = (data: {
99
+ side: SideOrLogical;
100
+ align: Align;
101
+ anchor: {
102
+ width: number;
103
+ height: number;
104
+ };
105
+ positioner: {
106
+ width: number;
107
+ height: number;
108
+ };
109
+ }) => number;
51
110
  declare function isNotNull<T>(value: T | null): value is T;
52
111
  declare function transformOrigin(options: {
53
112
  arrowWidth: number;
@@ -57,32 +116,35 @@ declare function getSideAndAlignFromPlacement(placement: Placement): readonly ["
57
116
 
58
117
  type RdxPopperAnchorElement = Element | ElementRef<Element> | VirtualElement | (() => Element | VirtualElement | null) | null | undefined;
59
118
  declare const context: () => {
60
- placedSide: _angular_core.Signal<"top" | "right" | "bottom" | "left" | undefined>;
61
- placedAlign: _angular_core.Signal<"start" | "center" | "end" | undefined>;
62
- arrowX: _angular_core.Signal<number | undefined>;
63
- arrowY: _angular_core.Signal<number | undefined>;
64
- shouldHideArrow: _angular_core.Signal<boolean>;
65
- isPositioned: _angular_core.Signal<boolean>;
66
- anchorHidden: _angular_core.Signal<boolean>;
119
+ placedSide: Signal<SideOrLogical | undefined>;
120
+ physicalPlacedSide: Signal<"top" | "right" | "bottom" | "left" | undefined>;
121
+ placedAlign: Signal<"start" | "center" | "end" | undefined>;
122
+ arrowX: Signal<number | undefined>;
123
+ arrowY: Signal<number | undefined>;
124
+ shouldHideArrow: Signal<boolean>;
125
+ isPositioned: Signal<boolean>;
126
+ anchorHidden: Signal<boolean>;
67
127
  };
68
128
  type PopperContentWrapperContext = ReturnType<typeof context>;
69
129
  declare const injectPopperContentWrapperContext: _radix_ng_primitives_core.InjectContext<{
70
- placedSide: _angular_core.Signal<"top" | "right" | "bottom" | "left" | undefined>;
71
- placedAlign: _angular_core.Signal<"start" | "center" | "end" | undefined>;
72
- arrowX: _angular_core.Signal<number | undefined>;
73
- arrowY: _angular_core.Signal<number | undefined>;
74
- shouldHideArrow: _angular_core.Signal<boolean>;
75
- isPositioned: _angular_core.Signal<boolean>;
76
- anchorHidden: _angular_core.Signal<boolean>;
130
+ placedSide: Signal<SideOrLogical | undefined>;
131
+ physicalPlacedSide: Signal<"top" | "right" | "bottom" | "left" | undefined>;
132
+ placedAlign: Signal<"start" | "center" | "end" | undefined>;
133
+ arrowX: Signal<number | undefined>;
134
+ arrowY: Signal<number | undefined>;
135
+ shouldHideArrow: Signal<boolean>;
136
+ isPositioned: Signal<boolean>;
137
+ anchorHidden: Signal<boolean>;
77
138
  }>;
78
139
  declare const providePopperContentWrapperContext: (useFactory: () => {
79
- placedSide: _angular_core.Signal<"top" | "right" | "bottom" | "left" | undefined>;
80
- placedAlign: _angular_core.Signal<"start" | "center" | "end" | undefined>;
81
- arrowX: _angular_core.Signal<number | undefined>;
82
- arrowY: _angular_core.Signal<number | undefined>;
83
- shouldHideArrow: _angular_core.Signal<boolean>;
84
- isPositioned: _angular_core.Signal<boolean>;
85
- anchorHidden: _angular_core.Signal<boolean>;
140
+ placedSide: Signal<SideOrLogical | undefined>;
141
+ physicalPlacedSide: Signal<"top" | "right" | "bottom" | "left" | undefined>;
142
+ placedAlign: Signal<"start" | "center" | "end" | undefined>;
143
+ arrowX: Signal<number | undefined>;
144
+ arrowY: Signal<number | undefined>;
145
+ shouldHideArrow: Signal<boolean>;
146
+ isPositioned: Signal<boolean>;
147
+ anchorHidden: Signal<boolean>;
86
148
  }) => Provider;
87
149
  declare class RdxPopperContentWrapper {
88
150
  private readonly elementRef;
@@ -97,25 +159,60 @@ declare class RdxPopperContentWrapper {
97
159
  /**
98
160
  * The preferred side of the anchor to render against when open.
99
161
  * Will be reversed when collisions occur and avoidCollisions is enabled.
162
+ *
163
+ * Accepts the logical `'inline-start'` / `'inline-end'` in addition to the physical sides; those
164
+ * resolve to `left` / `right` for the text direction (Base UI parity). The placed side reported via
165
+ * `data-side` / `placedSide()` echoes the requested kind: logical in → logical out (even after a
166
+ * collision flip), physical in → physical out.
167
+ */
168
+ readonly side: _angular_core.InputSignal<SideOrLogical>;
169
+ /**
170
+ * Text direction, used only to resolve a logical `side` (`inline-start` / `inline-end`) to a
171
+ * physical one. Falls back to an enclosing direction provider, then `ltr`.
100
172
  */
101
- readonly side: _angular_core.InputSignal<"top" | "right" | "bottom" | "left">;
173
+ readonly dir: _angular_core.InputSignal<Direction | undefined>;
174
+ private readonly direction;
175
+ /** The `side` input with any logical inline value resolved to a physical side for the direction. */
176
+ private readonly resolvedSide;
102
177
  /**
103
- * Distance between the anchor and the popup in pixels.
178
+ * Distance between the anchor and the popup in pixels. Also accepts an {@link OffsetFunction} that
179
+ * reads the anchor / positioner dimensions and the resolved side / align.
104
180
  */
105
- readonly sideOffset: _angular_core.InputSignalWithTransform<number, NumberInput>;
181
+ readonly sideOffset: _angular_core.InputSignalWithTransform<number | OffsetFunction, string | number | OffsetFunction | null | undefined>;
106
182
  /**
107
183
  * How to align the popup relative to the specified side. May change when collisions occur.
108
184
  */
109
185
  readonly align: _angular_core.InputSignal<"start" | "center" | "end">;
110
- /** An offset in pixels from the `start` or `end` alignment options. */
111
- readonly alignOffset: _angular_core.InputSignalWithTransform<number, NumberInput>;
186
+ /**
187
+ * An offset in pixels from the `start` or `end` alignment options. Also accepts an
188
+ * {@link OffsetFunction} (same signature as `sideOffset`).
189
+ */
190
+ readonly alignOffset: _angular_core.InputSignalWithTransform<number | OffsetFunction, string | number | OffsetFunction | null | undefined>;
112
191
  /**
113
192
  * Minimum distance to maintain between the arrow and the edges of the popup.
114
193
  * If your content has border-radius, this will prevent it from overflowing the corners.
115
194
  */
116
195
  readonly arrowPadding: _angular_core.InputSignalWithTransform<number, NumberInput>;
117
- /** When `true`, overrides the `side` and `align` preferences to prevent collisions with boundary edges. */
196
+ /**
197
+ * When `true`, overrides the `side` and `align` preferences to prevent collisions with boundary edges.
198
+ *
199
+ * @deprecated Use {@link collisionAvoidance} for fine-grained control (Base UI parity). Still honored:
200
+ * `false` disables all avoidance (unless a `collisionAvoidance` object is provided, which wins).
201
+ */
118
202
  readonly avoidCollisions: _angular_core.InputSignalWithTransform<boolean, BooleanInput>;
203
+ /**
204
+ * How the popup avoids colliding with the boundary edges, per axis (Base UI `collisionAvoidance`).
205
+ * Overrides the deprecated `avoidCollisions` and any per-primitive preset. An object here **fully
206
+ * replaces** the preset — omitted fields fall back to `side: 'flip'`, `align: 'flip'`,
207
+ * `fallbackAxisSide: 'end'`, never to the preset. See {@link RdxCollisionAvoidance}.
208
+ */
209
+ readonly collisionAvoidance: _angular_core.InputSignal<RdxCollisionAvoidance | undefined>;
210
+ /**
211
+ * Collision avoidance resolved against the deprecated `avoidCollisions` flag, the per-primitive
212
+ * config preset, and the wrapper defaults. Precedence: an explicit `collisionAvoidance` input →
213
+ * a legacy `avoidCollisions=false` (disables everything) → the config preset → the Base UI defaults.
214
+ */
215
+ private readonly effectiveCollisionAvoidance;
119
216
  /**
120
217
  * The element used as the collision boundary.
121
218
  * By default this is the viewport, though you can provide additional element(s) to be included in this check.
@@ -149,19 +246,19 @@ declare class RdxPopperContentWrapper {
149
246
  * Emits when the element is placed.
150
247
  */
151
248
  readonly placed: _angular_core.OutputEmitterRef<void>;
152
- readonly arrow: _angular_core.Signal<any>;
249
+ readonly arrow: Signal<any>;
153
250
  /**
154
251
  * When `true`, the content is rendered click/hover-through (`pointer-events: none`). Used for
155
252
  * cursor-following content (e.g. a tooltip tracking the pointer) that must never intercept the
156
253
  * pointer and steal hover from its trigger.
157
254
  */
158
255
  readonly nonInteractive: _angular_core.WritableSignal<boolean>;
159
- readonly shouldHideArrow: _angular_core.Signal<boolean>;
256
+ readonly shouldHideArrow: Signal<boolean>;
160
257
  /** Whether the arrow could not be centered on the anchor because the popup was shifted. */
161
- readonly arrowUncentered: _angular_core.Signal<boolean>;
162
- readonly arrowX: _angular_core.Signal<number | undefined>;
163
- readonly arrowY: _angular_core.Signal<number | undefined>;
164
- readonly anchorHidden: _angular_core.Signal<boolean>;
258
+ readonly arrowUncentered: Signal<boolean>;
259
+ readonly arrowX: Signal<number | undefined>;
260
+ readonly arrowY: Signal<number | undefined>;
261
+ readonly anchorHidden: Signal<boolean>;
165
262
  private readonly desiredPlacement;
166
263
  private readonly arrowSize;
167
264
  private readonly boundary;
@@ -183,23 +280,32 @@ declare class RdxPopperContentWrapper {
183
280
  /**
184
281
  * Whether the panel is positioned.
185
282
  */
186
- readonly isPositioned: _angular_core.Signal<boolean>;
283
+ readonly isPositioned: Signal<boolean>;
187
284
  /**
188
285
  * The current placement of the panel.
189
286
  */
190
- readonly placement: _angular_core.Signal<{
287
+ readonly placement: Signal<{
191
288
  side: Side;
192
289
  align: Align;
193
290
  } | undefined>;
194
291
  /**
195
- * The side the panel is currently placed against.
292
+ * The physical side the panel is currently placed against. Internal geometry (arrow position,
293
+ * transform origins) must use this — never {@link placedSide}, which can be logical.
196
294
  */
197
- readonly placedSide: _angular_core.Signal<"top" | "right" | "bottom" | "left" | undefined>;
295
+ readonly physicalPlacedSide: Signal<"top" | "right" | "bottom" | "left" | undefined>;
296
+ /**
297
+ * The side the panel is currently placed against, reported in the kind the consumer requested
298
+ * (Base UI positioner `side`): when a logical `side` was passed, a placed `left`/`right` maps back
299
+ * to `inline-start`/`inline-end` for the direction — so a post-collision flip stays logical and
300
+ * `[data-side="inline-start"]` CSS is direction-agnostic. A physical request is always reported
301
+ * physically.
302
+ */
303
+ readonly placedSide: Signal<SideOrLogical | undefined>;
198
304
  /**
199
305
  * The current alignment of the panel.
200
306
  */
201
- readonly placedAlign: _angular_core.Signal<"start" | "center" | "end" | undefined>;
202
- protected readonly style: _angular_core.Signal<{
307
+ readonly placedAlign: Signal<"start" | "center" | "end" | undefined>;
308
+ protected readonly style: Signal<{
203
309
  visibility: string;
204
310
  pointerEvents: string;
205
311
  position: "fixed" | "absolute";
@@ -218,10 +324,20 @@ declare class RdxPopperContentWrapper {
218
324
  '--positioner-height': string;
219
325
  '--transform-origin': string;
220
326
  }>;
327
+ /**
328
+ * Whether the positioner should actively track its anchor (recompute + `autoUpdate`). Defaults to
329
+ * always-on — the historical behavior, correct for a positioner that unmounts when its layer closes.
330
+ * A positioner that can stay mounted while closed (`keepMounted`) **overrides** this with its
331
+ * open/presence signal so a closed, hidden positioner doesn't spin an endless `autoUpdate`
332
+ * `requestAnimationFrame` loop (only relevant when `updatePositionStrategy: 'always'`). It's a
333
+ * reassignable field (not `readonly`) because the render effect below reads it lazily, after the
334
+ * subclass constructor has swapped it in.
335
+ */
336
+ protected positioningActive: Signal<boolean>;
221
337
  private readonly afterRenderEffect;
222
338
  constructor();
223
339
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<RdxPopperContentWrapper, never>;
224
- static ɵdir: _angular_core.ɵɵDirectiveDeclaration<RdxPopperContentWrapper, "[rdxPopperContentWrapper]", never, { "anchor": { "alias": "anchor"; "required": false; "isSignal": true; }; "side": { "alias": "side"; "required": false; "isSignal": true; }; "sideOffset": { "alias": "sideOffset"; "required": false; "isSignal": true; }; "align": { "alias": "align"; "required": false; "isSignal": true; }; "alignOffset": { "alias": "alignOffset"; "required": false; "isSignal": true; }; "arrowPadding": { "alias": "arrowPadding"; "required": false; "isSignal": true; }; "avoidCollisions": { "alias": "avoidCollisions"; "required": false; "isSignal": true; }; "collisionBoundary": { "alias": "collisionBoundary"; "required": false; "isSignal": true; }; "collisionPadding": { "alias": "collisionPadding"; "required": false; "isSignal": true; }; "sticky": { "alias": "sticky"; "required": false; "isSignal": true; }; "hideWhenDetached": { "alias": "hideWhenDetached"; "required": false; "isSignal": true; }; "positionStrategy": { "alias": "positionStrategy"; "required": false; "isSignal": true; }; "updatePositionStrategy": { "alias": "updatePositionStrategy"; "required": false; "isSignal": true; }; }, { "placed": "placed"; }, ["arrow"], never, true, never>;
340
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<RdxPopperContentWrapper, "[rdxPopperContentWrapper]", never, { "anchor": { "alias": "anchor"; "required": false; "isSignal": true; }; "side": { "alias": "side"; "required": false; "isSignal": true; }; "dir": { "alias": "dir"; "required": false; "isSignal": true; }; "sideOffset": { "alias": "sideOffset"; "required": false; "isSignal": true; }; "align": { "alias": "align"; "required": false; "isSignal": true; }; "alignOffset": { "alias": "alignOffset"; "required": false; "isSignal": true; }; "arrowPadding": { "alias": "arrowPadding"; "required": false; "isSignal": true; }; "avoidCollisions": { "alias": "avoidCollisions"; "required": false; "isSignal": true; }; "collisionAvoidance": { "alias": "collisionAvoidance"; "required": false; "isSignal": true; }; "collisionBoundary": { "alias": "collisionBoundary"; "required": false; "isSignal": true; }; "collisionPadding": { "alias": "collisionPadding"; "required": false; "isSignal": true; }; "sticky": { "alias": "sticky"; "required": false; "isSignal": true; }; "hideWhenDetached": { "alias": "hideWhenDetached"; "required": false; "isSignal": true; }; "positionStrategy": { "alias": "positionStrategy"; "required": false; "isSignal": true; }; "updatePositionStrategy": { "alias": "updatePositionStrategy"; "required": false; "isSignal": true; }; }, { "placed": "placed"; }, ["arrow"], never, true, never>;
225
341
  }
226
342
  /**
227
343
  * Providers a "thin" positioner that `extends RdxPopperContentWrapper` must include. Angular
@@ -280,22 +396,44 @@ declare class RdxPopperArrow {
280
396
  * `undefined` falls back to the wrapper's built-in default. Consumer template bindings always win.
281
397
  */
282
398
  interface RdxPopperContentConfig {
283
- side?: Side;
399
+ side?: SideOrLogical;
284
400
  align?: Align;
285
- sideOffset?: number;
286
- alignOffset?: number;
401
+ sideOffset?: number | OffsetFunction;
402
+ alignOffset?: number | OffsetFunction;
287
403
  arrowPadding?: number;
404
+ /**
405
+ * @deprecated Use {@link collisionAvoidance} instead. Kept for back-compat: `false` disables all
406
+ * collision avoidance (equivalent to `{ side: 'none', align: 'none', fallbackAxisSide: 'none' }`).
407
+ */
288
408
  avoidCollisions?: boolean;
409
+ /** Per-primitive collision-avoidance default (Base UI parity). See {@link RdxCollisionAvoidance}. */
410
+ collisionAvoidance?: RdxCollisionAvoidance;
289
411
  collisionPadding?: number | Partial<Record<Side, number>>;
290
412
  sticky?: 'partial' | 'always';
291
413
  hideWhenDetached?: boolean;
292
414
  positionStrategy?: 'fixed' | 'absolute';
293
415
  updatePositionStrategy?: 'optimized' | 'always';
294
416
  }
417
+ /**
418
+ * Collision-avoidance preset for dropdown-style popups (Base UI `DROPDOWN_COLLISION_AVOIDANCE`).
419
+ *
420
+ * Dropdowns (select / combobox / autocomplete / menu) strictly prefer their top/bottom placement and
421
+ * cap their height with `var(--available-height)`, so they must **not** fall back to the perpendicular
422
+ * (left/right) axis. `side`/`align` fall back to the wrapper default (`'flip'`).
423
+ */
424
+ declare const DROPDOWN_COLLISION_AVOIDANCE: RdxCollisionAvoidance;
425
+ /**
426
+ * Collision-avoidance preset for regular popups (Base UI `POPUP_COLLISION_AVOIDANCE`).
427
+ *
428
+ * Popovers / tooltips / preview cards aren't height-capped and may freely flip to the perpendicular
429
+ * axis, preferring the logical end side. This equals the wrapper's built-in default; it is exported so
430
+ * popups can state the intent explicitly.
431
+ */
432
+ declare const POPUP_COLLISION_AVOIDANCE: RdxCollisionAvoidance;
295
433
  declare const RdxPopperContentConfigToken: InjectionToken<RdxPopperContentConfig>;
296
434
  declare function provideRdxPopperContentConfig(config: RdxPopperContentConfig): Provider;
297
435
 
298
436
  declare const popperImports: (typeof RdxPopperAnchor | typeof RdxPopper | typeof RdxPopperContentWrapper | typeof RdxPopperContent)[];
299
437
 
300
- export { RdxPopper, RdxPopperAnchor, RdxPopperArrow, RdxPopperContent, RdxPopperContentConfigToken, RdxPopperContentWrapper, getSideAndAlignFromPlacement, injectPopperContentWrapperContext, isNotNull, legacyPopperVars, popperImports, providePopperContentWrapperContext, provideRdxPopperContentConfig, provideRdxPopperContentWrapper, transformOrigin };
301
- export type { Align, PopperContentWrapperContext, RdxPopperAnchorElement, RdxPopperContentConfig, Side };
438
+ export { DROPDOWN_COLLISION_AVOIDANCE, POPUP_COLLISION_AVOIDANCE, RdxPopper, RdxPopperAnchor, RdxPopperArrow, RdxPopperContent, RdxPopperContentConfigToken, RdxPopperContentWrapper, getSideAndAlignFromPlacement, injectPopperContentWrapperContext, isNotNull, legacyPopperVars, popperImports, providePopperContentWrapperContext, provideRdxPopperContentConfig, provideRdxPopperContentWrapper, resolvePhysicalSide, toLogicalSide, transformOrigin };
439
+ export type { Align, LogicalSide, OffsetFunction, PopperContentWrapperContext, RdxCollisionAvoidance, RdxPopperAnchorElement, RdxPopperContentConfig, ResolvedCollisionAvoidance, Side, SideOrLogical };
@@ -1,4 +1,4 @@
1
- import * as i0 from '@angular/core';
1
+ import * as _angular_core from '@angular/core';
2
2
  import { ElementRef } from '@angular/core';
3
3
 
4
4
  /**
@@ -22,14 +22,14 @@ declare class RdxPortal {
22
22
  * Specify a container to portal the content into. Can be an `ElementRef`, a native element, or a
23
23
  * CSS selector. Defaults to `document.body` when not set (or when a selector matches nothing).
24
24
  */
25
- readonly container: i0.InputSignal<RdxPortalContainer | undefined>;
25
+ readonly container: _angular_core.InputSignal<RdxPortalContainer | undefined>;
26
26
  private readonly _computedContainer;
27
- readonly computedContainer: i0.Signal<RdxPortalContainer | undefined>;
27
+ readonly computedContainer: _angular_core.Signal<RdxPortalContainer | undefined>;
28
28
  private readonly elementContainer;
29
29
  constructor();
30
30
  setContainer(container: RdxPortalContainer): void;
31
- static ɵfac: i0.ɵɵFactoryDeclaration<RdxPortal, never>;
32
- static ɵdir: i0.ɵɵDirectiveDeclaration<RdxPortal, "[rdxPortal]", ["rdxPortal"], { "container": { "alias": "container"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
31
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<RdxPortal, never>;
32
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<RdxPortal, "[rdxPortal]", ["rdxPortal"], { "container": { "alias": "container"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
33
33
  }
34
34
 
35
35
  /**
@@ -56,7 +56,17 @@ declare class RdxPortalPresence {
56
56
  * Container to portal the content into. Can be an `ElementRef`, a native element, or a CSS
57
57
  * selector. Defaults to `document.body` (or when a selector matches nothing).
58
58
  */
59
- readonly container: i0.InputSignal<RdxPortalContainer | undefined>;
59
+ readonly container: _angular_core.InputSignal<RdxPortalContainer | undefined>;
60
+ /**
61
+ * Keep the portal mounted (in the container) while the layer is closed. When `true` the content
62
+ * stays in the DOM regardless of the {@link RDX_PRESENCE_CONTEXT} `present` flag — the consumer is
63
+ * responsible for hiding it while closed (typically via the `data-closed` / `data-open` state on the
64
+ * positioner). Any `*rdxXxxPortal` wrapper opts in by mapping this input in its `hostDirectives`.
65
+ */
66
+ readonly keepMounted: _angular_core.InputSignalWithTransform<boolean, unknown>;
67
+ /** The layer's own open/present flag, OR'd with {@link keepMounted} to gate mount/unmount. */
68
+ private readonly contextPresent;
69
+ private readonly present;
60
70
  private readonly _computedContainer;
61
71
  private readonly elementContainer;
62
72
  /** The live view's root nodes, exposed as a signal so the relocation effect re-runs on (re)mount. */
@@ -67,8 +77,8 @@ declare class RdxPortalPresence {
67
77
  setContainer(container: RdxPortalContainer): void;
68
78
  private mountView;
69
79
  private destroyView;
70
- static ɵfac: i0.ɵɵFactoryDeclaration<RdxPortalPresence, never>;
71
- static ɵdir: i0.ɵɵDirectiveDeclaration<RdxPortalPresence, never, never, { "container": { "alias": "container"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
80
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<RdxPortalPresence, never>;
81
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<RdxPortalPresence, never, never, { "container": { "alias": "container"; "required": false; "isSignal": true; }; "keepMounted": { "alias": "keepMounted"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
72
82
  }
73
83
 
74
84
  export { RdxPortal, RdxPortalPresence, resolvePortalContainer };
@@ -268,7 +268,7 @@ declare class RdxPreviewCardPopup {
268
268
  private readonly registration;
269
269
  private readonly wrapper;
270
270
  protected readonly align: _angular_core.Signal<"start" | "center" | "end" | undefined>;
271
- protected readonly side: _angular_core.Signal<"top" | "right" | "bottom" | "left" | undefined>;
271
+ protected readonly side: _angular_core.Signal<i1$1.SideOrLogical | undefined>;
272
272
  /**
273
273
  * Event handler called when the escape key is down. Can be prevented.
274
274
  */
@@ -296,7 +296,7 @@ declare class RdxPreviewCardPopup {
296
296
  declare class RdxPreviewCardArrow {
297
297
  protected readonly rootContext: _radix_ng_primitives_preview_card.RdxPreviewCardRootContext;
298
298
  private readonly wrapper;
299
- protected readonly side: _angular_core.Signal<"top" | "right" | "bottom" | "left" | undefined>;
299
+ protected readonly side: _angular_core.Signal<i1$1.SideOrLogical | undefined>;
300
300
  protected readonly align: _angular_core.Signal<"start" | "center" | "end" | undefined>;
301
301
  protected readonly uncentered: _angular_core.Signal<boolean>;
302
302
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<RdxPreviewCardArrow, never>;
@@ -1,7 +1,7 @@
1
1
  import * as _angular_core from '@angular/core';
2
2
  import { Signal, ElementRef, InjectionToken, OutputRef, WritableSignal } from '@angular/core';
3
3
  import * as _radix_ng_primitives_core from '@radix-ng/primitives/core';
4
- import { AcceptableValue, RdxCancelableChangeEventDetails, RdxFormUiControlBase, RdxFormValueControl, RdxFloatingRootContext, Direction, ItemValueComparator, RdxTransitionStatus, RdxFormUiStateContext } from '@radix-ng/primitives/core';
4
+ import { AcceptableValue, RdxCancelableChangeEventDetails, RdxFormUiControlBase, RdxFormValueControl, RdxFloatingRootContext, Direction, ItemValueComparator, RdxTransitionStatus, RdxFormUiTouchTarget, RdxFormUiStateContext } from '@radix-ng/primitives/core';
5
5
  import * as _radix_ng_primitives_select from '@radix-ng/primitives/select';
6
6
  import * as i1 from '@radix-ng/primitives/composite';
7
7
  import { RdxCompositeItemRegistration } from '@radix-ng/primitives/composite';
@@ -11,6 +11,7 @@ import { RdxPopperContentWrapper } from '@radix-ng/primitives/popper';
11
11
  import * as i2 from '@radix-ng/primitives/floating-focus-manager';
12
12
  import { RdxInteractionType } from '@radix-ng/primitives/floating-focus-manager';
13
13
  import * as _radix_ng_primitives_types_radix_ng_primitives_core from '@radix-ng/primitives/types/radix-ng-primitives-core';
14
+ import { ControlValueAccessor } from '@angular/forms';
14
15
  import * as i1$2 from '@radix-ng/primitives/portal';
15
16
 
16
17
  declare class RdxSelectGroupLabel {
@@ -365,7 +366,7 @@ interface RdxSelectRootContext extends RdxFormUiStateContext {
365
366
  }
366
367
  declare const injectSelectRootContext: _radix_ng_primitives_core.InjectContext<RdxSelectRootContext>;
367
368
  declare const provideSelectRootContext: (useFactory: () => RdxSelectRootContext) => _angular_core.Provider;
368
- declare class RdxSelectRoot extends RdxFormUiControlBase implements RdxFormValueControl<AcceptableValue | AcceptableValue[] | undefined> {
369
+ declare class RdxSelectRoot extends RdxFormUiControlBase implements ControlValueAccessor, RdxFormValueControl<AcceptableValue | AcceptableValue[] | undefined> {
369
370
  readonly contentId: string;
370
371
  readonly open: _angular_core.ModelSignal<boolean>;
371
372
  /** Whether the current open was initiated by **touch** (ADR 0016 §3 — gates the anchored scroll lock). */
@@ -385,6 +386,8 @@ declare class RdxSelectRoot extends RdxFormUiControlBase implements RdxFormValue
385
386
  readonly defaultOpen: _angular_core.InputSignalWithTransform<boolean, unknown>;
386
387
  readonly multiple: _angular_core.InputSignalWithTransform<boolean, unknown>;
387
388
  readonly disabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
389
+ private readonly cvaDisabled;
390
+ readonly disabledState: Signal<boolean>;
388
391
  /** When `true`, the value cannot be changed by the user (the popup can still be opened to view it). */
389
392
  readonly readOnly: _angular_core.InputSignalWithTransform<boolean, unknown>;
390
393
  /** Marks the control as required — reflected on the trigger as `aria-required` / `data-required`. */
@@ -423,6 +426,18 @@ declare class RdxSelectRoot extends RdxFormUiControlBase implements RdxFormValue
423
426
  getOption(value: SelectOption['value']): SelectOption | undefined;
424
427
  setValue(value: AcceptableValue | undefined, reason?: RdxSelectValueChangeReason, event?: Event): boolean;
425
428
  setOpen(open: boolean, reason?: RdxSelectOpenChangeReason, event?: Event): boolean;
429
+ /** @ignore Bridge the CVA touched callback into the shared Signal Forms UI-state path. */
430
+ protected formUiTouchTarget(): RdxFormUiTouchTarget;
431
+ /** @ignore Write a form-owned value without emitting a user change. */
432
+ writeValue(value: AcceptableValue | AcceptableValue[] | undefined): void;
433
+ /** @ignore Register the Reactive Forms / ngModel value callback. */
434
+ registerOnChange(fn: (value: AcceptableValue | AcceptableValue[] | undefined) => void): void;
435
+ /** @ignore Register the Reactive Forms / ngModel touched callback. */
436
+ registerOnTouched(fn: () => void): void;
437
+ /** @ignore Merge form-owned disabled state with the public disabled input. */
438
+ setDisabledState(isDisabled: boolean): void;
439
+ private onChange?;
440
+ private onTouched?;
426
441
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<RdxSelectRoot, never>;
427
442
  static ɵdir: _angular_core.ɵɵDirectiveDeclaration<RdxSelectRoot, "[rdxSelectRoot]", ["rdxSelectRoot"], { "open": { "alias": "open"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; "defaultValue": { "alias": "defaultValue"; "required": false; "isSignal": true; }; "defaultOpen": { "alias": "defaultOpen"; "required": false; "isSignal": true; }; "multiple": { "alias": "multiple"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "readOnly": { "alias": "readOnly"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "modal": { "alias": "modal"; "required": false; "isSignal": true; }; "dirInput": { "alias": "dir"; "required": false; "isSignal": true; }; "isItemEqualToValue": { "alias": "isItemEqualToValue"; "required": false; "isSignal": true; }; "itemToStringLabel": { "alias": "itemToStringLabel"; "required": false; "isSignal": true; }; }, { "open": "openChange"; "value": "valueChange"; "onOpenChange": "onOpenChange"; "onValueChange": "onValueChange"; "onOpenChangeComplete": "onOpenChangeComplete"; }, never, never, true, [{ directive: typeof i1$1.RdxPopper; inputs: {}; outputs: {}; }]>;
428
443
  }
@@ -474,7 +489,6 @@ declare class RdxSelectTrigger {
474
489
  readonly disabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
475
490
  readonly isDisabled: _angular_core.Signal<boolean>;
476
491
  protected readonly triggerInteraction: i2.RdxTriggerInteraction;
477
- protected readonly invalidState: _angular_core.Signal<boolean>;
478
492
  /**
479
493
  * Tri-state *displayed* validity: when inside a `rdxFieldRoot` the field's gated `validState` is the
480
494
  * single source (so the field's `validationMode` keeps the trigger neutral until revealed), otherwise
@@ -1,5 +1,6 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { FieldTree } from '@angular/forms/signals';
3
+ import { BooleanInput } from '@radix-ng/primitives/core';
3
4
 
4
5
  /**
5
6
  * Bridges an Angular **Signal Forms** field into an enclosing `rdxFieldRoot` — with **no duplicate
@@ -16,9 +17,9 @@ import { FieldTree } from '@angular/forms/signals';
16
17
  *
17
18
  * It owns no model and runs no validation — Signal Forms remains the source of truth. It only registers
18
19
  * an {@link RdxFieldState} provider on the ancestor Field context so the field's `invalid` / `disabled` /
19
- * `required` / `dirty` / `touched` data-attributes and the error *content* (`rdxFieldError.messages()`)
20
- * read authoritative Signal Forms state. `filled` / `focused` stay on Field's DOM heuristic (partial
21
- * ownership — the seam supports it).
20
+ * `required` / `dirty` / `touched` data-attributes, tri-state pending validity, and the error *content*
21
+ * (`rdxFieldError.messages()`) read authoritative Signal Forms state. `filled` / `focused` stay on
22
+ * Field's DOM heuristic (partial ownership — the seam supports it).
22
23
  *
23
24
  * It reports the **actual** Signal Forms state only; the `Field` decides *when* to display it from its
24
25
  * `validationMode` (default `'onBlur'` — neutral until the field is touched or the form submitted). So an
@@ -51,9 +52,13 @@ declare class RdxSignalField {
51
52
  *
52
53
  * Registers an {@link RdxFormState} provider so the Form's aggregate `data-invalid` / `data-dirty` /
53
54
  * `data-touched` / `data-submitting` attributes and the submit guard read authoritative Signal Forms
54
- * state. This adapter owns only client-side state and `name`-routing; server errors stay a separate
55
- * eager channel applied through `rdxFormRoot[errors]`, and Signal Forms' own `submit()` owns the
56
- * submit lifecycle.
55
+ * state. By default this adapter owns only client-side state and `name`-routing; server errors stay a
56
+ * separate eager channel applied through `rdxFormRoot[errors]`. Add `rdxSignalSubmit` to delegate native
57
+ * submission to Signal Forms' own `submit()` lifecycle instead of `rdxFormRoot.onFormSubmit`:
58
+ *
59
+ * ```html
60
+ * <form rdxFormRoot [rdxSignalForm]="loginForm" rdxSignalSubmit>…</form>
61
+ * ```
57
62
  *
58
63
  * `errorsFor(name)` routes a field's errors to a `rdxFieldRoot` by its `name`, walking the `FieldTree`
59
64
  * so dotted paths into nested object/array fields resolve too (`address.street`, `items.0.name`). A
@@ -62,13 +67,22 @@ declare class RdxSignalField {
62
67
  * a field carries both, `rdxFieldError.messages()` deduplicates by text so the shared message is not
63
68
  * shown twice.
64
69
  *
65
- * See ADR 0018.
70
+ * See ADR 0018 and ADR 0020.
66
71
  *
67
72
  * @group Components
68
73
  */
69
74
  declare class RdxSignalForm {
70
75
  /** The Signal Forms root field (from `form(...)`) whose aggregate state drives the enclosing Form. */
71
76
  readonly form: i0.InputSignal<FieldTree<unknown>>;
77
+ /**
78
+ * Delegate native submission to Angular Signal Forms' `submit()` lifecycle. Opt-in so adding the
79
+ * state adapter to an existing 1.x form cannot silently replace its `(onFormSubmit)` side effects.
80
+ * The bound `form()` must define `submission.action`; Angular reports a descriptive error otherwise.
81
+ *
82
+ * @group Props
83
+ * @defaultValue false
84
+ */
85
+ readonly signalSubmit: i0.InputSignalWithTransform<boolean, BooleanInput>;
72
86
  private readonly formContext;
73
87
  constructor();
74
88
  /**
@@ -84,7 +98,7 @@ declare class RdxSignalForm {
84
98
  /** The current Signal Forms root field state (a `FieldTree` is callable). */
85
99
  private state;
86
100
  static ɵfac: i0.ɵɵFactoryDeclaration<RdxSignalForm, never>;
87
- static ɵdir: i0.ɵɵDirectiveDeclaration<RdxSignalForm, "form[rdxFormRoot][rdxSignalForm]", ["rdxSignalForm"], { "form": { "alias": "rdxSignalForm"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
101
+ static ɵdir: i0.ɵɵDirectiveDeclaration<RdxSignalForm, "form[rdxFormRoot][rdxSignalForm]", ["rdxSignalForm"], { "form": { "alias": "rdxSignalForm"; "required": true; "isSignal": true; }; "signalSubmit": { "alias": "rdxSignalSubmit"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
88
102
  }
89
103
 
90
104
  declare const _importsSignalForms: (typeof RdxSignalField | typeof RdxSignalForm)[];