@radix-ng/primitives 1.0.0-beta.3 → 1.0.0-beta.5
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 +1 -1
- 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 +3 -2
- package/fesm2022/radix-ng-primitives-alert-dialog.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-autocomplete.mjs +617 -659
- package/fesm2022/radix-ng-primitives-autocomplete.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-calendar.mjs +5 -3
- package/fesm2022/radix-ng-primitives-calendar.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-checkbox.mjs +33 -18
- package/fesm2022/radix-ng-primitives-checkbox.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-combobox.mjs +1305 -572
- 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 +1352 -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 +290 -120
- 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 +3 -3
- package/fesm2022/radix-ng-primitives-drawer.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-editable.mjs +12 -7
- package/fesm2022/radix-ng-primitives-editable.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 +803 -0
- package/fesm2022/radix-ng-primitives-floating-focus-manager.mjs.map +1 -0
- package/fesm2022/radix-ng-primitives-focus-scope.mjs +305 -70
- package/fesm2022/radix-ng-primitives-focus-scope.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-menu.mjs +893 -289
- 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 +144 -159
- package/fesm2022/radix-ng-primitives-navigation-menu.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-number-field.mjs +7 -2
- package/fesm2022/radix-ng-primitives-number-field.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-popover.mjs +284 -212
- package/fesm2022/radix-ng-primitives-popover.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-popper.mjs +94 -51
- package/fesm2022/radix-ng-primitives-popper.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-presence.mjs +1 -1
- package/fesm2022/radix-ng-primitives-presence.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-preview-card.mjs +141 -173
- package/fesm2022/radix-ng-primitives-preview-card.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-radio.mjs +19 -14
- package/fesm2022/radix-ng-primitives-radio.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 +241 -164
- package/fesm2022/radix-ng-primitives-select.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-slider.mjs +262 -29
- package/fesm2022/radix-ng-primitives-slider.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-stepper.mjs +16 -10
- package/fesm2022/radix-ng-primitives-stepper.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-switch.mjs +10 -5
- package/fesm2022/radix-ng-primitives-switch.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-tabs.mjs +15 -10
- package/fesm2022/radix-ng-primitives-tabs.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 +14 -7
- package/fesm2022/radix-ng-primitives-toggle-group.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-toggle.mjs +12 -6
- package/fesm2022/radix-ng-primitives-toggle.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 +251 -143
- package/fesm2022/radix-ng-primitives-tooltip.mjs.map +1 -1
- package/package.json +10 -1
- package/types/radix-ng-primitives-accordion.d.ts +4 -3
- package/types/radix-ng-primitives-autocomplete.d.ts +217 -152
- package/types/radix-ng-primitives-calendar.d.ts +5 -3
- package/types/radix-ng-primitives-checkbox.d.ts +27 -15
- package/types/radix-ng-primitives-combobox.d.ts +672 -283
- 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 +764 -14
- package/types/radix-ng-primitives-date-field.d.ts +3 -2
- package/types/radix-ng-primitives-dialog.d.ts +88 -32
- 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-editable.d.ts +11 -5
- package/types/radix-ng-primitives-field.d.ts +1 -0
- package/types/radix-ng-primitives-floating-focus-manager.d.ts +272 -0
- package/types/radix-ng-primitives-focus-scope.d.ts +132 -1
- package/types/radix-ng-primitives-menu.d.ts +192 -103
- package/types/radix-ng-primitives-navigation-menu.d.ts +37 -75
- package/types/radix-ng-primitives-number-field.d.ts +8 -3
- package/types/radix-ng-primitives-popover.d.ts +71 -92
- package/types/radix-ng-primitives-popper.d.ts +39 -9
- package/types/radix-ng-primitives-preview-card.d.ts +39 -72
- package/types/radix-ng-primitives-radio.d.ts +13 -6
- 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 +142 -109
- package/types/radix-ng-primitives-slider.d.ts +64 -12
- package/types/radix-ng-primitives-stepper.d.ts +15 -7
- package/types/radix-ng-primitives-switch.d.ts +10 -4
- package/types/radix-ng-primitives-tabs.d.ts +12 -6
- 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 +15 -8
- package/types/radix-ng-primitives-toggle.d.ts +10 -3
- package/types/radix-ng-primitives-toolbar.d.ts +3 -2
- package/types/radix-ng-primitives-tooltip.d.ts +61 -80
|
@@ -1,11 +1,12 @@
|
|
|
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';
|
|
5
|
+
import * as i2 from '@radix-ng/primitives/floating-focus-manager';
|
|
6
|
+
import { getInteractionTypeFromEvent, RdxFloatingFocusManager, provideFloatingFocusManagerConfig, createRdxTriggerInteraction, useTriggerFocusGuardAnchor } from '@radix-ng/primitives/floating-focus-manager';
|
|
4
7
|
import { outputFromObservable, outputToObservable } from '@angular/core/rxjs-interop';
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import * as i2 from '@radix-ng/primitives/focus-scope';
|
|
8
|
-
import { RdxFocusScope, provideRdxFocusScopeConfig } from '@radix-ng/primitives/focus-scope';
|
|
8
|
+
import { RdxDismiss } from '@radix-ng/primitives/dismissable-layer';
|
|
9
|
+
import { RdxFocusScope } from '@radix-ng/primitives/focus-scope';
|
|
9
10
|
import * as i1$1 from '@radix-ng/primitives/portal';
|
|
10
11
|
import { RdxPortalPresence } from '@radix-ng/primitives/portal';
|
|
11
12
|
import { provideRdxPresenceContext } from '@radix-ng/primitives/presence';
|
|
@@ -85,6 +86,11 @@ class RdxDialogRoot {
|
|
|
85
86
|
this.triggers = signal([], ...(ngDevMode ? [{ debugName: "triggers" }] : /* istanbul ignore next */ []));
|
|
86
87
|
this.payload = signal(undefined, ...(ngDevMode ? [{ debugName: "payload" }] : /* istanbul ignore next */ []));
|
|
87
88
|
this.nestedOpenCount = signal(0, ...(ngDevMode ? [{ debugName: "nestedOpenCount" }] : /* istanbul ignore next */ []));
|
|
89
|
+
this.openInteractionType = signal(null, ...(ngDevMode ? [{ debugName: "openInteractionType" }] : /* istanbul ignore next */ []));
|
|
90
|
+
this.closeInteractionType = signal(null, ...(ngDevMode ? [{ debugName: "closeInteractionType" }] : /* istanbul ignore next */ []));
|
|
91
|
+
this.preventUnmountOnClose = signal(false, ...(ngDevMode ? [{ debugName: "preventUnmountOnClose" }] : /* istanbul ignore next */ []));
|
|
92
|
+
this.pendingTriggerOpenInteractionType = signal(null, ...(ngDevMode ? [{ debugName: "pendingTriggerOpenInteractionType" }] : /* istanbul ignore next */ []));
|
|
93
|
+
this.present = computed(() => this.open() || this.preventUnmountOnClose(), ...(ngDevMode ? [{ debugName: "present" }] : /* istanbul ignore next */ []));
|
|
88
94
|
/** Whether this dialog is rendered inside another dialog. Fixed at construction. */
|
|
89
95
|
this.nested = !!this.parentRoot;
|
|
90
96
|
this.nestedDialogOpen = computed(() => this.nestedOpenCount() > 0, ...(ngDevMode ? [{ debugName: "nestedDialogOpen" }] : /* istanbul ignore next */ []));
|
|
@@ -94,6 +100,22 @@ class RdxDialogRoot {
|
|
|
94
100
|
this.effectiveModal = computed(() => (this.variant.forceModal ? true : this.modal()), ...(ngDevMode ? [{ debugName: "effectiveModal" }] : /* istanbul ignore next */ []));
|
|
95
101
|
/** Effective dismissal flag: disabled when the input asks, or when the variant forces it (alerts). */
|
|
96
102
|
this.effectiveDisablePointerDismissal = computed(() => this.disablePointerDismissal() || this.variant.forcePointerDismissalDisabled, ...(ngDevMode ? [{ debugName: "effectiveDisablePointerDismissal" }] : /* istanbul ignore next */ []));
|
|
103
|
+
/**
|
|
104
|
+
* The shared per-popup floating context (ADR 0015 §1) — `open` mirrors the dialog's open state, the
|
|
105
|
+
* trigger registry is bridged from {@link registerTrigger}, and the reference / floating elements are
|
|
106
|
+
* set by the trigger / popup. The new dismissal + focus engines read this once the popup migrates.
|
|
107
|
+
*/
|
|
108
|
+
this.floatingContext = createFloatingRootContext({
|
|
109
|
+
ownerDocument: inject(ElementRef).nativeElement.ownerDocument,
|
|
110
|
+
open: () => this.open()
|
|
111
|
+
});
|
|
112
|
+
// Keep the floating context's reference element in sync with the active trigger.
|
|
113
|
+
effect(() => this.floatingContext.setReferenceElement(this.trigger() ?? null));
|
|
114
|
+
effect(() => {
|
|
115
|
+
if (this.open() && this.preventUnmountOnClose()) {
|
|
116
|
+
this.preventUnmountOnClose.set(false);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
97
119
|
let previousOpen = this.open();
|
|
98
120
|
effect(() => {
|
|
99
121
|
const defaultOpen = this.defaultOpen();
|
|
@@ -134,30 +156,57 @@ class RdxDialogRoot {
|
|
|
134
156
|
}
|
|
135
157
|
});
|
|
136
158
|
}
|
|
137
|
-
show(trigger = this.trigger(), payload, triggerId, reason = 'none', event
|
|
159
|
+
show(trigger = this.trigger(), payload, triggerId, reason = 'none', event) {
|
|
160
|
+
const shouldAdoptPayload = trigger !== undefined || payload !== undefined;
|
|
161
|
+
if (this.open()) {
|
|
162
|
+
if (trigger) {
|
|
163
|
+
this.trigger.set(trigger);
|
|
164
|
+
}
|
|
165
|
+
if (triggerId !== undefined) {
|
|
166
|
+
this.triggerId.set(triggerId);
|
|
167
|
+
}
|
|
168
|
+
// Only adopt the payload when a trigger context is actually provided, so a bare
|
|
169
|
+
// imperative re-show on an already-open dialog doesn't clobber the live payload.
|
|
170
|
+
if (shouldAdoptPayload) {
|
|
171
|
+
this.payload.set(payload);
|
|
172
|
+
}
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const resolvedEvent = event ?? new Event('dialog.open-change');
|
|
176
|
+
const change = this.createOpenChangeEvent(true, reason, resolvedEvent, trigger, triggerId ?? this.triggerId());
|
|
177
|
+
this.onOpenChange.emit(change.payload);
|
|
178
|
+
if (change.eventDetails.isCanceled()) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
this.openInteractionType.set(this.consumeOpenInteractionType(event));
|
|
138
182
|
if (trigger) {
|
|
139
183
|
this.trigger.set(trigger);
|
|
140
184
|
}
|
|
141
185
|
if (triggerId !== undefined) {
|
|
142
186
|
this.triggerId.set(triggerId);
|
|
143
187
|
}
|
|
144
|
-
|
|
145
|
-
// imperative re-show on an already-open dialog doesn't clobber the live payload.
|
|
146
|
-
if (trigger !== undefined || payload !== undefined) {
|
|
188
|
+
if (shouldAdoptPayload) {
|
|
147
189
|
this.payload.set(payload);
|
|
148
190
|
}
|
|
149
|
-
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
191
|
+
this.preventUnmountOnClose.set(false);
|
|
152
192
|
this.open.set(true);
|
|
153
|
-
this.
|
|
193
|
+
this.floatingContext.events.emit('openchange', { open: true, reason, event: change.eventDetails.event });
|
|
154
194
|
}
|
|
155
|
-
close(reason = 'none', event
|
|
195
|
+
close(reason = 'none', event) {
|
|
156
196
|
if (!this.open()) {
|
|
157
197
|
return;
|
|
158
198
|
}
|
|
199
|
+
const resolvedEvent = event ?? new Event('dialog.open-change');
|
|
200
|
+
const change = this.createOpenChangeEvent(false, reason, resolvedEvent, this.trigger(), this.triggerId());
|
|
201
|
+
this.onOpenChange.emit(change.payload);
|
|
202
|
+
if (change.eventDetails.isCanceled()) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
this.pendingTriggerOpenInteractionType.set(null);
|
|
206
|
+
this.closeInteractionType.set(getInteractionTypeFromEvent(event));
|
|
207
|
+
this.preventUnmountOnClose.set(change.shouldPreventUnmountOnClose());
|
|
159
208
|
this.open.set(false);
|
|
160
|
-
this.
|
|
209
|
+
this.floatingContext.events.emit('openchange', { open: false, reason, event: change.eventDetails.event });
|
|
161
210
|
}
|
|
162
211
|
toggle(triggerId, trigger, payload, event = new Event('dialog.open-change')) {
|
|
163
212
|
if (this.open() && this.trigger() === trigger) {
|
|
@@ -166,9 +215,15 @@ class RdxDialogRoot {
|
|
|
166
215
|
}
|
|
167
216
|
this.show(trigger, payload, triggerId, 'trigger-press', event);
|
|
168
217
|
}
|
|
218
|
+
setTriggerOpenInteractionType(type) {
|
|
219
|
+
this.pendingTriggerOpenInteractionType.set(type);
|
|
220
|
+
}
|
|
169
221
|
registerTrigger(id, trigger, payload) {
|
|
170
222
|
this.registeredTriggers.set(id, { element: trigger, payload });
|
|
171
223
|
this.triggers.update((triggers) => (triggers.includes(trigger) ? triggers : [...triggers, trigger]));
|
|
224
|
+
// Bridge into the floating context's trigger registry — the new dismissal/focus engines read it
|
|
225
|
+
// for inside-element checks (a press/focus on the trigger counts as inside, ADR 0015 §2).
|
|
226
|
+
this.floatingContext.triggers.add(trigger);
|
|
172
227
|
if (this.triggerId() === id || (!this.trigger() && this.triggerId() === null)) {
|
|
173
228
|
this.trigger.set(trigger);
|
|
174
229
|
this.payload.set(payload());
|
|
@@ -178,6 +233,7 @@ class RdxDialogRoot {
|
|
|
178
233
|
this.registeredTriggers.delete(id);
|
|
179
234
|
}
|
|
180
235
|
this.triggers.update((triggers) => triggers.filter((candidate) => candidate !== trigger));
|
|
236
|
+
this.floatingContext.triggers.delete(trigger);
|
|
181
237
|
if (!this.destroyRef.destroyed && this.trigger() === trigger) {
|
|
182
238
|
const next = this.registeredTriggers.entries().next().value;
|
|
183
239
|
if (this.triggerId() !== null) {
|
|
@@ -208,29 +264,52 @@ class RdxDialogRoot {
|
|
|
208
264
|
this.payload.set(trigger.payload());
|
|
209
265
|
}
|
|
210
266
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
267
|
+
createOpenChangeEvent(open, reason, event, trigger, triggerId) {
|
|
268
|
+
const change = createCancelableChangeEventDetails(reason, event, trigger);
|
|
269
|
+
return {
|
|
270
|
+
eventDetails: change.eventDetails,
|
|
271
|
+
shouldPreventUnmountOnClose: change.shouldPreventUnmountOnClose,
|
|
272
|
+
payload: {
|
|
273
|
+
open,
|
|
274
|
+
triggerId,
|
|
275
|
+
trigger: change.eventDetails.trigger,
|
|
276
|
+
reason: change.eventDetails.reason,
|
|
277
|
+
event: change.eventDetails.event,
|
|
278
|
+
eventDetails: change.eventDetails
|
|
279
|
+
}
|
|
280
|
+
};
|
|
219
281
|
}
|
|
220
282
|
emitOpenChangeComplete(open) {
|
|
221
283
|
if (!this.destroyRef.destroyed) {
|
|
222
284
|
this.onOpenChangeComplete.emit(open);
|
|
223
285
|
}
|
|
224
286
|
}
|
|
287
|
+
consumeOpenInteractionType(event) {
|
|
288
|
+
const pending = this.pendingTriggerOpenInteractionType();
|
|
289
|
+
this.pendingTriggerOpenInteractionType.set(null);
|
|
290
|
+
return pending ?? getInteractionTypeFromEvent(event);
|
|
291
|
+
}
|
|
225
292
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxDialogRoot, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
226
|
-
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: [
|
|
293
|
+
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: [
|
|
294
|
+
provideRdxDialogRootContext(context),
|
|
295
|
+
// New floating foundation (ADR 0015/0017 migration). Inherit-or-create tree so a nested dialog
|
|
296
|
+
// shares its parent's tree; the per-popup root context bridges open / triggers / reference.
|
|
297
|
+
provideFloatingTree(),
|
|
298
|
+
provideFloatingRootContext(() => inject(RdxDialogRoot).floatingContext)
|
|
299
|
+
], exportAs: ["rdxDialogRoot"], ngImport: i0 }); }
|
|
227
300
|
}
|
|
228
301
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxDialogRoot, decorators: [{
|
|
229
302
|
type: Directive,
|
|
230
303
|
args: [{
|
|
231
304
|
selector: '[rdxDialogRoot]',
|
|
232
305
|
exportAs: 'rdxDialogRoot',
|
|
233
|
-
providers: [
|
|
306
|
+
providers: [
|
|
307
|
+
provideRdxDialogRootContext(context),
|
|
308
|
+
// New floating foundation (ADR 0015/0017 migration). Inherit-or-create tree so a nested dialog
|
|
309
|
+
// shares its parent's tree; the per-popup root context bridges open / triggers / reference.
|
|
310
|
+
provideFloatingTree(),
|
|
311
|
+
provideFloatingRootContext(() => inject(RdxDialogRoot).floatingContext)
|
|
312
|
+
]
|
|
234
313
|
}]
|
|
235
314
|
}], 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"] }] } });
|
|
236
315
|
function contextFor(root) {
|
|
@@ -239,6 +318,7 @@ function contextFor(root) {
|
|
|
239
318
|
titleId: root.titleId.asReadonly(),
|
|
240
319
|
descriptionId: root.descriptionId.asReadonly(),
|
|
241
320
|
isOpen: root.open,
|
|
321
|
+
present: root.present,
|
|
242
322
|
modal: root.effectiveModal,
|
|
243
323
|
disablePointerDismissal: root.effectiveDisablePointerDismissal,
|
|
244
324
|
role: root.role,
|
|
@@ -246,12 +326,15 @@ function contextFor(root) {
|
|
|
246
326
|
trigger: root.trigger.asReadonly(),
|
|
247
327
|
triggers: root.triggers.asReadonly(),
|
|
248
328
|
payload: root.payload.asReadonly(),
|
|
329
|
+
openInteractionType: root.openInteractionType.asReadonly(),
|
|
330
|
+
closeInteractionType: root.closeInteractionType.asReadonly(),
|
|
249
331
|
nested: root.nested,
|
|
250
332
|
nestedDialogOpen: root.nestedDialogOpen,
|
|
251
333
|
setTitleId: (id) => root.titleId.set(id),
|
|
252
334
|
setDescriptionId: (id) => root.descriptionId.set(id),
|
|
253
335
|
registerTransitionElement: (element) => root.registerTransitionElement(element),
|
|
254
336
|
registerTrigger: (id, trigger, payload) => root.registerTrigger(id, trigger, payload),
|
|
337
|
+
setTriggerOpenInteractionType: (type) => root.setTriggerOpenInteractionType(type),
|
|
255
338
|
open: (trigger, payload, triggerId, reason, event) => root.show(trigger, payload, triggerId, reason, event),
|
|
256
339
|
close: (reason, event) => root.close(reason, event),
|
|
257
340
|
toggle: (triggerId, trigger, payload, event) => root.toggle(triggerId, trigger, payload, event)
|
|
@@ -260,13 +343,27 @@ function contextFor(root) {
|
|
|
260
343
|
|
|
261
344
|
/**
|
|
262
345
|
* An overlay displayed beneath the dialog popup.
|
|
346
|
+
*
|
|
347
|
+
* Decorative-only, so it carries `role="presentation"` (Base UI `DialogBackdrop`). By default a **nested**
|
|
348
|
+
* dialog renders no backdrop — the parent's already dims the page; stacking a second one double-darkens
|
|
349
|
+
* and intercepts the parent's outside-press. Set `forceRender` to opt back in.
|
|
263
350
|
*/
|
|
264
351
|
class RdxDialogBackdrop {
|
|
265
352
|
constructor() {
|
|
266
353
|
this.rootContext = injectRdxDialogRootContext();
|
|
354
|
+
/** Render the backdrop even for a nested dialog (off by default, matching Base UI). */
|
|
355
|
+
this.forceRender = input(false, { ...(ngDevMode ? { debugName: "forceRender" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
356
|
+
// The backdrop is a second portal root (a body sibling of the popup). It is registered as owned DOM
|
|
357
|
+
// footprint for primitive-specific checks, but it is not a marker/aria keep-set member.
|
|
358
|
+
const floatingContext = inject(RDX_FLOATING_ROOT_CONTEXT, { optional: true });
|
|
359
|
+
if (floatingContext) {
|
|
360
|
+
const host = inject(ElementRef).nativeElement;
|
|
361
|
+
floatingContext.addFloatingElement(host);
|
|
362
|
+
inject(DestroyRef).onDestroy(() => floatingContext.removeFloatingElement(host));
|
|
363
|
+
}
|
|
267
364
|
}
|
|
268
365
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxDialogBackdrop, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
269
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "
|
|
366
|
+
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 }); }
|
|
270
367
|
}
|
|
271
368
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxDialogBackdrop, decorators: [{
|
|
272
369
|
type: Directive,
|
|
@@ -274,6 +371,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
274
371
|
selector: '[rdxDialogBackdrop]',
|
|
275
372
|
exportAs: 'rdxDialogBackdrop',
|
|
276
373
|
host: {
|
|
374
|
+
role: 'presentation',
|
|
375
|
+
'[hidden]': 'rootContext.nested && !forceRender()',
|
|
277
376
|
'[attr.data-closed]': 'rootContext.isOpen() ? undefined : ""',
|
|
278
377
|
'[attr.data-ending-style]': 'rootContext.transitionStatus() === "ending" ? "" : undefined',
|
|
279
378
|
'[attr.data-open]': 'rootContext.isOpen() ? "" : undefined',
|
|
@@ -283,7 +382,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
283
382
|
'[attr.data-nested-dialog-open]': 'rootContext.nestedDialogOpen() ? "" : undefined'
|
|
284
383
|
}
|
|
285
384
|
}]
|
|
286
|
-
}] });
|
|
385
|
+
}], ctorParameters: () => [], propDecorators: { forceRender: [{ type: i0.Input, args: [{ isSignal: true, alias: "forceRender", required: false }] }] } });
|
|
287
386
|
|
|
288
387
|
/**
|
|
289
388
|
* A button that closes the dialog.
|
|
@@ -331,117 +430,170 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
331
430
|
}]
|
|
332
431
|
}], ctorParameters: () => [] });
|
|
333
432
|
|
|
433
|
+
/** Composite navigation keys a Dialog popup keeps to itself, so they never reach an enclosing Menu / Composite. */
|
|
434
|
+
const COMPOSITE_KEYS = new Set(['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Home', 'End']);
|
|
435
|
+
const DIALOG_INTERNAL_BACKDROP_ATTR = 'data-rdx-dialog-internal-backdrop';
|
|
334
436
|
/**
|
|
335
437
|
* A container for the dialog contents.
|
|
438
|
+
*
|
|
439
|
+
* **ADR 0015/0017 Phase-4 migration — Dialog is the PILOT cutover onto the new floating dismissal +
|
|
440
|
+
* focus engine. Browser-verified** by `apps/visual-regression/tests/dialog.behavior.spec.ts` (trap,
|
|
441
|
+
* initial / return focus, Escape / outside-press / focus-out dismissal, nested-Escape deepest-first,
|
|
442
|
+
* backdrop-not-marked).
|
|
443
|
+
*
|
|
444
|
+
* **Mapping (legacy → new):**
|
|
445
|
+
* - `RdxDismissableLayer` (legacy) → `RdxFloatingNodeRegistration` (registers the tree node) +
|
|
446
|
+
* `RdxDismiss` (Escape / outside-press; reads the root context + node).
|
|
447
|
+
* - `RdxFocusScope` (direct) → `RdxFloatingFocusManager` (composes the reworked focus scope; trap +
|
|
448
|
+
* markOthers + close-on-focus-out), driven by `provideFloatingFocusManagerConfig`.
|
|
449
|
+
* - `disableOutsidePointerEvents` → the focus manager's `inert` pass marks outside elements
|
|
450
|
+
* non-interactive for a modal (finding #4), scoped to siblings of the popup's ancestor chain instead
|
|
451
|
+
* of a global `body { pointer-events: none }` lock — so the popup needs no `pointer-events: auto`.
|
|
452
|
+
* - focus-out close moved from the dismissal capability (`focusOutside: () => false`) to the manager
|
|
453
|
+
* (`manager.focusOut`), per ADR 0017 §3.
|
|
454
|
+
* - `isEventOnTrigger` preventDefault → removed: the trigger is in `context.triggers`, so the engine
|
|
455
|
+
* treats a press/focus on it as **inside** (no close-then-reopen).
|
|
456
|
+
*
|
|
457
|
+
* **Parity notes:**
|
|
458
|
+
* - **Lifecycle split (resolved 2026-06-16):** `enabled` is `open || transitionStatus === 'ending'`, so
|
|
459
|
+
* the trap / return-focus machinery survives the exit animation, while the manager's marker + isolation
|
|
460
|
+
* passes additionally key off `open` and release at close-start (Base UI `markOthers` gating).
|
|
461
|
+
* - **`trap-focus` split:** the manager traps focus for `modal === true` and `'trap-focus'`, but applies
|
|
462
|
+
* real `inert` isolation only for `modal === true`, matching Base UI's public contract that
|
|
463
|
+
* `modal="trap-focus"` leaves outside pointer interaction enabled.
|
|
464
|
+
* - **`returnFocus` orchestration (resolved 2026-06-16):** the manager now owns the return-focus *target*
|
|
465
|
+
* via the focus scope's `returnFocus` config seam (the scope owns the *timing* — its queued post-unmount
|
|
466
|
+
* frame). Dialog leaves it at the default (`returnFocus: true` → return to the element focused before
|
|
467
|
+
* open), so behavior is unchanged; a consumer can now also pass `false` / an element / a callback.
|
|
336
468
|
*/
|
|
337
469
|
class RdxDialogPopup {
|
|
338
470
|
constructor() {
|
|
339
471
|
this.rootContext = injectRdxDialogRootContext();
|
|
340
|
-
this.
|
|
472
|
+
this.host = inject(ElementRef).nativeElement;
|
|
473
|
+
this.floatingContext = inject(RDX_FLOATING_ROOT_CONTEXT);
|
|
474
|
+
this.registration = inject(RDX_FLOATING_REGISTRATION, { optional: true });
|
|
475
|
+
this.focusManager = inject(RdxFloatingFocusManager);
|
|
341
476
|
this.focusScope = inject(RdxFocusScope);
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
/**
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
this.
|
|
350
|
-
/**
|
|
351
|
-
* Event handler called when a pointerdown event happens outside of the popup. Can be prevented.
|
|
352
|
-
*/
|
|
353
|
-
this.pointerDownOutside = outputFromObservable(outputToObservable(this.dismissableLayer.pointerDownOutside));
|
|
354
|
-
/**
|
|
355
|
-
* Event handler called when focus moves outside of the popup. Can be prevented.
|
|
356
|
-
*/
|
|
357
|
-
this.focusOutside = outputFromObservable(outputToObservable(this.dismissableLayer.focusOutside));
|
|
358
|
-
/**
|
|
359
|
-
* Event handler called when an interaction happens outside of the popup. Can be prevented.
|
|
360
|
-
*/
|
|
361
|
-
this.interactOutside = outputFromObservable(outputToObservable(this.dismissableLayer.interactOutside));
|
|
362
|
-
/**
|
|
363
|
-
* Event handler called before focus moves into the popup. Can be prevented.
|
|
364
|
-
*/
|
|
477
|
+
/** Event handler called when the escape key is down. Can be prevented. */
|
|
478
|
+
this.escapeKeyDown = output();
|
|
479
|
+
/** Event handler called when a pointerdown event happens outside of the popup. Can be prevented. */
|
|
480
|
+
this.pointerDownOutside = output();
|
|
481
|
+
/** Event handler called when focus moves outside of the popup. Can be prevented. */
|
|
482
|
+
this.focusOutside = output();
|
|
483
|
+
/** Event handler called when an interaction (pointer / focus) happens outside of the popup. */
|
|
484
|
+
this.interactOutside = output();
|
|
485
|
+
/** Event handler called before focus moves into the popup. Can be prevented. */
|
|
365
486
|
this.openAutoFocus = outputFromObservable(outputToObservable(this.focusScope.mountAutoFocus));
|
|
366
|
-
/**
|
|
367
|
-
* Event handler called before focus returns after the popup is removed. Can be prevented.
|
|
368
|
-
*/
|
|
487
|
+
/** Event handler called before focus returns after the popup is removed. Can be prevented. */
|
|
369
488
|
this.closeAutoFocus = outputFromObservable(outputToObservable(this.focusScope.unmountAutoFocus));
|
|
370
|
-
//
|
|
371
|
-
|
|
372
|
-
//
|
|
373
|
-
//
|
|
374
|
-
//
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
const unregisterTransitionElement = this.rootContext.registerTransitionElement(inject(ElementRef).nativeElement);
|
|
378
|
-
inject(DestroyRef).onDestroy(unregisterTransitionElement);
|
|
379
|
-
this.dismissableLayer.pointerDownOutside.subscribe((event) => {
|
|
380
|
-
this.dismissDetails = { reason: 'outside-press', event };
|
|
381
|
-
// A pointerdown on the trigger is an "outside" press relative to the portaled popup.
|
|
382
|
-
// Let the trigger's own click toggle the dialog instead of dismissing here (which would
|
|
383
|
-
// close and then immediately reopen).
|
|
384
|
-
if (this.isEventOnTrigger(event)) {
|
|
385
|
-
event.preventDefault();
|
|
386
|
-
}
|
|
489
|
+
// The popup element is this layer's floating element (inside-surface for containment checks).
|
|
490
|
+
this.floatingContext.setFloatingElement(this.host);
|
|
491
|
+
// Scroll lock follows Base UI (`open && modal === true`): released at close-start so the page is
|
|
492
|
+
// scrollable again as the exit animation plays. Background pointer/AT isolation is no longer a
|
|
493
|
+
// global body lock — the focus manager applies real `inert` to outside elements (finding #4).
|
|
494
|
+
useScrollLock(computed(() => this.rootContext.modal() === true && this.rootContext.isOpen()), {
|
|
495
|
+
referenceElement: () => this.host
|
|
387
496
|
});
|
|
388
|
-
this.
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
497
|
+
const unregisterTransitionElement = this.rootContext.registerTransitionElement(this.host);
|
|
498
|
+
inject(DestroyRef).onDestroy(unregisterTransitionElement);
|
|
499
|
+
// Base UI always renders an internal backdrop for a fully modal dialog. It is invisible and exists
|
|
500
|
+
// even when consumers also render `rdxDialogBackdrop`: outside pointer events land on this owned
|
|
501
|
+
// target instead of being swallowed by inert page content.
|
|
502
|
+
const injector = inject(Injector);
|
|
503
|
+
afterNextRender(() => setupInternalBackdrop(this.host, injector, {
|
|
504
|
+
marker: DIALOG_INTERNAL_BACKDROP_ATTR,
|
|
505
|
+
isOpen: () => this.rootContext.isOpen(),
|
|
506
|
+
shouldRender: () => this.rootContext.modal() === true,
|
|
507
|
+
cutout: () => this.host.closest('[rdxDialogViewport]'),
|
|
508
|
+
passThrough: () => this.host.closest('[rdxDialogViewport]') !== null
|
|
509
|
+
}));
|
|
510
|
+
// Dismissal (Base UI Dialog outside-press policy): Escape always closes; an outside press closes
|
|
511
|
+
// only the **topmost** dialog (a parent with an open nested dialog never self-closes) and only when
|
|
512
|
+
// pointer dismissal is enabled. A fully modal dialog uses the internal backdrop and intentional
|
|
513
|
+
// outside-press timing (click, not pointerdown). Focus-out is owned by the focus manager (below).
|
|
514
|
+
new RdxDismiss(this.floatingContext, () => this.registration?.node() ?? null, {
|
|
515
|
+
escapeKey: () => true,
|
|
516
|
+
outsidePress: () => this.isTopmost() && !this.rootContext.disablePointerDismissal(),
|
|
517
|
+
outsidePressEvent: () => (this.rootContext.modal() === true ? 'intentional' : 'sloppy'),
|
|
518
|
+
focusOutside: () => false,
|
|
519
|
+
onEscapeKeyDown: (event) => this.escapeKeyDown.emit(event),
|
|
520
|
+
onPointerDownOutside: (event) => {
|
|
521
|
+
this.pointerDownOutside.emit(event);
|
|
522
|
+
this.interactOutside.emit(event);
|
|
523
|
+
},
|
|
524
|
+
onDismiss: (reason, event) => {
|
|
525
|
+
this.rootContext.close(reason === 'escape-key' ? 'escape-key' : 'outside-press', event);
|
|
392
526
|
}
|
|
393
527
|
});
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
// Escape always closes (standard a11y behavior).
|
|
402
|
-
if ((reason === 'outside-press' || reason === 'focus-out') && this.rootContext.disablePointerDismissal()) {
|
|
403
|
-
return;
|
|
528
|
+
// Focus-out close (ADR 0017 §3) — the manager emits when focus leaves a non-modal dialog to an
|
|
529
|
+
// unrelated node; re-expose as `focusOutside` (preventable) and close unless vetoed.
|
|
530
|
+
this.focusManager.focusOut.subscribe((event) => {
|
|
531
|
+
this.focusOutside.emit(event);
|
|
532
|
+
this.interactOutside.emit(event);
|
|
533
|
+
if (!event.defaultPrevented) {
|
|
534
|
+
this.rootContext.close('focus-out', event);
|
|
404
535
|
}
|
|
405
|
-
this.rootContext.close(reason, event);
|
|
406
536
|
});
|
|
407
537
|
}
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
return
|
|
538
|
+
/** This dialog is the topmost (deepest open) one — it has no open nested dialog above it. */
|
|
539
|
+
isTopmost() {
|
|
540
|
+
return this.rootContext.isOpen() && !this.rootContext.nestedDialogOpen();
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* Composite navigation keys (arrows / Home / End) are kept inside the dialog (Base UI `DialogPopup`):
|
|
544
|
+
* a dialog opened from inside a Menu / Menubar / Composite must not let an arrow press bubble out and
|
|
545
|
+
* move the outer collection's active item.
|
|
546
|
+
*/
|
|
547
|
+
onKeyDown(event) {
|
|
548
|
+
if (COMPOSITE_KEYS.has(event.key)) {
|
|
549
|
+
event.stopPropagation();
|
|
550
|
+
}
|
|
411
551
|
}
|
|
412
552
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxDialogPopup, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
413
|
-
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: [
|
|
414
|
-
|
|
553
|
+
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: [
|
|
554
|
+
provideFloatingFocusManagerConfig(() => {
|
|
415
555
|
const rootContext = injectRdxDialogRootContext();
|
|
416
556
|
return {
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
557
|
+
// Trap for a modal or trap-focus dialog (Base UI `modal={modal !== false}`).
|
|
558
|
+
modal: () => rootContext.modal() === true || rootContext.modal() === 'trap-focus',
|
|
559
|
+
// Full modal blocks outside pointer interaction; `trap-focus` only traps focus.
|
|
560
|
+
inert: () => rootContext.modal() === true,
|
|
561
|
+
// Active for the whole MOUNTED lifetime — including an explicit
|
|
562
|
+
// `preventUnmountOnClose()` cycle after the exit transition — matching Base UI's
|
|
563
|
+
// `FloatingFocusManager disabled={!mounted}` (NOT `open`) for trap/return-focus.
|
|
564
|
+
// Marker + isolation are additionally gated on `open` inside the manager.
|
|
565
|
+
enabled: () => rootContext.present(),
|
|
566
|
+
restoreFocus: () => 'popup',
|
|
567
|
+
openInteractionType: () => rootContext.openInteractionType(),
|
|
568
|
+
closeInteractionType: () => rootContext.closeInteractionType(),
|
|
569
|
+
closeOnFocusOut: () => !rootContext.disablePointerDismissal()
|
|
424
570
|
};
|
|
425
571
|
})
|
|
426
|
-
], exportAs: ["rdxDialogPopup"], hostDirectives: [{ directive: i1.
|
|
572
|
+
], exportAs: ["rdxDialogPopup"], hostDirectives: [{ directive: i1.RdxFloatingNodeRegistration }, { directive: i2.RdxFloatingFocusManager }], ngImport: i0 }); }
|
|
427
573
|
}
|
|
428
574
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxDialogPopup, decorators: [{
|
|
429
575
|
type: Directive,
|
|
430
576
|
args: [{
|
|
431
577
|
selector: '[rdxDialogPopup]',
|
|
432
578
|
exportAs: 'rdxDialogPopup',
|
|
433
|
-
hostDirectives: [
|
|
579
|
+
hostDirectives: [RdxFloatingNodeRegistration, RdxFloatingFocusManager],
|
|
434
580
|
providers: [
|
|
435
|
-
|
|
436
|
-
const rootContext = injectRdxDialogRootContext();
|
|
437
|
-
return {
|
|
438
|
-
disableOutsidePointerEvents: computed(() => rootContext.modal() === true)
|
|
439
|
-
};
|
|
440
|
-
}),
|
|
441
|
-
provideRdxFocusScopeConfig(() => {
|
|
581
|
+
provideFloatingFocusManagerConfig(() => {
|
|
442
582
|
const rootContext = injectRdxDialogRootContext();
|
|
443
583
|
return {
|
|
444
|
-
|
|
584
|
+
// Trap for a modal or trap-focus dialog (Base UI `modal={modal !== false}`).
|
|
585
|
+
modal: () => rootContext.modal() === true || rootContext.modal() === 'trap-focus',
|
|
586
|
+
// Full modal blocks outside pointer interaction; `trap-focus` only traps focus.
|
|
587
|
+
inert: () => rootContext.modal() === true,
|
|
588
|
+
// Active for the whole MOUNTED lifetime — including an explicit
|
|
589
|
+
// `preventUnmountOnClose()` cycle after the exit transition — matching Base UI's
|
|
590
|
+
// `FloatingFocusManager disabled={!mounted}` (NOT `open`) for trap/return-focus.
|
|
591
|
+
// Marker + isolation are additionally gated on `open` inside the manager.
|
|
592
|
+
enabled: () => rootContext.present(),
|
|
593
|
+
restoreFocus: () => 'popup',
|
|
594
|
+
openInteractionType: () => rootContext.openInteractionType(),
|
|
595
|
+
closeInteractionType: () => rootContext.closeInteractionType(),
|
|
596
|
+
closeOnFocusOut: () => !rootContext.disablePointerDismissal()
|
|
445
597
|
};
|
|
446
598
|
})
|
|
447
599
|
],
|
|
@@ -457,7 +609,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
457
609
|
'[attr.data-state]': 'rootContext.isOpen() ? "open" : "closed"',
|
|
458
610
|
'[attr.data-nested]': 'rootContext.nested ? "" : undefined',
|
|
459
611
|
'[attr.data-nested-dialog-open]': 'rootContext.nestedDialogOpen() ? "" : undefined',
|
|
460
|
-
'[id]': 'rootContext.contentId'
|
|
612
|
+
'[id]': 'rootContext.contentId',
|
|
613
|
+
'(keydown)': 'onKeyDown($event)'
|
|
461
614
|
}
|
|
462
615
|
}]
|
|
463
616
|
}], 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"] }] } });
|
|
@@ -480,7 +633,7 @@ class RdxDialogPortal {
|
|
|
480
633
|
this.container = input(...(ngDevMode ? [undefined, { debugName: "container" }] : /* istanbul ignore next */ []));
|
|
481
634
|
}
|
|
482
635
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxDialogPortal, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
483
|
-
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().
|
|
636
|
+
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 }); }
|
|
484
637
|
}
|
|
485
638
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxDialogPortal, decorators: [{
|
|
486
639
|
type: Directive,
|
|
@@ -488,7 +641,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
488
641
|
selector: 'ng-template[rdxDialogPortal]',
|
|
489
642
|
exportAs: 'rdxDialogPortal',
|
|
490
643
|
hostDirectives: [{ directive: RdxPortalPresence, inputs: ['container'] }],
|
|
491
|
-
providers: [provideRdxPresenceContext(() => ({ present: injectRdxDialogRootContext().
|
|
644
|
+
providers: [provideRdxPresenceContext(() => ({ present: injectRdxDialogRootContext().present }))]
|
|
492
645
|
}]
|
|
493
646
|
}], propDecorators: { container: [{ type: i0.Input, args: [{ isSignal: true, alias: "container", required: false }] }] } });
|
|
494
647
|
/**
|
|
@@ -499,9 +652,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
499
652
|
class RdxDialogPortalMisuseGuard {
|
|
500
653
|
constructor() {
|
|
501
654
|
if (isDevMode()) {
|
|
502
|
-
|
|
655
|
+
rdxDevError('dialog/portal-on-element', '`rdxDialogPortal` is now a structural directive. ' +
|
|
503
656
|
'Use `<ng-template rdxDialogPortal>` around the backdrop and popup. ' +
|
|
504
|
-
'rdxDialogPortalPresence has been removed.
|
|
657
|
+
'rdxDialogPortalPresence has been removed.', 'components/dialog');
|
|
505
658
|
}
|
|
506
659
|
}
|
|
507
660
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxDialogPortalMisuseGuard, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
@@ -565,6 +718,13 @@ class RdxDialogTrigger {
|
|
|
565
718
|
this.triggerId = computed(() => this.id() ?? this.generatedId, ...(ngDevMode ? [{ debugName: "triggerId" }] : /* istanbul ignore next */ []));
|
|
566
719
|
this.rootContext = computed(() => this.handle()?.context() ?? this.parentRootContext, ...(ngDevMode ? [{ debugName: "rootContext" }] : /* istanbul ignore next */ []));
|
|
567
720
|
this.isOpen = computed(() => this.rootContext()?.isOpen() === true && this.rootContext()?.trigger() === this.elementRef.nativeElement, ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
|
|
721
|
+
this.triggerInteraction = createRdxTriggerInteraction({
|
|
722
|
+
trigger: () => this.elementRef.nativeElement,
|
|
723
|
+
activeTrigger: () => this.rootContext()?.trigger(),
|
|
724
|
+
open: () => this.rootContext()?.isOpen() ?? false,
|
|
725
|
+
disabled: () => this.disabled(),
|
|
726
|
+
contentId: () => this.rootContext()?.contentId
|
|
727
|
+
});
|
|
568
728
|
effect((onCleanup) => {
|
|
569
729
|
const handle = this.handle();
|
|
570
730
|
if (handle) {
|
|
@@ -574,11 +734,17 @@ class RdxDialogTrigger {
|
|
|
574
734
|
onCleanup(untracked(() => this.parentRootContext.registerTrigger(this.triggerId(), this.elementRef.nativeElement, () => this.payload())));
|
|
575
735
|
}
|
|
576
736
|
});
|
|
737
|
+
useTriggerFocusGuardAnchor({
|
|
738
|
+
trigger: () => this.elementRef.nativeElement,
|
|
739
|
+
contentId: () => this.rootContext()?.contentId,
|
|
740
|
+
enabled: () => this.triggerInteraction.isActive()
|
|
741
|
+
});
|
|
577
742
|
}
|
|
578
743
|
handleClick(event) {
|
|
579
744
|
if (this.disabled()) {
|
|
580
745
|
return;
|
|
581
746
|
}
|
|
747
|
+
this.rootContext()?.setTriggerOpenInteractionType(this.triggerInteraction.clickInteractionType(event));
|
|
582
748
|
if (this.handle()) {
|
|
583
749
|
this.handle().toggle(this.triggerId(), event);
|
|
584
750
|
}
|
|
@@ -586,8 +752,11 @@ class RdxDialogTrigger {
|
|
|
586
752
|
this.parentRootContext?.toggle(this.triggerId(), this.elementRef.nativeElement, this.payload(), event);
|
|
587
753
|
}
|
|
588
754
|
}
|
|
755
|
+
handlePointerDown(event) {
|
|
756
|
+
this.triggerInteraction.recordPointerDown(event);
|
|
757
|
+
}
|
|
589
758
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxDialogTrigger, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
590
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxDialogTrigger, isStandalone: true, selector: "button[rdxDialogTrigger]", inputs: { handle: { classPropertyName: "handle", publicName: "handle", isSignal: true, isRequired: false, transformFunction: null }, payload: { classPropertyName: "payload", publicName: "payload", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "type": "button" }, listeners: { "click": "handleClick($event)" }, properties: { "attr.aria-haspopup": "\"dialog\"", "attr.aria-controls": "rootContext()?.contentId", "attr.aria-expanded": "
|
|
759
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxDialogTrigger, isStandalone: true, selector: "button[rdxDialogTrigger]", inputs: { handle: { classPropertyName: "handle", publicName: "handle", isSignal: true, isRequired: false, transformFunction: null }, payload: { classPropertyName: "payload", publicName: "payload", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "type": "button" }, listeners: { "click": "handleClick($event)", "pointerdown": "handlePointerDown($event)" }, properties: { "attr.aria-haspopup": "\"dialog\"", "attr.aria-controls": "rootContext()?.contentId", "attr.aria-expanded": "triggerInteraction.ariaExpanded()", "attr.data-state": "triggerInteraction.dataState()", "attr.data-popup-open": "triggerInteraction.dataPopupOpen()", "attr.disabled": "triggerInteraction.disabled() ? \"\" : undefined", "id": "triggerId()" } }, exportAs: ["rdxDialogTrigger"], ngImport: i0 }); }
|
|
591
760
|
}
|
|
592
761
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxDialogTrigger, decorators: [{
|
|
593
762
|
type: Directive,
|
|
@@ -598,12 +767,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
598
767
|
type: 'button',
|
|
599
768
|
'[attr.aria-haspopup]': '"dialog"',
|
|
600
769
|
'[attr.aria-controls]': 'rootContext()?.contentId',
|
|
601
|
-
'[attr.aria-expanded]': '
|
|
602
|
-
'[attr.data-state]': '
|
|
603
|
-
'[attr.data-popup-open]': '
|
|
604
|
-
'[attr.disabled]': 'disabled() ? "" : undefined',
|
|
770
|
+
'[attr.aria-expanded]': 'triggerInteraction.ariaExpanded()',
|
|
771
|
+
'[attr.data-state]': 'triggerInteraction.dataState()',
|
|
772
|
+
'[attr.data-popup-open]': 'triggerInteraction.dataPopupOpen()',
|
|
773
|
+
'[attr.disabled]': 'triggerInteraction.disabled() ? "" : undefined',
|
|
605
774
|
'[id]': 'triggerId()',
|
|
606
|
-
'(click)': 'handleClick($event)'
|
|
775
|
+
'(click)': 'handleClick($event)',
|
|
776
|
+
'(pointerdown)': 'handlePointerDown($event)'
|
|
607
777
|
}
|
|
608
778
|
}]
|
|
609
779
|
}], ctorParameters: () => [], propDecorators: { handle: [{ type: i0.Input, args: [{ isSignal: true, alias: "handle", required: false }] }], payload: [{ type: i0.Input, args: [{ isSignal: true, alias: "payload", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }] } });
|