@radix-ng/primitives 1.0.0-beta.2 → 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 (104) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +76 -6
  3. package/fesm2022/radix-ng-primitives-accordion.mjs +5 -3
  4. package/fesm2022/radix-ng-primitives-accordion.mjs.map +1 -1
  5. package/fesm2022/radix-ng-primitives-alert-dialog.mjs +31 -24
  6. package/fesm2022/radix-ng-primitives-alert-dialog.mjs.map +1 -1
  7. package/fesm2022/radix-ng-primitives-autocomplete.mjs +1744 -0
  8. package/fesm2022/radix-ng-primitives-autocomplete.mjs.map +1 -0
  9. package/fesm2022/radix-ng-primitives-calendar.mjs +5 -3
  10. package/fesm2022/radix-ng-primitives-calendar.mjs.map +1 -1
  11. package/fesm2022/radix-ng-primitives-combobox.mjs +1399 -606
  12. package/fesm2022/radix-ng-primitives-combobox.mjs.map +1 -1
  13. package/fesm2022/radix-ng-primitives-config.mjs +13 -4
  14. package/fesm2022/radix-ng-primitives-config.mjs.map +1 -1
  15. package/fesm2022/radix-ng-primitives-context-menu.mjs +51 -10
  16. package/fesm2022/radix-ng-primitives-context-menu.mjs.map +1 -1
  17. package/fesm2022/radix-ng-primitives-core.mjs +1345 -64
  18. package/fesm2022/radix-ng-primitives-core.mjs.map +1 -1
  19. package/fesm2022/radix-ng-primitives-date-field.mjs +5 -3
  20. package/fesm2022/radix-ng-primitives-date-field.mjs.map +1 -1
  21. package/fesm2022/radix-ng-primitives-dialog.mjs +271 -145
  22. package/fesm2022/radix-ng-primitives-dialog.mjs.map +1 -1
  23. package/fesm2022/radix-ng-primitives-direction-provider.mjs +70 -0
  24. package/fesm2022/radix-ng-primitives-direction-provider.mjs.map +1 -0
  25. package/fesm2022/radix-ng-primitives-dismissable-layer.mjs +519 -184
  26. package/fesm2022/radix-ng-primitives-dismissable-layer.mjs.map +1 -1
  27. package/fesm2022/radix-ng-primitives-drawer.mjs +154 -64
  28. package/fesm2022/radix-ng-primitives-drawer.mjs.map +1 -1
  29. package/fesm2022/radix-ng-primitives-field.mjs +3 -2
  30. package/fesm2022/radix-ng-primitives-field.mjs.map +1 -1
  31. package/fesm2022/radix-ng-primitives-floating-focus-manager.mjs +517 -0
  32. package/fesm2022/radix-ng-primitives-floating-focus-manager.mjs.map +1 -0
  33. package/fesm2022/radix-ng-primitives-focus-scope.mjs +296 -70
  34. package/fesm2022/radix-ng-primitives-focus-scope.mjs.map +1 -1
  35. package/fesm2022/radix-ng-primitives-menu.mjs +894 -299
  36. package/fesm2022/radix-ng-primitives-menu.mjs.map +1 -1
  37. package/fesm2022/radix-ng-primitives-menubar.mjs +32 -4
  38. package/fesm2022/radix-ng-primitives-menubar.mjs.map +1 -1
  39. package/fesm2022/radix-ng-primitives-navigation-menu.mjs +176 -207
  40. package/fesm2022/radix-ng-primitives-navigation-menu.mjs.map +1 -1
  41. package/fesm2022/radix-ng-primitives-popover.mjs +250 -250
  42. package/fesm2022/radix-ng-primitives-popover.mjs.map +1 -1
  43. package/fesm2022/radix-ng-primitives-popper.mjs +94 -45
  44. package/fesm2022/radix-ng-primitives-popper.mjs.map +1 -1
  45. package/fesm2022/radix-ng-primitives-portal.mjs +107 -17
  46. package/fesm2022/radix-ng-primitives-portal.mjs.map +1 -1
  47. package/fesm2022/radix-ng-primitives-presence.mjs +262 -79
  48. package/fesm2022/radix-ng-primitives-presence.mjs.map +1 -1
  49. package/fesm2022/radix-ng-primitives-preview-card.mjs +172 -218
  50. package/fesm2022/radix-ng-primitives-preview-card.mjs.map +1 -1
  51. package/fesm2022/radix-ng-primitives-roving-focus.mjs +4 -2
  52. package/fesm2022/radix-ng-primitives-roving-focus.mjs.map +1 -1
  53. package/fesm2022/radix-ng-primitives-scroll-area.mjs +5 -4
  54. package/fesm2022/radix-ng-primitives-scroll-area.mjs.map +1 -1
  55. package/fesm2022/radix-ng-primitives-select.mjs +303 -234
  56. package/fesm2022/radix-ng-primitives-select.mjs.map +1 -1
  57. package/fesm2022/radix-ng-primitives-slider.mjs +5 -3
  58. package/fesm2022/radix-ng-primitives-slider.mjs.map +1 -1
  59. package/fesm2022/radix-ng-primitives-stepper.mjs +5 -3
  60. package/fesm2022/radix-ng-primitives-stepper.mjs.map +1 -1
  61. package/fesm2022/radix-ng-primitives-time-field.mjs +5 -3
  62. package/fesm2022/radix-ng-primitives-time-field.mjs.map +1 -1
  63. package/fesm2022/radix-ng-primitives-toast.mjs +15 -36
  64. package/fesm2022/radix-ng-primitives-toast.mjs.map +1 -1
  65. package/fesm2022/radix-ng-primitives-toggle-group.mjs +5 -3
  66. package/fesm2022/radix-ng-primitives-toggle-group.mjs.map +1 -1
  67. package/fesm2022/radix-ng-primitives-toolbar.mjs +5 -3
  68. package/fesm2022/radix-ng-primitives-toolbar.mjs.map +1 -1
  69. package/fesm2022/radix-ng-primitives-tooltip.mjs +105 -145
  70. package/fesm2022/radix-ng-primitives-tooltip.mjs.map +1 -1
  71. package/package.json +14 -1
  72. package/types/radix-ng-primitives-accordion.d.ts +4 -3
  73. package/types/radix-ng-primitives-alert-dialog.d.ts +17 -11
  74. package/types/radix-ng-primitives-autocomplete.d.ts +661 -0
  75. package/types/radix-ng-primitives-calendar.d.ts +5 -3
  76. package/types/radix-ng-primitives-combobox.d.ts +727 -293
  77. package/types/radix-ng-primitives-config.d.ts +1 -1
  78. package/types/radix-ng-primitives-context-menu.d.ts +15 -5
  79. package/types/radix-ng-primitives-core.d.ts +762 -14
  80. package/types/radix-ng-primitives-date-field.d.ts +3 -2
  81. package/types/radix-ng-primitives-dialog.d.ts +107 -55
  82. package/types/radix-ng-primitives-direction-provider.d.ts +41 -0
  83. package/types/radix-ng-primitives-dismissable-layer.d.ts +147 -99
  84. package/types/radix-ng-primitives-drawer.d.ts +49 -22
  85. package/types/radix-ng-primitives-field.d.ts +1 -0
  86. package/types/radix-ng-primitives-floating-focus-manager.d.ts +175 -0
  87. package/types/radix-ng-primitives-focus-scope.d.ts +132 -1
  88. package/types/radix-ng-primitives-menu.d.ts +204 -112
  89. package/types/radix-ng-primitives-navigation-menu.d.ts +61 -101
  90. package/types/radix-ng-primitives-popover.d.ts +82 -115
  91. package/types/radix-ng-primitives-popper.d.ts +46 -10
  92. package/types/radix-ng-primitives-portal.d.ts +53 -8
  93. package/types/radix-ng-primitives-presence.d.ts +98 -17
  94. package/types/radix-ng-primitives-preview-card.d.ts +63 -95
  95. package/types/radix-ng-primitives-roving-focus.d.ts +7 -6
  96. package/types/radix-ng-primitives-scroll-area.d.ts +2 -2
  97. package/types/radix-ng-primitives-select.d.ts +192 -158
  98. package/types/radix-ng-primitives-slider.d.ts +5 -4
  99. package/types/radix-ng-primitives-stepper.d.ts +4 -3
  100. package/types/radix-ng-primitives-time-field.d.ts +3 -2
  101. package/types/radix-ng-primitives-toast.d.ts +7 -7
  102. package/types/radix-ng-primitives-toggle-group.d.ts +5 -4
  103. package/types/radix-ng-primitives-toolbar.d.ts +3 -2
  104. package/types/radix-ng-primitives-tooltip.d.ts +48 -84
@@ -1,7 +1,8 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Type, Provider, InputSignal, WritableSignal, ModelSignal, Signal, InjectionToken, InputSignalWithTransform, OutputRef, ElementRef, Injector, EffectRef, EffectCleanupRegisterFn, CreateEffectOptions } from '@angular/core';
2
+ import { Type, Provider, InputSignal, WritableSignal, ModelSignal, Signal, InjectionToken, InputSignalWithTransform, OutputRef, Injector, ElementRef, EffectRef, EffectCleanupRegisterFn, CreateEffectOptions } from '@angular/core';
3
3
  import { ControlValueAccessor } from '@angular/forms';
4
4
  import { DateValue, Time, CalendarDateTime, ZonedDateTime } from '@internationalized/date';
5
+ import * as _radix_ng_primitives_core from '@radix-ng/primitives/core';
5
6
 
6
7
  type DataOrientation = 'vertical' | 'horizontal';
7
8
  /** Layout direction for bidirectional text. */
@@ -548,6 +549,74 @@ declare function handleCalendarInitialFocus(calendar: HTMLElement): void;
548
549
  declare function normalizeHourCycle(hourCycle: HourCycle): "h11" | "h23" | undefined;
549
550
  declare function normalizeHour12(hourCycle: HourCycle): boolean | undefined;
550
551
 
552
+ /**
553
+ * Base URL of the documentation site. Each primitive's docs are also served as plain
554
+ * Markdown at `/<section>/<slug>.md`, which both humans and AI agents can open.
555
+ */
556
+ declare const DOCS_BASE_URL = "https://radix-ng.com";
557
+ /**
558
+ * Full URL to a primitive's plain-Markdown docs page.
559
+ * @param docsPath Documentation path for the owning primitive (e.g. `'components/select'`).
560
+ */
561
+ declare function docsUrl(docsPath: string): string;
562
+ /**
563
+ * Emits a deduplicated dev-mode `console.warn` for a recoverable misuse.
564
+ *
565
+ * No-op outside `isDevMode()`, so production builds stay silent (and the message
566
+ * assembly tree-shakes out). Dedupes per `code` per page load, replacing the
567
+ * hand-rolled `warned` flags individual primitives used to carry.
568
+ *
569
+ * @param code Stable, greppable `<primitive>/<slug>` identifier (e.g. `'select/trigger-element'`).
570
+ * @param message Human-readable explanation of the misuse and how to fix it.
571
+ * @param docsPath Optional docs path appended as a `See <url>` hint (e.g. `'components/select'`).
572
+ */
573
+ declare function rdxDevWarning(code: string, message: string, docsPath?: string): void;
574
+ /**
575
+ * Throws a dev-mode `Error` for unrecoverable misuse (broken markup that cannot work).
576
+ *
577
+ * Unlike {@link rdxDevWarning} this always throws when reached — callers that want the
578
+ * check to stay dev-only should guard the call with `isDevMode()`.
579
+ *
580
+ * @param code Stable, greppable `<primitive>/<slug>` identifier (e.g. `'popover/portal-on-element'`).
581
+ * @param message Human-readable explanation of the misuse and how to fix it.
582
+ * @param docsPath Optional docs path appended as a `See <url>` hint (e.g. `'components/popover'`).
583
+ */
584
+ declare function rdxDevError(code: string, message: string, docsPath?: string): never;
585
+ /**
586
+ * Test-only: clears the per-code dedup set so warning specs stay isolated from one another.
587
+ */
588
+ declare function resetRdxDevWarnings(): void;
589
+ /**
590
+ * Dev-mode check: warns when a trigger part sits on a host element that is neither natively
591
+ * interactive (`<button>`, `<a>`, `<input>`) nor made focusable with `tabindex`.
592
+ *
593
+ * Only meaningful for triggers whose selector accepts arbitrary elements **and** that do not
594
+ * adapt their own ARIA/keyboard handling for non-button hosts — triggers scoped to
595
+ * `button[...]` already enforce this at the selector level, and triggers that auto-apply
596
+ * `role`/`tabindex` (e.g. `rdxMenuTrigger`) handle it themselves.
597
+ *
598
+ * Must be called inside an injection context (a directive constructor), where the host element
599
+ * already exists. No-op outside `isDevMode()` and on synthetic hosts (component / `ng-template`),
600
+ * where `HOST_TAG_NAME` is absent.
601
+ *
602
+ * @param triggerName Selector name used in the message, e.g. `'rdxPreviewCardTrigger'`.
603
+ * @param code Stable diagnostics code, e.g. `'preview-card/trigger-element'`.
604
+ * @param docsPath Docs path for the See-link, e.g. `'components/preview-card'`.
605
+ */
606
+ declare function rdxCheckTriggerElement(triggerName: string, code: string, docsPath: string): void;
607
+ /**
608
+ * Dev-mode check: warns when a label part sits on a non-`<label>` host. The `for` attribute only
609
+ * associates a `<label>` with a control, so a label part placed on any other element is not
610
+ * programmatically connected to its control.
611
+ *
612
+ * Must be called inside an injection context. No-op outside `isDevMode()` and on synthetic hosts.
613
+ *
614
+ * @param labelName Selector name used in the message, e.g. `'rdxFieldLabel'`.
615
+ * @param code Stable diagnostics code, e.g. `'field/unassociated-label'`.
616
+ * @param docsPath Docs path for the See-link, e.g. `'components/field'`.
617
+ */
618
+ declare function rdxCheckLabelElement(labelName: string, code: string, docsPath: string): void;
619
+
551
620
  declare function handleAndDispatchCustomEvent<E extends CustomEvent, OriginalEvent extends Event>(name: string, handler: ((event: E) => void) | undefined, detail: {
552
621
  originalEvent: OriginalEvent;
553
622
  } & (E extends CustomEvent<infer D> ? D : never)): void;
@@ -790,6 +859,658 @@ interface RdxFormCheckboxControl extends RdxFormUiControl {
790
859
  readonly value?: undefined;
791
860
  }
792
861
 
862
+ interface RdxCancelableChangeEventDetails<Reason extends string = string> {
863
+ reason: Reason;
864
+ event: Event;
865
+ trigger: HTMLElement | undefined;
866
+ cancel: () => void;
867
+ isCanceled: () => boolean;
868
+ preventUnmountOnClose: () => void;
869
+ }
870
+ interface RdxCancelableChangeEventTransaction<Reason extends string = string> {
871
+ eventDetails: RdxCancelableChangeEventDetails<Reason>;
872
+ shouldPreventUnmountOnClose: () => boolean;
873
+ }
874
+ declare function createCancelableChangeEventDetails<Reason extends string>(reason: Reason, event: Event, trigger?: HTMLElement): RdxCancelableChangeEventTransaction<Reason>;
875
+
876
+ /**
877
+ * The typed event map for the **shared tree-level coordination channel** on {@link RdxFloatingTree}.
878
+ * Neutral by design — it ships no events initially. Each capability (hover-close, virtual focus, menu
879
+ * coordination, list navigation) **augments** this interface via module augmentation rather than
880
+ * emitting untyped strings:
881
+ *
882
+ * ```ts
883
+ * declare module '@radix-ng/primitives/core' {
884
+ * interface RdxFloatingEventMap {
885
+ * virtualfocus: { id: string; element: HTMLElement | null };
886
+ * }
887
+ * }
888
+ * ```
889
+ *
890
+ * **`openchange` belongs here only per-popup, not per-tree.** Base UI emits `openchange` on the
891
+ * per-popup `FloatingRootStore.events` (`FloatingRootStore.ts:121`), not on the shared
892
+ * `FloatingTreeStore.events`. For open-state changes, use {@link RdxFloatingRootContextEventMap} on
893
+ * the popup's {@link RdxFloatingRootContext.events}, not this tree-level channel.
894
+ */
895
+ interface RdxFloatingEventMap {
896
+ }
897
+ /**
898
+ * The typed event map for the **per-popup events channel** on {@link RdxFloatingRootContext} —
899
+ * the Angular counterpart of Base UI's `FloatingRootStore.events` (`FloatingRootStore.ts:121`).
900
+ * Unlike the tree channel (one per coordinating root shared by all nested popups), this channel
901
+ * lives **on the root context** so each popup has its own scoped emitter with no cross-popup bleed.
902
+ */
903
+ interface RdxFloatingRootContextEventMap {
904
+ /** The popup's open-state changed. `reason` mirrors Base UI open-change reason strings. */
905
+ openchange: {
906
+ open: boolean;
907
+ reason?: string;
908
+ event?: Event;
909
+ };
910
+ }
911
+ /**
912
+ * Neutral typed event channel — the Angular counterpart of Base UI's `createEventEmitter()`, typed
913
+ * over `M` (a {@link RdxFloatingEventMap} sub-type for tree-level or a
914
+ * {@link RdxFloatingRootContextEventMap} sub-type for per-popup).
915
+ */
916
+ interface RdxFloatingEvents<M extends object = RdxFloatingEventMap> {
917
+ emit<K extends keyof M>(event: K, data: M[K]): void;
918
+ on<K extends keyof M>(event: K, listener: (data: M[K]) => void): void;
919
+ off<K extends keyof M>(event: K, listener: (data: M[K]) => void): void;
920
+ }
921
+ /**
922
+ * Creates an {@link RdxFloatingEvents} emitter backed by `Map<event, Set<listener>>`, mirroring Base
923
+ * UI's implementation: synchronous dispatch, set-deduplicated listeners, no replay.
924
+ *
925
+ * **Snapshot dispatch:** `emit()` snapshots the listener set before iterating so that a listener
926
+ * calling `on()`/`off()` during dispatch does not cause skip/revisit issues.
927
+ */
928
+ declare function createFloatingEvents<M extends object = RdxFloatingEventMap>(): RdxFloatingEvents<M>;
929
+
930
+ /**
931
+ * The shared **mounted / open** lifecycle contract every floating capability reads (ADR 0015 §1,
932
+ * cross-cutting across ADRs 0015/0016/0017). It separates *presence* from *activeness*, which Base
933
+ * UI keeps distinct (`DialogStore` / `popupStoreUtils`):
934
+ *
935
+ * - **`mounted()`** — the node/popup is rendered. Stays `true` through an animated exit (Base UI's
936
+ * `FloatingFocusManager` is `disabled={!mounted}`, so the focus trap survives the exit).
937
+ * - **`open()`** — the popup is logically open. Flips to `false` **immediately** on close, *before*
938
+ * unmount.
939
+ *
940
+ * The interesting middle state is **closed-but-mounted** (`mounted() && !open()`): an animated exit,
941
+ * or a popup deliberately kept mounted after close. Its effects are **per-effect**, matching Base UI
942
+ * (it is *not* "everything off"):
943
+ *
944
+ * | concern | on `open() === false` (still mounted) |
945
+ * | -------------------------------- | ------------------------------------- |
946
+ * | dismissal (Escape/outside-press) | **inactive** — capability reads `open()` |
947
+ * | `markOthers` marker / aria-hidden| **released** |
948
+ * | internal backdrop | **`inert`** |
949
+ * | scroll lock | **unlocked** (predicate keys off `open()`) |
950
+ * | focus trap + return-focus | **persist until unmount** (read `mounted()`) |
951
+ *
952
+ * So every consumer reads `open()` for behavior and `mounted()` for presence — never conflating them.
953
+ *
954
+ * @remarks
955
+ * `preventUnmountOnClose` (Base UI's per-close one-shot that holds a popup *mounted after close*) is a
956
+ * **close-transaction** concern, not a standing boolean — modeling it as a persistent
957
+ * `signal<boolean>` would pin the popup mounted forever after the first prevented close. Whether Radix
958
+ * exposes it publicly or maps it onto `RdxPortalPresence` (keep-mounted) is pinned in ADR 0015/0017
959
+ * Phase 0; it is intentionally **not** baked into this contract.
960
+ */
961
+ interface RdxFloatingLifecycle {
962
+ /** Presence: the node/popup is rendered (stays `true` during an animated exit). */
963
+ readonly mounted: () => boolean;
964
+ /** Activeness: the popup is open (flips `false` immediately on close, before unmount). */
965
+ readonly open: () => boolean;
966
+ }
967
+
968
+ /**
969
+ * Per-popup store of active **trigger** elements — the Angular counterpart of Base UI's
970
+ * `triggerElements` (`PopupTriggerMap`) on each `FloatingRootStore`. One registry lives on each
971
+ * {@link RdxFloatingRootContext} (NOT on the shared tree, and NOT on the node — the context can exist
972
+ * without a node, e.g. the node-optional Navigation Menu case). Scoping it per-context is what keeps
973
+ * one independent popup's trigger from counting as inside-content for an unrelated popup.
974
+ *
975
+ * Within its context it is read by **both** the dismissal engine (ADR 0015 — outside-press / focus-out
976
+ * containment) and the focus manager (ADR 0017 — inside-element checks), so the two never drift into
977
+ * different inside-element sets (ADR 0015 §1 pillar 3, §2). A trigger is plain inside-content: it has
978
+ * **no** floating node and **no** parent — only its membership is stored here.
979
+ *
980
+ * Matching mirrors Base UI's `isTargetInsideEnabledTrigger`: a target counts when it is exactly a
981
+ * registered element ({@link hasElement}) **or** a descendant of one ({@link hasMatchingElement}).
982
+ * Membership is by **reference** (`Set.has` / `Node.contains`), so it stays correct **cross-realm** for
983
+ * elements from another `Window` / iframe — where `target instanceof Element` against the local realm
984
+ * would wrongly return `false`.
985
+ */
986
+ declare class RdxTriggerRegistry {
987
+ private readonly elements;
988
+ /** Registers `element` as a trigger. Idempotent. */
989
+ add(element: Element): void;
990
+ /** Removes `element` from the registry. */
991
+ delete(element: Element): void;
992
+ /**
993
+ * Exact membership — Base UI `triggerElements.hasElement(target)`. Uses reference identity
994
+ * (`Set.has`), **not** `instanceof Element`, so a trigger from another realm/iframe still matches.
995
+ */
996
+ hasElement(target: EventTarget | Node | null): boolean;
997
+ /**
998
+ * Ancestor match — Base UI `hasMatchingElement((t) => contains(t, target))`: `true` when any
999
+ * registered trigger contains `target`. Catches a press/focus landing on a child of the trigger.
1000
+ */
1001
+ hasMatchingElement(target: Node | null): boolean;
1002
+ /** `true` when `target` is a registered trigger or lives inside one. */
1003
+ contains(target: EventTarget | Node | null): boolean;
1004
+ }
1005
+
1006
+ /** Initialization for a {@link RdxFloatingRootContext}. */
1007
+ interface RdxFloatingRootContextInit {
1008
+ ownerDocument: Document;
1009
+ /** Popup open-state lifecycle. Defaults to `() => false`. */
1010
+ open?: () => boolean;
1011
+ floatingElement?: HTMLElement | null;
1012
+ referenceElement?: Element | null;
1013
+ }
1014
+ /**
1015
+ * The per-popup **root context / store** — the Angular counterpart of Base UI's `FloatingRootStore`
1016
+ * (`FloatingRootContext`). It is a **separate entity from {@link RdxFloatingNode}** (which is only
1017
+ * `id` + `parent` + a context ref), mirroring Base UI: the node carries tree membership, the root
1018
+ * context carries the popup's `open`, elements, and trigger registry.
1019
+ *
1020
+ * Crucially it can exist **without** a node — the `getEmptyRootContext()` analog ({@link
1021
+ * createFloatingRootContext}) — which is what lets a **node-optional** capability (Navigation Menu,
1022
+ * ADR 0015 §1 / ADR 0017 #12) still read `open()`, `triggers`, and the elements while its tree node is
1023
+ * temporarily absent. A dismissal/focus capability therefore references a root context **mandatorily**
1024
+ * and a node **optionally**.
1025
+ *
1026
+ * `floatingElement` / `referenceElement` are exposed read-only and mutated **only** through the
1027
+ * validated setters, so a consumer cannot bypass the owner-`Document` invariant with a raw assignment.
1028
+ */
1029
+ declare class RdxFloatingRootContext {
1030
+ readonly ownerDocument: Document;
1031
+ /** Neutral popup open-state lifecycle (singular). Tree traversal's `onlyOpen` filter reads this. */
1032
+ readonly open: () => boolean;
1033
+ /** Per-popup trigger registry (Base UI `triggerElements`), read by both dismissal and focus. */
1034
+ readonly triggers: RdxTriggerRegistry;
1035
+ /**
1036
+ * Per-popup typed event channel (Base UI `FloatingRootStore.events`). Scoped to this popup,
1037
+ * so open-change events carry no cross-popup bleed. Use `events.emit('openchange', …)` when
1038
+ * the popup's `open` state changes; dismissal / focus manager subscribe here, not on the tree.
1039
+ */
1040
+ readonly events: RdxFloatingEvents<RdxFloatingRootContextEventMap>;
1041
+ private floatingElementRef;
1042
+ private referenceElementRef;
1043
+ private readonly floatingElementsRef;
1044
+ constructor(init: RdxFloatingRootContextInit);
1045
+ /** The floating (popup) element, once it renders. `null` while mounted-but-not-yet-rendered. */
1046
+ get floatingElement(): HTMLElement | null;
1047
+ /** The reference (anchor / trigger) element the popup is positioned against. */
1048
+ get referenceElement(): Element | null;
1049
+ /**
1050
+ * **All** of this layer's own root elements — the popup plus any extra roots a primitive owns (e.g.
1051
+ * a Dialog backdrop relocated as a separate body sibling). This is DOM-footprint bookkeeping for
1052
+ * primitive-specific checks; `markOthers` keep-sets are intentionally narrower and do not include
1053
+ * sibling roots such as backdrops. Distinct from {@link floatingElement} (the single popup, used for
1054
+ * press / focus containment).
1055
+ */
1056
+ get floatingElements(): ReadonlySet<Element>;
1057
+ /** Assigns the floating (popup) element, validating it shares this context's `ownerDocument`. */
1058
+ setFloatingElement(element: HTMLElement | null): void;
1059
+ /** Registers an additional owned root element (e.g. a backdrop) into {@link floatingElements}. */
1060
+ addFloatingElement(element: Element): void;
1061
+ /** Removes a previously {@link addFloatingElement | added} owned root element. */
1062
+ removeFloatingElement(element: Element): void;
1063
+ /** Assigns the reference element, validating it shares this context's `ownerDocument`. */
1064
+ setReferenceElement(element: Element | null): void;
1065
+ private assertSameDocument;
1066
+ }
1067
+ /**
1068
+ * Creates a standalone {@link RdxFloatingRootContext} **without** a tree node — the Angular counterpart
1069
+ * of Base UI's `getEmptyRootContext()`. Use it for a node-optional capability that needs a root context
1070
+ * before (or without) registering a floating node.
1071
+ */
1072
+ declare function createFloatingRootContext(init: RdxFloatingRootContextInit): RdxFloatingRootContext;
1073
+
1074
+ /**
1075
+ * Module-private construction key. The {@link RdxFloatingNode} constructor requires it, and its type is
1076
+ * a non-exported `unique symbol`, so consumers can neither name it (compile error) nor produce it
1077
+ * (runtime guard) — a node can only be created through {@link RdxFloatingTree.register}, never a loose
1078
+ * `new RdxFloatingNode(...)` that would exist outside `tree.all`.
1079
+ */
1080
+ declare const NODE_CONSTRUCT_KEY: unique symbol;
1081
+ /**
1082
+ * A neutral node in the shared floating tree — the Angular counterpart of Base UI's `FloatingNode`
1083
+ * (`{ id, parentId, context? }`). It is deliberately **lightweight**: tree membership only. The
1084
+ * popup's `open` state, trigger registry, and elements live on the separate {@link
1085
+ * RdxFloatingRootContext}, exactly as Base UI splits `FloatingNode` from `FloatingRootStore`.
1086
+ *
1087
+ * `parent` and `context` are exposed **read-only**; they are mutated **only** through {@link
1088
+ * RdxFloatingTree.setParent} / {@link RdxFloatingTree.setContext} (which enforce the cycle and
1089
+ * owner-`Document` invariants). The backing state is held in a module-private `WeakMap`, so a consumer
1090
+ * cannot reach around the tree with `node.parent = …` / `node.context = …`.
1091
+ *
1092
+ * `context` may be `null` (a contextless intermediate node). Open-ness is read from the context — there
1093
+ * is no `open` on the node — and tree traversal's `onlyOpen` filter reads `node.context?.open()`,
1094
+ * mirroring Base UI's `getNodeChildren` filtering on `child.context?.open`. Presence (`mounted`) is
1095
+ * implicit: a node is mounted **iff** it is registered in the tree.
1096
+ */
1097
+ declare class RdxFloatingNode {
1098
+ readonly id: string;
1099
+ /** Which tree (store) this node belongs to — Base UI's `externalTree`. */
1100
+ readonly tree: RdxFloatingTree;
1101
+ /** @internal — constructed only by {@link RdxFloatingTree.register} (guarded by a module-private key). */
1102
+ constructor(construct: typeof NODE_CONSTRUCT_KEY, id: string, tree: RdxFloatingTree, parent: RdxFloatingNode | null, context: RdxFloatingRootContext | null);
1103
+ /** Resolved **logical** parent (DI-derived). Reassign via {@link RdxFloatingTree.setParent}. */
1104
+ get parent(): RdxFloatingNode | null;
1105
+ /** The per-popup root context/store. `null` for a contextless node. Reassign via `tree.setContext`. */
1106
+ get context(): RdxFloatingRootContext | null;
1107
+ }
1108
+ /**
1109
+ * Discriminated parent-assignment override (ADR 0015 §1). A nullable optional would collapse
1110
+ * `undefined`/`null` in Angular signals, so "no override" (`inherit`) and "explicit independent root"
1111
+ * (`root`) must be **distinct** values — they must not both reduce to `parent == null`.
1112
+ */
1113
+ type RdxFloatingParentOverride = {
1114
+ kind: 'inherit';
1115
+ } | {
1116
+ kind: 'root';
1117
+ } | {
1118
+ kind: 'node';
1119
+ parent: RdxFloatingNode;
1120
+ };
1121
+ /** Initialization for {@link RdxFloatingTree.register}. The caller resolves `inherit` before calling. */
1122
+ interface RdxFloatingNodeInit {
1123
+ id: string;
1124
+ /** Already-resolved logical parent (`null` for a root). */
1125
+ parent: RdxFloatingNode | null;
1126
+ /** The per-popup root context. `null` for a contextless intermediate node. */
1127
+ context: RdxFloatingRootContext | null;
1128
+ }
1129
+ /**
1130
+ * The shared floating tree (node store) — the Angular counterpart of Base UI's `FloatingTreeStore`.
1131
+ *
1132
+ * It owns a flat set of {@link RdxFloatingNode | nodes} linked by `parent`, an adjacency index for
1133
+ * O(1) child lookup, and a neutral typed {@link RdxFloatingEvents | event channel}. It owns **neither**
1134
+ * trigger registries **nor** `open` state — those live per-popup on each {@link RdxFloatingRootContext}
1135
+ * (Base UI keeps them on the root store, not the tree store). Dismissal (ADR 0015) and the focus
1136
+ * manager (ADR 0017) read the **same** nodes, traversal, and events; neither owns the tree.
1137
+ *
1138
+ * Ancestry is **logical** (DI-derived), not DOM-derived, so portal relocation never changes ownership
1139
+ * (ADR 0015 §1). Independent roots are **not** coordinated against each other (Base UI parity): the
1140
+ * tree only answers questions *within* itself.
1141
+ *
1142
+ * **Performance:** `isRegistered()` is O(1) via `nodeSet`; `directChildren()` is O(1) via the
1143
+ * `childrenOf` adjacency map; `ancestors()` is O(depth); `children()` is O(n) total.
1144
+ */
1145
+ declare class RdxFloatingTree {
1146
+ /**
1147
+ * Neutral typed event channel (hover-close, virtual focus, menu coordination, list nav). Private to
1148
+ * this tree, which is scoped-by-default (one per coordinating root via `provideFloatingTree()`), so
1149
+ * events never leak across unrelated popups — matching Base UI's per-`FloatingTree` events.
1150
+ */
1151
+ readonly events: RdxFloatingEvents;
1152
+ /** O(1) membership test and snapshot for `all`. */
1153
+ private readonly nodeSet;
1154
+ /**
1155
+ * Adjacency index: maps each node (or `null` for root nodes) to its direct children in
1156
+ * registration order. Maintained in sync by `register`, `unregister`, and `setParent`.
1157
+ * Eliminates the O(n) `filter` per node in recursive traversal. Invariant: only **non-empty**
1158
+ * arrays are stored — an entry is pruned the moment its last child leaves, so a key never
1159
+ * outlives its node (see `removeFromChildrenOf`).
1160
+ */
1161
+ private readonly childrenOf;
1162
+ /** Registers a new node. `init.parent` must already be resolved (DI layer handles `inherit`). */
1163
+ register(init: RdxFloatingNodeInit): RdxFloatingNode;
1164
+ /** Removes a node from the tree. Children are **not** removed — they keep their `parent` reference. */
1165
+ unregister(node: RdxFloatingNode): void;
1166
+ /**
1167
+ * Associates / re-associates / clears a node's root context after registration (Base UI attaches
1168
+ * the context once the floating element resolves). Validates the new context's owner-`Document`
1169
+ * against the nearest context-bearing **ancestor** (through contextless intermediates) **and**
1170
+ * every context-bearing **descendant**, so a contextless intermediate can never bridge two
1171
+ * documents. Allows the `null → context → null` lifecycle.
1172
+ */
1173
+ setContext(node: RdxFloatingNode, context: RdxFloatingRootContext | null): void;
1174
+ /** Reparents an existing node (detached composition / explicit `node` override), with cycle guard. */
1175
+ setParent(node: RdxFloatingNode, parent: RdxFloatingNode | null): void;
1176
+ /**
1177
+ * Direct + transitive children, in registration order. The `onlyOpen` filter (default `true`)
1178
+ * filters the **result** by each node's `context?.open()` lifecycle but **never** aborts recursion
1179
+ * at a closed node, so a keep-mounted/closed parent never hides an open grandchild (Base UI
1180
+ * `getNodeChildren`, ADR 0015 §1 traversal contract).
1181
+ *
1182
+ * Dismissal children queries pass `onlyOpen: true` (the `hasBlockingChild` pattern in the
1183
+ * capability). The focus manager's focus-return check passes `onlyOpen: false` explicitly (Base UI
1184
+ * `FloatingFocusManager.tsx:842`) — so focus inside a closed-but-mounted descendant still counts as
1185
+ * "inside the tree". Always pass `onlyOpen` explicitly for non-dismissal paths; do not inherit the
1186
+ * default.
1187
+ */
1188
+ children(node: RdxFloatingNode, options?: {
1189
+ onlyOpen?: boolean;
1190
+ }): RdxFloatingNode[];
1191
+ /**
1192
+ * Logical ancestors of `node`, nearest first (Base UI `getNodeAncestors`). The walk **stops at an
1193
+ * unregistered node**: Base UI resolves ancestry by `parentId` lookup in the live nodes array, so
1194
+ * unregistering a parent breaks the chain (a removed middle node truncates ancestry — its children
1195
+ * keep the raw `parent` identity but it no longer appears as an ancestor). This avoids a "ghost"
1196
+ * ancestor lingering in DI-ownership / document / dismissal/focus traversal when Angular destroys a
1197
+ * parent before its child.
1198
+ */
1199
+ ancestors(node: RdxFloatingNode): RdxFloatingNode[];
1200
+ /** Snapshot of all registered nodes (debugging / diagnostics). Registration order is preserved. */
1201
+ get all(): readonly RdxFloatingNode[];
1202
+ /** Direct children of `parent` in registration order. O(1) via the adjacency map. */
1203
+ private directChildren;
1204
+ private addToChildrenOf;
1205
+ private removeFromChildrenOf;
1206
+ /**
1207
+ * Nearest context-bearing node walking up from `node` (inclusive), skipping contextless ancestors.
1208
+ * Stops at an unregistered node (same ghost-ancestry rule as {@link ancestors}).
1209
+ */
1210
+ private nearestContext;
1211
+ /** All contexts among `node`'s transitive descendants (skips `node` itself). */
1212
+ private descendantContexts;
1213
+ /**
1214
+ * Dev-only: validates every context in `node`'s subtree (node + transitive descendants) against
1215
+ * the nearest context-bearing ancestor walking up from `newParent`. Shared between `setParent`
1216
+ * (moves a subtree under a new parent) to keep the document-consistency rule in one place.
1217
+ */
1218
+ private assertSubtreeDocuments;
1219
+ /** Whether `node` is currently registered in this tree. O(1). */
1220
+ private isRegistered;
1221
+ /**
1222
+ * Guards that `node` actually belongs to **this** tree and is still registered — so a tree can
1223
+ * never mutate/traverse a node owned by another tree (which would leave `node.tree` pointing
1224
+ * elsewhere while its ancestry leads here) or one that was already unregistered.
1225
+ */
1226
+ private assertOwnedNode;
1227
+ /** A parent (when given) must belong to this tree **and** still be registered. */
1228
+ private assertRegisterableParent;
1229
+ /** Owner-`Document` consistency between a node's context and a related (ancestor/descendant) context. */
1230
+ private assertContextDocument;
1231
+ }
1232
+
1233
+ /**
1234
+ * Registers a {@link RdxFloatingNode} into the shared {@link RdxFloatingTree} for its DI subtree and
1235
+ * propagates the registration handle to descendants — the reusable Angular counterpart of mounting a
1236
+ * Base UI `<FloatingNode>` (ADR 0015 §1, Phase 1). It is the **single** place that runs the handle
1237
+ * pattern; the dismissal capability (ADR 0015) and the focus manager (ADR 0017) **consume** the node /
1238
+ * context / tree it registers, they do not re-implement registration.
1239
+ *
1240
+ * **What it owns vs. what it reads.** It provides its own {@link RdxFloatingRegistrationContext} (so
1241
+ * descendants resolve it with `skipSelf`) and registers/unregisters a node reactively. It does **not**
1242
+ * create the tree or the root context — a coordination-boundary primitive root supplies those
1243
+ * (`provideFloatingTree()` inherit-or-create + `provideFloatingRootContext()`); this directive injects
1244
+ * them. With **no** enclosing tree it runs **node-optional** (`status() === 'detached'`, `node() ===
1245
+ * null`), reading its context directly — the standalone `rdxDismissableLayer` case.
1246
+ *
1247
+ * **Resolution (per {@link RdxFloatingParentOverride}).** Only an `inherit` node depends on the DI
1248
+ * parent, so only it waits on a `pending` parent; `root` / `node` overrides are independent and register
1249
+ * immediately. The node carries the injected {@link RDX_FLOATING_ROOT_CONTEXT} (or `null` for a
1250
+ * contextless intermediate). All teardown (re-resolution **and** destroy) unregisters the node and
1251
+ * reverts the handle.
1252
+ */
1253
+ declare class RdxFloatingNodeRegistration {
1254
+ /** Explicit tree for detached sibling composition — Base UI's `externalTree`. */
1255
+ readonly externalTree: i0.InputSignal<RdxFloatingTree | null>;
1256
+ /** How this node's logical parent is resolved. Defaults to `inherit` (nearest DI ancestor). */
1257
+ readonly parentOverride: i0.InputSignal<RdxFloatingParentOverride>;
1258
+ /** Own handle — the WRITER side (concrete class); this directive is the only writer. */
1259
+ private readonly selfReg;
1260
+ /** Nearest ancestor handle — the READER side (reader-typed token), or `null` at the top. */
1261
+ private readonly parentReg;
1262
+ /** The enclosing tree, if a coordination boundary provided one (else node-optional). */
1263
+ private readonly ambientTree;
1264
+ /** This node's per-popup context, or `null` for a contextless intermediate node. */
1265
+ private readonly rootContext;
1266
+ private readonly nodeId;
1267
+ /** This directive's node once registered (`null` while `pending` / `detached`). */
1268
+ readonly node: Signal<RdxFloatingNode | null>;
1269
+ /** Lifecycle phase of this directive's registration (`pending` | `detached` | `registered`). */
1270
+ readonly status: Signal<_radix_ng_primitives_core.RdxFloatingRegistrationStatus>;
1271
+ /** The tree this node joined (`null` until `registered`). */
1272
+ readonly tree: Signal<RdxFloatingTree | null>;
1273
+ constructor();
1274
+ static ɵfac: i0.ɵɵFactoryDeclaration<RdxFloatingNodeRegistration, never>;
1275
+ static ɵdir: i0.ɵɵDirectiveDeclaration<RdxFloatingNodeRegistration, "[rdxFloatingNode]", ["rdxFloatingNode"], { "externalTree": { "alias": "externalTree"; "required": false; "isSignal": true; }; "parentOverride": { "alias": "parentOverride"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
1276
+ }
1277
+
1278
+ /** The three lifecycle phases of a {@link RdxFloatingRegistrationContext} (see {@link RegistrationState}). */
1279
+ type RdxFloatingRegistrationStatus = 'pending' | 'detached' | 'registered';
1280
+ /**
1281
+ * The **read-only projection** of a registration handle — what a **descendant** injects (it reads its
1282
+ * nearest ancestor's handle to resolve its logical parent, ADR 0015 §1). It deliberately exposes only
1283
+ * the reactive reads (`status` / `tree` / `node`) and **not** the writers (`register` / `markDetached` /
1284
+ * `clear`), which belong solely to the directive that owns the handle: a descendant must never be able
1285
+ * to clear or re-point its parent's registration. The {@link RDX_FLOATING_REGISTRATION} token is typed
1286
+ * as this reader, so the ergonomic, cast-free injection path is read-only; the owning directive uses the
1287
+ * concrete {@link RdxFloatingRegistrationContext} type (the writer) for its own handle.
1288
+ */
1289
+ interface RdxFloatingRegistrationReader {
1290
+ readonly status: Signal<RdxFloatingRegistrationStatus>;
1291
+ readonly tree: Signal<RdxFloatingTree | null>;
1292
+ readonly node: Signal<RdxFloatingNode | null>;
1293
+ }
1294
+ /**
1295
+ * A **stable DI handle** created at injector formation time and filled in at runtime once the
1296
+ * registration directive resolves its `externalTree` / `parentOverride` inputs.
1297
+ *
1298
+ * **Why a handle, not direct token replacement.** Angular injectors are sealed at creation — a
1299
+ * directive that resolves its tree from a runtime `externalTree` input cannot change what
1300
+ * `RDX_FLOATING_TREE` resolves to for its subtree afterwards. The handle is the object that _is_
1301
+ * provided at creation; its internal state signal changes at runtime. Descendants inject the handle
1302
+ * (with `skipSelf: true`) and read `parentReg.tree()` / `parentReg.node()` reactively — they never
1303
+ * depend on tokens being swapped post-construction.
1304
+ *
1305
+ * **Atomicity.** `tree` and `node` are **not** separate `WritableSignal`s — independent `.set()`
1306
+ * calls would create intermediate states where `node.tree !== tree`. Instead there is **one** private
1307
+ * {@link RegistrationState} signal; `register(tree, node)` sets the `registered` payload after asserting
1308
+ * `node.tree === tree`, `markDetached()` records "resolved, no node", and `clear()` reverts to
1309
+ * `pending`. The `tree`/`node`/`status` reads are `computed()` over that one signal, so they can never
1310
+ * disagree.
1311
+ *
1312
+ * **Registration directive usage pattern:**
1313
+ *
1314
+ * ```ts
1315
+ * @Directive({ providers: [provideFloatingRegistration()] })
1316
+ * class SomeFloatingDirective {
1317
+ * // Own handle — the WRITER side. Inject the concrete type so register()/markDetached()/clear() are
1318
+ * // available; this is the only place that writes this handle.
1319
+ * private readonly selfReg = inject(RdxFloatingRegistrationContext);
1320
+ * // Parent handle — the READER side (token is reader-typed). A descendant can read status/tree/node
1321
+ * // but cannot mutate the parent's registration.
1322
+ * private readonly parentReg = inject(RDX_FLOATING_REGISTRATION, { optional: true, skipSelf: true });
1323
+ * private readonly ambientTree = inject(RDX_FLOATING_TREE, { optional: true });
1324
+ *
1325
+ * constructor() {
1326
+ * effect((onCleanup) => {
1327
+ * const override = this.parentOverride(); // { kind: 'inherit' | 'root' | 'node' }
1328
+ *
1329
+ * // ONLY an `inherit` node depends on the DI parent, so only it waits on a `pending` parent (a
1330
+ * // `pending` parent is NOT "no parent"; reading status() subscribes us, so we re-run when it
1331
+ * // flips). `root` / `node` overrides are independent of the DI ancestor and register NOW —
1332
+ * // waiting on the DI parent would wrongly stall them, or strand them if that parent is destroyed.
1333
+ * if (override.kind === 'inherit' && this.parentReg?.status() === 'pending') return;
1334
+ *
1335
+ * // Logical parent from the override (DI parent only for `inherit`; a `detached` parent reads
1336
+ * // null → this node becomes a root in its tree).
1337
+ * const parentNode =
1338
+ * override.kind === 'node' ? override.parent
1339
+ * : override.kind === 'root' ? null
1340
+ * : (this.parentReg?.node() ?? null); // 'inherit'
1341
+ *
1342
+ * // Tree selection (resolveFloatingTree's logic; inject() is illegal inside effect()). A `node`
1343
+ * // override must join its parent's tree.
1344
+ * const externalTree = this.externalTreeInput(); // input() signal
1345
+ * const resolvedTree =
1346
+ * (override.kind === 'node' ? override.parent.tree : undefined) ??
1347
+ * externalTree ?? this.parentReg?.tree() ?? this.ambientTree;
1348
+ * if (!resolvedTree) {
1349
+ * this.selfReg.markDetached(); // node-optional: resolved, but no tree → no node
1350
+ * return;
1351
+ * }
1352
+ *
1353
+ * const node = resolvedTree.register({ id: ..., parent: parentNode, context: ... });
1354
+ * this.selfReg.register(resolvedTree, node);
1355
+ *
1356
+ * onCleanup(() => {
1357
+ * resolvedTree.unregister(node);
1358
+ * this.selfReg.clear(); // transient: back to 'pending' until the effect re-resolves
1359
+ * });
1360
+ * });
1361
+ * }
1362
+ * }
1363
+ * ```
1364
+ */
1365
+ declare class RdxFloatingRegistrationContext implements RdxFloatingRegistrationReader {
1366
+ private readonly _state;
1367
+ /**
1368
+ * Lifecycle phase: `pending` (resolving — children must wait), `detached` (resolved, node-optional),
1369
+ * or `registered`. A `computed()` over the one internal state signal. See {@link RegistrationState}.
1370
+ */
1371
+ readonly status: Signal<RdxFloatingRegistrationStatus>;
1372
+ /**
1373
+ * The tree this directive joined, or `null` unless `status() === 'registered'`. A `computed()`
1374
+ * derived from the internal state — always consistent with {@link node} and {@link status}.
1375
+ */
1376
+ readonly tree: Signal<RdxFloatingTree | null>;
1377
+ /**
1378
+ * The node this directive registered, or `null` unless `status() === 'registered'`. A `computed()`
1379
+ * derived from the internal state — always consistent with {@link tree}
1380
+ * (`node.tree === tree` is invariant in the `registered` state).
1381
+ */
1382
+ readonly node: Signal<RdxFloatingNode | null>;
1383
+ /**
1384
+ * Atomically records the resolved tree and the registered node (`status → 'registered'`). Asserts
1385
+ * `node.tree === tree` so no state where `tree` and `node` point to different stores can exist.
1386
+ * Called by the directive inside `effect()` after `tree.register(…)` succeeds.
1387
+ */
1388
+ register(tree: RdxFloatingTree, node: RdxFloatingNode): void;
1389
+ /**
1390
+ * Records that the directive **resolved but has no node** (`status → 'detached'`): node-optional —
1391
+ * no tree was available (e.g. a standalone `rdxDismissableLayer`). Distinct from `pending`: a child
1392
+ * treats a `detached` parent as absent (inherits `null`), whereas it must **wait** on a `pending` one.
1393
+ */
1394
+ markDetached(): void;
1395
+ /**
1396
+ * Reverts to `pending` (the `onCleanup` counterpart of {@link register} / {@link markDetached}).
1397
+ * Called after `tree.unregister(node)` so the handle re-enters the "resolving" phase until the
1398
+ * directive's effect re-runs; `tree`/`node` read `null` again.
1399
+ */
1400
+ clear(): void;
1401
+ }
1402
+ /**
1403
+ * DI token for the nearest ancestor's registration handle, **typed as the read-only
1404
+ * {@link RdxFloatingRegistrationReader}**. A descendant injects it with `{ optional: true, skipSelf:
1405
+ * true }` to read its parent's `status` / `tree` / `node` — and, because the token is reader-typed,
1406
+ * **cannot** call the parent's writers (`register` / `markDetached` / `clear`) without a deliberate
1407
+ * cast. The owning directive writes through its own handle, which it injects by the concrete
1408
+ * {@link RdxFloatingRegistrationContext} type instead (see {@link provideFloatingRegistration}).
1409
+ */
1410
+ declare const RDX_FLOATING_REGISTRATION: InjectionToken<RdxFloatingRegistrationReader>;
1411
+ /**
1412
+ * Seals a fresh registration handle into this directive's injector at creation time. Returns **two**
1413
+ * providers backed by **one** instance: the concrete {@link RdxFloatingRegistrationContext} (the
1414
+ * writer, injected by the owning directive) and a reader-typed {@link RDX_FLOATING_REGISTRATION} alias
1415
+ * (`useExisting`) that descendants inject. Splitting writer from reader is what stops a descendant from
1416
+ * mutating its parent's registration. Call this in a directive's `providers` array; the directive then
1417
+ * calls `selfReg.register(tree, node)` / `markDetached()` / `clear()` on its own (writer) handle.
1418
+ */
1419
+ declare function provideFloatingRegistration(): Provider[];
1420
+
1421
+ /** Default marker attribute on the imperatively-created internal backdrop element. */
1422
+ declare const RDX_INTERNAL_BACKDROP_ATTR = "data-rdx-internal-backdrop";
1423
+ interface RdxInternalBackdropOptions {
1424
+ /** Whether the backdrop should exist (modal && reason-appropriate). Reactive — read in an effect. */
1425
+ shouldRender: () => boolean;
1426
+ /** Whether the popup is open. Reactive — drives `inert` during the closed-but-mounted exit window. */
1427
+ isOpen: () => boolean;
1428
+ /**
1429
+ * The element to keep interactive through a clip-path "cutout" (e.g. the trigger, so a toggle-close
1430
+ * click still reaches it), or `null` for a full backdrop. Read once when the backdrop is created.
1431
+ */
1432
+ cutout?: () => Element | null;
1433
+ /** Marker attribute applied to the backdrop. Defaults to {@link RDX_INTERNAL_BACKDROP_ATTR}. */
1434
+ marker?: string;
1435
+ /** Let pointer/wheel events pass through the backdrop while keeping it mounted for lifecycle parity. */
1436
+ passThrough?: () => boolean;
1437
+ }
1438
+ /**
1439
+ * Renders Base UI's **internal backdrop** for a modal floating popup: a full-viewport element that
1440
+ * intercepts background pointer events (so the page behind the popup is non-interactive) **and** is
1441
+ * itself the outside-press target — clicking it lets the dismissal capability close the popup. This is
1442
+ * why a plain `inert` pass on outside elements is not enough: an inert element dispatches no pointer
1443
+ * event, so the popup could never close on an outside click. An optional clip-path cutout keeps the
1444
+ * trigger (or another region) clickable.
1445
+ *
1446
+ * Inserted as a **sibling before the positioner** — a sibling, not a child: a `position: fixed` element
1447
+ * inside a transformed positioner would be clipped to the positioner's box, not the viewport. The
1448
+ * positioner's own stacking (its `z-index`) keeps the popup above the backdrop.
1449
+ *
1450
+ * Call from the positioner directive inside `afterNextRender` (so the structural portal has already
1451
+ * relocated the positioner into its container).
1452
+ */
1453
+ declare function setupInternalBackdrop(positioner: HTMLElement, injector: Injector, options: RdxInternalBackdropOptions): void;
1454
+
1455
+ /**
1456
+ * The nearest shared {@link RdxFloatingTree}. **Scoped-by-default, sharing explicit** — strict Base UI
1457
+ * parity: Base UI creates a `FloatingTree` only at the **coordination boundary** (e.g. a top-level Menu
1458
+ * renders `<FloatingTree>`, a nested submenu does **not** — it inherits the parent's store,
1459
+ * `MenuRoot.tsx:533`). There is deliberately **no** application-root provider, so the token resolves only
1460
+ * under a root that opts in with {@link provideFloatingTree}; elsewhere injecting it optionally yields
1461
+ * `null` and the primitive is its own independent root (`parent === null`).
1462
+ */
1463
+ declare const RDX_FLOATING_TREE: InjectionToken<RdxFloatingTree>;
1464
+ /**
1465
+ * Provides a {@link RdxFloatingTree} for a subtree — the Angular `FloatingTree` analogue. **Inherit-or-
1466
+ * create:** it returns the **nearest ancestor tree** if one is already provided above, and creates a new
1467
+ * one **only at the top coordination boundary**. This is what makes it safe for a nesting-capable root
1468
+ * (Menu/Menubar/Context Menu/nested Dialog) to put it in `providers` unconditionally — a **nested**
1469
+ * instance inherits the parent's tree (so its node parents correctly), while the **top** instance starts
1470
+ * the store. (An always-new tree on a nested root would split ancestry / throw `cross-tree-parent`.)
1471
+ *
1472
+ * **Tree selection is separate from parent assignment** (Base UI: `tree = externalTree ?? contextTree`,
1473
+ * `parentId = nearest FloatingNodeContext`). This helper + {@link resolveFloatingTree} own tree
1474
+ * selection. Parent assignment is resolved at runtime via the `RdxFloatingRegistrationContext` handle
1475
+ * (`parentReg.node()` in `effect()`). In particular `{ kind: 'root' }` is **not** tree isolation — it
1476
+ * sets `parent = null` **within the same tree**. A genuinely separate tree is supplied explicitly via
1477
+ * `resolveFloatingTree(externalTree)`.
1478
+ */
1479
+ declare function provideFloatingTree(): Provider;
1480
+ /**
1481
+ * Resolves **which tree** a node joins — the tree-selection contract, the Angular counterpart of Base
1482
+ * UI's `externalTree ?? contextTree` (`FloatingTree.tsx:25`). An explicit `externalTree` wins,
1483
+ * otherwise the nearest injected {@link RDX_FLOATING_TREE} (or `null` → the capability runs
1484
+ * **node-optional**). Parent assignment is separate — resolved reactively via
1485
+ * `parentReg.node()` from the {@link RDX_FLOATING_REGISTRATION} handle, not via a token.
1486
+ *
1487
+ * For a **detached** node registered with an explicit `{ kind: 'node', parent }` override from a sibling
1488
+ * injector, the nearest injected tree may be absent or a *different* tree than `parent.tree` — so the
1489
+ * caller **must** pass `externalTree = override.parent.tree` here, so the node joins its parent's tree (the
1490
+ * cross-tree invariant then holds). Must be called in an injection context when `externalTree` is omitted.
1491
+ */
1492
+ declare function resolveFloatingTree(externalTree?: RdxFloatingTree | null): RdxFloatingTree | null;
1493
+ /**
1494
+ * The shared per-popup {@link RdxFloatingRootContext} — the Angular counterpart of Base UI's
1495
+ * `FloatingRootContext`, created by `useFloatingRootContext` at the **primitive root** and **received**
1496
+ * by `useDismiss` / `FloatingFocusManager` (they never create their own). Mirroring that: a primitive
1497
+ * root (Dialog/Popover/Menu/…) creates **one** context and provides it here; the dismissal capability
1498
+ * (ADR 0015) and the focus manager (ADR 0017) read the **same** context, so `open`, `triggers`, and the
1499
+ * elements are never split across mechanisms.
1500
+ *
1501
+ * Optional: a **standalone** `rdxDismissableLayer` (no enclosing primitive root) has none, and
1502
+ * {@link injectFloatingRootContext} creates a fallback for that case only.
1503
+ */
1504
+ declare const RDX_FLOATING_ROOT_CONTEXT: InjectionToken<RdxFloatingRootContext>;
1505
+ /** Provides the shared {@link RdxFloatingRootContext} for a primitive root's subtree. */
1506
+ declare function provideFloatingRootContext(factory: () => RdxFloatingRootContext): Provider;
1507
+ /**
1508
+ * Returns the shared {@link RdxFloatingRootContext} provided by an enclosing primitive root, or creates
1509
+ * a **standalone fallback** via `fallback()` when none is provided (a bare `rdxDismissableLayer`). Must be
1510
+ * called in an injection context.
1511
+ */
1512
+ declare function injectFloatingRootContext(fallback: () => RdxFloatingRootContext): RdxFloatingRootContext;
1513
+
793
1514
  declare function injectDocument(): Document;
794
1515
 
795
1516
  declare function elementSize({ elementRef, injector }: {
@@ -800,7 +1521,13 @@ declare function elementSize({ elementRef, injector }: {
800
1521
  height: number;
801
1522
  }>;
802
1523
 
803
- declare function getActiveElement(): Element | null;
1524
+ /**
1525
+ * The deepest active element, descending into open shadow roots. Pass a specific `root`
1526
+ * (`Document` or `ShadowRoot`) to read focus in that document — defaults to the global `document`
1527
+ * (backward compatible). A focus scope passes its host's `ownerDocument` so it stays correct across
1528
+ * iframes / multi-document environments.
1529
+ */
1530
+ declare function getActiveElement(root?: DocumentOrShadowRoot): Element | null;
804
1531
 
805
1532
  /**
806
1533
  * Creates a resize observer effect for element
@@ -817,19 +1544,40 @@ declare function resizeEffect(options: {
817
1544
  onResize: ResizeObserverCallback;
818
1545
  }): EffectRef;
819
1546
 
1547
+ /** Marker attribute set on `<html>` while scroll is locked (a strategy-independent test/CSS hook). */
1548
+ declare const RDX_SCROLL_LOCKED_ATTR = "data-rdx-scroll-locked";
1549
+ interface RdxScrollLockOptions {
1550
+ /** Element whose owner document should be locked. Defaults to Angular's injected DOCUMENT. */
1551
+ referenceElement?: () => Element | null;
1552
+ }
820
1553
  /**
821
- * Locks page scrolling while `active()` is `true`, and restores the original state when it becomes
822
- * `false` or the calling context is destroyed.
823
- *
824
- * Locks **both** `<body>` and `<html>`: a `body { overflow: hidden }` lock alone does *not* stop the
825
- * page when `<html>` is the scroller (e.g. a global `overflow-y: scroll`, as Storybook sets), because
826
- * body-overflow only propagates to the viewport when `<html>`'s overflow is `visible`. The width of
827
- * the removed scrollbar is added as `padding-right` on `<html>` so the page doesn't shift.
1554
+ * Locks page scrolling while `active()` is `true`, restoring the original state when it becomes `false`
1555
+ * or the calling context is destroyed.
828
1556
  *
829
- * Lock ownership is shared across all callers via a single module-level counter, so nested or
830
- * concurrent overlays compose correctly. Must be called in an injection context.
1557
+ * This is the full Base UI `useScrollLock` behavioral set (ADR 0016 §1): it picks the **overlay** strategy
1558
+ * (plain `overflow: hidden`) for iOS / overlay-scrollbar documents and the **inset** strategy for desktop
1559
+ * scrollbars — the latter preserves the scroll position, reserves the scrollbar gutter (no content shift),
1560
+ * bails out during a Safari pinch-zoom, and re-locks on resize. Locks compose across all callers in the
1561
+ * same document via a shared per-`Document` ref count, and state is isolated per `Document` (iframe-safe).
1562
+ * No-op on the server. Must be called in an injection context.
1563
+ */
1564
+ declare function useScrollLock(active: Signal<boolean>, options?: RdxScrollLockOptions): void;
1565
+ /** Options for {@link useAnchoredScrollLock}. */
1566
+ interface RdxAnchoredScrollLockOptions {
1567
+ /** Whether the popup was opened by **touch** — the near-fullscreen gate applies only then. */
1568
+ touchOpen: () => boolean;
1569
+ /** The popup / positioner element whose width decides the touch gate (measured vs the viewport). */
1570
+ element: () => HTMLElement | null;
1571
+ }
1572
+ /**
1573
+ * Scroll lock for an **anchored** popup (Base UI `useAnchoredPopupScrollLock`, ADR 0016 §3). For a
1574
+ * non-touch open it behaves exactly like {@link useScrollLock} (locks while `enabled()`). For a **touch**
1575
+ * open it locks **only** when the popup is effectively viewport-width (`popupWidth >= viewportWidth -
1576
+ * 20px`) — otherwise the page stays scrollable so the user can swipe outside to dismiss the popup. The
1577
+ * width is measured off `element()`; reading `offsetWidth` forces layout, so it is accurate even before
1578
+ * the popup is positioned (visibility does not affect layout). Must be called in an injection context.
831
1579
  */
832
- declare function useScrollLock(active: Signal<boolean>): void;
1580
+ declare function useAnchoredScrollLock(enabled: Signal<boolean>, options: RdxAnchoredScrollLockOptions): void;
833
1581
 
834
1582
  type ArrowKeyOptions = 'horizontal' | 'vertical' | 'both';
835
1583
  interface ArrowNavigationOptions {
@@ -1100,5 +1848,5 @@ declare enum RdxPositionAlign {
1100
1848
  End = "end"
1101
1849
  }
1102
1850
 
1103
- export { A, ALT, ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT, ARROW_UP, ASTERISK, BACKSPACE, CAPS_LOCK, CONTROL, CTRL, DELETE, END, ENTER, ESCAPE, F1, F10, F11, F12, F2, F3, F4, F5, F6, F7, F8, F9, HOME, META, P, PAGE_DOWN, PAGE_UP, RdxControlValueAccessor, RdxIdGenerator, RdxLiveAnnouncer, RdxPositionAlign, RdxPositionSide, SHIFT, SPACE, SPACE_CODE, TAB, TIME_GRANULARITIES, a, areAllDaysBetweenValid, clamp, createContent, createContext, createFormatter, createMonth, createMonths, elementSize, getActiveElement, getDaysBetween, getDaysInMonth, getDefaultDate, getDefaultTime, getLastFirstDayOfWeek, getMaxTransitionDuration, getNextLastDayOfWeek, getOptsByGranularity, getPlaceholder, getSegmentElements, getWeekNumber, handleAndDispatchCustomEvent, handleCalendarInitialFocus, hasTime, initializeSegmentValues, injectControlValueAccessor, injectDocument, injectId, isAcceptableSegmentKey, isAfter, isAfterOrSame, isBefore, isBeforeOrSame, isBetween, isBetweenInclusive, isCalendarDateTime, isEqual, isItemEqualToValue, isNullish, isNumberString, isSegmentNavigationKey, isZonedDateTime, itemToStringLabel, itemToStringValue, j, k, n, normalizeDateStep, normalizeHour12, normalizeHourCycle, p, provideToken, provideValueAccessor, resizeEffect, roundToStepPrecision, segmentBuilders, snapValueToStep, syncSegmentValues, syncTimeSegmentValues, toDate, useArrowNavigation, useDateField, useFilter, useGraceArea, useListHighlight, usePointerDrag, useScrollLock, useTransitionStatus, watch };
1104
- export type { AcceptableValue, AnyExceptLiteral, AriaLivePoliteness, BooleanInput, CreateMonthProps, DataOrientation, DateAndTimeSegmentObj, DateFormatterOptions, DateMatcher, DateRange, DateSegmentObj, DateSegmentPart, DateStep, DayPeriod, Direction, EditableSegmentPart, FilterPredicates, Formatter, Granularity, HourCycle, InjectContext, ItemValueComparator, ListHighlight, Month, NonEditableSegmentPart, Nullable, NumberInput, PlaceholderMap, RdxFormCheckboxControl, RdxFormStateInput, RdxFormUiControl, RdxFormValueControl, RdxPointerDragHandlers, RdxTransitionStatus, RdxTransitionStatusRef, RdxValidationError, SafeFunction, SegmentContentObj, SegmentPart, SegmentValueObj, TimeGranularity, TimeSegmentObj, TimeSegmentPart, TimeValue, UseDateFieldProps, UseFilterOptions, UseListHighlightOptions };
1851
+ export { A, ALT, ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT, ARROW_UP, ASTERISK, BACKSPACE, CAPS_LOCK, CONTROL, CTRL, DELETE, DOCS_BASE_URL, END, ENTER, ESCAPE, F1, F10, F11, F12, F2, F3, F4, F5, F6, F7, F8, F9, HOME, META, P, PAGE_DOWN, PAGE_UP, RDX_FLOATING_REGISTRATION, RDX_FLOATING_ROOT_CONTEXT, RDX_FLOATING_TREE, RDX_INTERNAL_BACKDROP_ATTR, RDX_SCROLL_LOCKED_ATTR, RdxControlValueAccessor, RdxFloatingNode, RdxFloatingNodeRegistration, RdxFloatingRegistrationContext, RdxFloatingRootContext, RdxFloatingTree, RdxIdGenerator, RdxLiveAnnouncer, RdxPositionAlign, RdxPositionSide, RdxTriggerRegistry, SHIFT, SPACE, SPACE_CODE, TAB, TIME_GRANULARITIES, a, areAllDaysBetweenValid, clamp, createCancelableChangeEventDetails, createContent, createContext, createFloatingEvents, createFloatingRootContext, createFormatter, createMonth, createMonths, docsUrl, elementSize, getActiveElement, getDaysBetween, getDaysInMonth, getDefaultDate, getDefaultTime, getLastFirstDayOfWeek, getMaxTransitionDuration, getNextLastDayOfWeek, getOptsByGranularity, getPlaceholder, getSegmentElements, getWeekNumber, handleAndDispatchCustomEvent, handleCalendarInitialFocus, hasTime, initializeSegmentValues, injectControlValueAccessor, injectDocument, injectFloatingRootContext, injectId, isAcceptableSegmentKey, isAfter, isAfterOrSame, isBefore, isBeforeOrSame, isBetween, isBetweenInclusive, isCalendarDateTime, isEqual, isItemEqualToValue, isNullish, isNumberString, isSegmentNavigationKey, isZonedDateTime, itemToStringLabel, itemToStringValue, j, k, n, normalizeDateStep, normalizeHour12, normalizeHourCycle, p, provideFloatingRegistration, provideFloatingRootContext, provideFloatingTree, provideToken, provideValueAccessor, rdxCheckLabelElement, rdxCheckTriggerElement, rdxDevError, rdxDevWarning, resetRdxDevWarnings, resizeEffect, resolveFloatingTree, roundToStepPrecision, segmentBuilders, setupInternalBackdrop, snapValueToStep, syncSegmentValues, syncTimeSegmentValues, toDate, useAnchoredScrollLock, useArrowNavigation, useDateField, useFilter, useGraceArea, useListHighlight, usePointerDrag, useScrollLock, useTransitionStatus, watch };
1852
+ export type { AcceptableValue, AnyExceptLiteral, AriaLivePoliteness, BooleanInput, CreateMonthProps, DataOrientation, DateAndTimeSegmentObj, DateFormatterOptions, DateMatcher, DateRange, DateSegmentObj, DateSegmentPart, DateStep, DayPeriod, Direction, EditableSegmentPart, FilterPredicates, Formatter, Granularity, HourCycle, InjectContext, ItemValueComparator, ListHighlight, Month, NonEditableSegmentPart, Nullable, NumberInput, PlaceholderMap, RdxAnchoredScrollLockOptions, RdxCancelableChangeEventDetails, RdxCancelableChangeEventTransaction, RdxFloatingEventMap, RdxFloatingEvents, RdxFloatingLifecycle, RdxFloatingNodeInit, RdxFloatingParentOverride, RdxFloatingRegistrationReader, RdxFloatingRegistrationStatus, RdxFloatingRootContextEventMap, RdxFloatingRootContextInit, RdxFormCheckboxControl, RdxFormStateInput, RdxFormUiControl, RdxFormValueControl, RdxInternalBackdropOptions, RdxPointerDragHandlers, RdxScrollLockOptions, RdxTransitionStatus, RdxTransitionStatusRef, RdxValidationError, SafeFunction, SegmentContentObj, SegmentPart, SegmentValueObj, TimeGranularity, TimeSegmentObj, TimeSegmentPart, TimeValue, UseDateFieldProps, UseFilterOptions, UseListHighlightOptions };