@almoamendev/ngx-md3 0.0.16 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -10,6 +10,8 @@ import { ComponentPortal, CdkPortalOutlet } from '@angular/cdk/portal';
|
|
|
10
10
|
import * as i2 from '@angular/cdk/scrolling';
|
|
11
11
|
import { CdkScrollable } from '@angular/cdk/scrolling';
|
|
12
12
|
import { FormControlName } from '@angular/forms';
|
|
13
|
+
import { CdkDialogContainer, Dialog as Dialog$1 } from '@angular/cdk/dialog';
|
|
14
|
+
import { hasModifierKey } from '@angular/cdk/keycodes';
|
|
13
15
|
import { Overlay, OverlayConfig } from '@angular/cdk/overlay';
|
|
14
16
|
|
|
15
17
|
class TypeDisplay {
|
|
@@ -3087,42 +3089,139 @@ const DIALOG_DATA = new InjectionToken('MD3_DIALOG_DATA');
|
|
|
3087
3089
|
const DIALOG_CONFIG = new InjectionToken('MD3_DIALOG_CONFIG');
|
|
3088
3090
|
const DIALOG_COMPONENT = new InjectionToken('MD3_DIALOG_COMPONENT');
|
|
3089
3091
|
const DIALOG_EXIT_ANIMATION_FALLBACK_MS = 300;
|
|
3092
|
+
const HIDDEN_CLASS = 'md3-dialog-hidden';
|
|
3090
3093
|
class DialogRef {
|
|
3091
|
-
|
|
3092
|
-
previouslyFocusedElement;
|
|
3094
|
+
cdkRef;
|
|
3093
3095
|
closed = new Subject();
|
|
3094
|
-
|
|
3096
|
+
closing = new Subject();
|
|
3097
|
+
closePromise = null;
|
|
3098
|
+
closeStarted = false;
|
|
3099
|
+
closeSettled = false;
|
|
3100
|
+
hidden = false;
|
|
3095
3101
|
dialogInstance;
|
|
3096
3102
|
/**
|
|
3097
3103
|
* Filled by DialogService after the user component is attached. Keeping the
|
|
3098
3104
|
* instance here lets callers imperatively update inputs when that is useful.
|
|
3099
3105
|
*/
|
|
3100
3106
|
componentInstance;
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
this.
|
|
3107
|
+
/** Overlay hosting the dialog, useful to reach the panel and the scrim. */
|
|
3108
|
+
get overlayRef() {
|
|
3109
|
+
return this.cdkRef.overlayRef;
|
|
3110
|
+
}
|
|
3111
|
+
/** Whether the dialog started closing. Such a dialog cannot be hidden or shown anymore. */
|
|
3112
|
+
get isClosing() {
|
|
3113
|
+
return this.closeStarted;
|
|
3114
|
+
}
|
|
3115
|
+
/** Whether the dialog is currently hidden behind another dialog. */
|
|
3116
|
+
get isHidden() {
|
|
3117
|
+
return this.hidden;
|
|
3118
|
+
}
|
|
3119
|
+
constructor(cdkRef, disableCloseEvents) {
|
|
3120
|
+
this.cdkRef = cdkRef;
|
|
3121
|
+
// Closing always goes through this reference so the exit animation can
|
|
3122
|
+
// run before the CDK disposes the overlay. Detachments triggered from
|
|
3123
|
+
// the outside (navigation, scroll strategy) still settle this ref.
|
|
3124
|
+
this.cdkRef.closed.pipe(take(1)).subscribe((result) => this.settle(result));
|
|
3125
|
+
if (!disableCloseEvents) {
|
|
3126
|
+
this.connectCloseEvents();
|
|
3127
|
+
}
|
|
3104
3128
|
}
|
|
3129
|
+
/** Closes the dialog. The promise resolves once the exit animation is done. */
|
|
3105
3130
|
close(result) {
|
|
3106
|
-
if (this.
|
|
3131
|
+
if (this.closePromise) {
|
|
3132
|
+
return this.closePromise;
|
|
3133
|
+
}
|
|
3134
|
+
this.startClosing();
|
|
3135
|
+
this.closePromise = this.startCloseAnimation().then(() => this.cdkRef.close(result));
|
|
3136
|
+
return this.closePromise;
|
|
3137
|
+
}
|
|
3138
|
+
/**
|
|
3139
|
+
* Hides the dialog with the closing animation while keeping it alive, so
|
|
3140
|
+
* its content, form state and subscriptions survive until show() is called.
|
|
3141
|
+
* The promise resolves once the dialog is out of sight.
|
|
3142
|
+
*/
|
|
3143
|
+
hide() {
|
|
3144
|
+
if (this.hidden || this.closeStarted) {
|
|
3145
|
+
return Promise.resolve();
|
|
3146
|
+
}
|
|
3147
|
+
this.hidden = true;
|
|
3148
|
+
this.toggleHiddenState(true);
|
|
3149
|
+
this.dialogInstance?.isActive.set(false);
|
|
3150
|
+
return this.waitForSurfaceAnimation();
|
|
3151
|
+
}
|
|
3152
|
+
/**
|
|
3153
|
+
* Brings a hidden dialog back with the opening animation. Focus moves back
|
|
3154
|
+
* into the dialog even when it was never hidden, which is what makes it
|
|
3155
|
+
* usable again once the dialog above it closes.
|
|
3156
|
+
*/
|
|
3157
|
+
show() {
|
|
3158
|
+
if (this.closeStarted) {
|
|
3107
3159
|
return;
|
|
3108
3160
|
}
|
|
3109
|
-
this.
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3161
|
+
if (this.hidden) {
|
|
3162
|
+
this.hidden = false;
|
|
3163
|
+
this.toggleHiddenState(false);
|
|
3164
|
+
this.dialogInstance?.isActive.set(true);
|
|
3165
|
+
}
|
|
3166
|
+
this.dialogInstance?._recaptureFocus();
|
|
3167
|
+
}
|
|
3168
|
+
/** Emits when the dialog starts closing, before the exit animation runs. */
|
|
3169
|
+
beforeClosed() {
|
|
3170
|
+
return this.closing.asObservable();
|
|
3171
|
+
}
|
|
3172
|
+
afterClosed() {
|
|
3173
|
+
return this.closed.asObservable();
|
|
3174
|
+
}
|
|
3175
|
+
connectCloseEvents() {
|
|
3176
|
+
// The CDK config keeps `disableClose` on, so the scrim and Escape key
|
|
3177
|
+
// are handled here instead and the dialog can animate out.
|
|
3178
|
+
this.cdkRef.backdropClick.pipe(take(1)).subscribe(() => {
|
|
3179
|
+
this.cdkRef.containerInstance._closeInteractionType = 'mouse';
|
|
3180
|
+
this.close();
|
|
3181
|
+
});
|
|
3182
|
+
this.cdkRef.keydownEvents.pipe(filter((event) => event.key === 'Escape' && !hasModifierKey(event)), take(1)).subscribe((event) => {
|
|
3183
|
+
event.preventDefault();
|
|
3184
|
+
this.cdkRef.containerInstance._closeInteractionType = 'keyboard';
|
|
3185
|
+
this.close();
|
|
3117
3186
|
});
|
|
3118
3187
|
}
|
|
3188
|
+
toggleHiddenState(isHidden) {
|
|
3189
|
+
const overlayRef = this.cdkRef.overlayRef;
|
|
3190
|
+
const panel = overlayRef.overlayElement;
|
|
3191
|
+
const backdrop = overlayRef.backdropElement;
|
|
3192
|
+
panel.classList.toggle(HIDDEN_CLASS, isHidden);
|
|
3193
|
+
backdrop?.classList.toggle(HIDDEN_CLASS, isHidden);
|
|
3194
|
+
// `inert` keeps a hidden dialog out of the tab order and away from
|
|
3195
|
+
// assistive technology without removing it from the DOM.
|
|
3196
|
+
if (isHidden) {
|
|
3197
|
+
panel.setAttribute('inert', '');
|
|
3198
|
+
}
|
|
3199
|
+
else {
|
|
3200
|
+
panel.removeAttribute('inert');
|
|
3201
|
+
}
|
|
3202
|
+
}
|
|
3203
|
+
startClosing() {
|
|
3204
|
+
if (this.closeStarted) {
|
|
3205
|
+
return;
|
|
3206
|
+
}
|
|
3207
|
+
this.closeStarted = true;
|
|
3208
|
+
this.closing.next();
|
|
3209
|
+
this.closing.complete();
|
|
3210
|
+
}
|
|
3119
3211
|
startCloseAnimation() {
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
backdrop?.classList.add('md3-dialog-closing');
|
|
3212
|
+
const overlayRef = this.cdkRef.overlayRef;
|
|
3213
|
+
const wasVisible = !this.hidden;
|
|
3214
|
+
overlayRef.overlayElement.classList.add('md3-dialog-closing');
|
|
3215
|
+
overlayRef.backdropElement?.classList.add('md3-dialog-closing');
|
|
3125
3216
|
this.dialogInstance?.isActive.set(false);
|
|
3217
|
+
// A hidden dialog already played the exit animation, so there is
|
|
3218
|
+
// nothing left to transition and it can be disposed right away.
|
|
3219
|
+
return wasVisible ? this.waitForSurfaceAnimation() : Promise.resolve();
|
|
3220
|
+
}
|
|
3221
|
+
/** Resolves when the surface finished animating, with a timeout as a safety net. */
|
|
3222
|
+
waitForSurfaceAnimation() {
|
|
3223
|
+
const panel = this.cdkRef.overlayRef.overlayElement;
|
|
3224
|
+
const surface = panel.querySelector('.md3-dialog-container') ?? panel;
|
|
3126
3225
|
return new Promise((resolve) => {
|
|
3127
3226
|
let isResolved = false;
|
|
3128
3227
|
let timeoutId = setTimeout(done, DIALOG_EXIT_ANIMATION_FALLBACK_MS);
|
|
@@ -3132,82 +3231,182 @@ class DialogRef {
|
|
|
3132
3231
|
}
|
|
3133
3232
|
isResolved = true;
|
|
3134
3233
|
clearTimeout(timeoutId);
|
|
3135
|
-
|
|
3234
|
+
surface.removeEventListener('transitionend', onTransitionEnd);
|
|
3136
3235
|
resolve();
|
|
3137
3236
|
}
|
|
3138
3237
|
function onTransitionEnd(event) {
|
|
3139
|
-
if (event.target ===
|
|
3238
|
+
if (event.target === surface) {
|
|
3140
3239
|
done();
|
|
3141
3240
|
}
|
|
3142
3241
|
}
|
|
3143
|
-
|
|
3242
|
+
surface.addEventListener('transitionend', onTransitionEnd);
|
|
3144
3243
|
});
|
|
3145
3244
|
}
|
|
3146
|
-
|
|
3147
|
-
|
|
3245
|
+
/** Emits the result once the CDK reference is closed. Focus is restored by the container. */
|
|
3246
|
+
settle(result) {
|
|
3247
|
+
if (this.closeSettled) {
|
|
3248
|
+
return;
|
|
3249
|
+
}
|
|
3250
|
+
// Covers detachments that did not go through close(), so anything
|
|
3251
|
+
// waiting on beforeClosed() is still notified.
|
|
3252
|
+
this.startClosing();
|
|
3253
|
+
this.closeSettled = true;
|
|
3254
|
+
this.closed.next(result);
|
|
3255
|
+
this.closed.complete();
|
|
3148
3256
|
}
|
|
3149
3257
|
}
|
|
3150
3258
|
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3259
|
+
/**
|
|
3260
|
+
* Material 3 dialog shell. It extends the CDK dialog container, so focus
|
|
3261
|
+
* trapping, focus restoration and the ARIA attributes are handled by the CDK
|
|
3262
|
+
* while this component only owns the MD3 surface and its animation.
|
|
3263
|
+
*/
|
|
3264
|
+
class Dialog extends CdkDialogContainer {
|
|
3154
3265
|
isActive = signal(false, /* @ts-ignore */
|
|
3155
3266
|
...(ngDevMode ? [{ debugName: "isActive" }] : /* istanbul ignore next */ []));
|
|
3156
3267
|
config = inject(DIALOG_CONFIG, { optional: true }) ?? {};
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3268
|
+
/**
|
|
3269
|
+
* Started by DialogService, either right after the dialog is created or
|
|
3270
|
+
* once the dialog it replaces has finished animating out. The animation
|
|
3271
|
+
* runs on the next frame so the surface has a real from/to state.
|
|
3272
|
+
*/
|
|
3273
|
+
startEnterAnimation() {
|
|
3161
3274
|
requestAnimationFrame(() => {
|
|
3162
3275
|
this.isActive.set(true);
|
|
3163
3276
|
});
|
|
3164
3277
|
}
|
|
3165
3278
|
/**
|
|
3166
|
-
* The
|
|
3167
|
-
*
|
|
3279
|
+
* @deprecated The CDK dialog service attaches the content. Kept for
|
|
3280
|
+
* backwards compatibility and will be removed in a future release.
|
|
3168
3281
|
*/
|
|
3169
3282
|
attachContent(component, injector) {
|
|
3170
|
-
|
|
3171
|
-
return this.portalOutlet().attachComponentPortal(portal);
|
|
3283
|
+
return this.attachComponentPortal(new ComponentPortal(component, null, injector));
|
|
3172
3284
|
}
|
|
3173
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Dialog, deps:
|
|
3174
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "
|
|
3285
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Dialog, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
3286
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.0", type: Dialog, isStandalone: true, selector: "md3-dialog", usesInheritance: true, ngImport: i0, template: "<!-- The role and aria attributes live on the host element, applied by the CDK dialog container. -->\n<section\n class=\"md3-dialog-container\"\n [class.md3-active]=\"isActive()\"\n [class.md-scheme-dark]=\"config.scheme == 'dark'\"\n [class.md-scheme-light]=\"config.scheme == 'light'\"\n [attr.dir]=\"config.direction ?? null\">\n <ng-template cdkPortalOutlet></ng-template>\n</section>\n", styles: ["::ng-deep .cdk-overlay-container{position:fixed;z-index:1000;inset:0;pointer-events:none;font-size:1rem}::ng-deep .cdk-overlay-container .md3-dialog-scrim{position:absolute;inset:0;pointer-events:auto;background-color:rgb(var(--md-scheme-scrim));transition:all var(--md-motion-expressive-slow-spatial-duration) var(--md-motion-expressive-slow-spatial-easing);opacity:.32}::ng-deep .cdk-overlay-container .md3-dialog-scrim.md3-dialog-opening{opacity:0}::ng-deep .cdk-overlay-container .md3-dialog-scrim.md3-dialog-closing{opacity:0}::ng-deep .cdk-overlay-container .md3-dialog-scrim.md3-dialog-hidden{opacity:0}::ng-deep .cdk-overlay-container .md3-dialog-scrim.md3-dialog-scrim-kept{opacity:.32;transition:none}::ng-deep .cdk-overlay-container .md3-dialog-scrim.md3-dialog-scrim-instant{transition:none}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper{position:absolute;display:flex;inset:0;pointer-events:none;padding:3.5rem 1.5rem;overflow:hidden}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper .md3-dialog-panel{position:absolute;pointer-events:auto;max-width:35em}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper .md3-dialog-panel.md3-dialog-closing,::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper .md3-dialog-panel.md3-dialog-hidden{pointer-events:none}:host{display:block;max-width:100%;min-width:17.5em;outline:0}:host .md3-dialog-container{display:block;width:100%;max-height:inherit;overflow:auto;color:rgb(var(--md-scheme-on-surface));background-color:rgb(var(--md-scheme-surface-container-high));border-radius:var(--md-border-radius-xlarge);box-shadow:var(--md-shadow-6dp);transform-origin:center;transition:all var(--md-motion-expressive-default-spatial-duration) var(--md-motion-expressive-default-spatial-easing);opacity:0;transform:scaleY(.8);margin-top:-8rem}:host .md3-dialog-container.md3-active{opacity:1;transform:scaleY(1);margin-top:0}\n"], dependencies: [{ kind: "directive", type: CdkPortalOutlet, selector: "[cdkPortalOutlet]", inputs: ["cdkPortalOutlet"], outputs: ["attached"], exportAs: ["cdkPortalOutlet"] }] });
|
|
3175
3287
|
}
|
|
3176
3288
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Dialog, decorators: [{
|
|
3177
3289
|
type: Component,
|
|
3178
3290
|
args: [{ selector: 'md3-dialog', imports: [
|
|
3179
3291
|
CdkPortalOutlet,
|
|
3180
|
-
], template: "<section\n class=\"md3-dialog-container\"\n [class.md3-active]=\"isActive()\"\n [class.md-scheme-dark]=\"config.scheme == 'dark'\"\n [class.md-scheme-light]=\"config.scheme == 'light'\"\n [attr.dir]=\"config.direction ?? null\"
|
|
3181
|
-
}]
|
|
3292
|
+
], template: "<!-- The role and aria attributes live on the host element, applied by the CDK dialog container. -->\n<section\n class=\"md3-dialog-container\"\n [class.md3-active]=\"isActive()\"\n [class.md-scheme-dark]=\"config.scheme == 'dark'\"\n [class.md-scheme-light]=\"config.scheme == 'light'\"\n [attr.dir]=\"config.direction ?? null\">\n <ng-template cdkPortalOutlet></ng-template>\n</section>\n", styles: ["::ng-deep .cdk-overlay-container{position:fixed;z-index:1000;inset:0;pointer-events:none;font-size:1rem}::ng-deep .cdk-overlay-container .md3-dialog-scrim{position:absolute;inset:0;pointer-events:auto;background-color:rgb(var(--md-scheme-scrim));transition:all var(--md-motion-expressive-slow-spatial-duration) var(--md-motion-expressive-slow-spatial-easing);opacity:.32}::ng-deep .cdk-overlay-container .md3-dialog-scrim.md3-dialog-opening{opacity:0}::ng-deep .cdk-overlay-container .md3-dialog-scrim.md3-dialog-closing{opacity:0}::ng-deep .cdk-overlay-container .md3-dialog-scrim.md3-dialog-hidden{opacity:0}::ng-deep .cdk-overlay-container .md3-dialog-scrim.md3-dialog-scrim-kept{opacity:.32;transition:none}::ng-deep .cdk-overlay-container .md3-dialog-scrim.md3-dialog-scrim-instant{transition:none}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper{position:absolute;display:flex;inset:0;pointer-events:none;padding:3.5rem 1.5rem;overflow:hidden}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper .md3-dialog-panel{position:absolute;pointer-events:auto;max-width:35em}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper .md3-dialog-panel.md3-dialog-closing,::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper .md3-dialog-panel.md3-dialog-hidden{pointer-events:none}:host{display:block;max-width:100%;min-width:17.5em;outline:0}:host .md3-dialog-container{display:block;width:100%;max-height:inherit;overflow:auto;color:rgb(var(--md-scheme-on-surface));background-color:rgb(var(--md-scheme-surface-container-high));border-radius:var(--md-border-radius-xlarge);box-shadow:var(--md-shadow-6dp);transform-origin:center;transition:all var(--md-motion-expressive-default-spatial-duration) var(--md-motion-expressive-default-spatial-easing);opacity:0;transform:scaleY(.8);margin-top:-8rem}:host .md3-dialog-container.md3-active{opacity:1;transform:scaleY(1);margin-top:0}\n"] }]
|
|
3293
|
+
}] });
|
|
3182
3294
|
|
|
3295
|
+
const OPENING_CLASS = 'md3-dialog-opening';
|
|
3296
|
+
const SCRIM_KEPT_CLASS = 'md3-dialog-scrim-kept';
|
|
3297
|
+
const SCRIM_INSTANT_CLASS = 'md3-dialog-scrim-instant';
|
|
3298
|
+
/**
|
|
3299
|
+
* Head start given to a dialog that leaves the screen before the dialog taking
|
|
3300
|
+
* its place animates in. Long enough to read as one dialog replacing another,
|
|
3301
|
+
* short enough to still feel like a single gesture.
|
|
3302
|
+
*/
|
|
3303
|
+
const DIALOG_HANDOVER_DELAY_MS = 160;
|
|
3183
3304
|
class DialogService {
|
|
3305
|
+
cdkDialog = inject(Dialog$1);
|
|
3184
3306
|
overlay = inject(Overlay);
|
|
3185
3307
|
injector = inject(Injector);
|
|
3186
3308
|
document = inject(DOCUMENT);
|
|
3309
|
+
/** Open dialogs, from the first one opened to the one currently on top. */
|
|
3310
|
+
refs = [];
|
|
3311
|
+
/**
|
|
3312
|
+
* Page scrolling is blocked here instead of per overlay, so stacked dialogs
|
|
3313
|
+
* cannot unblock the page while another dialog is still open.
|
|
3314
|
+
*/
|
|
3315
|
+
scrollBlock = this.overlay.scrollStrategies.block();
|
|
3316
|
+
/** Element that was focused before the first dialog of the stack opened. */
|
|
3317
|
+
rootTrigger = null;
|
|
3318
|
+
get openDialogs() {
|
|
3319
|
+
return this.refs;
|
|
3320
|
+
}
|
|
3187
3321
|
open(component, config = {}) {
|
|
3188
3322
|
const dialogConfig = this.mergeConfig(config);
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
//
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3323
|
+
if (this.refs.length === 0) {
|
|
3324
|
+
this.rootTrigger = this.getFocusedElement();
|
|
3325
|
+
}
|
|
3326
|
+
// The dialog that is on screen starts leaving first, so the two dialogs
|
|
3327
|
+
// never animate on top of each other.
|
|
3328
|
+
const previousRef = this.topRef();
|
|
3329
|
+
if (previousRef) {
|
|
3330
|
+
this.dismissPrevious(previousRef, dialogConfig);
|
|
3331
|
+
}
|
|
3332
|
+
let dialogRef;
|
|
3333
|
+
const cdkRef = this.cdkDialog.open(component, {
|
|
3334
|
+
// The MD3 shell replaces the default CDK container, so the overlay,
|
|
3335
|
+
// focus trap and focus restoration come from the CDK while the shell
|
|
3336
|
+
// only renders the surface.
|
|
3337
|
+
container: {
|
|
3338
|
+
type: Dialog,
|
|
3339
|
+
providers: () => [
|
|
3340
|
+
{ provide: DIALOG_CONFIG, useValue: dialogConfig },
|
|
3341
|
+
],
|
|
3342
|
+
},
|
|
3343
|
+
// These tokens make the dialog controllable from the dynamic
|
|
3344
|
+
// component without coupling that component to the service.
|
|
3345
|
+
providers: (cdkDialogRef) => {
|
|
3346
|
+
dialogRef = new DialogRef(cdkDialogRef, dialogConfig.disableCloseEvents);
|
|
3347
|
+
return [
|
|
3348
|
+
{ provide: DialogRef, useValue: dialogRef },
|
|
3349
|
+
{ provide: DIALOG_DATA, useValue: dialogConfig.data },
|
|
3350
|
+
{ provide: DIALOG_CONFIG, useValue: dialogConfig },
|
|
3351
|
+
{ provide: DIALOG_COMPONENT, useValue: component },
|
|
3352
|
+
];
|
|
3353
|
+
},
|
|
3354
|
+
data: dialogConfig.data,
|
|
3355
|
+
role: dialogConfig.role,
|
|
3356
|
+
ariaModal: true,
|
|
3357
|
+
ariaLabel: dialogConfig.ariaLabel || null,
|
|
3358
|
+
ariaLabelledBy: dialogConfig.ariaLabelledBy || null,
|
|
3359
|
+
ariaDescribedBy: dialogConfig.ariaDescribedBy || null,
|
|
3360
|
+
direction: dialogConfig.direction ?? undefined,
|
|
3361
|
+
viewContainerRef: dialogConfig.viewContainerRef,
|
|
3362
|
+
injector: dialogConfig.injector,
|
|
3363
|
+
hasBackdrop: true,
|
|
3364
|
+
backdropClass: ['md3-dialog-scrim', OPENING_CLASS],
|
|
3365
|
+
panelClass: ['md3-dialog-panel', OPENING_CLASS],
|
|
3366
|
+
positionStrategy: this.overlay.position()
|
|
3367
|
+
.global()
|
|
3368
|
+
.centerHorizontally()
|
|
3369
|
+
.centerVertically(),
|
|
3370
|
+
// Scrolling is blocked by the service for the whole stack.
|
|
3371
|
+
scrollStrategy: this.overlay.scrollStrategies.noop(),
|
|
3372
|
+
// Focus goes back to whatever opened the stack, even when the dialog
|
|
3373
|
+
// that was open underneath has been closed in the meantime.
|
|
3374
|
+
restoreFocus: this.rootTrigger ?? true,
|
|
3375
|
+
// Scrim and Escape handling lives in DialogRef so the dialog can
|
|
3376
|
+
// play its exit animation before the overlay is disposed.
|
|
3377
|
+
disableClose: true,
|
|
3378
|
+
});
|
|
3379
|
+
dialogRef.dialogInstance = cdkRef.containerInstance;
|
|
3380
|
+
dialogRef.componentInstance = cdkRef.componentInstance ?? undefined;
|
|
3381
|
+
if (cdkRef.componentRef) {
|
|
3382
|
+
this.bindDataToInputs(cdkRef.componentRef, dialogConfig);
|
|
3383
|
+
}
|
|
3384
|
+
this.refs.push(dialogRef);
|
|
3385
|
+
this.updateScrollBlock();
|
|
3386
|
+
if (previousRef) {
|
|
3387
|
+
setTimeout(() => this.startReplacingAnimation(dialogRef, previousRef), DIALOG_HANDOVER_DELAY_MS);
|
|
3388
|
+
}
|
|
3389
|
+
else {
|
|
3390
|
+
this.startOpenAnimation(dialogRef);
|
|
3391
|
+
}
|
|
3392
|
+
// Restoring starts with the exit animation, so the dialog underneath is
|
|
3393
|
+
// already coming back while this one is fading out.
|
|
3394
|
+
dialogRef.beforeClosed().subscribe(() => this.restorePreviousDialog(dialogRef));
|
|
3395
|
+
dialogRef.afterClosed().subscribe(() => this.removeRef(dialogRef));
|
|
3204
3396
|
return dialogRef;
|
|
3205
3397
|
}
|
|
3398
|
+
/** Closes every open dialog, including the hidden ones. */
|
|
3399
|
+
closeAll() {
|
|
3400
|
+
// Closed from the bottom up so a closing dialog never brings back a
|
|
3401
|
+
// hidden dialog that is about to close as well.
|
|
3402
|
+
return Promise.all([...this.refs].map((ref) => ref.close())).then(() => undefined);
|
|
3403
|
+
}
|
|
3206
3404
|
mergeConfig(config) {
|
|
3207
3405
|
return {
|
|
3208
3406
|
data: config.data,
|
|
3209
3407
|
bindDataToInputs: config.bindDataToInputs ?? false,
|
|
3210
3408
|
disableCloseEvents: config.disableCloseEvents ?? false,
|
|
3409
|
+
previousDialog: config.previousDialog ?? 'close',
|
|
3211
3410
|
role: config.role ?? 'dialog',
|
|
3212
3411
|
ariaLabel: config.ariaLabel ?? '',
|
|
3213
3412
|
ariaLabelledBy: config.ariaLabelledBy ?? '',
|
|
@@ -3218,20 +3417,17 @@ class DialogService {
|
|
|
3218
3417
|
injector: config.injector ?? this.injector,
|
|
3219
3418
|
};
|
|
3220
3419
|
}
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
.centerVertically(),
|
|
3230
|
-
scrollStrategy: this.overlay.scrollStrategies.block(),
|
|
3231
|
-
});
|
|
3232
|
-
return this.overlay.create(overlayConfig);
|
|
3420
|
+
/**
|
|
3421
|
+
* Hides or closes the dialog that is on screen. Its scrim stays up until
|
|
3422
|
+
* the dialog taking over is ready for it, so the page behind never
|
|
3423
|
+
* brightens between the two dialogs.
|
|
3424
|
+
*/
|
|
3425
|
+
dismissPrevious(previousRef, config) {
|
|
3426
|
+
previousRef.overlayRef.backdropElement?.classList.add(SCRIM_KEPT_CLASS);
|
|
3427
|
+
return config.previousDialog === 'hide' ? previousRef.hide() : this.closeAll();
|
|
3233
3428
|
}
|
|
3234
|
-
startOpenAnimation(
|
|
3429
|
+
startOpenAnimation(dialogRef) {
|
|
3430
|
+
const overlayRef = dialogRef.overlayRef;
|
|
3235
3431
|
const panel = overlayRef.overlayElement;
|
|
3236
3432
|
const backdrop = overlayRef.backdropElement;
|
|
3237
3433
|
// The overlay is inserted already rendered. Keep an initial class for
|
|
@@ -3240,22 +3436,53 @@ class DialogService {
|
|
|
3240
3436
|
requestAnimationFrame(() => {
|
|
3241
3437
|
setTimeout(() => {
|
|
3242
3438
|
panel.getBoundingClientRect();
|
|
3243
|
-
panel.classList.remove(
|
|
3244
|
-
backdrop?.classList.remove(
|
|
3439
|
+
panel.classList.remove(OPENING_CLASS);
|
|
3440
|
+
backdrop?.classList.remove(OPENING_CLASS);
|
|
3245
3441
|
}, 100);
|
|
3246
3442
|
});
|
|
3443
|
+
dialogRef.dialogInstance?.startEnterAnimation();
|
|
3247
3444
|
}
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3445
|
+
/** Opening animation for a dialog that took the place of another one. */
|
|
3446
|
+
startReplacingAnimation(dialogRef, previousRef) {
|
|
3447
|
+
if (dialogRef.isClosing) {
|
|
3448
|
+
return;
|
|
3449
|
+
}
|
|
3450
|
+
const panel = dialogRef.overlayRef.overlayElement;
|
|
3451
|
+
const backdrop = dialogRef.overlayRef.backdropElement;
|
|
3452
|
+
this.beginScrimHandover(previousRef, dialogRef);
|
|
3453
|
+
panel.getBoundingClientRect();
|
|
3454
|
+
panel.classList.remove(OPENING_CLASS);
|
|
3455
|
+
backdrop?.classList.remove(OPENING_CLASS);
|
|
3456
|
+
this.endScrimHandover(previousRef, dialogRef);
|
|
3457
|
+
dialogRef.dialogInstance?.startEnterAnimation();
|
|
3458
|
+
}
|
|
3459
|
+
/** Opening animation for a dialog coming back from behind a closing one. */
|
|
3460
|
+
startRestoringAnimation(closingRef, previousRef) {
|
|
3461
|
+
if (previousRef.isClosing) {
|
|
3462
|
+
return;
|
|
3463
|
+
}
|
|
3464
|
+
this.beginScrimHandover(closingRef, previousRef);
|
|
3465
|
+
previousRef.show();
|
|
3466
|
+
this.endScrimHandover(closingRef, previousRef);
|
|
3467
|
+
}
|
|
3468
|
+
/**
|
|
3469
|
+
* Moves both scrims to their new value in a single frame, without a
|
|
3470
|
+
* transition on either side, so the dim behind the dialogs never changes.
|
|
3471
|
+
*/
|
|
3472
|
+
beginScrimHandover(fromRef, toRef) {
|
|
3473
|
+
const from = fromRef.overlayRef.backdropElement;
|
|
3474
|
+
const to = toRef.overlayRef.backdropElement;
|
|
3475
|
+
from?.classList.add(SCRIM_INSTANT_CLASS);
|
|
3476
|
+
from?.classList.remove(SCRIM_KEPT_CLASS);
|
|
3477
|
+
to?.classList.add(SCRIM_INSTANT_CLASS);
|
|
3478
|
+
}
|
|
3479
|
+
/** Gives the scrims their transitions back once the handover is painted. */
|
|
3480
|
+
endScrimHandover(fromRef, toRef) {
|
|
3481
|
+
const from = fromRef.overlayRef.backdropElement;
|
|
3482
|
+
const to = toRef.overlayRef.backdropElement;
|
|
3483
|
+
requestAnimationFrame(() => {
|
|
3484
|
+
from?.classList.remove(SCRIM_INSTANT_CLASS);
|
|
3485
|
+
to?.classList.remove(SCRIM_INSTANT_CLASS);
|
|
3259
3486
|
});
|
|
3260
3487
|
}
|
|
3261
3488
|
bindDataToInputs(componentRef, config) {
|
|
@@ -3268,34 +3495,58 @@ class DialogService {
|
|
|
3268
3495
|
componentRef.setInput(inputName, inputValue);
|
|
3269
3496
|
});
|
|
3270
3497
|
}
|
|
3271
|
-
|
|
3272
|
-
|
|
3498
|
+
canBindDataToInputs(data) {
|
|
3499
|
+
return typeof data === 'object' && data !== null && !Array.isArray(data);
|
|
3500
|
+
}
|
|
3501
|
+
/** Last dialog of the stack that is not closing yet. */
|
|
3502
|
+
topRef() {
|
|
3503
|
+
for (let index = this.refs.length - 1; index >= 0; index--) {
|
|
3504
|
+
if (!this.refs[index].isClosing) {
|
|
3505
|
+
return this.refs[index];
|
|
3506
|
+
}
|
|
3507
|
+
}
|
|
3508
|
+
return undefined;
|
|
3509
|
+
}
|
|
3510
|
+
restorePreviousDialog(ref) {
|
|
3511
|
+
const index = this.refs.indexOf(ref);
|
|
3512
|
+
if (index < 1) {
|
|
3513
|
+
return;
|
|
3514
|
+
}
|
|
3515
|
+
for (let previousIndex = index - 1; previousIndex >= 0; previousIndex--) {
|
|
3516
|
+
const previousRef = this.refs[previousIndex];
|
|
3517
|
+
if (previousRef.isClosing) {
|
|
3518
|
+
continue;
|
|
3519
|
+
}
|
|
3520
|
+
// The closing dialog gets the same head start it was given when it
|
|
3521
|
+
// took the screen, and keeps the scrim until then.
|
|
3522
|
+
ref.overlayRef.backdropElement?.classList.add(SCRIM_KEPT_CLASS);
|
|
3523
|
+
setTimeout(() => this.startRestoringAnimation(ref, previousRef), DIALOG_HANDOVER_DELAY_MS);
|
|
3273
3524
|
return;
|
|
3274
3525
|
}
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
(
|
|
3290
|
-
}
|
|
3526
|
+
}
|
|
3527
|
+
removeRef(ref) {
|
|
3528
|
+
const index = this.refs.indexOf(ref);
|
|
3529
|
+
if (index === -1) {
|
|
3530
|
+
return;
|
|
3531
|
+
}
|
|
3532
|
+
this.refs.splice(index, 1);
|
|
3533
|
+
if (this.refs.length === 0) {
|
|
3534
|
+
this.rootTrigger = null;
|
|
3535
|
+
}
|
|
3536
|
+
this.updateScrollBlock();
|
|
3537
|
+
}
|
|
3538
|
+
updateScrollBlock() {
|
|
3539
|
+
if (this.refs.length > 0) {
|
|
3540
|
+
this.scrollBlock.enable();
|
|
3541
|
+
}
|
|
3542
|
+
else {
|
|
3543
|
+
this.scrollBlock.disable();
|
|
3544
|
+
}
|
|
3291
3545
|
}
|
|
3292
3546
|
getFocusedElement() {
|
|
3293
3547
|
const activeElement = this.document.activeElement;
|
|
3294
3548
|
return activeElement instanceof HTMLElement ? activeElement : null;
|
|
3295
3549
|
}
|
|
3296
|
-
canBindDataToInputs(data) {
|
|
3297
|
-
return typeof data === 'object' && data !== null && !Array.isArray(data);
|
|
3298
|
-
}
|
|
3299
3550
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: DialogService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
3300
3551
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: DialogService, providedIn: 'root' });
|
|
3301
3552
|
}
|