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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/README.md +1 -1
  2. package/fesm2022/radix-ng-primitives-accordion.mjs +5 -3
  3. package/fesm2022/radix-ng-primitives-accordion.mjs.map +1 -1
  4. package/fesm2022/radix-ng-primitives-alert-dialog.mjs +3 -2
  5. package/fesm2022/radix-ng-primitives-alert-dialog.mjs.map +1 -1
  6. package/fesm2022/radix-ng-primitives-autocomplete.mjs +617 -659
  7. package/fesm2022/radix-ng-primitives-autocomplete.mjs.map +1 -1
  8. package/fesm2022/radix-ng-primitives-calendar.mjs +5 -3
  9. package/fesm2022/radix-ng-primitives-calendar.mjs.map +1 -1
  10. package/fesm2022/radix-ng-primitives-combobox.mjs +1305 -572
  11. package/fesm2022/radix-ng-primitives-combobox.mjs.map +1 -1
  12. package/fesm2022/radix-ng-primitives-config.mjs +13 -4
  13. package/fesm2022/radix-ng-primitives-config.mjs.map +1 -1
  14. package/fesm2022/radix-ng-primitives-context-menu.mjs +51 -10
  15. package/fesm2022/radix-ng-primitives-context-menu.mjs.map +1 -1
  16. package/fesm2022/radix-ng-primitives-core.mjs +1345 -64
  17. package/fesm2022/radix-ng-primitives-core.mjs.map +1 -1
  18. package/fesm2022/radix-ng-primitives-date-field.mjs +5 -3
  19. package/fesm2022/radix-ng-primitives-date-field.mjs.map +1 -1
  20. package/fesm2022/radix-ng-primitives-dialog.mjs +240 -112
  21. package/fesm2022/radix-ng-primitives-dialog.mjs.map +1 -1
  22. package/fesm2022/radix-ng-primitives-direction-provider.mjs +70 -0
  23. package/fesm2022/radix-ng-primitives-direction-provider.mjs.map +1 -0
  24. package/fesm2022/radix-ng-primitives-dismissable-layer.mjs +519 -184
  25. package/fesm2022/radix-ng-primitives-dismissable-layer.mjs.map +1 -1
  26. package/fesm2022/radix-ng-primitives-drawer.mjs +3 -3
  27. package/fesm2022/radix-ng-primitives-drawer.mjs.map +1 -1
  28. package/fesm2022/radix-ng-primitives-field.mjs +3 -2
  29. package/fesm2022/radix-ng-primitives-field.mjs.map +1 -1
  30. package/fesm2022/radix-ng-primitives-floating-focus-manager.mjs +517 -0
  31. package/fesm2022/radix-ng-primitives-floating-focus-manager.mjs.map +1 -0
  32. package/fesm2022/radix-ng-primitives-focus-scope.mjs +296 -70
  33. package/fesm2022/radix-ng-primitives-focus-scope.mjs.map +1 -1
  34. package/fesm2022/radix-ng-primitives-menu.mjs +861 -286
  35. package/fesm2022/radix-ng-primitives-menu.mjs.map +1 -1
  36. package/fesm2022/radix-ng-primitives-menubar.mjs +32 -4
  37. package/fesm2022/radix-ng-primitives-menubar.mjs.map +1 -1
  38. package/fesm2022/radix-ng-primitives-navigation-menu.mjs +144 -159
  39. package/fesm2022/radix-ng-primitives-navigation-menu.mjs.map +1 -1
  40. package/fesm2022/radix-ng-primitives-popover.mjs +220 -205
  41. package/fesm2022/radix-ng-primitives-popover.mjs.map +1 -1
  42. package/fesm2022/radix-ng-primitives-popper.mjs +94 -51
  43. package/fesm2022/radix-ng-primitives-popper.mjs.map +1 -1
  44. package/fesm2022/radix-ng-primitives-presence.mjs +1 -1
  45. package/fesm2022/radix-ng-primitives-presence.mjs.map +1 -1
  46. package/fesm2022/radix-ng-primitives-preview-card.mjs +141 -173
  47. package/fesm2022/radix-ng-primitives-preview-card.mjs.map +1 -1
  48. package/fesm2022/radix-ng-primitives-roving-focus.mjs +4 -2
  49. package/fesm2022/radix-ng-primitives-roving-focus.mjs.map +1 -1
  50. package/fesm2022/radix-ng-primitives-scroll-area.mjs +5 -4
  51. package/fesm2022/radix-ng-primitives-scroll-area.mjs.map +1 -1
  52. package/fesm2022/radix-ng-primitives-select.mjs +211 -156
  53. package/fesm2022/radix-ng-primitives-select.mjs.map +1 -1
  54. package/fesm2022/radix-ng-primitives-slider.mjs +5 -3
  55. package/fesm2022/radix-ng-primitives-slider.mjs.map +1 -1
  56. package/fesm2022/radix-ng-primitives-stepper.mjs +5 -3
  57. package/fesm2022/radix-ng-primitives-stepper.mjs.map +1 -1
  58. package/fesm2022/radix-ng-primitives-time-field.mjs +5 -3
  59. package/fesm2022/radix-ng-primitives-time-field.mjs.map +1 -1
  60. package/fesm2022/radix-ng-primitives-toast.mjs +15 -36
  61. package/fesm2022/radix-ng-primitives-toast.mjs.map +1 -1
  62. package/fesm2022/radix-ng-primitives-toggle-group.mjs +5 -3
  63. package/fesm2022/radix-ng-primitives-toggle-group.mjs.map +1 -1
  64. package/fesm2022/radix-ng-primitives-toolbar.mjs +5 -3
  65. package/fesm2022/radix-ng-primitives-toolbar.mjs.map +1 -1
  66. package/fesm2022/radix-ng-primitives-tooltip.mjs +73 -110
  67. package/fesm2022/radix-ng-primitives-tooltip.mjs.map +1 -1
  68. package/package.json +10 -1
  69. package/types/radix-ng-primitives-accordion.d.ts +4 -3
  70. package/types/radix-ng-primitives-autocomplete.d.ts +217 -152
  71. package/types/radix-ng-primitives-calendar.d.ts +5 -3
  72. package/types/radix-ng-primitives-combobox.d.ts +672 -283
  73. package/types/radix-ng-primitives-config.d.ts +1 -1
  74. package/types/radix-ng-primitives-context-menu.d.ts +15 -5
  75. package/types/radix-ng-primitives-core.d.ts +762 -14
  76. package/types/radix-ng-primitives-date-field.d.ts +3 -2
  77. package/types/radix-ng-primitives-dialog.d.ts +77 -32
  78. package/types/radix-ng-primitives-direction-provider.d.ts +41 -0
  79. package/types/radix-ng-primitives-dismissable-layer.d.ts +147 -99
  80. package/types/radix-ng-primitives-field.d.ts +1 -0
  81. package/types/radix-ng-primitives-floating-focus-manager.d.ts +175 -0
  82. package/types/radix-ng-primitives-focus-scope.d.ts +132 -1
  83. package/types/radix-ng-primitives-menu.d.ts +186 -103
  84. package/types/radix-ng-primitives-navigation-menu.d.ts +37 -75
  85. package/types/radix-ng-primitives-popover.d.ts +59 -92
  86. package/types/radix-ng-primitives-popper.d.ts +39 -9
  87. package/types/radix-ng-primitives-preview-card.d.ts +39 -72
  88. package/types/radix-ng-primitives-roving-focus.d.ts +7 -6
  89. package/types/radix-ng-primitives-scroll-area.d.ts +2 -2
  90. package/types/radix-ng-primitives-select.d.ts +145 -108
  91. package/types/radix-ng-primitives-slider.d.ts +5 -4
  92. package/types/radix-ng-primitives-stepper.d.ts +4 -3
  93. package/types/radix-ng-primitives-time-field.d.ts +3 -2
  94. package/types/radix-ng-primitives-toast.d.ts +7 -7
  95. package/types/radix-ng-primitives-toggle-group.d.ts +5 -4
  96. package/types/radix-ng-primitives-toolbar.d.ts +3 -2
  97. package/types/radix-ng-primitives-tooltip.d.ts +24 -67
@@ -26,7 +26,8 @@ declare class RdxDateFieldRootDirective {
26
26
  * The locale to use for formatting dates.
27
27
  */
28
28
  readonly locale: _angular_core.InputSignal<string>;
29
- readonly dir: _angular_core.InputSignal<Direction>;
29
+ readonly dirInput: _angular_core.InputSignal<Direction | undefined>;
30
+ readonly dir: Signal<Direction>;
30
31
  /**
31
32
  * The minimum valid date that can be entered.
32
33
  */
@@ -138,7 +139,7 @@ declare class RdxDateFieldRootDirective {
138
139
  */
139
140
  setFocusedElement(el: HTMLElement): void;
140
141
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<RdxDateFieldRootDirective, never>;
141
- static ɵdir: _angular_core.ɵɵDirectiveDeclaration<RdxDateFieldRootDirective, "[rdxDateFieldRoot]", ["rdxDateFieldRoot"], { "value": { "alias": "value"; "required": false; "isSignal": true; }; "isDateUnavailable": { "alias": "isDateUnavailable"; "required": false; "isSignal": true; }; "hourCycle": { "alias": "hourCycle"; "required": false; "isSignal": true; }; "granularity": { "alias": "granularity"; "required": false; "isSignal": true; }; "locale": { "alias": "locale"; "required": false; "isSignal": true; }; "dir": { "alias": "dir"; "required": false; "isSignal": true; }; "minValue": { "alias": "minValue"; "required": false; "isSignal": true; }; "maxValue": { "alias": "maxValue"; "required": false; "isSignal": true; }; "hideTimeZone": { "alias": "hideTimeZone"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "step": { "alias": "step"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "placeholder": "placeholderChange"; }, ["segmentInputs"], never, true, never>;
142
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<RdxDateFieldRootDirective, "[rdxDateFieldRoot]", ["rdxDateFieldRoot"], { "value": { "alias": "value"; "required": false; "isSignal": true; }; "isDateUnavailable": { "alias": "isDateUnavailable"; "required": false; "isSignal": true; }; "hourCycle": { "alias": "hourCycle"; "required": false; "isSignal": true; }; "granularity": { "alias": "granularity"; "required": false; "isSignal": true; }; "locale": { "alias": "locale"; "required": false; "isSignal": true; }; "dirInput": { "alias": "dir"; "required": false; "isSignal": true; }; "minValue": { "alias": "minValue"; "required": false; "isSignal": true; }; "maxValue": { "alias": "maxValue"; "required": false; "isSignal": true; }; "hideTimeZone": { "alias": "hideTimeZone"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "step": { "alias": "step"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "placeholder": "placeholderChange"; }, ["segmentInputs"], never, true, never>;
142
143
  }
143
144
 
144
145
  declare class RdxDateFieldInputDirective {
@@ -3,10 +3,10 @@ import { InjectionToken, Provider, Signal, ElementRef } from '@angular/core';
3
3
  import * as i1 from '@radix-ng/primitives/portal';
4
4
  import { RdxPortalContainer } from '@radix-ng/primitives/portal';
5
5
  import * as _radix_ng_primitives_core from '@radix-ng/primitives/core';
6
- import { RdxTransitionStatus, BooleanInput } from '@radix-ng/primitives/core';
6
+ import { RdxTransitionStatus, BooleanInput, RdxCancelableChangeEventDetails, RdxFloatingRootContext } from '@radix-ng/primitives/core';
7
7
  import * as _radix_ng_primitives_dialog from '@radix-ng/primitives/dialog';
8
- import * as i1$1 from '@radix-ng/primitives/dismissable-layer';
9
- import * as i2 from '@radix-ng/primitives/focus-scope';
8
+ import { RdxOutsidePressDomEvent } from '@radix-ng/primitives/dismissable-layer';
9
+ import * as i2 from '@radix-ng/primitives/floating-focus-manager';
10
10
 
11
11
  /**
12
12
  * Structural directive that teleports the dialog content (backdrop + popup) into a container (default
@@ -81,18 +81,21 @@ declare function provideRdxDialogVariant(variant: Partial<RdxDialogVariant>): Pr
81
81
 
82
82
  type RdxDialogModal = boolean | 'trap-focus';
83
83
  type RdxDialogOpenChangeReason = 'trigger-press' | 'close-press' | 'outside-press' | 'focus-out' | 'escape-key' | 'swipe' | 'imperative-action' | 'none';
84
+ type RdxDialogOpenChangeEventDetails = RdxCancelableChangeEventDetails<RdxDialogOpenChangeReason>;
84
85
  interface RdxDialogOpenChange {
85
86
  open: boolean;
86
87
  triggerId: string | null;
87
88
  trigger: HTMLElement | undefined;
88
89
  reason: RdxDialogOpenChangeReason;
89
90
  event: Event;
91
+ eventDetails: RdxDialogOpenChangeEventDetails;
90
92
  }
91
93
  interface RdxDialogRootContext {
92
94
  contentId: string;
93
95
  titleId: Signal<string | undefined>;
94
96
  descriptionId: Signal<string | undefined>;
95
97
  isOpen: Signal<boolean>;
98
+ present: Signal<boolean>;
96
99
  /** Effective modality (the variant can pin this to `true`). */
97
100
  modal: Signal<RdxDialogModal>;
98
101
  /** Effective outside-press / focus-out dismissal flag (the variant can force it on). */
@@ -175,6 +178,8 @@ declare class RdxDialogRoot {
175
178
  readonly triggers: _angular_core.WritableSignal<HTMLElement[]>;
176
179
  readonly payload: _angular_core.WritableSignal<unknown>;
177
180
  readonly nestedOpenCount: _angular_core.WritableSignal<number>;
181
+ private readonly preventUnmountOnClose;
182
+ readonly present: Signal<boolean>;
178
183
  /** Whether this dialog is rendered inside another dialog. Fixed at construction. */
179
184
  readonly nested: boolean;
180
185
  readonly nestedDialogOpen: Signal<boolean>;
@@ -184,6 +189,12 @@ declare class RdxDialogRoot {
184
189
  readonly effectiveModal: Signal<RdxDialogModal>;
185
190
  /** Effective dismissal flag: disabled when the input asks, or when the variant forces it (alerts). */
186
191
  readonly effectiveDisablePointerDismissal: Signal<boolean>;
192
+ /**
193
+ * The shared per-popup floating context (ADR 0015 §1) — `open` mirrors the dialog's open state, the
194
+ * trigger registry is bridged from {@link registerTrigger}, and the reference / floating elements are
195
+ * set by the trigger / popup. The new dismissal + focus engines read this once the popup migrates.
196
+ */
197
+ readonly floatingContext: RdxFloatingRootContext;
187
198
  constructor();
188
199
  show(trigger?: HTMLElement | undefined, payload?: unknown, triggerId?: string, reason?: RdxDialogOpenChangeReason, event?: Event): void;
189
200
  close(reason?: RdxDialogOpenChangeReason, event?: Event): void;
@@ -192,7 +203,7 @@ declare class RdxDialogRoot {
192
203
  /** Increments the nested-open counter and returns a release callback that decrements it. */
193
204
  openNestedChild(): () => void;
194
205
  private syncTriggerId;
195
- private emitOpenChange;
206
+ private createOpenChangeEvent;
196
207
  private emitOpenChangeComplete;
197
208
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<RdxDialogRoot, never>;
198
209
  static ɵdir: _angular_core.ɵɵDirectiveDeclaration<RdxDialogRoot, "[rdxDialogRoot]", ["rdxDialogRoot"], { "open": { "alias": "open"; "required": false; "isSignal": true; }; "defaultOpen": { "alias": "defaultOpen"; "required": false; "isSignal": true; }; "triggerId": { "alias": "triggerId"; "required": false; "isSignal": true; }; "defaultTriggerId": { "alias": "defaultTriggerId"; "required": false; "isSignal": true; }; "modal": { "alias": "modal"; "required": false; "isSignal": true; }; "disablePointerDismissal": { "alias": "disablePointerDismissal"; "required": false; "isSignal": true; }; "handle": { "alias": "handle"; "required": false; "isSignal": true; }; }, { "open": "openChange"; "triggerId": "triggerIdChange"; "onOpenChange": "onOpenChange"; "onOpenChangeComplete": "onOpenChangeComplete"; }, never, never, true, never>;
@@ -232,11 +243,18 @@ declare class RdxDialogTrigger {
232
243
 
233
244
  /**
234
245
  * An overlay displayed beneath the dialog popup.
246
+ *
247
+ * Decorative-only, so it carries `role="presentation"` (Base UI `DialogBackdrop`). By default a **nested**
248
+ * dialog renders no backdrop — the parent's already dims the page; stacking a second one double-darkens
249
+ * and intercepts the parent's outside-press. Set `forceRender` to opt back in.
235
250
  */
236
251
  declare class RdxDialogBackdrop {
237
252
  protected readonly rootContext: _radix_ng_primitives_dialog.RdxDialogRootContext;
253
+ /** Render the backdrop even for a nested dialog (off by default, matching Base UI). */
254
+ readonly forceRender: _angular_core.InputSignalWithTransform<boolean, BooleanInput>;
255
+ constructor();
238
256
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<RdxDialogBackdrop, never>;
239
- static ɵdir: _angular_core.ɵɵDirectiveDeclaration<RdxDialogBackdrop, "[rdxDialogBackdrop]", ["rdxDialogBackdrop"], {}, {}, never, never, true, never>;
257
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<RdxDialogBackdrop, "[rdxDialogBackdrop]", ["rdxDialogBackdrop"], { "forceRender": { "alias": "forceRender"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
240
258
  }
241
259
 
242
260
  /**
@@ -253,40 +271,67 @@ declare class RdxDialogViewport {
253
271
 
254
272
  /**
255
273
  * A container for the dialog contents.
274
+ *
275
+ * **ADR 0015/0017 Phase-4 migration — Dialog is the PILOT cutover onto the new floating dismissal +
276
+ * focus engine. Browser-verified** by `apps/visual-regression/tests/dialog.behavior.spec.ts` (trap,
277
+ * initial / return focus, Escape / outside-press / focus-out dismissal, nested-Escape deepest-first,
278
+ * backdrop-not-marked).
279
+ *
280
+ * **Mapping (legacy → new):**
281
+ * - `RdxDismissableLayer` (legacy) → `RdxFloatingNodeRegistration` (registers the tree node) +
282
+ * `RdxDismiss` (Escape / outside-press; reads the root context + node).
283
+ * - `RdxFocusScope` (direct) → `RdxFloatingFocusManager` (composes the reworked focus scope; trap +
284
+ * markOthers + close-on-focus-out), driven by `provideFloatingFocusManagerConfig`.
285
+ * - `disableOutsidePointerEvents` → the focus manager's `inert` pass marks outside elements
286
+ * non-interactive for a modal (finding #4), scoped to siblings of the popup's ancestor chain instead
287
+ * of a global `body { pointer-events: none }` lock — so the popup needs no `pointer-events: auto`.
288
+ * - focus-out close moved from the dismissal capability (`focusOutside: () => false`) to the manager
289
+ * (`manager.focusOut`), per ADR 0017 §3.
290
+ * - `isEventOnTrigger` preventDefault → removed: the trigger is in `context.triggers`, so the engine
291
+ * treats a press/focus on it as **inside** (no close-then-reopen).
292
+ *
293
+ * **Parity notes:**
294
+ * - **Lifecycle split (resolved 2026-06-16):** `enabled` is `open || transitionStatus === 'ending'`, so
295
+ * the trap / return-focus machinery survives the exit animation, while the manager's marker + isolation
296
+ * passes additionally key off `open` and release at close-start (Base UI `markOthers` gating).
297
+ * - **`trap-focus` split:** the manager traps focus for `modal === true` and `'trap-focus'`, but applies
298
+ * real `inert` isolation only for `modal === true`, matching Base UI's public contract that
299
+ * `modal="trap-focus"` leaves outside pointer interaction enabled.
300
+ * - **`returnFocus` orchestration (resolved 2026-06-16):** the manager now owns the return-focus *target*
301
+ * via the focus scope's `returnFocus` config seam (the scope owns the *timing* — its queued post-unmount
302
+ * frame). Dialog leaves it at the default (`returnFocus: true` → return to the element focused before
303
+ * open), so behavior is unchanged; a consumer can now also pass `false` / an element / a callback.
256
304
  */
257
305
  declare class RdxDialogPopup {
258
306
  protected readonly rootContext: _radix_ng_primitives_dialog.RdxDialogRootContext;
259
- private readonly dismissableLayer;
307
+ private readonly host;
308
+ private readonly floatingContext;
309
+ private readonly registration;
310
+ private readonly focusManager;
260
311
  private readonly focusScope;
261
- private dismissDetails;
262
- /**
263
- * Event handler called when the escape key is down. Can be prevented.
264
- */
265
- readonly escapeKeyDown: _angular_core.OutputRef<KeyboardEvent>;
266
- /**
267
- * Event handler called when a pointerdown event happens outside of the popup. Can be prevented.
268
- */
269
- readonly pointerDownOutside: _angular_core.OutputRef<PointerEvent>;
270
- /**
271
- * Event handler called when focus moves outside of the popup. Can be prevented.
272
- */
273
- readonly focusOutside: _angular_core.OutputRef<FocusEvent>;
274
- /**
275
- * Event handler called when an interaction happens outside of the popup. Can be prevented.
276
- */
277
- readonly interactOutside: _angular_core.OutputRef<PointerEvent | FocusEvent>;
278
- /**
279
- * Event handler called before focus moves into the popup. Can be prevented.
280
- */
312
+ /** Event handler called when the escape key is down. Can be prevented. */
313
+ readonly escapeKeyDown: _angular_core.OutputEmitterRef<KeyboardEvent>;
314
+ /** Event handler called when a pointerdown event happens outside of the popup. Can be prevented. */
315
+ readonly pointerDownOutside: _angular_core.OutputEmitterRef<RdxOutsidePressDomEvent>;
316
+ /** Event handler called when focus moves outside of the popup. Can be prevented. */
317
+ readonly focusOutside: _angular_core.OutputEmitterRef<FocusEvent>;
318
+ /** Event handler called when an interaction (pointer / focus) happens outside of the popup. */
319
+ readonly interactOutside: _angular_core.OutputEmitterRef<RdxOutsidePressDomEvent | FocusEvent>;
320
+ /** Event handler called before focus moves into the popup. Can be prevented. */
281
321
  readonly openAutoFocus: _angular_core.OutputRef<Event>;
282
- /**
283
- * Event handler called before focus returns after the popup is removed. Can be prevented.
284
- */
322
+ /** Event handler called before focus returns after the popup is removed. Can be prevented. */
285
323
  readonly closeAutoFocus: _angular_core.OutputRef<Event>;
286
324
  constructor();
287
- private isEventOnTrigger;
325
+ /** This dialog is the topmost (deepest open) one — it has no open nested dialog above it. */
326
+ private isTopmost;
327
+ /**
328
+ * Composite navigation keys (arrows / Home / End) are kept inside the dialog (Base UI `DialogPopup`):
329
+ * a dialog opened from inside a Menu / Menubar / Composite must not let an arrow press bubble out and
330
+ * move the outer collection's active item.
331
+ */
332
+ protected onKeyDown(event: KeyboardEvent): void;
288
333
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<RdxDialogPopup, never>;
289
- static ɵdir: _angular_core.ɵɵDirectiveDeclaration<RdxDialogPopup, "[rdxDialogPopup]", ["rdxDialogPopup"], {}, { "escapeKeyDown": "escapeKeyDown"; "pointerDownOutside": "pointerDownOutside"; "focusOutside": "focusOutside"; "interactOutside": "interactOutside"; "openAutoFocus": "openAutoFocus"; "closeAutoFocus": "closeAutoFocus"; }, never, never, true, [{ directive: typeof i1$1.RdxDismissableLayer; inputs: {}; outputs: {}; }, { directive: typeof i2.RdxFocusScope; inputs: {}; outputs: {}; }]>;
334
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<RdxDialogPopup, "[rdxDialogPopup]", ["rdxDialogPopup"], {}, { "escapeKeyDown": "escapeKeyDown"; "pointerDownOutside": "pointerDownOutside"; "focusOutside": "focusOutside"; "interactOutside": "interactOutside"; "openAutoFocus": "openAutoFocus"; "closeAutoFocus": "closeAutoFocus"; }, never, never, true, [{ directive: typeof _radix_ng_primitives_core.RdxFloatingNodeRegistration; inputs: {}; outputs: {}; }, { directive: typeof i2.RdxFloatingFocusManager; inputs: {}; outputs: {}; }]>;
290
335
  }
291
336
 
292
337
  /**
@@ -328,4 +373,4 @@ declare class RdxDialogModule {
328
373
  }
329
374
 
330
375
  export { RDX_DIALOG_VARIANT, RdxDialogBackdrop, RdxDialogClose, RdxDialogDescription, RdxDialogHandle, RdxDialogModule, RdxDialogPopup, RdxDialogPortal, RdxDialogPortalMisuseGuard, RdxDialogRoot, RdxDialogTitle, RdxDialogTrigger, RdxDialogViewport, createRdxDialogHandle, dialogImports, injectRdxDialogRootContext, provideRdxDialogRootContext, provideRdxDialogVariant };
331
- export type { RdxDialogModal, RdxDialogOpenChange, RdxDialogOpenChangeReason, RdxDialogRole, RdxDialogRootContext, RdxDialogVariant };
376
+ export type { RdxDialogModal, RdxDialogOpenChange, RdxDialogOpenChangeEventDetails, RdxDialogOpenChangeReason, RdxDialogRole, RdxDialogRootContext, RdxDialogVariant };
@@ -0,0 +1,41 @@
1
+ import * as _angular_core from '@angular/core';
2
+ import { InjectionToken, Signal, Provider } from '@angular/core';
3
+ import { Direction } from '@radix-ng/primitives/core';
4
+
5
+ type RdxDirectionValue = Direction | Signal<Direction> | (() => Direction);
6
+ declare const RDX_DIRECTION: InjectionToken<Signal<Direction>>;
7
+ /**
8
+ * Provides a primitive behavior direction through Angular DI.
9
+ */
10
+ declare function provideDirection(direction: RdxDirectionValue): Provider;
11
+ /**
12
+ * Provides a reading direction for descendant primitives.
13
+ *
14
+ * This controls primitive behavior only. Set the native `dir` attribute or CSS `direction`
15
+ * separately when the DOM itself should render right-to-left.
16
+ */
17
+ declare class RdxDirectionProvider {
18
+ private readonly parentDirection;
19
+ /**
20
+ * The reading direction for descendant primitives.
21
+ *
22
+ * @defaultValue 'ltr'
23
+ */
24
+ readonly directionInput: _angular_core.InputSignal<Direction | undefined>;
25
+ /** Alias for Angular templates that prefer `[dir]`. */
26
+ readonly dirInput: _angular_core.InputSignal<Direction | undefined>;
27
+ readonly direction: Signal<Direction>;
28
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<RdxDirectionProvider, never>;
29
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<RdxDirectionProvider, "[rdxDirectionProvider]", ["rdxDirectionProvider"], { "directionInput": { "alias": "direction"; "required": false; "isSignal": true; }; "dirInput": { "alias": "dir"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
30
+ }
31
+ /**
32
+ * Reads the effective primitive direction.
33
+ *
34
+ * Priority: explicit local input, nearest DI direction provider, global `provideRadixNG({ dir })`.
35
+ */
36
+ declare function injectDirection(localDirection?: Signal<Direction | undefined>): Signal<Direction>;
37
+
38
+ declare const directionProviderImports: (typeof RdxDirectionProvider)[];
39
+
40
+ export { RDX_DIRECTION, RdxDirectionProvider, directionProviderImports, injectDirection, provideDirection };
41
+ export type { RdxDirectionValue };
@@ -1,6 +1,137 @@
1
- import * as _angular_core from '@angular/core';
2
- import { Signal, InjectionToken, Provider } from '@angular/core';
3
- import { BooleanInput } from '@radix-ng/primitives/core';
1
+ import { RdxFloatingRootContext, RdxFloatingNode, BooleanInput } from '@radix-ng/primitives/core';
2
+ import * as i0 from '@angular/core';
3
+
4
+ /** Why a dismissal was requested — mirrors Base UI's open-change `reason` strings (`useDismiss.ts`). */
5
+ type RdxDismissReason = 'escape-key' | 'outside-press' | 'focus-outside';
6
+ type RdxOutsidePressDomEvent = MouseEvent | PointerEvent | TouchEvent;
7
+ /**
8
+ * Configuration for {@link RdxDismiss}. Every flag is a getter so it can be a signal
9
+ * read (reactive) or a plain predicate. The `on*` pre-hooks are **preventable**: call
10
+ * `event.preventDefault()` inside one to veto that dismissal (the layer then stays open).
11
+ */
12
+ /** When an outside press dismisses: `'sloppy'` closes on `pointerdown`, `'intentional'` on `click`. */
13
+ type RdxOutsidePressEvent = 'sloppy' | 'intentional';
14
+ /**
15
+ * `outsidePressEvent` config (Base UI). Either a single mode for every pointer type, or a per-pointer-type
16
+ * map (`{ mouse, touch, pen }`) resolved against the pointer type of the active press. A missing key falls
17
+ * back to `'sloppy'`.
18
+ */
19
+ type RdxOutsidePressEventConfig = RdxOutsidePressEvent | {
20
+ mouse?: RdxOutsidePressEvent;
21
+ touch?: RdxOutsidePressEvent;
22
+ pen?: RdxOutsidePressEvent;
23
+ };
24
+ interface RdxDismissProps {
25
+ /** Whole-capability gate (on top of `context.open()`). Default `() => true`. */
26
+ enabled?: () => boolean;
27
+ /** Whether Escape requests dismissal. Default `() => true`. */
28
+ escapeKey?: () => boolean;
29
+ /**
30
+ * Whether this layer's Escape **bubbles** to ancestor layers (Base UI `bubbles.escapeKey`). Default
31
+ * `() => false` — Escape closes only the deepest layer. `true` re-emits to the parent too (this is
32
+ * Menu's `closeParentOnEsc`: a submenu's Escape also closes the parent menu).
33
+ */
34
+ escapeKeyBubbles?: () => boolean;
35
+ /**
36
+ * Whether an outside pointer press requests dismissal. Default `() => true`. Receives the press
37
+ * **event**, so a layer can decide per target / button / pointer (e.g. Dialog: only the topmost
38
+ * dialog dismisses; only its own backdrop counts).
39
+ */
40
+ outsidePress?: (event: Event) => boolean;
41
+ /**
42
+ * When an outside press dismisses (Base UI `outsidePressEvent`). `'sloppy'` (default) closes on
43
+ * `pointerdown` — immediate, OS-like. `'intentional'` closes on `click` — requires a full
44
+ * press-and-release on the same outside target, and suppresses the click when the press **started
45
+ * inside** (so selecting text and dragging out does not dismiss). May be a per-pointer-type map
46
+ * resolved against the active press (`{ mouse: 'intentional', touch: 'sloppy' }`).
47
+ */
48
+ outsidePressEvent?: () => RdxOutsidePressEventConfig;
49
+ /**
50
+ * Whether this layer's outside-press **bubbles** to ancestor layers (Base UI `bubbles.outsidePress`).
51
+ * Default `() => true` — an outside press closes the whole stack. `false` makes an open non-bubbling
52
+ * child block the parent (only the deepest closes).
53
+ */
54
+ outsidePressBubbles?: () => boolean;
55
+ /** Whether focus leaving the layer requests dismissal. Default `() => true`. */
56
+ focusOutside?: () => boolean;
57
+ /** Preventable pre-hook for Escape. */
58
+ onEscapeKeyDown?: (event: KeyboardEvent) => void;
59
+ /** Preventable pre-hook for an outside pointer press. */
60
+ onPointerDownOutside?: (event: RdxOutsidePressDomEvent) => void;
61
+ /** Preventable pre-hook for focus moving outside. */
62
+ onFocusOutside?: (event: FocusEvent) => void;
63
+ /** Called when a non-prevented dismissal is requested. */
64
+ onDismiss?: (reason: RdxDismissReason, event: Event) => void;
65
+ }
66
+ /**
67
+ * The **dismiss** mechanism (ADR 0015) — the Angular counterpart of Base UI's `useDismiss`. It
68
+ * **references** a {@link RdxFloatingRootContext} (mandatory: `open` / `triggers` / elements live there)
69
+ * and a {@link RdxFloatingNode} (**optional**: a node-optional / Navigation-Menu state has `node ===
70
+ * null`); it never creates them. It listens for Escape / outside-press / focus-out and **requests** a
71
+ * dismissal via `onDismiss` when an interaction lands outside the logical layer and this layer owns the
72
+ * event.
73
+ *
74
+ * **Logical, not DOM-order, containment.** "Inside" is resolved through the shared tree — this popup's
75
+ * floating element + its registered triggers, **plus** the same for every open descendant node, **plus**
76
+ * this layer's own {@link branches}. So a portal-relocated child still counts as inside its parent — which
77
+ * a DOM-order containment check cannot do.
78
+ *
79
+ * **Ownership & propagation (`hasBlockingChild`).** Each layer publishes per-event `bubbles` flags
80
+ * (`escapeKeyBubbles` default `false`, `outsidePressBubbles` default `true`); an ancestor reads its open
81
+ * children's flags via {@link childBubbles}. A non-bubbling layer **yields** to a non-bubbling open child,
82
+ * so by default Escape closes only the **deepest** layer while an outside press closes the **whole stack**.
83
+ * A layer with `escapeKeyBubbles = true` re-emits to its parent (Menu's `closeParentOnEsc`). The owning
84
+ * non-bubbling Escape layer also `stopPropagation()`s so the key does not reach app handlers.
85
+ *
86
+ * **Press / IME hardening.** Outside-press honors `outsidePressEvent` (`'sloppy'` → `pointerdown`,
87
+ * `'intentional'` → `click` with press-start-inside drag-out suppression), ignores non-primary mouse
88
+ * buttons and scrollbar presses, resets on `pointercancel`, and ignores Escape while an IME composition is
89
+ * active. **Touch** sloppy-mode gesture hardening (long-press / drag-distance thresholds) is ported here
90
+ * but only unit-exercisable via synthetic events — real on-device behavior is covered by
91
+ * `apps/visual-regression` (Playwright).
92
+ *
93
+ * **Scope.** Must be constructed in an injection context (`DestroyRef` / `PLATFORM_ID`). No-op on the
94
+ * server.
95
+ */
96
+ declare class RdxDismiss {
97
+ readonly context: RdxFloatingRootContext;
98
+ readonly node: () => RdxFloatingNode | null;
99
+ /** This layer's own inside-content set (branches that live outside the popup DOM). */
100
+ readonly branches: Set<Element>;
101
+ /** This mechanism's active-ness: the popup is open **and** dismissal is enabled. */
102
+ readonly active: () => boolean;
103
+ constructor(context: RdxFloatingRootContext, node: () => RdxFloatingNode | null, config?: RdxDismissProps);
104
+ /**
105
+ * Whether `target` is logically inside this layer: within this popup's floating element or one of its
106
+ * registered triggers, within any **open descendant** node's floating element / triggers, or within a
107
+ * registered branch.
108
+ */
109
+ private isInside;
110
+ /** `target` is inside a context's floating element or one of its registered triggers. */
111
+ private contextContains;
112
+ /**
113
+ * Whether an open descendant blocks this layer for `reason` (so it should yield). A child blocks when
114
+ * it does **not** let the event bubble past it ({@link childBubbles} is `false`). Base UI's
115
+ * `hasBlockingChild` (`useDismiss.ts:170`) is a capability-local function — **not** a tree method — so
116
+ * the tree stays neutral.
117
+ */
118
+ private hasBlockingChild;
119
+ }
120
+
121
+ /**
122
+ * Marks its host as **inside** the enclosing floating layer (ADR 0015): a pointer / focus interaction on
123
+ * it does not count as "outside", so it never dismisses the popup. It registers the host into the
124
+ * floating root context's trigger registry — {@link RdxDismiss}'s `isInside` check reads it.
125
+ *
126
+ * Use it for an element that lives **outside** the popup DOM but logically belongs to the layer — e.g. a
127
+ * Combobox input / trigger / chips / clear button that keeps focus (or is clicked) while the listbox is
128
+ * open.
129
+ */
130
+ declare class RdxFloatingInsideElement {
131
+ constructor();
132
+ static ɵfac: i0.ɵɵFactoryDeclaration<RdxFloatingInsideElement, never>;
133
+ static ɵdir: i0.ɵɵDirectiveDeclaration<RdxFloatingInsideElement, "[rdxFloatingInside]", ["rdxFloatingInside"], {}, {}, never, never, true, never>;
134
+ }
4
135
 
5
136
  /**
6
137
  * Listens for when focus happens outside a DOM subtree.
@@ -8,16 +139,16 @@ import { BooleanInput } from '@radix-ng/primitives/core';
8
139
  */
9
140
  declare class RdxFocusOutside {
10
141
  #private;
11
- readonly enabledInput: _angular_core.InputSignalWithTransform<boolean, BooleanInput>;
142
+ readonly enabledInput: i0.InputSignalWithTransform<boolean, BooleanInput>;
12
143
  set enabled(value: boolean);
13
144
  get enabled(): boolean;
14
- readonly focusOutside: _angular_core.OutputEmitterRef<FocusEvent>;
145
+ readonly focusOutside: i0.OutputEmitterRef<FocusEvent>;
15
146
  private readonly isFocusInsideDOMTree;
16
147
  private readonly focusCaptureHandler;
17
148
  private readonly blurCaptureHandler;
18
149
  constructor();
19
- static ɵfac: _angular_core.ɵɵFactoryDeclaration<RdxFocusOutside, never>;
20
- static ɵdir: _angular_core.ɵɵDirectiveDeclaration<RdxFocusOutside, "[rdxFocusOutside]", ["rdxFocusOutside"], { "enabledInput": { "alias": "enabled"; "required": false; "isSignal": true; }; }, { "focusOutside": "focusOutside"; }, never, never, true, never>;
150
+ static ɵfac: i0.ɵɵFactoryDeclaration<RdxFocusOutside, never>;
151
+ static ɵdir: i0.ɵɵDirectiveDeclaration<RdxFocusOutside, "[rdxFocusOutside]", ["rdxFocusOutside"], { "enabledInput": { "alias": "enabled"; "required": false; "isSignal": true; }; }, { "focusOutside": "focusOutside"; }, never, never, true, never>;
21
152
  }
22
153
  /**
23
154
  * Listens for `pointerdown` outside a DOM subtree. We use `pointerdown` rather than `pointerup`
@@ -27,108 +158,25 @@ declare class RdxFocusOutside {
27
158
  declare class RdxPointerDownOutside {
28
159
  #private;
29
160
  private readonly elementRef;
30
- readonly enabledInput: _angular_core.InputSignalWithTransform<boolean, BooleanInput>;
161
+ readonly enabledInput: i0.InputSignalWithTransform<boolean, BooleanInput>;
31
162
  set enabled(value: boolean);
32
163
  get enabled(): boolean;
33
- readonly pointerDownOutside: _angular_core.OutputEmitterRef<PointerEvent>;
164
+ readonly pointerDownOutside: i0.OutputEmitterRef<PointerEvent>;
34
165
  private readonly isPointerInsideDOMTree;
35
166
  private readonly handleAndDispatchPointerDownOutsideEvent;
36
167
  private handleClick?;
37
168
  constructor();
38
- static ɵfac: _angular_core.ɵɵFactoryDeclaration<RdxPointerDownOutside, never>;
39
- static ɵdir: _angular_core.ɵɵDirectiveDeclaration<RdxPointerDownOutside, "[rdxPointerDownOutside]", ["rdxPointerDownOutside"], { "enabledInput": { "alias": "enabled"; "required": false; "isSignal": true; }; }, { "pointerDownOutside": "pointerDownOutside"; }, never, never, true, never>;
169
+ static ɵfac: i0.ɵɵFactoryDeclaration<RdxPointerDownOutside, never>;
170
+ static ɵdir: i0.ɵɵDirectiveDeclaration<RdxPointerDownOutside, "[rdxPointerDownOutside]", ["rdxPointerDownOutside"], { "enabledInput": { "alias": "enabled"; "required": false; "isSignal": true; }; }, { "pointerDownOutside": "pointerDownOutside"; }, never, never, true, never>;
40
171
  }
41
172
  declare class RdxEscapeKeyDown {
42
173
  private readonly elementRef;
43
174
  private readonly destroyRef;
44
- readonly escapeKeyDown: _angular_core.OutputEmitterRef<KeyboardEvent>;
175
+ readonly escapeKeyDown: i0.OutputEmitterRef<KeyboardEvent>;
45
176
  constructor();
46
- static ɵfac: _angular_core.ɵɵFactoryDeclaration<RdxEscapeKeyDown, never>;
47
- static ɵdir: _angular_core.ɵɵDirectiveDeclaration<RdxEscapeKeyDown, "[rdxEscapeKeyDown]", ["rdxEscapeKeyDown"], {}, { "escapeKeyDown": "escapeKeyDown"; }, never, never, true, never>;
177
+ static ɵfac: i0.ɵɵFactoryDeclaration<RdxEscapeKeyDown, never>;
178
+ static ɵdir: i0.ɵɵDirectiveDeclaration<RdxEscapeKeyDown, "[rdxEscapeKeyDown]", ["rdxEscapeKeyDown"], {}, { "escapeKeyDown": "escapeKeyDown"; }, never, never, true, never>;
48
179
  }
49
180
 
50
- declare class RdxDismissableLayer {
51
- private readonly elementRef;
52
- private readonly injector;
53
- private readonly destroyRef;
54
- private readonly context;
55
- private readonly config;
56
- private readonly rdxPointerDownOutside;
57
- private readonly rdxFocusOutside;
58
- private readonly rdxEscapeKeyDown;
59
- /**
60
- * Event handler called when the escape key is down.
61
- * Can be prevented.
62
- */
63
- readonly escapeKeyDown: _angular_core.OutputEmitterRef<KeyboardEvent>;
64
- /**
65
- * Event handler called when a `pointerdown` event happens outside of the `DismissableLayer`.
66
- * Can be prevented.
67
- */
68
- readonly pointerDownOutside: _angular_core.OutputEmitterRef<PointerEvent>;
69
- /**
70
- * Event handler called when the focus moves outside of the `DismissableLayer`.
71
- * Can be prevented.
72
- */
73
- readonly focusOutside: _angular_core.OutputEmitterRef<FocusEvent>;
74
- /**
75
- * Event handler called when an interaction happens outside the `DismissableLayer`.
76
- * Specifically, when a `pointerdown` event happens outside or focus moves outside of it.
77
- * Can be prevented.
78
- */
79
- readonly interactOutside: _angular_core.OutputEmitterRef<FocusEvent | PointerEvent>;
80
- /**
81
- * Handler called when the `DismissableLayer` should be dismissed
82
- */
83
- readonly dismiss: _angular_core.OutputEmitterRef<void>;
84
- /**
85
- * When `true`, hover/focus/click interactions will be disabled on elements outside
86
- * the `DismissableLayer`. Users will need to click twice on outside elements to
87
- * interact with them: once to close the `DismissableLayer`, and again to trigger the element.
88
- */
89
- readonly disableOutsidePointerEvents: _angular_core.InputSignalWithTransform<boolean | undefined, BooleanInput>;
90
- readonly isOutsidePointerEventsDisabled: _angular_core.Signal<boolean>;
91
- protected readonly isBodyPointerEventsDisabled: _angular_core.Signal<boolean>;
92
- protected readonly isPointerEventsEnabled: _angular_core.Signal<boolean>;
93
- private readonly index;
94
- /** The topmost layer in the stack — the only one that should react to the Escape key. */
95
- private readonly isHighestLayer;
96
- constructor();
97
- /**
98
- * Toggles `pointer-events: none` on the document body while any layer has
99
- * `disableOutsidePointerEvents`. Ownership is shared across all layers via
100
- * {@link originalBodyPointerEvents}: the original value is saved only on the global
101
- * `0 -> >0` transition and restored only when the count returns to `0`.
102
- */
103
- private setupBodyPointerEvents;
104
- static ɵfac: _angular_core.ɵɵFactoryDeclaration<RdxDismissableLayer, never>;
105
- static ɵdir: _angular_core.ɵɵDirectiveDeclaration<RdxDismissableLayer, "[rdxDismissableLayer]", ["rdxDismissableLayer"], { "disableOutsidePointerEvents": { "alias": "disableOutsidePointerEvents"; "required": false; "isSignal": true; }; }, { "escapeKeyDown": "escapeKeyDown"; "pointerDownOutside": "pointerDownOutside"; "focusOutside": "focusOutside"; "interactOutside": "interactOutside"; "dismiss": "dismiss"; }, never, never, true, [{ directive: typeof RdxPointerDownOutside; inputs: {}; outputs: {}; }, { directive: typeof RdxFocusOutside; inputs: {}; outputs: {}; }, { directive: typeof RdxEscapeKeyDown; inputs: {}; outputs: {}; }]>;
106
- }
107
-
108
- declare class RdxDismissableLayerBranch {
109
- private readonly elementRef;
110
- private readonly destroyRef;
111
- private readonly dismissableLayersContext;
112
- constructor();
113
- static ɵfac: _angular_core.ɵɵFactoryDeclaration<RdxDismissableLayerBranch, never>;
114
- static ɵdir: _angular_core.ɵɵDirectiveDeclaration<RdxDismissableLayerBranch, "[rdxDismissableLayerBranch]", never, {}, {}, never, never, true, never>;
115
- }
116
-
117
- declare const RdxDismissableLayersContextToken: InjectionToken<{
118
- layersRoot: _angular_core.WritableSignal<RdxDismissableLayer[]>;
119
- layersWithOutsidePointerEventsDisabled: Signal<RdxDismissableLayer[]>;
120
- branches: _angular_core.WritableSignal<HTMLElement[]>;
121
- }>;
122
- type RdxDismissableLayerConfig = {
123
- /**
124
- * When `true`, hover/focus/click interactions will be disabled on elements outside
125
- * the `DismissableLayer`. Users will need to click twice on outside elements to
126
- * interact with them: once to close the `DismissableLayer`, and again to trigger the element.
127
- */
128
- disableOutsidePointerEvents: Signal<boolean>;
129
- };
130
- declare const RdxDismissableLayerConfigToken: InjectionToken<RdxDismissableLayerConfig>;
131
- declare function provideRdxDismissableLayerConfig(factory: () => RdxDismissableLayerConfig): Provider;
132
-
133
- export { RdxDismissableLayer, RdxDismissableLayerBranch, RdxDismissableLayerConfigToken, RdxDismissableLayersContextToken, RdxEscapeKeyDown, RdxFocusOutside, RdxPointerDownOutside, provideRdxDismissableLayerConfig };
134
- export type { RdxDismissableLayerConfig };
181
+ export { RdxDismiss, RdxEscapeKeyDown, RdxFloatingInsideElement, RdxFocusOutside, RdxPointerDownOutside };
182
+ export type { RdxDismissProps, RdxDismissReason, RdxOutsidePressDomEvent, RdxOutsidePressEvent, RdxOutsidePressEventConfig };
@@ -188,6 +188,7 @@ declare class RdxFieldLabel {
188
188
  */
189
189
  readonly id: _angular_core.InputSignal<string>;
190
190
  readonly htmlFor: () => string;
191
+ constructor();
191
192
  protected readonly dataAttr: (value: boolean) => "" | undefined;
192
193
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<RdxFieldLabel, never>;
193
194
  static ɵdir: _angular_core.ɵɵDirectiveDeclaration<RdxFieldLabel, "[rdxFieldLabel]", ["rdxFieldLabel"], { "id": { "alias": "id"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;