@radix-ng/primitives 1.0.9 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -5
- package/fesm2022/radix-ng-primitives-autocomplete.mjs +35 -19
- package/fesm2022/radix-ng-primitives-autocomplete.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-checkbox.mjs +55 -7
- package/fesm2022/radix-ng-primitives-checkbox.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-combobox.mjs +46 -21
- package/fesm2022/radix-ng-primitives-combobox.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-composite.mjs +221 -36
- package/fesm2022/radix-ng-primitives-composite.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-core.mjs +189 -26
- package/fesm2022/radix-ng-primitives-core.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-date-field.mjs +29 -13
- package/fesm2022/radix-ng-primitives-date-field.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-field.mjs +127 -23
- package/fesm2022/radix-ng-primitives-field.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-floating-focus-manager.mjs +9 -0
- package/fesm2022/radix-ng-primitives-floating-focus-manager.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-focus-scope.mjs +241 -95
- package/fesm2022/radix-ng-primitives-focus-scope.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-form.mjs +35 -4
- package/fesm2022/radix-ng-primitives-form.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-input.mjs +87 -15
- package/fesm2022/radix-ng-primitives-input.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-menu.mjs +131 -16
- package/fesm2022/radix-ng-primitives-menu.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-navigation-menu.mjs +17 -3
- package/fesm2022/radix-ng-primitives-navigation-menu.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-popper.mjs +223 -29
- package/fesm2022/radix-ng-primitives-popper.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-portal.mjs +16 -5
- package/fesm2022/radix-ng-primitives-portal.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-scroll-area.mjs +6 -3
- package/fesm2022/radix-ng-primitives-scroll-area.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-select.mjs +49 -17
- package/fesm2022/radix-ng-primitives-select.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-signal-forms.mjs +27 -11
- package/fesm2022/radix-ng-primitives-signal-forms.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-time-field.mjs +24 -10
- package/fesm2022/radix-ng-primitives-time-field.mjs.map +1 -1
- package/package.json +8 -7
- package/types/radix-ng-primitives-autocomplete.d.ts +7 -2
- package/types/radix-ng-primitives-checkbox.d.ts +10 -1
- package/types/radix-ng-primitives-combobox.d.ts +29 -2
- package/types/radix-ng-primitives-composite.d.ts +48 -2
- package/types/radix-ng-primitives-core.d.ts +165 -104
- package/types/radix-ng-primitives-date-field.d.ts +21 -8
- package/types/radix-ng-primitives-field.d.ts +55 -8
- package/types/radix-ng-primitives-floating-focus-manager.d.ts +7 -0
- package/types/radix-ng-primitives-focus-scope.d.ts +61 -44
- package/types/radix-ng-primitives-form.d.ts +9 -0
- package/types/radix-ng-primitives-input.d.ts +10 -2
- package/types/radix-ng-primitives-menu.d.ts +59 -7
- package/types/radix-ng-primitives-navigation-menu.d.ts +2 -2
- package/types/radix-ng-primitives-popover.d.ts +2 -2
- package/types/radix-ng-primitives-popper.d.ts +186 -48
- package/types/radix-ng-primitives-portal.d.ts +18 -8
- package/types/radix-ng-primitives-preview-card.d.ts +2 -2
- package/types/radix-ng-primitives-select.d.ts +17 -3
- package/types/radix-ng-primitives-signal-forms.d.ts +22 -8
- package/types/radix-ng-primitives-time-field.d.ts +21 -10
- package/types/radix-ng-primitives-tooltip.d.ts +2 -2
- package/fesm2022/radix-ng-primitives-focus-guards.mjs +0 -53
- package/fesm2022/radix-ng-primitives-focus-guards.mjs.map +0 -1
- package/focus-guards/README.md +0 -1
- package/types/radix-ng-primitives-focus-guards.d.ts +0 -15
|
@@ -25,7 +25,10 @@ declare function createAriaOwnsAnchor(ownerDocument: Document, portalId: string)
|
|
|
25
25
|
* trigger steps onto the guard instead of jumping into the content.
|
|
26
26
|
*/
|
|
27
27
|
declare function disableFocusInside(container: HTMLElement): void;
|
|
28
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* Restores the tabbability that {@link disableFocusInside} suspended. Base UI `enableFocusInside`.
|
|
30
|
+
* Collects markers across the composed tree so guards inside shadow roots / slots are restored too.
|
|
31
|
+
*/
|
|
29
32
|
declare function enableFocusInside(container: HTMLElement): void;
|
|
30
33
|
/**
|
|
31
34
|
* Whether a focus event crossed the `container` boundary — its `relatedTarget` (the other side of the
|
|
@@ -134,6 +137,54 @@ type RdxFocusScopeConfig = {
|
|
|
134
137
|
declare const RdxFocusScopeConfigToken: InjectionToken<RdxFocusScopeConfig>;
|
|
135
138
|
declare function provideRdxFocusScopeConfig(factory: () => RdxFocusScopeConfig): Provider;
|
|
136
139
|
|
|
140
|
+
/**
|
|
141
|
+
* Tabbable / focusable detection ported from Base UI (`floating-ui-react/utils/tabbable.ts`). Unlike the
|
|
142
|
+
* old `TreeWalker` approximation this:
|
|
143
|
+
* - **pierces shadow DOM and `<slot>`** via composed traversal (`getComposedChildren`), so guards / portal
|
|
144
|
+
* tab-order see candidates inside web components and projected content;
|
|
145
|
+
* - honours **`inert`** ancestors, **`hidden`**, and computed-style visibility (`display`/`visibility`/
|
|
146
|
+
* `checkVisibility()` content-visibility);
|
|
147
|
+
* - treats **`<details>`/`<summary>`**, **`contenteditable`**, and **`audio`/`video[controls]`** as
|
|
148
|
+
* focusable with the right implicit tab index;
|
|
149
|
+
* - applies **radio-group** semantics (only the checked radio of a named group — or the first when none is
|
|
150
|
+
* checked — is tabbable).
|
|
151
|
+
*
|
|
152
|
+
* `composedContains` is the shadow-aware `Node.contains`, kept here as the low-level primitive so this
|
|
153
|
+
* module stays a leaf (only `@radix-ng/primitives/core`); `utils.ts` re-exports it for back-compat.
|
|
154
|
+
*/
|
|
155
|
+
type FocusableElement = HTMLElement | SVGElement;
|
|
156
|
+
/**
|
|
157
|
+
* Shadow-DOM-aware containment: whether `node` is `container` or lives inside it, crossing shadow roots
|
|
158
|
+
* via their `host` (unlike `Node.contains`, which stops at a shadow boundary). Base UI `contains`.
|
|
159
|
+
*/
|
|
160
|
+
declare function composedContains(container: Node, node: Node | null): boolean;
|
|
161
|
+
/** Collects elements matching `selector` across the composed tree (shadow / slot aware). */
|
|
162
|
+
declare function queryComposedAll(container: ParentNode, selector: string): HTMLElement[];
|
|
163
|
+
/** Whether `element` is reachable by Tab right now (Base UI `isTabbable`). */
|
|
164
|
+
declare function isTabbable(element: Element | null): boolean;
|
|
165
|
+
/** All programmatically focusable elements inside `container`, in composed document order. */
|
|
166
|
+
declare function focusable(container: Element): FocusableElement[];
|
|
167
|
+
/** All keyboard-tabbable elements inside `container`, in composed document order (Base UI `tabbable`). */
|
|
168
|
+
declare function tabbable(container: Element): FocusableElement[];
|
|
169
|
+
/**
|
|
170
|
+
* Back-compat alias for {@link tabbable} — historically returned a looser `TreeWalker` approximation;
|
|
171
|
+
* now the full Base UI tabbable set. Kept so existing imports (`getTabbableCandidates`) keep working.
|
|
172
|
+
*/
|
|
173
|
+
declare function getTabbableCandidates(container: Element): FocusableElement[];
|
|
174
|
+
/** The first and last tabbable elements inside `container`. */
|
|
175
|
+
declare function getTabbableEdges(container: Element): readonly [FocusableElement | undefined, FocusableElement | undefined];
|
|
176
|
+
/**
|
|
177
|
+
* The next tabbable in the document after the current focus (Base UI `getNextTabbable`) — used by the
|
|
178
|
+
* portal-focus bridge's trailing guard to step focus past the popup. Falls back to `reference`.
|
|
179
|
+
*/
|
|
180
|
+
declare function getNextTabbable(reference: Element | null): FocusableElement | null;
|
|
181
|
+
/** The previous tabbable in the document before the current focus (Base UI `getPreviousTabbable`). */
|
|
182
|
+
declare function getPreviousTabbable(reference: Element | null): FocusableElement | null;
|
|
183
|
+
/** The tabbable immediately after `reference` in the document, wrapping (Base UI `getTabbableAfterElement`). */
|
|
184
|
+
declare function getTabbableAfterElement(reference: Element | null): FocusableElement | null;
|
|
185
|
+
/** The tabbable immediately before `reference` in the document, wrapping (Base UI `getTabbableBeforeElement`). */
|
|
186
|
+
declare function getTabbableBeforeElement(reference: Element | null): FocusableElement | null;
|
|
187
|
+
|
|
137
188
|
declare const AUTOFOCUS_ON_MOUNT = "focusScope.autoFocusOnMount";
|
|
138
189
|
declare const AUTOFOCUS_ON_UNMOUNT = "focusScope.autoFocusOnUnmount";
|
|
139
190
|
declare const EVENT_OPTIONS: {
|
|
@@ -148,52 +199,18 @@ type FocusableTarget = HTMLElement | {
|
|
|
148
199
|
* Falls back to `event.target` when `composedPath` is unavailable.
|
|
149
200
|
*/
|
|
150
201
|
declare function getEventTarget(event: Event): EventTarget | null;
|
|
151
|
-
/**
|
|
152
|
-
* Shadow-DOM-aware containment: whether `node` is `container` or lives inside it, crossing shadow roots
|
|
153
|
-
* via their `host` (unlike `Node.contains`, which stops at a shadow boundary).
|
|
154
|
-
*/
|
|
155
|
-
declare function composedContains(container: Node, node: Node | null): boolean;
|
|
156
202
|
/**
|
|
157
203
|
* Attempts focusing the first element in a list of candidates.
|
|
158
204
|
* Stops when focus has actually moved.
|
|
159
|
-
*/
|
|
160
|
-
declare function focusFirst(candidates: HTMLElement[], { select }?: {
|
|
161
|
-
select?: boolean | undefined;
|
|
162
|
-
}): true | undefined;
|
|
163
|
-
/**
|
|
164
|
-
* Returns a list of potential tabbable candidates.
|
|
165
|
-
*
|
|
166
|
-
* NOTE: This is only a close approximation. For example it doesn't take into account cases like when
|
|
167
|
-
* elements are not visible. This cannot be worked out easily by just reading a property, but rather
|
|
168
|
-
* necessitate runtime knowledge (computed styles, etc). We deal with these cases separately.
|
|
169
205
|
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
|
|
173
|
-
declare function getTabbableCandidates(container: HTMLElement): HTMLElement[];
|
|
174
|
-
declare function isHidden(node: HTMLElement, { upTo }: {
|
|
175
|
-
upTo?: HTMLElement;
|
|
176
|
-
}): boolean;
|
|
177
|
-
/**
|
|
178
|
-
* Returns the first visible element in a list.
|
|
179
|
-
* NOTE: Only checks visibility up to the `container`.
|
|
206
|
+
* Reads the active element from `root` (a focus scope passes its host's `ownerDocument`); falling back to
|
|
207
|
+
* the global `document` would, in an iframe / multi-document scope, never detect that focus moved inside
|
|
208
|
+
* the inner document and so walk past every candidate, leaving focus on the last one instead of the first.
|
|
180
209
|
*/
|
|
181
|
-
declare function
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
declare function getTabbableEdges(container: HTMLElement): readonly [HTMLElement | undefined, HTMLElement | undefined];
|
|
186
|
-
/**
|
|
187
|
-
* The next tabbable in the document after the current focus (Base UI `getNextTabbable`) — used by the
|
|
188
|
-
* portal-focus bridge's trailing guard to step focus past the popup. Falls back to `reference`.
|
|
189
|
-
*/
|
|
190
|
-
declare function getNextTabbable(reference: Element | null): HTMLElement | null;
|
|
191
|
-
/** The previous tabbable in the document before the current focus (Base UI `getPreviousTabbable`). */
|
|
192
|
-
declare function getPreviousTabbable(reference: Element | null): HTMLElement | null;
|
|
193
|
-
/** The tabbable immediately after `reference` in the document, wrapping (Base UI `getTabbableAfterElement`). */
|
|
194
|
-
declare function getTabbableAfterElement(reference: Element | null): HTMLElement | null;
|
|
195
|
-
/** The tabbable immediately before `reference` in the document, wrapping (Base UI `getTabbableBeforeElement`). */
|
|
196
|
-
declare function getTabbableBeforeElement(reference: Element | null): HTMLElement | null;
|
|
210
|
+
declare function focusFirst(candidates: FocusableElement[], { select, root }?: {
|
|
211
|
+
select?: boolean;
|
|
212
|
+
root?: DocumentOrShadowRoot;
|
|
213
|
+
}): true | undefined;
|
|
197
214
|
declare function isSelectableInput(element: any): element is FocusableTarget & {
|
|
198
215
|
select: () => void;
|
|
199
216
|
};
|
|
@@ -201,5 +218,5 @@ declare function focus(element?: FocusableTarget | null, { select }?: {
|
|
|
201
218
|
select?: boolean | undefined;
|
|
202
219
|
}): void;
|
|
203
220
|
|
|
204
|
-
export { AUTOFOCUS_ON_MOUNT, AUTOFOCUS_ON_UNMOUNT, EVENT_OPTIONS, FOCUS_GUARD_ATTR, FOCUS_GUARD_STYLE, RdxFocusScope, RdxFocusScopeConfigToken, composedContains, createAriaOwnsAnchor, createFocusGuard, disableFocusInside, enableFocusInside,
|
|
205
|
-
export type { FocusScopeContext, RdxFocusScopeConfig };
|
|
221
|
+
export { AUTOFOCUS_ON_MOUNT, AUTOFOCUS_ON_UNMOUNT, EVENT_OPTIONS, FOCUS_GUARD_ATTR, FOCUS_GUARD_STYLE, RdxFocusScope, RdxFocusScopeConfigToken, composedContains, createAriaOwnsAnchor, createFocusGuard, disableFocusInside, enableFocusInside, focus, focusFirst, focusable, getEventTarget, getNextTabbable, getPreviousTabbable, getTabbableAfterElement, getTabbableBeforeElement, getTabbableCandidates, getTabbableEdges, injectFocusScopeContext, isOutsideEvent, isSelectableInput, isTabbable, provideFocusScopeContext, provideRdxFocusScopeConfig, queryComposedAll, tabbable, useFocusGuardsTabbability };
|
|
222
|
+
export type { FocusScopeContext, FocusableElement, RdxFocusScopeConfig };
|
|
@@ -49,6 +49,13 @@ interface RdxFormState {
|
|
|
49
49
|
dirty?: () => boolean;
|
|
50
50
|
touched?: () => boolean;
|
|
51
51
|
submitting?: () => boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Optional Angular-owned submission lifecycle. Returning `undefined` keeps the Form's Base UI-style
|
|
54
|
+
* synchronous submit path; returning a Promise delegates this submit to the adapter. Signal Forms
|
|
55
|
+
* uses this only when its opt-in `rdxSignalSubmit` input is present, and calls Angular's public
|
|
56
|
+
* `submit()` API so validation, touched state, concurrency, and submission errors stay Angular-owned.
|
|
57
|
+
*/
|
|
58
|
+
submit?: () => Promise<boolean> | undefined;
|
|
52
59
|
/**
|
|
53
60
|
* Per-name **client** validation errors (e.g. `rdxSignalForm`'s Signal Forms name-routing). Surfaced
|
|
54
61
|
* through the Form's `clientErrorsFor` channel and gated by `validationMode` like any client validity.
|
|
@@ -190,6 +197,8 @@ declare class RdxFormRoot {
|
|
|
190
197
|
*/
|
|
191
198
|
clearStateProvider(provider: RdxFormState | null, previous: RdxFormState | null): void;
|
|
192
199
|
onSubmit(event: SubmitEvent): void;
|
|
200
|
+
private finishDelegatedSubmit;
|
|
201
|
+
private focusFirstInvalidField;
|
|
193
202
|
onReset(): void;
|
|
194
203
|
private readonly resetTimers;
|
|
195
204
|
constructor();
|
|
@@ -24,6 +24,7 @@ interface RdxInputValueChangeEvent {
|
|
|
24
24
|
declare class RdxInputDirective implements RdxFormValueControl<RdxInputValue | undefined> {
|
|
25
25
|
private readonly element;
|
|
26
26
|
private readonly fieldRootContext;
|
|
27
|
+
private readonly ngControlState;
|
|
27
28
|
private initialValue;
|
|
28
29
|
private defaultValueApplied;
|
|
29
30
|
private readonly filledValue;
|
|
@@ -78,6 +79,8 @@ declare class RdxInputDirective implements RdxFormValueControl<RdxInputValue | u
|
|
|
78
79
|
* @group Props
|
|
79
80
|
*/
|
|
80
81
|
readonly invalid: _angular_core.InputSignalWithTransform<boolean, BooleanInput>;
|
|
82
|
+
/** Whether async validation is pending. Pending inputs publish neither valid nor invalid state. */
|
|
83
|
+
readonly pending: _angular_core.InputSignalWithTransform<boolean, BooleanInput>;
|
|
81
84
|
/**
|
|
82
85
|
* Whether the input has been touched. A two-way model: the input sets it on
|
|
83
86
|
* blur (emitting `touchedChange`, which Signal Forms' `[formField]` listens
|
|
@@ -133,8 +136,11 @@ declare class RdxInputDirective implements RdxFormValueControl<RdxInputValue | u
|
|
|
133
136
|
* @group Emits
|
|
134
137
|
*/
|
|
135
138
|
readonly touch: _angular_core.OutputEmitterRef<void>;
|
|
136
|
-
/**
|
|
139
|
+
/** Validation errors from the input and a same-host Reactive/template-driven form control. */
|
|
140
|
+
readonly validationErrors: _angular_core.Signal<readonly RdxValidationError[]>;
|
|
141
|
+
/** The input's binary invalidity, including a same-host Reactive/template-driven form control. */
|
|
137
142
|
private readonly ownInvalid;
|
|
143
|
+
private readonly pendingState;
|
|
138
144
|
/**
|
|
139
145
|
* Tri-state *displayed* validity: inside a `rdxFieldRoot` the field's gated `validState` is the single
|
|
140
146
|
* source (so a field whose `validationMode` defers display (e.g. `onBlur`) keeps the input neutral until revealed), otherwise
|
|
@@ -153,11 +159,13 @@ declare class RdxInputDirective implements RdxFormValueControl<RdxInputValue | u
|
|
|
153
159
|
onFocus(): void;
|
|
154
160
|
onBlur(): void;
|
|
155
161
|
onInput(event: Event): void;
|
|
162
|
+
/** Reset the interaction baseline; Angular Signal Forms calls this from `FieldState.reset()`. */
|
|
163
|
+
reset(): void;
|
|
156
164
|
syncFieldState(): void;
|
|
157
165
|
private writeValue;
|
|
158
166
|
protected readonly dataAttr: (value: boolean) => "" | undefined;
|
|
159
167
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<RdxInputDirective, never>;
|
|
160
|
-
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<RdxInputDirective, "input[rdxInput]", ["rdxInput"], { "id": { "alias": "id"; "required": false; "isSignal": true; }; "name": { "alias": "name"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; "defaultValue": { "alias": "defaultValue"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "invalid": { "alias": "invalid"; "required": false; "isSignal": true; }; "touched": { "alias": "touched"; "required": false; "isSignal": true; }; "dirty": { "alias": "dirty"; "required": false; "isSignal": true; }; "errors": { "alias": "errors"; "required": false; "isSignal": true; }; "minLength": { "alias": "minLength"; "required": false; "isSignal": true; }; "maxLength": { "alias": "maxLength"; "required": false; "isSignal": true; }; "pattern": { "alias": "pattern"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "touched": "touchedChange"; "onValueChange": "onValueChange"; "touch": "touch"; }, never, never, true, never>;
|
|
168
|
+
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<RdxInputDirective, "input[rdxInput]", ["rdxInput"], { "id": { "alias": "id"; "required": false; "isSignal": true; }; "name": { "alias": "name"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; "defaultValue": { "alias": "defaultValue"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "invalid": { "alias": "invalid"; "required": false; "isSignal": true; }; "pending": { "alias": "pending"; "required": false; "isSignal": true; }; "touched": { "alias": "touched"; "required": false; "isSignal": true; }; "dirty": { "alias": "dirty"; "required": false; "isSignal": true; }; "errors": { "alias": "errors"; "required": false; "isSignal": true; }; "minLength": { "alias": "minLength"; "required": false; "isSignal": true; }; "maxLength": { "alias": "maxLength"; "required": false; "isSignal": true; }; "pattern": { "alias": "pattern"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "touched": "touchedChange"; "onValueChange": "onValueChange"; "touch": "touch"; }, never, never, true, never>;
|
|
161
169
|
}
|
|
162
170
|
|
|
163
171
|
export { RdxInputDirective };
|
|
@@ -11,6 +11,29 @@ import * as i1$1 from '@radix-ng/primitives/composite';
|
|
|
11
11
|
import * as i1$2 from '@radix-ng/primitives/portal';
|
|
12
12
|
import { RdxOutsidePressDomEvent } from '@radix-ng/primitives/dismissable-layer';
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Submenu "safe polygon" — a faithful port of Floating UI's `safePolygon` algorithm
|
|
16
|
+
* (https://floating-ui.com/docs/useHover#safepolygon), adapted to this library.
|
|
17
|
+
*
|
|
18
|
+
* While a submenu is open by hover, the parent submenu owns the decision to close itself: a
|
|
19
|
+
* document-level `mousemove` handler keeps it open as long as the cursor is heading toward the
|
|
20
|
+
* popup inside a safe quadrilateral (built from the cursor's exit point and the popup rect), and
|
|
21
|
+
* closes it once the cursor leaves that area. Combined with the pointer-events "tunnel" below
|
|
22
|
+
* (`applyPointerTunnel`), siblings cannot steal the open submenu during a diagonal traversal.
|
|
23
|
+
*
|
|
24
|
+
* Differences from the upstream implementation:
|
|
25
|
+
* - `elements.domReference` / `elements.floating` → `reference` / `floating` (plain elements).
|
|
26
|
+
* - `placement.split('-')[0]` → the `side` option (read live from the popup's `data-side`).
|
|
27
|
+
* - the Floating UI tree (`tree` / `nodeId`) → the `hasOpenChild` callback, backed by the
|
|
28
|
+
* module-level open-submenu registry.
|
|
29
|
+
*
|
|
30
|
+
* This deliberately does NOT reuse the core `useGraceArea` composable (tooltip / navigation-menu /
|
|
31
|
+
* popover): that one is a simpler convex-hull grace area with no velocity gating, trough handling, or
|
|
32
|
+
* pointer-events tunnel — none of which it needs, but all of which the submenu does to match Base UI
|
|
33
|
+
* and to stop sibling triggers from stealing the open submenu mid-traversal.
|
|
34
|
+
*/
|
|
35
|
+
type MenuSide = 'top' | 'bottom' | 'left' | 'right';
|
|
36
|
+
|
|
14
37
|
type RdxMenuTransitionStatus = 'starting' | 'ending' | undefined;
|
|
15
38
|
/**
|
|
16
39
|
* Why an open/close happened instantly (no transition). Mirrors Base UI's menu `data-instant`:
|
|
@@ -81,6 +104,12 @@ interface RdxMenuRootContext {
|
|
|
81
104
|
trigger: Signal<HTMLElement | undefined>;
|
|
82
105
|
/** The popup element, once mounted. Used by submenu safe-polygon geometry. */
|
|
83
106
|
popupElement: Signal<HTMLElement | undefined>;
|
|
107
|
+
/**
|
|
108
|
+
* The popup's **physical** placed side (`top`/`bottom`/`left`/`right`) for safe-polygon geometry —
|
|
109
|
+
* never the popup's `data-side`, which is the logical `inline-start`/`inline-end` echo when a logical
|
|
110
|
+
* side was requested and which the geometry cannot interpret.
|
|
111
|
+
*/
|
|
112
|
+
popupPhysicalSide: Signal<MenuSide | undefined>;
|
|
84
113
|
beforeContentFocusGuard: Signal<HTMLElement | null>;
|
|
85
114
|
transitionStatus: Signal<RdxMenuTransitionStatus>;
|
|
86
115
|
close: (reason?: RdxMenuOpenChangeReason, event?: Event) => void;
|
|
@@ -92,7 +121,7 @@ interface RdxMenuRootContext {
|
|
|
92
121
|
/** Open the menu without moving focus into the popup (used for menubar hover-switching). */
|
|
93
122
|
showWithoutAutoFocus: (reason?: RdxMenuOpenChangeReason, event?: Event) => void;
|
|
94
123
|
registerTrigger: (el: HTMLElement) => () => void;
|
|
95
|
-
registerPopup: (el: HTMLElement) => () => void;
|
|
124
|
+
registerPopup: (el: HTMLElement, physicalSide?: Signal<MenuSide | undefined>) => () => void;
|
|
96
125
|
setBeforeContentFocusGuard: (element: HTMLElement | null) => void;
|
|
97
126
|
registerTransitionElement: (element: HTMLElement) => () => void;
|
|
98
127
|
registerPopupArrowNavigationHandler: (handler: (offset: 1 | -1) => boolean) => () => void;
|
|
@@ -195,6 +224,9 @@ declare class RdxMenuRoot {
|
|
|
195
224
|
readonly onOpenChangeComplete: _angular_core.OutputEmitterRef<boolean>;
|
|
196
225
|
readonly trigger: _angular_core.WritableSignal<HTMLElement | undefined>;
|
|
197
226
|
readonly popupElement: _angular_core.WritableSignal<HTMLElement | undefined>;
|
|
227
|
+
private readonly popupPhysicalSideSource;
|
|
228
|
+
/** Physical placed side read through the popup's own signal (undefined until the popup is placed). */
|
|
229
|
+
readonly popupPhysicalSide: Signal<MenuSide | undefined>;
|
|
198
230
|
readonly beforeContentFocusGuard: _angular_core.WritableSignal<HTMLElement | null>;
|
|
199
231
|
readonly transitionStatus: Signal<_radix_ng_primitives_core.RdxTransitionStatus>;
|
|
200
232
|
readonly activeIndex: _angular_core.WritableSignal<number | null>;
|
|
@@ -239,7 +271,7 @@ declare class RdxMenuRoot {
|
|
|
239
271
|
*/
|
|
240
272
|
closeEntireMenu(reason?: RdxMenuOpenChangeReason, event?: Event): void;
|
|
241
273
|
registerTrigger(el: HTMLElement): () => void;
|
|
242
|
-
registerPopup(el: HTMLElement): () => void;
|
|
274
|
+
registerPopup(el: HTMLElement, physicalSide?: Signal<MenuSide | undefined>): () => void;
|
|
243
275
|
setBeforeContentFocusGuard(element: HTMLElement | null): void;
|
|
244
276
|
registerTransitionElement(element: HTMLElement): () => void;
|
|
245
277
|
registerPopupArrowNavigationHandler(handler: (offset: 1 | -1) => boolean): () => void;
|
|
@@ -359,6 +391,15 @@ declare class RdxMenuSubTrigger {
|
|
|
359
391
|
protected onArrowLeft(event: Event): void;
|
|
360
392
|
protected onPointerMove(event: PointerEvent): void;
|
|
361
393
|
protected onPointerLeave(): void;
|
|
394
|
+
/**
|
|
395
|
+
* Backup cancellation for a `pointerleave` that Chrome can drop during a fast pointer sweep across
|
|
396
|
+
* adjacent triggers, which would otherwise leave the delayed hover-open armed and open a stale
|
|
397
|
+
* submenu for a trigger the pointer has already left (stranding the parent at `pointer-events: none`).
|
|
398
|
+
* `pointerout` bubbles, so it also fires while crossing between the trigger's own descendants — those
|
|
399
|
+
* (where `relatedTarget` is still inside the trigger) must not cancel the pending open. Base UI parity
|
|
400
|
+
* (`guardStaleOpen`); safe on this single, hover-driven trigger.
|
|
401
|
+
*/
|
|
402
|
+
protected onPointerOut(event: PointerEvent): void;
|
|
362
403
|
protected clearHighlight(): void;
|
|
363
404
|
private closeSiblingSubmenus;
|
|
364
405
|
private clearSiblingHighlights;
|
|
@@ -376,11 +417,15 @@ declare class RdxMenuSubTrigger {
|
|
|
376
417
|
* This replaces the consumer-owned `@if (root.open())` mount: it adds both teleporting *and*
|
|
377
418
|
* exit-animation support. Apply it with the `*` microsyntax on the positioner —
|
|
378
419
|
* `<div *rdxMenuPortal rdxMenuPositioner>` — or as an explicit `<ng-template rdxMenuPortal>`. For a
|
|
379
|
-
* custom container,
|
|
420
|
+
* custom container, a backdrop alongside the positioner (multi-root), or `[keepMounted]`, use the
|
|
421
|
+
* explicit form.
|
|
422
|
+
*
|
|
423
|
+
* Set `[keepMounted]="true"` to keep the popup mounted while the menu is closed (the positioner's
|
|
424
|
+
* `data-closed` state hides it) — inherited from {@link RdxPortalPresence}.
|
|
380
425
|
*/
|
|
381
426
|
declare class RdxMenuPortal {
|
|
382
427
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<RdxMenuPortal, never>;
|
|
383
|
-
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<RdxMenuPortal, "ng-template[rdxMenuPortal]", ["rdxMenuPortal"], {}, {}, never, never, true, [{ directive: typeof i1$2.RdxPortalPresence; inputs: { "container": "container"; }; outputs: {}; }]>;
|
|
428
|
+
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<RdxMenuPortal, "ng-template[rdxMenuPortal]", ["rdxMenuPortal"], {}, {}, never, never, true, [{ directive: typeof i1$2.RdxPortalPresence; inputs: { "container": "container"; "keepMounted": "keepMounted"; }; outputs: {}; }]>;
|
|
384
429
|
}
|
|
385
430
|
/**
|
|
386
431
|
* Dev-mode guard: `rdxMenuPortal` is a structural directive. The old `<div rdxMenuPortal>` markup
|
|
@@ -402,6 +447,13 @@ declare class RdxMenuPortalMisuseGuard {
|
|
|
402
447
|
declare class RdxMenuPositioner extends RdxPopperContentWrapper {
|
|
403
448
|
protected readonly rootContext: _radix_ng_primitives_menu.RdxMenuRootContext;
|
|
404
449
|
protected readonly legacyVars: Record<string, string>;
|
|
450
|
+
/**
|
|
451
|
+
* Whether the popup is shown: open, or still running its closing transition. Drives both the native
|
|
452
|
+
* `hidden` (finding: a closed keep-mounted popup must hide itself) and — via `positioningActive` —
|
|
453
|
+
* whether the inherited wrapper keeps tracking the anchor, so a closed keep-mounted positioner stops
|
|
454
|
+
* its `autoUpdate` `requestAnimationFrame` loop (finding: endless rAF while closed).
|
|
455
|
+
*/
|
|
456
|
+
protected readonly visible: _angular_core.Signal<boolean>;
|
|
405
457
|
constructor();
|
|
406
458
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<RdxMenuPositioner, never>;
|
|
407
459
|
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<RdxMenuPositioner, "[rdxMenuPositioner]", ["rdxMenuPositioner"], {}, {}, never, never, true, never>;
|
|
@@ -422,7 +474,7 @@ declare class RdxMenuPopup {
|
|
|
422
474
|
private search;
|
|
423
475
|
private searchTimer;
|
|
424
476
|
protected readonly align: _angular_core.Signal<"center" | "end" | "start" | undefined>;
|
|
425
|
-
protected readonly side: _angular_core.Signal<
|
|
477
|
+
protected readonly side: _angular_core.Signal<i1.SideOrLogical | undefined>;
|
|
426
478
|
/**
|
|
427
479
|
* Event handler called when the escape key is pressed. Can be prevented.
|
|
428
480
|
*/
|
|
@@ -456,7 +508,7 @@ declare class RdxMenuPopup {
|
|
|
456
508
|
private currentItemIndex;
|
|
457
509
|
private focusMenuItem;
|
|
458
510
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<RdxMenuPopup, never>;
|
|
459
|
-
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<RdxMenuPopup, "[rdxMenuPopup]", ["rdxMenuPopup"], {}, { "escapeKeyDown": "escapeKeyDown"; "pointerDownOutside": "pointerDownOutside"; "focusOutside": "focusOutside"; "interactOutside": "interactOutside"; "openAutoFocus": "openAutoFocus"; "closeAutoFocus": "closeAutoFocus"; }, never, never, true, [{ directive: typeof i1.RdxPopperContent; inputs: {}; outputs: {}; }, { directive: typeof _radix_ng_primitives_core.RdxFloatingNodeRegistration; inputs: {}; outputs: {}; }, { directive: typeof _radix_ng_primitives_floating_focus_manager.RdxFloatingFocusManager; inputs: {}; outputs: {}; }, { directive: typeof i1$1.RdxCompositeList; inputs: {}; outputs: {}; }]>;
|
|
511
|
+
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<RdxMenuPopup, "[rdxMenuPopup]", ["rdxMenuPopup"], {}, { "escapeKeyDown": "escapeKeyDown"; "pointerDownOutside": "pointerDownOutside"; "focusOutside": "focusOutside"; "interactOutside": "interactOutside"; "openAutoFocus": "openAutoFocus"; "closeAutoFocus": "closeAutoFocus"; }, never, never, true, [{ directive: typeof i1.RdxPopperContent; inputs: {}; outputs: {}; }, { directive: typeof _radix_ng_primitives_core.RdxFloatingNodeRegistration; inputs: {}; outputs: {}; }, { directive: typeof _radix_ng_primitives_floating_focus_manager.RdxFloatingFocusManager; inputs: { "returnFocus": "finalFocus"; }; outputs: {}; }, { directive: typeof i1$1.RdxCompositeList; inputs: {}; outputs: {}; }]>;
|
|
460
512
|
}
|
|
461
513
|
|
|
462
514
|
/**
|
|
@@ -509,7 +561,7 @@ declare class RdxMenuBackdrop {
|
|
|
509
561
|
declare class RdxMenuArrow {
|
|
510
562
|
protected readonly rootContext: _radix_ng_primitives_menu.RdxMenuRootContext;
|
|
511
563
|
private readonly wrapper;
|
|
512
|
-
protected readonly side: _angular_core.Signal<
|
|
564
|
+
protected readonly side: _angular_core.Signal<i1.SideOrLogical | undefined>;
|
|
513
565
|
protected readonly align: _angular_core.Signal<"center" | "end" | "start" | undefined>;
|
|
514
566
|
protected readonly uncentered: _angular_core.Signal<boolean>;
|
|
515
567
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<RdxMenuArrow, never>;
|
|
@@ -383,7 +383,7 @@ declare class RdxNavigationMenuPopup {
|
|
|
383
383
|
private readonly registration;
|
|
384
384
|
private readonly wrapper;
|
|
385
385
|
private readonly elementRef;
|
|
386
|
-
protected readonly side: _angular_core.Signal<
|
|
386
|
+
protected readonly side: _angular_core.Signal<i1$1.SideOrLogical | undefined>;
|
|
387
387
|
protected readonly align: _angular_core.Signal<"start" | "center" | "end" | undefined>;
|
|
388
388
|
protected readonly id: _angular_core.Signal<string>;
|
|
389
389
|
/**
|
|
@@ -420,7 +420,7 @@ declare class RdxNavigationMenuPopup {
|
|
|
420
420
|
declare class RdxNavigationMenuArrow {
|
|
421
421
|
protected readonly rootContext: _radix_ng_primitives_navigation_menu.RdxNavigationMenuRootContext;
|
|
422
422
|
private readonly wrapper;
|
|
423
|
-
protected readonly side: _angular_core.Signal<
|
|
423
|
+
protected readonly side: _angular_core.Signal<i1$1.SideOrLogical | undefined>;
|
|
424
424
|
protected readonly align: _angular_core.Signal<"start" | "center" | "end" | undefined>;
|
|
425
425
|
protected readonly uncentered: _angular_core.Signal<boolean>;
|
|
426
426
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<RdxNavigationMenuArrow, never>;
|
|
@@ -304,7 +304,7 @@ declare class RdxPopoverPopup {
|
|
|
304
304
|
private readonly focusScope;
|
|
305
305
|
private readonly wrapper;
|
|
306
306
|
protected readonly align: _angular_core.Signal<"start" | "center" | "end" | undefined>;
|
|
307
|
-
protected readonly side: _angular_core.Signal<
|
|
307
|
+
protected readonly side: _angular_core.Signal<i1$1.SideOrLogical | undefined>;
|
|
308
308
|
/** Event handler called when the escape key is down. Can be prevented. */
|
|
309
309
|
readonly escapeKeyDown: _angular_core.OutputEmitterRef<KeyboardEvent>;
|
|
310
310
|
/** Event handler called when a pointerdown event happens outside of the popup. Can be prevented. */
|
|
@@ -328,7 +328,7 @@ declare class RdxPopoverPopup {
|
|
|
328
328
|
declare class RdxPopoverArrow {
|
|
329
329
|
protected readonly rootContext: _radix_ng_primitives_popover.RdxPopoverRootContext;
|
|
330
330
|
private readonly wrapper;
|
|
331
|
-
protected readonly side: _angular_core.Signal<
|
|
331
|
+
protected readonly side: _angular_core.Signal<i1$1.SideOrLogical | undefined>;
|
|
332
332
|
protected readonly align: _angular_core.Signal<"start" | "center" | "end" | undefined>;
|
|
333
333
|
protected readonly uncentered: _angular_core.Signal<boolean>;
|
|
334
334
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<RdxPopoverArrow, never>;
|