@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.
- package/LICENSE +1 -1
- package/README.md +76 -6
- package/fesm2022/radix-ng-primitives-accordion.mjs +5 -3
- package/fesm2022/radix-ng-primitives-accordion.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-alert-dialog.mjs +31 -24
- package/fesm2022/radix-ng-primitives-alert-dialog.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-autocomplete.mjs +1744 -0
- package/fesm2022/radix-ng-primitives-autocomplete.mjs.map +1 -0
- package/fesm2022/radix-ng-primitives-calendar.mjs +5 -3
- package/fesm2022/radix-ng-primitives-calendar.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-combobox.mjs +1399 -606
- package/fesm2022/radix-ng-primitives-combobox.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-config.mjs +13 -4
- package/fesm2022/radix-ng-primitives-config.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-context-menu.mjs +51 -10
- package/fesm2022/radix-ng-primitives-context-menu.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-core.mjs +1345 -64
- package/fesm2022/radix-ng-primitives-core.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-date-field.mjs +5 -3
- package/fesm2022/radix-ng-primitives-date-field.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-dialog.mjs +271 -145
- package/fesm2022/radix-ng-primitives-dialog.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-direction-provider.mjs +70 -0
- package/fesm2022/radix-ng-primitives-direction-provider.mjs.map +1 -0
- package/fesm2022/radix-ng-primitives-dismissable-layer.mjs +519 -184
- package/fesm2022/radix-ng-primitives-dismissable-layer.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-drawer.mjs +154 -64
- package/fesm2022/radix-ng-primitives-drawer.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-field.mjs +3 -2
- package/fesm2022/radix-ng-primitives-field.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-floating-focus-manager.mjs +517 -0
- package/fesm2022/radix-ng-primitives-floating-focus-manager.mjs.map +1 -0
- package/fesm2022/radix-ng-primitives-focus-scope.mjs +296 -70
- package/fesm2022/radix-ng-primitives-focus-scope.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-menu.mjs +894 -299
- package/fesm2022/radix-ng-primitives-menu.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-menubar.mjs +32 -4
- package/fesm2022/radix-ng-primitives-menubar.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-navigation-menu.mjs +176 -207
- package/fesm2022/radix-ng-primitives-navigation-menu.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-popover.mjs +250 -250
- package/fesm2022/radix-ng-primitives-popover.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-popper.mjs +94 -45
- package/fesm2022/radix-ng-primitives-popper.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-portal.mjs +107 -17
- package/fesm2022/radix-ng-primitives-portal.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-presence.mjs +262 -79
- package/fesm2022/radix-ng-primitives-presence.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-preview-card.mjs +172 -218
- package/fesm2022/radix-ng-primitives-preview-card.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-roving-focus.mjs +4 -2
- package/fesm2022/radix-ng-primitives-roving-focus.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-scroll-area.mjs +5 -4
- package/fesm2022/radix-ng-primitives-scroll-area.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-select.mjs +303 -234
- package/fesm2022/radix-ng-primitives-select.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-slider.mjs +5 -3
- package/fesm2022/radix-ng-primitives-slider.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-stepper.mjs +5 -3
- package/fesm2022/radix-ng-primitives-stepper.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-time-field.mjs +5 -3
- package/fesm2022/radix-ng-primitives-time-field.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-toast.mjs +15 -36
- package/fesm2022/radix-ng-primitives-toast.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-toggle-group.mjs +5 -3
- package/fesm2022/radix-ng-primitives-toggle-group.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-toolbar.mjs +5 -3
- package/fesm2022/radix-ng-primitives-toolbar.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-tooltip.mjs +105 -145
- package/fesm2022/radix-ng-primitives-tooltip.mjs.map +1 -1
- package/package.json +14 -1
- package/types/radix-ng-primitives-accordion.d.ts +4 -3
- package/types/radix-ng-primitives-alert-dialog.d.ts +17 -11
- package/types/radix-ng-primitives-autocomplete.d.ts +661 -0
- package/types/radix-ng-primitives-calendar.d.ts +5 -3
- package/types/radix-ng-primitives-combobox.d.ts +727 -293
- package/types/radix-ng-primitives-config.d.ts +1 -1
- package/types/radix-ng-primitives-context-menu.d.ts +15 -5
- package/types/radix-ng-primitives-core.d.ts +762 -14
- package/types/radix-ng-primitives-date-field.d.ts +3 -2
- package/types/radix-ng-primitives-dialog.d.ts +107 -55
- package/types/radix-ng-primitives-direction-provider.d.ts +41 -0
- package/types/radix-ng-primitives-dismissable-layer.d.ts +147 -99
- package/types/radix-ng-primitives-drawer.d.ts +49 -22
- package/types/radix-ng-primitives-field.d.ts +1 -0
- package/types/radix-ng-primitives-floating-focus-manager.d.ts +175 -0
- package/types/radix-ng-primitives-focus-scope.d.ts +132 -1
- package/types/radix-ng-primitives-menu.d.ts +204 -112
- package/types/radix-ng-primitives-navigation-menu.d.ts +61 -101
- package/types/radix-ng-primitives-popover.d.ts +82 -115
- package/types/radix-ng-primitives-popper.d.ts +46 -10
- package/types/radix-ng-primitives-portal.d.ts +53 -8
- package/types/radix-ng-primitives-presence.d.ts +98 -17
- package/types/radix-ng-primitives-preview-card.d.ts +63 -95
- package/types/radix-ng-primitives-roving-focus.d.ts +7 -6
- package/types/radix-ng-primitives-scroll-area.d.ts +2 -2
- package/types/radix-ng-primitives-select.d.ts +192 -158
- package/types/radix-ng-primitives-slider.d.ts +5 -4
- package/types/radix-ng-primitives-stepper.d.ts +4 -3
- package/types/radix-ng-primitives-time-field.d.ts +3 -2
- package/types/radix-ng-primitives-toast.d.ts +7 -7
- package/types/radix-ng-primitives-toggle-group.d.ts +5 -4
- package/types/radix-ng-primitives-toolbar.d.ts +3 -2
- package/types/radix-ng-primitives-tooltip.d.ts +48 -84
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, booleanAttribute, inject, DestroyRef, model, input, output, signal, computed, effect, untracked, Directive,
|
|
3
|
-
import
|
|
2
|
+
import { InjectionToken, booleanAttribute, inject, DestroyRef, model, input, output, signal, computed, ElementRef, effect, untracked, Directive, Injector, afterNextRender, isDevMode, NgModule } from '@angular/core';
|
|
3
|
+
import * as i1 from '@radix-ng/primitives/core';
|
|
4
|
+
import { createContext, useTransitionStatus, injectId, createFloatingRootContext, createCancelableChangeEventDetails, provideFloatingTree, provideFloatingRootContext, RDX_FLOATING_ROOT_CONTEXT, RDX_FLOATING_REGISTRATION, useScrollLock, setupInternalBackdrop, RdxFloatingNodeRegistration, rdxDevError } from '@radix-ng/primitives/core';
|
|
4
5
|
import { outputFromObservable, outputToObservable } from '@angular/core/rxjs-interop';
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import { RdxFocusScope
|
|
6
|
+
import { RdxDismiss } from '@radix-ng/primitives/dismissable-layer';
|
|
7
|
+
import * as i2 from '@radix-ng/primitives/floating-focus-manager';
|
|
8
|
+
import { RdxFloatingFocusManager, provideFloatingFocusManagerConfig } from '@radix-ng/primitives/floating-focus-manager';
|
|
9
|
+
import { RdxFocusScope } from '@radix-ng/primitives/focus-scope';
|
|
9
10
|
import * as i1$1 from '@radix-ng/primitives/portal';
|
|
10
|
-
import {
|
|
11
|
-
import
|
|
12
|
-
import { provideRdxPresenceContext, RdxPresenceDirective } from '@radix-ng/primitives/presence';
|
|
11
|
+
import { RdxPortalPresence } from '@radix-ng/primitives/portal';
|
|
12
|
+
import { provideRdxPresenceContext } from '@radix-ng/primitives/presence';
|
|
13
13
|
|
|
14
14
|
const DEFAULT_VARIANT = {
|
|
15
15
|
role: 'dialog',
|
|
@@ -86,6 +86,8 @@ class RdxDialogRoot {
|
|
|
86
86
|
this.triggers = signal([], ...(ngDevMode ? [{ debugName: "triggers" }] : /* istanbul ignore next */ []));
|
|
87
87
|
this.payload = signal(undefined, ...(ngDevMode ? [{ debugName: "payload" }] : /* istanbul ignore next */ []));
|
|
88
88
|
this.nestedOpenCount = signal(0, ...(ngDevMode ? [{ debugName: "nestedOpenCount" }] : /* istanbul ignore next */ []));
|
|
89
|
+
this.preventUnmountOnClose = signal(false, ...(ngDevMode ? [{ debugName: "preventUnmountOnClose" }] : /* istanbul ignore next */ []));
|
|
90
|
+
this.present = computed(() => this.open() || this.preventUnmountOnClose(), ...(ngDevMode ? [{ debugName: "present" }] : /* istanbul ignore next */ []));
|
|
89
91
|
/** Whether this dialog is rendered inside another dialog. Fixed at construction. */
|
|
90
92
|
this.nested = !!this.parentRoot;
|
|
91
93
|
this.nestedDialogOpen = computed(() => this.nestedOpenCount() > 0, ...(ngDevMode ? [{ debugName: "nestedDialogOpen" }] : /* istanbul ignore next */ []));
|
|
@@ -95,6 +97,22 @@ class RdxDialogRoot {
|
|
|
95
97
|
this.effectiveModal = computed(() => (this.variant.forceModal ? true : this.modal()), ...(ngDevMode ? [{ debugName: "effectiveModal" }] : /* istanbul ignore next */ []));
|
|
96
98
|
/** Effective dismissal flag: disabled when the input asks, or when the variant forces it (alerts). */
|
|
97
99
|
this.effectiveDisablePointerDismissal = computed(() => this.disablePointerDismissal() || this.variant.forcePointerDismissalDisabled, ...(ngDevMode ? [{ debugName: "effectiveDisablePointerDismissal" }] : /* istanbul ignore next */ []));
|
|
100
|
+
/**
|
|
101
|
+
* The shared per-popup floating context (ADR 0015 §1) — `open` mirrors the dialog's open state, the
|
|
102
|
+
* trigger registry is bridged from {@link registerTrigger}, and the reference / floating elements are
|
|
103
|
+
* set by the trigger / popup. The new dismissal + focus engines read this once the popup migrates.
|
|
104
|
+
*/
|
|
105
|
+
this.floatingContext = createFloatingRootContext({
|
|
106
|
+
ownerDocument: inject(ElementRef).nativeElement.ownerDocument,
|
|
107
|
+
open: () => this.open()
|
|
108
|
+
});
|
|
109
|
+
// Keep the floating context's reference element in sync with the active trigger.
|
|
110
|
+
effect(() => this.floatingContext.setReferenceElement(this.trigger() ?? null));
|
|
111
|
+
effect(() => {
|
|
112
|
+
if (this.open() && this.preventUnmountOnClose()) {
|
|
113
|
+
this.preventUnmountOnClose.set(false);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
98
116
|
let previousOpen = this.open();
|
|
99
117
|
effect(() => {
|
|
100
118
|
const defaultOpen = this.defaultOpen();
|
|
@@ -136,29 +154,51 @@ class RdxDialogRoot {
|
|
|
136
154
|
});
|
|
137
155
|
}
|
|
138
156
|
show(trigger = this.trigger(), payload, triggerId, reason = 'none', event = new Event('dialog.open-change')) {
|
|
157
|
+
const shouldAdoptPayload = trigger !== undefined || payload !== undefined;
|
|
158
|
+
if (this.open()) {
|
|
159
|
+
if (trigger) {
|
|
160
|
+
this.trigger.set(trigger);
|
|
161
|
+
}
|
|
162
|
+
if (triggerId !== undefined) {
|
|
163
|
+
this.triggerId.set(triggerId);
|
|
164
|
+
}
|
|
165
|
+
// Only adopt the payload when a trigger context is actually provided, so a bare
|
|
166
|
+
// imperative re-show on an already-open dialog doesn't clobber the live payload.
|
|
167
|
+
if (shouldAdoptPayload) {
|
|
168
|
+
this.payload.set(payload);
|
|
169
|
+
}
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const change = this.createOpenChangeEvent(true, reason, event, trigger, triggerId ?? this.triggerId());
|
|
173
|
+
this.onOpenChange.emit(change.payload);
|
|
174
|
+
if (change.eventDetails.isCanceled()) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
139
177
|
if (trigger) {
|
|
140
178
|
this.trigger.set(trigger);
|
|
141
179
|
}
|
|
142
180
|
if (triggerId !== undefined) {
|
|
143
181
|
this.triggerId.set(triggerId);
|
|
144
182
|
}
|
|
145
|
-
|
|
146
|
-
// imperative re-show on an already-open dialog doesn't clobber the live payload.
|
|
147
|
-
if (trigger !== undefined || payload !== undefined) {
|
|
183
|
+
if (shouldAdoptPayload) {
|
|
148
184
|
this.payload.set(payload);
|
|
149
185
|
}
|
|
150
|
-
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
186
|
+
this.preventUnmountOnClose.set(false);
|
|
153
187
|
this.open.set(true);
|
|
154
|
-
this.
|
|
188
|
+
this.floatingContext.events.emit('openchange', { open: true, reason, event: change.eventDetails.event });
|
|
155
189
|
}
|
|
156
190
|
close(reason = 'none', event = new Event('dialog.open-change')) {
|
|
157
191
|
if (!this.open()) {
|
|
158
192
|
return;
|
|
159
193
|
}
|
|
194
|
+
const change = this.createOpenChangeEvent(false, reason, event, this.trigger(), this.triggerId());
|
|
195
|
+
this.onOpenChange.emit(change.payload);
|
|
196
|
+
if (change.eventDetails.isCanceled()) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
this.preventUnmountOnClose.set(change.shouldPreventUnmountOnClose());
|
|
160
200
|
this.open.set(false);
|
|
161
|
-
this.
|
|
201
|
+
this.floatingContext.events.emit('openchange', { open: false, reason, event: change.eventDetails.event });
|
|
162
202
|
}
|
|
163
203
|
toggle(triggerId, trigger, payload, event = new Event('dialog.open-change')) {
|
|
164
204
|
if (this.open() && this.trigger() === trigger) {
|
|
@@ -170,6 +210,9 @@ class RdxDialogRoot {
|
|
|
170
210
|
registerTrigger(id, trigger, payload) {
|
|
171
211
|
this.registeredTriggers.set(id, { element: trigger, payload });
|
|
172
212
|
this.triggers.update((triggers) => (triggers.includes(trigger) ? triggers : [...triggers, trigger]));
|
|
213
|
+
// Bridge into the floating context's trigger registry — the new dismissal/focus engines read it
|
|
214
|
+
// for inside-element checks (a press/focus on the trigger counts as inside, ADR 0015 §2).
|
|
215
|
+
this.floatingContext.triggers.add(trigger);
|
|
173
216
|
if (this.triggerId() === id || (!this.trigger() && this.triggerId() === null)) {
|
|
174
217
|
this.trigger.set(trigger);
|
|
175
218
|
this.payload.set(payload());
|
|
@@ -179,6 +222,7 @@ class RdxDialogRoot {
|
|
|
179
222
|
this.registeredTriggers.delete(id);
|
|
180
223
|
}
|
|
181
224
|
this.triggers.update((triggers) => triggers.filter((candidate) => candidate !== trigger));
|
|
225
|
+
this.floatingContext.triggers.delete(trigger);
|
|
182
226
|
if (!this.destroyRef.destroyed && this.trigger() === trigger) {
|
|
183
227
|
const next = this.registeredTriggers.entries().next().value;
|
|
184
228
|
if (this.triggerId() !== null) {
|
|
@@ -209,14 +253,20 @@ class RdxDialogRoot {
|
|
|
209
253
|
this.payload.set(trigger.payload());
|
|
210
254
|
}
|
|
211
255
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
256
|
+
createOpenChangeEvent(open, reason, event, trigger, triggerId) {
|
|
257
|
+
const change = createCancelableChangeEventDetails(reason, event, trigger);
|
|
258
|
+
return {
|
|
259
|
+
eventDetails: change.eventDetails,
|
|
260
|
+
shouldPreventUnmountOnClose: change.shouldPreventUnmountOnClose,
|
|
261
|
+
payload: {
|
|
262
|
+
open,
|
|
263
|
+
triggerId,
|
|
264
|
+
trigger: change.eventDetails.trigger,
|
|
265
|
+
reason: change.eventDetails.reason,
|
|
266
|
+
event: change.eventDetails.event,
|
|
267
|
+
eventDetails: change.eventDetails
|
|
268
|
+
}
|
|
269
|
+
};
|
|
220
270
|
}
|
|
221
271
|
emitOpenChangeComplete(open) {
|
|
222
272
|
if (!this.destroyRef.destroyed) {
|
|
@@ -224,14 +274,26 @@ class RdxDialogRoot {
|
|
|
224
274
|
}
|
|
225
275
|
}
|
|
226
276
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxDialogRoot, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
227
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxDialogRoot, isStandalone: true, selector: "[rdxDialogRoot]", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, defaultOpen: { classPropertyName: "defaultOpen", publicName: "defaultOpen", isSignal: true, isRequired: false, transformFunction: null }, triggerId: { classPropertyName: "triggerId", publicName: "triggerId", isSignal: true, isRequired: false, transformFunction: null }, defaultTriggerId: { classPropertyName: "defaultTriggerId", publicName: "defaultTriggerId", isSignal: true, isRequired: false, transformFunction: null }, modal: { classPropertyName: "modal", publicName: "modal", isSignal: true, isRequired: false, transformFunction: null }, disablePointerDismissal: { classPropertyName: "disablePointerDismissal", publicName: "disablePointerDismissal", isSignal: true, isRequired: false, transformFunction: null }, handle: { classPropertyName: "handle", publicName: "handle", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange", triggerId: "triggerIdChange", onOpenChange: "onOpenChange", onOpenChangeComplete: "onOpenChangeComplete" }, providers: [
|
|
277
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxDialogRoot, isStandalone: true, selector: "[rdxDialogRoot]", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, defaultOpen: { classPropertyName: "defaultOpen", publicName: "defaultOpen", isSignal: true, isRequired: false, transformFunction: null }, triggerId: { classPropertyName: "triggerId", publicName: "triggerId", isSignal: true, isRequired: false, transformFunction: null }, defaultTriggerId: { classPropertyName: "defaultTriggerId", publicName: "defaultTriggerId", isSignal: true, isRequired: false, transformFunction: null }, modal: { classPropertyName: "modal", publicName: "modal", isSignal: true, isRequired: false, transformFunction: null }, disablePointerDismissal: { classPropertyName: "disablePointerDismissal", publicName: "disablePointerDismissal", isSignal: true, isRequired: false, transformFunction: null }, handle: { classPropertyName: "handle", publicName: "handle", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange", triggerId: "triggerIdChange", onOpenChange: "onOpenChange", onOpenChangeComplete: "onOpenChangeComplete" }, providers: [
|
|
278
|
+
provideRdxDialogRootContext(context),
|
|
279
|
+
// New floating foundation (ADR 0015/0017 migration). Inherit-or-create tree so a nested dialog
|
|
280
|
+
// shares its parent's tree; the per-popup root context bridges open / triggers / reference.
|
|
281
|
+
provideFloatingTree(),
|
|
282
|
+
provideFloatingRootContext(() => inject(RdxDialogRoot).floatingContext)
|
|
283
|
+
], exportAs: ["rdxDialogRoot"], ngImport: i0 }); }
|
|
228
284
|
}
|
|
229
285
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxDialogRoot, decorators: [{
|
|
230
286
|
type: Directive,
|
|
231
287
|
args: [{
|
|
232
288
|
selector: '[rdxDialogRoot]',
|
|
233
289
|
exportAs: 'rdxDialogRoot',
|
|
234
|
-
providers: [
|
|
290
|
+
providers: [
|
|
291
|
+
provideRdxDialogRootContext(context),
|
|
292
|
+
// New floating foundation (ADR 0015/0017 migration). Inherit-or-create tree so a nested dialog
|
|
293
|
+
// shares its parent's tree; the per-popup root context bridges open / triggers / reference.
|
|
294
|
+
provideFloatingTree(),
|
|
295
|
+
provideFloatingRootContext(() => inject(RdxDialogRoot).floatingContext)
|
|
296
|
+
]
|
|
235
297
|
}]
|
|
236
298
|
}], ctorParameters: () => [], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], defaultOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultOpen", required: false }] }], triggerId: [{ type: i0.Input, args: [{ isSignal: true, alias: "triggerId", required: false }] }, { type: i0.Output, args: ["triggerIdChange"] }], defaultTriggerId: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultTriggerId", required: false }] }], modal: [{ type: i0.Input, args: [{ isSignal: true, alias: "modal", required: false }] }], disablePointerDismissal: [{ type: i0.Input, args: [{ isSignal: true, alias: "disablePointerDismissal", required: false }] }], handle: [{ type: i0.Input, args: [{ isSignal: true, alias: "handle", required: false }] }], onOpenChange: [{ type: i0.Output, args: ["onOpenChange"] }], onOpenChangeComplete: [{ type: i0.Output, args: ["onOpenChangeComplete"] }] } });
|
|
237
299
|
function contextFor(root) {
|
|
@@ -240,6 +302,7 @@ function contextFor(root) {
|
|
|
240
302
|
titleId: root.titleId.asReadonly(),
|
|
241
303
|
descriptionId: root.descriptionId.asReadonly(),
|
|
242
304
|
isOpen: root.open,
|
|
305
|
+
present: root.present,
|
|
243
306
|
modal: root.effectiveModal,
|
|
244
307
|
disablePointerDismissal: root.effectiveDisablePointerDismissal,
|
|
245
308
|
role: root.role,
|
|
@@ -261,13 +324,27 @@ function contextFor(root) {
|
|
|
261
324
|
|
|
262
325
|
/**
|
|
263
326
|
* An overlay displayed beneath the dialog popup.
|
|
327
|
+
*
|
|
328
|
+
* Decorative-only, so it carries `role="presentation"` (Base UI `DialogBackdrop`). By default a **nested**
|
|
329
|
+
* dialog renders no backdrop — the parent's already dims the page; stacking a second one double-darkens
|
|
330
|
+
* and intercepts the parent's outside-press. Set `forceRender` to opt back in.
|
|
264
331
|
*/
|
|
265
332
|
class RdxDialogBackdrop {
|
|
266
333
|
constructor() {
|
|
267
334
|
this.rootContext = injectRdxDialogRootContext();
|
|
335
|
+
/** Render the backdrop even for a nested dialog (off by default, matching Base UI). */
|
|
336
|
+
this.forceRender = input(false, { ...(ngDevMode ? { debugName: "forceRender" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
337
|
+
// The backdrop is a second portal root (a body sibling of the popup). It is registered as owned DOM
|
|
338
|
+
// footprint for primitive-specific checks, but it is not a marker/aria keep-set member.
|
|
339
|
+
const floatingContext = inject(RDX_FLOATING_ROOT_CONTEXT, { optional: true });
|
|
340
|
+
if (floatingContext) {
|
|
341
|
+
const host = inject(ElementRef).nativeElement;
|
|
342
|
+
floatingContext.addFloatingElement(host);
|
|
343
|
+
inject(DestroyRef).onDestroy(() => floatingContext.removeFloatingElement(host));
|
|
344
|
+
}
|
|
268
345
|
}
|
|
269
346
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxDialogBackdrop, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
270
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "
|
|
347
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxDialogBackdrop, isStandalone: true, selector: "[rdxDialogBackdrop]", inputs: { forceRender: { classPropertyName: "forceRender", publicName: "forceRender", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "presentation" }, properties: { "hidden": "rootContext.nested && !forceRender()", "attr.data-closed": "rootContext.isOpen() ? undefined : \"\"", "attr.data-ending-style": "rootContext.transitionStatus() === \"ending\" ? \"\" : undefined", "attr.data-open": "rootContext.isOpen() ? \"\" : undefined", "attr.data-starting-style": "rootContext.transitionStatus() === \"starting\" ? \"\" : undefined", "attr.data-state": "rootContext.isOpen() ? \"open\" : \"closed\"", "attr.data-nested": "rootContext.nested ? \"\" : undefined", "attr.data-nested-dialog-open": "rootContext.nestedDialogOpen() ? \"\" : undefined" } }, exportAs: ["rdxDialogBackdrop"], ngImport: i0 }); }
|
|
271
348
|
}
|
|
272
349
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxDialogBackdrop, decorators: [{
|
|
273
350
|
type: Directive,
|
|
@@ -275,6 +352,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
275
352
|
selector: '[rdxDialogBackdrop]',
|
|
276
353
|
exportAs: 'rdxDialogBackdrop',
|
|
277
354
|
host: {
|
|
355
|
+
role: 'presentation',
|
|
356
|
+
'[hidden]': 'rootContext.nested && !forceRender()',
|
|
278
357
|
'[attr.data-closed]': 'rootContext.isOpen() ? undefined : ""',
|
|
279
358
|
'[attr.data-ending-style]': 'rootContext.transitionStatus() === "ending" ? "" : undefined',
|
|
280
359
|
'[attr.data-open]': 'rootContext.isOpen() ? "" : undefined',
|
|
@@ -284,7 +363,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
284
363
|
'[attr.data-nested-dialog-open]': 'rootContext.nestedDialogOpen() ? "" : undefined'
|
|
285
364
|
}
|
|
286
365
|
}]
|
|
287
|
-
}] });
|
|
366
|
+
}], ctorParameters: () => [], propDecorators: { forceRender: [{ type: i0.Input, args: [{ isSignal: true, alias: "forceRender", required: false }] }] } });
|
|
288
367
|
|
|
289
368
|
/**
|
|
290
369
|
* A button that closes the dialog.
|
|
@@ -332,111 +411,164 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
332
411
|
}]
|
|
333
412
|
}], ctorParameters: () => [] });
|
|
334
413
|
|
|
414
|
+
/** Composite navigation keys a Dialog popup keeps to itself, so they never reach an enclosing Menu / Composite. */
|
|
415
|
+
const COMPOSITE_KEYS = new Set(['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Home', 'End']);
|
|
416
|
+
const DIALOG_INTERNAL_BACKDROP_ATTR = 'data-rdx-dialog-internal-backdrop';
|
|
335
417
|
/**
|
|
336
418
|
* A container for the dialog contents.
|
|
419
|
+
*
|
|
420
|
+
* **ADR 0015/0017 Phase-4 migration — Dialog is the PILOT cutover onto the new floating dismissal +
|
|
421
|
+
* focus engine. Browser-verified** by `apps/visual-regression/tests/dialog.behavior.spec.ts` (trap,
|
|
422
|
+
* initial / return focus, Escape / outside-press / focus-out dismissal, nested-Escape deepest-first,
|
|
423
|
+
* backdrop-not-marked).
|
|
424
|
+
*
|
|
425
|
+
* **Mapping (legacy → new):**
|
|
426
|
+
* - `RdxDismissableLayer` (legacy) → `RdxFloatingNodeRegistration` (registers the tree node) +
|
|
427
|
+
* `RdxDismiss` (Escape / outside-press; reads the root context + node).
|
|
428
|
+
* - `RdxFocusScope` (direct) → `RdxFloatingFocusManager` (composes the reworked focus scope; trap +
|
|
429
|
+
* markOthers + close-on-focus-out), driven by `provideFloatingFocusManagerConfig`.
|
|
430
|
+
* - `disableOutsidePointerEvents` → the focus manager's `inert` pass marks outside elements
|
|
431
|
+
* non-interactive for a modal (finding #4), scoped to siblings of the popup's ancestor chain instead
|
|
432
|
+
* of a global `body { pointer-events: none }` lock — so the popup needs no `pointer-events: auto`.
|
|
433
|
+
* - focus-out close moved from the dismissal capability (`focusOutside: () => false`) to the manager
|
|
434
|
+
* (`manager.focusOut`), per ADR 0017 §3.
|
|
435
|
+
* - `isEventOnTrigger` preventDefault → removed: the trigger is in `context.triggers`, so the engine
|
|
436
|
+
* treats a press/focus on it as **inside** (no close-then-reopen).
|
|
437
|
+
*
|
|
438
|
+
* **Parity notes:**
|
|
439
|
+
* - **Lifecycle split (resolved 2026-06-16):** `enabled` is `open || transitionStatus === 'ending'`, so
|
|
440
|
+
* the trap / return-focus machinery survives the exit animation, while the manager's marker + isolation
|
|
441
|
+
* passes additionally key off `open` and release at close-start (Base UI `markOthers` gating).
|
|
442
|
+
* - **`trap-focus` split:** the manager traps focus for `modal === true` and `'trap-focus'`, but applies
|
|
443
|
+
* real `inert` isolation only for `modal === true`, matching Base UI's public contract that
|
|
444
|
+
* `modal="trap-focus"` leaves outside pointer interaction enabled.
|
|
445
|
+
* - **`returnFocus` orchestration (resolved 2026-06-16):** the manager now owns the return-focus *target*
|
|
446
|
+
* via the focus scope's `returnFocus` config seam (the scope owns the *timing* — its queued post-unmount
|
|
447
|
+
* frame). Dialog leaves it at the default (`returnFocus: true` → return to the element focused before
|
|
448
|
+
* open), so behavior is unchanged; a consumer can now also pass `false` / an element / a callback.
|
|
337
449
|
*/
|
|
338
450
|
class RdxDialogPopup {
|
|
339
451
|
constructor() {
|
|
340
452
|
this.rootContext = injectRdxDialogRootContext();
|
|
341
|
-
this.
|
|
453
|
+
this.host = inject(ElementRef).nativeElement;
|
|
454
|
+
this.floatingContext = inject(RDX_FLOATING_ROOT_CONTEXT);
|
|
455
|
+
this.registration = inject(RDX_FLOATING_REGISTRATION, { optional: true });
|
|
456
|
+
this.focusManager = inject(RdxFloatingFocusManager);
|
|
342
457
|
this.focusScope = inject(RdxFocusScope);
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
/**
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
this.
|
|
351
|
-
/**
|
|
352
|
-
* Event handler called when a pointerdown event happens outside of the popup. Can be prevented.
|
|
353
|
-
*/
|
|
354
|
-
this.pointerDownOutside = outputFromObservable(outputToObservable(this.dismissableLayer.pointerDownOutside));
|
|
355
|
-
/**
|
|
356
|
-
* Event handler called when focus moves outside of the popup. Can be prevented.
|
|
357
|
-
*/
|
|
358
|
-
this.focusOutside = outputFromObservable(outputToObservable(this.dismissableLayer.focusOutside));
|
|
359
|
-
/**
|
|
360
|
-
* Event handler called when an interaction happens outside of the popup. Can be prevented.
|
|
361
|
-
*/
|
|
362
|
-
this.interactOutside = outputFromObservable(outputToObservable(this.dismissableLayer.interactOutside));
|
|
363
|
-
/**
|
|
364
|
-
* Event handler called before focus moves into the popup. Can be prevented.
|
|
365
|
-
*/
|
|
458
|
+
/** Event handler called when the escape key is down. Can be prevented. */
|
|
459
|
+
this.escapeKeyDown = output();
|
|
460
|
+
/** Event handler called when a pointerdown event happens outside of the popup. Can be prevented. */
|
|
461
|
+
this.pointerDownOutside = output();
|
|
462
|
+
/** Event handler called when focus moves outside of the popup. Can be prevented. */
|
|
463
|
+
this.focusOutside = output();
|
|
464
|
+
/** Event handler called when an interaction (pointer / focus) happens outside of the popup. */
|
|
465
|
+
this.interactOutside = output();
|
|
466
|
+
/** Event handler called before focus moves into the popup. Can be prevented. */
|
|
366
467
|
this.openAutoFocus = outputFromObservable(outputToObservable(this.focusScope.mountAutoFocus));
|
|
367
|
-
/**
|
|
368
|
-
* Event handler called before focus returns after the popup is removed. Can be prevented.
|
|
369
|
-
*/
|
|
468
|
+
/** Event handler called before focus returns after the popup is removed. Can be prevented. */
|
|
370
469
|
this.closeAutoFocus = outputFromObservable(outputToObservable(this.focusScope.unmountAutoFocus));
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
// close and then immediately reopen).
|
|
379
|
-
if (this.isEventOnTrigger(event)) {
|
|
380
|
-
event.preventDefault();
|
|
381
|
-
}
|
|
470
|
+
// The popup element is this layer's floating element (inside-surface for containment checks).
|
|
471
|
+
this.floatingContext.setFloatingElement(this.host);
|
|
472
|
+
// Scroll lock follows Base UI (`open && modal === true`): released at close-start so the page is
|
|
473
|
+
// scrollable again as the exit animation plays. Background pointer/AT isolation is no longer a
|
|
474
|
+
// global body lock — the focus manager applies real `inert` to outside elements (finding #4).
|
|
475
|
+
useScrollLock(computed(() => this.rootContext.modal() === true && this.rootContext.isOpen()), {
|
|
476
|
+
referenceElement: () => this.host
|
|
382
477
|
});
|
|
383
|
-
this.
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
478
|
+
const unregisterTransitionElement = this.rootContext.registerTransitionElement(this.host);
|
|
479
|
+
inject(DestroyRef).onDestroy(unregisterTransitionElement);
|
|
480
|
+
// Base UI always renders an internal backdrop for a fully modal dialog. It is invisible and exists
|
|
481
|
+
// even when consumers also render `rdxDialogBackdrop`: outside pointer events land on this owned
|
|
482
|
+
// target instead of being swallowed by inert page content.
|
|
483
|
+
const injector = inject(Injector);
|
|
484
|
+
afterNextRender(() => setupInternalBackdrop(this.host, injector, {
|
|
485
|
+
marker: DIALOG_INTERNAL_BACKDROP_ATTR,
|
|
486
|
+
isOpen: () => this.rootContext.isOpen(),
|
|
487
|
+
shouldRender: () => this.rootContext.modal() === true,
|
|
488
|
+
cutout: () => this.host.closest('[rdxDialogViewport]'),
|
|
489
|
+
passThrough: () => this.host.closest('[rdxDialogViewport]') !== null
|
|
490
|
+
}));
|
|
491
|
+
// Dismissal (Base UI Dialog outside-press policy): Escape always closes; an outside press closes
|
|
492
|
+
// only the **topmost** dialog (a parent with an open nested dialog never self-closes) and only when
|
|
493
|
+
// pointer dismissal is enabled. A fully modal dialog uses the internal backdrop and intentional
|
|
494
|
+
// outside-press timing (click, not pointerdown). Focus-out is owned by the focus manager (below).
|
|
495
|
+
new RdxDismiss(this.floatingContext, () => this.registration?.node() ?? null, {
|
|
496
|
+
escapeKey: () => true,
|
|
497
|
+
outsidePress: () => this.isTopmost() && !this.rootContext.disablePointerDismissal(),
|
|
498
|
+
outsidePressEvent: () => (this.rootContext.modal() === true ? 'intentional' : 'sloppy'),
|
|
499
|
+
focusOutside: () => false,
|
|
500
|
+
onEscapeKeyDown: (event) => this.escapeKeyDown.emit(event),
|
|
501
|
+
onPointerDownOutside: (event) => {
|
|
502
|
+
this.pointerDownOutside.emit(event);
|
|
503
|
+
this.interactOutside.emit(event);
|
|
504
|
+
},
|
|
505
|
+
onDismiss: (reason, event) => {
|
|
506
|
+
this.rootContext.close(reason === 'escape-key' ? 'escape-key' : 'outside-press', event);
|
|
387
507
|
}
|
|
388
508
|
});
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
// Escape always closes (standard a11y behavior).
|
|
397
|
-
if ((reason === 'outside-press' || reason === 'focus-out') && this.rootContext.disablePointerDismissal()) {
|
|
398
|
-
return;
|
|
509
|
+
// Focus-out close (ADR 0017 §3) — the manager emits when focus leaves a non-modal dialog to an
|
|
510
|
+
// unrelated node; re-expose as `focusOutside` (preventable) and close unless vetoed.
|
|
511
|
+
this.focusManager.focusOut.subscribe((event) => {
|
|
512
|
+
this.focusOutside.emit(event);
|
|
513
|
+
this.interactOutside.emit(event);
|
|
514
|
+
if (!event.defaultPrevented) {
|
|
515
|
+
this.rootContext.close('focus-out', event);
|
|
399
516
|
}
|
|
400
|
-
this.rootContext.close(reason, event);
|
|
401
517
|
});
|
|
402
518
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
return
|
|
519
|
+
/** This dialog is the topmost (deepest open) one — it has no open nested dialog above it. */
|
|
520
|
+
isTopmost() {
|
|
521
|
+
return this.rootContext.isOpen() && !this.rootContext.nestedDialogOpen();
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Composite navigation keys (arrows / Home / End) are kept inside the dialog (Base UI `DialogPopup`):
|
|
525
|
+
* a dialog opened from inside a Menu / Menubar / Composite must not let an arrow press bubble out and
|
|
526
|
+
* move the outer collection's active item.
|
|
527
|
+
*/
|
|
528
|
+
onKeyDown(event) {
|
|
529
|
+
if (COMPOSITE_KEYS.has(event.key)) {
|
|
530
|
+
event.stopPropagation();
|
|
531
|
+
}
|
|
406
532
|
}
|
|
407
533
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxDialogPopup, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
408
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxDialogPopup, isStandalone: true, selector: "[rdxDialogPopup]", outputs: { escapeKeyDown: "escapeKeyDown", pointerDownOutside: "pointerDownOutside", focusOutside: "focusOutside", interactOutside: "interactOutside", openAutoFocus: "openAutoFocus", closeAutoFocus: "closeAutoFocus" }, host: { properties: { "attr.role": "rootContext.role", "attr.aria-modal": "rootContext.modal() === true ? \"true\" : undefined", "attr.aria-describedby": "rootContext.descriptionId()", "attr.aria-labelledby": "rootContext.titleId()", "attr.data-closed": "rootContext.isOpen() ? undefined : \"\"", "attr.data-ending-style": "rootContext.transitionStatus() === \"ending\" ? \"\" : undefined", "attr.data-open": "rootContext.isOpen() ? \"\" : undefined", "attr.data-starting-style": "rootContext.transitionStatus() === \"starting\" ? \"\" : undefined", "attr.data-state": "rootContext.isOpen() ? \"open\" : \"closed\"", "attr.data-nested": "rootContext.nested ? \"\" : undefined", "attr.data-nested-dialog-open": "rootContext.nestedDialogOpen() ? \"\" : undefined", "id": "rootContext.contentId" } }, providers: [
|
|
409
|
-
|
|
534
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxDialogPopup, isStandalone: true, selector: "[rdxDialogPopup]", outputs: { escapeKeyDown: "escapeKeyDown", pointerDownOutside: "pointerDownOutside", focusOutside: "focusOutside", interactOutside: "interactOutside", openAutoFocus: "openAutoFocus", closeAutoFocus: "closeAutoFocus" }, host: { listeners: { "keydown": "onKeyDown($event)" }, properties: { "attr.role": "rootContext.role", "attr.aria-modal": "rootContext.modal() === true ? \"true\" : undefined", "attr.aria-describedby": "rootContext.descriptionId()", "attr.aria-labelledby": "rootContext.titleId()", "attr.data-closed": "rootContext.isOpen() ? undefined : \"\"", "attr.data-ending-style": "rootContext.transitionStatus() === \"ending\" ? \"\" : undefined", "attr.data-open": "rootContext.isOpen() ? \"\" : undefined", "attr.data-starting-style": "rootContext.transitionStatus() === \"starting\" ? \"\" : undefined", "attr.data-state": "rootContext.isOpen() ? \"open\" : \"closed\"", "attr.data-nested": "rootContext.nested ? \"\" : undefined", "attr.data-nested-dialog-open": "rootContext.nestedDialogOpen() ? \"\" : undefined", "id": "rootContext.contentId" } }, providers: [
|
|
535
|
+
provideFloatingFocusManagerConfig(() => {
|
|
410
536
|
const rootContext = injectRdxDialogRootContext();
|
|
411
537
|
return {
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
538
|
+
// Trap for a modal or trap-focus dialog (Base UI `modal={modal !== false}`).
|
|
539
|
+
modal: () => rootContext.modal() === true || rootContext.modal() === 'trap-focus',
|
|
540
|
+
// Full modal blocks outside pointer interaction; `trap-focus` only traps focus.
|
|
541
|
+
inert: () => rootContext.modal() === true,
|
|
542
|
+
// Active for the whole MOUNTED lifetime — including an explicit
|
|
543
|
+
// `preventUnmountOnClose()` cycle after the exit transition — matching Base UI's
|
|
544
|
+
// `FloatingFocusManager disabled={!mounted}` (NOT `open`) for trap/return-focus.
|
|
545
|
+
// Marker + isolation are additionally gated on `open` inside the manager.
|
|
546
|
+
enabled: () => rootContext.present(),
|
|
547
|
+
closeOnFocusOut: () => !rootContext.disablePointerDismissal()
|
|
419
548
|
};
|
|
420
549
|
})
|
|
421
|
-
], exportAs: ["rdxDialogPopup"], hostDirectives: [{ directive: i1.
|
|
550
|
+
], exportAs: ["rdxDialogPopup"], hostDirectives: [{ directive: i1.RdxFloatingNodeRegistration }, { directive: i2.RdxFloatingFocusManager }], ngImport: i0 }); }
|
|
422
551
|
}
|
|
423
552
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxDialogPopup, decorators: [{
|
|
424
553
|
type: Directive,
|
|
425
554
|
args: [{
|
|
426
555
|
selector: '[rdxDialogPopup]',
|
|
427
556
|
exportAs: 'rdxDialogPopup',
|
|
428
|
-
hostDirectives: [
|
|
557
|
+
hostDirectives: [RdxFloatingNodeRegistration, RdxFloatingFocusManager],
|
|
429
558
|
providers: [
|
|
430
|
-
|
|
431
|
-
const rootContext = injectRdxDialogRootContext();
|
|
432
|
-
return {
|
|
433
|
-
disableOutsidePointerEvents: computed(() => rootContext.modal() === true)
|
|
434
|
-
};
|
|
435
|
-
}),
|
|
436
|
-
provideRdxFocusScopeConfig(() => {
|
|
559
|
+
provideFloatingFocusManagerConfig(() => {
|
|
437
560
|
const rootContext = injectRdxDialogRootContext();
|
|
438
561
|
return {
|
|
439
|
-
|
|
562
|
+
// Trap for a modal or trap-focus dialog (Base UI `modal={modal !== false}`).
|
|
563
|
+
modal: () => rootContext.modal() === true || rootContext.modal() === 'trap-focus',
|
|
564
|
+
// Full modal blocks outside pointer interaction; `trap-focus` only traps focus.
|
|
565
|
+
inert: () => rootContext.modal() === true,
|
|
566
|
+
// Active for the whole MOUNTED lifetime — including an explicit
|
|
567
|
+
// `preventUnmountOnClose()` cycle after the exit transition — matching Base UI's
|
|
568
|
+
// `FloatingFocusManager disabled={!mounted}` (NOT `open`) for trap/return-focus.
|
|
569
|
+
// Marker + isolation are additionally gated on `open` inside the manager.
|
|
570
|
+
enabled: () => rootContext.present(),
|
|
571
|
+
closeOnFocusOut: () => !rootContext.disablePointerDismissal()
|
|
440
572
|
};
|
|
441
573
|
})
|
|
442
574
|
],
|
|
@@ -452,69 +584,63 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
452
584
|
'[attr.data-state]': 'rootContext.isOpen() ? "open" : "closed"',
|
|
453
585
|
'[attr.data-nested]': 'rootContext.nested ? "" : undefined',
|
|
454
586
|
'[attr.data-nested-dialog-open]': 'rootContext.nestedDialogOpen() ? "" : undefined',
|
|
455
|
-
'[id]': 'rootContext.contentId'
|
|
587
|
+
'[id]': 'rootContext.contentId',
|
|
588
|
+
'(keydown)': 'onKeyDown($event)'
|
|
456
589
|
}
|
|
457
590
|
}]
|
|
458
591
|
}], ctorParameters: () => [], propDecorators: { escapeKeyDown: [{ type: i0.Output, args: ["escapeKeyDown"] }], pointerDownOutside: [{ type: i0.Output, args: ["pointerDownOutside"] }], focusOutside: [{ type: i0.Output, args: ["focusOutside"] }], interactOutside: [{ type: i0.Output, args: ["interactOutside"] }], openAutoFocus: [{ type: i0.Output, args: ["openAutoFocus"] }], closeAutoFocus: [{ type: i0.Output, args: ["closeAutoFocus"] }] } });
|
|
459
592
|
|
|
460
593
|
/**
|
|
461
|
-
*
|
|
594
|
+
* Structural directive that teleports the dialog content (backdrop + popup) into a container (default
|
|
595
|
+
* `document.body`) while the dialog is open, and keeps it mounted until the CSS exit `@keyframes` on
|
|
596
|
+
* every root element finish.
|
|
597
|
+
*
|
|
598
|
+
* Dialog has two root nodes (backdrop + popup), so use the explicit `<ng-template rdxDialogPortal>`
|
|
599
|
+
* form. Pass `[container]` to portal into a different element.
|
|
462
600
|
*/
|
|
463
601
|
class RdxDialogPortal {
|
|
464
602
|
constructor() {
|
|
465
|
-
this.rootContext = injectRdxDialogRootContext();
|
|
466
603
|
/**
|
|
467
|
-
* Optional container to portal the content into. Defaults to `document.body`.
|
|
604
|
+
* Optional container to portal the content into. Defaults to `document.body`. Declared here (and
|
|
605
|
+
* forwarded to the composed {@link RdxPortalPresence}) so that the drawer and alert-dialog portals
|
|
606
|
+
* can re-expose it through their own `hostDirectives`.
|
|
468
607
|
*/
|
|
469
608
|
this.container = input(...(ngDevMode ? [undefined, { debugName: "container" }] : /* istanbul ignore next */ []));
|
|
470
609
|
}
|
|
471
610
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxDialogPortal, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
472
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxDialogPortal, isStandalone: true, selector: "[rdxDialogPortal]", inputs: { container: { classPropertyName: "container", publicName: "container", isSignal: true, isRequired: false, transformFunction: null } },
|
|
611
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxDialogPortal, isStandalone: true, selector: "ng-template[rdxDialogPortal]", inputs: { container: { classPropertyName: "container", publicName: "container", isSignal: true, isRequired: false, transformFunction: null } }, providers: [provideRdxPresenceContext(() => ({ present: injectRdxDialogRootContext().present }))], exportAs: ["rdxDialogPortal"], hostDirectives: [{ directive: i1$1.RdxPortalPresence, inputs: ["container", "container"] }], ngImport: i0 }); }
|
|
473
612
|
}
|
|
474
613
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxDialogPortal, decorators: [{
|
|
475
614
|
type: Directive,
|
|
476
615
|
args: [{
|
|
477
|
-
selector: '[rdxDialogPortal]',
|
|
616
|
+
selector: 'ng-template[rdxDialogPortal]',
|
|
478
617
|
exportAs: 'rdxDialogPortal',
|
|
479
|
-
hostDirectives: [
|
|
480
|
-
|
|
481
|
-
directive: RdxPortal,
|
|
482
|
-
inputs: ['container']
|
|
483
|
-
}
|
|
484
|
-
],
|
|
485
|
-
host: {
|
|
486
|
-
'[attr.data-closed]': 'rootContext.isOpen() ? undefined : ""',
|
|
487
|
-
'[attr.data-open]': 'rootContext.isOpen() ? "" : undefined',
|
|
488
|
-
'[attr.data-state]': 'rootContext.isOpen() ? "open" : "closed"'
|
|
489
|
-
}
|
|
618
|
+
hostDirectives: [{ directive: RdxPortalPresence, inputs: ['container'] }],
|
|
619
|
+
providers: [provideRdxPresenceContext(() => ({ present: injectRdxDialogRootContext().present }))]
|
|
490
620
|
}]
|
|
491
621
|
}], propDecorators: { container: [{ type: i0.Input, args: [{ isSignal: true, alias: "container", required: false }] }] } });
|
|
492
|
-
|
|
493
622
|
/**
|
|
494
|
-
*
|
|
623
|
+
* Dev-mode guard: `rdxDialogPortal` used to be an attribute directive on a `<div>`. It is now
|
|
624
|
+
* structural, so the old `<div rdxDialogPortal>` markup would silently stop portaling — fail loudly
|
|
625
|
+
* instead.
|
|
495
626
|
*/
|
|
496
|
-
class
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
627
|
+
class RdxDialogPortalMisuseGuard {
|
|
628
|
+
constructor() {
|
|
629
|
+
if (isDevMode()) {
|
|
630
|
+
rdxDevError('dialog/portal-on-element', '`rdxDialogPortal` is now a structural directive. ' +
|
|
631
|
+
'Use `<ng-template rdxDialogPortal>` around the backdrop and popup. ' +
|
|
632
|
+
'rdxDialogPortalPresence has been removed.', 'components/dialog');
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxDialogPortalMisuseGuard, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
636
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxDialogPortalMisuseGuard, isStandalone: true, selector: "[rdxDialogPortal]:not(ng-template)", ngImport: i0 }); }
|
|
504
637
|
}
|
|
505
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type:
|
|
638
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxDialogPortalMisuseGuard, decorators: [{
|
|
506
639
|
type: Directive,
|
|
507
640
|
args: [{
|
|
508
|
-
selector: 'ng-template
|
|
509
|
-
hostDirectives: [RdxPresenceDirective],
|
|
510
|
-
providers: [
|
|
511
|
-
provideRdxPresenceContext(() => {
|
|
512
|
-
const context = injectRdxDialogRootContext();
|
|
513
|
-
return { present: context.isOpen };
|
|
514
|
-
})
|
|
515
|
-
]
|
|
641
|
+
selector: '[rdxDialogPortal]:not(ng-template)'
|
|
516
642
|
}]
|
|
517
|
-
}] });
|
|
643
|
+
}], ctorParameters: () => [] });
|
|
518
644
|
|
|
519
645
|
/**
|
|
520
646
|
* An accessible title for the dialog.
|
|
@@ -709,8 +835,8 @@ function createRdxDialogHandle() {
|
|
|
709
835
|
const dialogImports = [
|
|
710
836
|
RdxDialogRoot,
|
|
711
837
|
RdxDialogTrigger,
|
|
712
|
-
RdxDialogPortalPresence,
|
|
713
838
|
RdxDialogPortal,
|
|
839
|
+
RdxDialogPortalMisuseGuard,
|
|
714
840
|
RdxDialogBackdrop,
|
|
715
841
|
RdxDialogViewport,
|
|
716
842
|
RdxDialogPopup,
|
|
@@ -722,8 +848,8 @@ class RdxDialogModule {
|
|
|
722
848
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxDialogModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
723
849
|
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.9", ngImport: i0, type: RdxDialogModule, imports: [RdxDialogRoot,
|
|
724
850
|
RdxDialogTrigger,
|
|
725
|
-
RdxDialogPortalPresence,
|
|
726
851
|
RdxDialogPortal,
|
|
852
|
+
RdxDialogPortalMisuseGuard,
|
|
727
853
|
RdxDialogBackdrop,
|
|
728
854
|
RdxDialogViewport,
|
|
729
855
|
RdxDialogPopup,
|
|
@@ -731,8 +857,8 @@ class RdxDialogModule {
|
|
|
731
857
|
RdxDialogDescription,
|
|
732
858
|
RdxDialogClose], exports: [RdxDialogRoot,
|
|
733
859
|
RdxDialogTrigger,
|
|
734
|
-
RdxDialogPortalPresence,
|
|
735
860
|
RdxDialogPortal,
|
|
861
|
+
RdxDialogPortalMisuseGuard,
|
|
736
862
|
RdxDialogBackdrop,
|
|
737
863
|
RdxDialogViewport,
|
|
738
864
|
RdxDialogPopup,
|
|
@@ -753,5 +879,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
753
879
|
* Generated bundle index. Do not edit.
|
|
754
880
|
*/
|
|
755
881
|
|
|
756
|
-
export { RDX_DIALOG_VARIANT, RdxDialogBackdrop, RdxDialogClose, RdxDialogDescription, RdxDialogHandle, RdxDialogModule, RdxDialogPopup, RdxDialogPortal,
|
|
882
|
+
export { RDX_DIALOG_VARIANT, RdxDialogBackdrop, RdxDialogClose, RdxDialogDescription, RdxDialogHandle, RdxDialogModule, RdxDialogPopup, RdxDialogPortal, RdxDialogPortalMisuseGuard, RdxDialogRoot, RdxDialogTitle, RdxDialogTrigger, RdxDialogViewport, createRdxDialogHandle, dialogImports, injectRdxDialogRootContext, provideRdxDialogRootContext, provideRdxDialogVariant };
|
|
757
883
|
//# sourceMappingURL=radix-ng-primitives-dialog.mjs.map
|