Skip to content

Commit a6e95f1

Browse files
committed
refactor(material/dialog): reduce action selector specificity
Reworks the dialog to reduce the specificity of the selector targeting dialogs with an action section. This is an attempt at making the change easier to land internally.
1 parent 9ab104b commit a6e95f1

File tree

5 files changed

+104
-41
lines changed

5 files changed

+104
-41
lines changed

src/material/dialog/dialog-container.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ export const CLOSE_ANIMATION_DURATION = 75;
6868
'[attr.aria-label]': '_config.ariaLabel',
6969
'[attr.aria-describedby]': '_config.ariaDescribedBy || null',
7070
'[class._mat-animation-noopable]': '!_animationsEnabled',
71+
'[class.mat-mdc-dialog-container-with-actions]': '_actionSectionCount > 0',
7172
},
7273
})
7374
export class MatDialogContainer extends CdkDialogContainer<MatDialogConfig> implements OnDestroy {
@@ -77,6 +78,9 @@ export class MatDialogContainer extends CdkDialogContainer<MatDialogConfig> impl
7778
/** Whether animations are enabled. */
7879
_animationsEnabled: boolean = this._animationMode !== 'NoopAnimations';
7980

81+
/** Number of actions projected in the dialog. */
82+
protected _actionSectionCount = 0;
83+
8084
/** Host element of the dialog container component. */
8185
private _hostElement: HTMLElement = this._elementRef.nativeElement;
8286
/** Duration of the dialog open animation. */
@@ -194,6 +198,15 @@ export class MatDialogContainer extends CdkDialogContainer<MatDialogConfig> impl
194198
}
195199
}
196200

201+
/**
202+
* Updates the number action sections.
203+
* @param delta Increase/decrease in the number of sections.
204+
*/
205+
_updateActionSectionCount(delta: number) {
206+
this._actionSectionCount += delta;
207+
this._changeDetectorRef.markForCheck();
208+
}
209+
197210
/**
198211
* Completes the dialog open by clearing potential animation classes, trapping
199212
* focus and emitting an opened event.

src/material/dialog/dialog-content-directives.ts

Lines changed: 43 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -88,39 +88,27 @@ export class MatDialogClose implements OnInit, OnChanges {
8888
}
8989
}
9090

91-
/**
92-
* Title of a dialog element. Stays fixed to the top of the dialog when scrolling.
93-
*/
94-
@Directive({
95-
selector: '[mat-dialog-title], [matDialogTitle]',
96-
exportAs: 'matDialogTitle',
97-
standalone: true,
98-
host: {
99-
'class': 'mat-mdc-dialog-title mdc-dialog__title',
100-
'[id]': 'id',
101-
},
102-
})
103-
export class MatDialogTitle implements OnInit, OnDestroy {
104-
@Input() id: string = `mat-mdc-dialog-title-${dialogElementUid++}`;
105-
91+
@Directive({standalone: true})
92+
export abstract class MatDialogLayoutSection implements OnInit, OnDestroy {
10693
constructor(
10794
// The dialog title directive is always used in combination with a `MatDialogRef`.
10895
// tslint:disable-next-line: lightweight-tokens
109-
@Optional() private _dialogRef: MatDialogRef<any>,
96+
@Optional() protected _dialogRef: MatDialogRef<any>,
11097
private _elementRef: ElementRef<HTMLElement>,
11198
private _dialog: MatDialog,
11299
) {}
113100

101+
protected abstract _onAdd(): void;
102+
protected abstract _onRemove(): void;
103+
114104
ngOnInit() {
115105
if (!this._dialogRef) {
116106
this._dialogRef = getClosestDialog(this._elementRef, this._dialog.openDialogs)!;
117107
}
118108

119109
if (this._dialogRef) {
120110
Promise.resolve().then(() => {
121-
// Note: we null check the queue, because there are some internal
122-
// tests that are mocking out `MatDialogRef` incorrectly.
123-
this._dialogRef._containerInstance?._addAriaLabelledBy?.(this.id);
111+
this._onAdd();
124112
});
125113
}
126114
}
@@ -132,12 +120,38 @@ export class MatDialogTitle implements OnInit, OnDestroy {
132120

133121
if (instance) {
134122
Promise.resolve().then(() => {
135-
instance._removeAriaLabelledBy?.(this.id);
123+
this._onRemove();
136124
});
137125
}
138126
}
139127
}
140128

129+
/**
130+
* Title of a dialog element. Stays fixed to the top of the dialog when scrolling.
131+
*/
132+
@Directive({
133+
selector: '[mat-dialog-title], [matDialogTitle]',
134+
exportAs: 'matDialogTitle',
135+
standalone: true,
136+
host: {
137+
'class': 'mat-mdc-dialog-title mdc-dialog__title',
138+
'[id]': 'id',
139+
},
140+
})
141+
export class MatDialogTitle extends MatDialogLayoutSection {
142+
@Input() id: string = `mat-mdc-dialog-title-${dialogElementUid++}`;
143+
144+
protected _onAdd() {
145+
// Note: we null check the queue, because there are some internal
146+
// tests that are mocking out `MatDialogRef` incorrectly.
147+
this._dialogRef._containerInstance?._addAriaLabelledBy?.(this.id);
148+
}
149+
150+
protected override _onRemove(): void {
151+
this._dialogRef?._containerInstance?._removeAriaLabelledBy?.(this.id);
152+
}
153+
}
154+
141155
/**
142156
* Scrollable content container of a dialog.
143157
*/
@@ -162,11 +176,19 @@ export class MatDialogContent {}
162176
'[class.mat-mdc-dialog-actions-align-end]': 'align === "end"',
163177
},
164178
})
165-
export class MatDialogActions {
179+
export class MatDialogActions extends MatDialogLayoutSection {
166180
/**
167181
* Horizontal alignment of action buttons.
168182
*/
169183
@Input() align?: 'start' | 'center' | 'end';
184+
185+
protected _onAdd() {
186+
this._dialogRef._containerInstance?._updateActionSectionCount?.(1);
187+
}
188+
189+
protected override _onRemove(): void {
190+
this._dialogRef._containerInstance?._updateActionSectionCount?.(-1);
191+
}
170192
}
171193

172194
/**

src/material/dialog/dialog.scss

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ $mat-dialog-content-max-height: 65vh !default;
1515
// don't expose this value as variable.
1616
$mat-dialog-button-horizontal-margin: 8px !default;
1717

18+
// Whether to emit fallback values for the structural styles. Previously MDC was emitting
19+
// paddings in the static styles which meant that users would get them even if they didn't
20+
// include the `dialog-base`. Eventually we should clean up the usages of this flag.
21+
$_emit-fallbacks: true;
22+
1823
// Note that we disable fallback declarations, but we don't disable fallback
1924
// values, because there are a lot of internal apps that don't include a proper
2025
// theme in their tests.
@@ -55,21 +60,31 @@ $mat-dialog-button-horizontal-margin: 8px !default;
5560
}
5661
}
5762

63+
@mixin _use-mat-tokens {
64+
@include mdc-helpers.disable-mdc-fallback-declarations {
65+
@include token-utils.use-tokens(tokens-mat-dialog.$prefix,
66+
tokens-mat-dialog.get-token-slots()) {
67+
@content;
68+
}
69+
}
70+
}
71+
5872
// This needs extra specificity so it doesn't get overwritten by the CDK structural styles.
5973
.cdk-overlay-pane.mat-mdc-dialog-panel {
60-
@include token-utils.use-tokens(tokens-mat-dialog.$prefix, tokens-mat-dialog.get-token-slots()) {
61-
@include token-utils.create-token-slot(max-width, container-max-width);
62-
@include token-utils.create-token-slot(min-width, container-min-width);
74+
@include _use-mat-tokens {
75+
@include token-utils.create-token-slot(max-width, container-max-width, $_emit-fallbacks);
76+
@include token-utils.create-token-slot(min-width, container-min-width, $_emit-fallbacks);
6377

6478
@media (variables.$xsmall) {
65-
@include token-utils.create-token-slot(max-width, container-small-max-width);
79+
@include token-utils.create-token-slot(max-width, container-small-max-width,
80+
$_emit-fallbacks);
6681
}
6782
}
6883
}
6984

7085
.mat-mdc-dialog-title {
71-
@include token-utils.use-tokens(tokens-mat-dialog.$prefix, tokens-mat-dialog.get-token-slots()) {
72-
@include token-utils.create-token-slot(padding, headline-padding);
86+
@include _use-mat-tokens {
87+
@include token-utils.create-token-slot(padding, headline-padding, $_emit-fallbacks);
7388
}
7489
}
7590

@@ -78,14 +93,17 @@ $mat-dialog-button-horizontal-margin: 8px !default;
7893
.mat-mdc-dialog-content {
7994
display: block;
8095

81-
@include token-utils.use-tokens(tokens-mat-dialog.$prefix, tokens-mat-dialog.get-token-slots()) {
96+
@include _use-mat-tokens {
8297
// These styles need a bit more specificity.
8398
.mat-mdc-dialog-container & {
84-
@include token-utils.create-token-slot(padding, content-padding);
99+
@include token-utils.create-token-slot(padding, content-padding, $_emit-fallbacks);
85100
}
86101

87-
.mat-mdc-dialog-container:has(.mat-mdc-dialog-actions) & {
88-
@include token-utils.create-token-slot(padding, with-actions-content-padding);
102+
// Note: we can achieve this with a `:has` selector, but it results in an
103+
// increased specificity which breaks a lot of internal clients.
104+
.mat-mdc-dialog-container-with-actions & {
105+
@include token-utils.create-token-slot(padding, with-actions-content-padding,
106+
$_emit-fallbacks);
89107
}
90108
}
91109

@@ -97,9 +115,9 @@ $mat-dialog-button-horizontal-margin: 8px !default;
97115
.mat-mdc-dialog-actions {
98116
// For backwards compatibility, actions align at start by default. MDC usually
99117
// aligns actions at the end of the container.
100-
@include token-utils.use-tokens(tokens-mat-dialog.$prefix, tokens-mat-dialog.get-token-slots()) {
101-
@include token-utils.create-token-slot(padding, actions-padding);
102-
@include token-utils.create-token-slot(justify-content, actions-alignment);
118+
@include _use-mat-tokens {
119+
@include token-utils.create-token-slot(padding, actions-padding, $_emit-fallbacks);
120+
@include token-utils.create-token-slot(justify-content, actions-alignment, $_emit-fallbacks);
103121
}
104122

105123
// .mat-mdc-dialog-actions-align-{start|center|end} are set by directive input "align"

src/material/dialog/public-api.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,12 @@
99
export * from './dialog';
1010
export * from './dialog-config';
1111
export * from './dialog-ref';
12-
export * from './dialog-content-directives';
12+
export {
13+
MatDialogActions,
14+
MatDialogClose,
15+
MatDialogTitle,
16+
MatDialogContent,
17+
} from './dialog-content-directives';
1318
export {MatDialogContainer} from './dialog-container';
1419
export * from './module';
1520
export {matDialogAnimations, _defaultParams} from './dialog-animations';

tools/public_api_guard/material/dialog.md

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,13 @@ export class MatDialog implements OnDestroy {
113113
}
114114

115115
// @public
116-
export class MatDialogActions {
116+
export class MatDialogActions extends MatDialogLayoutSection {
117117
align?: 'start' | 'center' | 'end';
118118
// (undocumented)
119+
protected _onAdd(): void;
120+
// (undocumented)
121+
protected _onRemove(): void;
122+
// (undocumented)
119123
static ɵdir: i0.ɵɵDirectiveDeclaration<MatDialogActions, "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", never, { "align": { "alias": "align"; "required": false; }; }, {}, never, never, true, never>;
120124
// (undocumented)
121125
static ɵfac: i0.ɵɵFactoryDeclaration<MatDialogActions, never>;
@@ -184,6 +188,7 @@ export class MatDialogConfig<D = any> {
184188
// @public (undocumented)
185189
export class MatDialogContainer extends CdkDialogContainer<MatDialogConfig> implements OnDestroy {
186190
constructor(elementRef: ElementRef, focusTrapFactory: FocusTrapFactory, _document: any, dialogConfig: MatDialogConfig, interactivityChecker: InteractivityChecker, ngZone: NgZone, overlayRef: OverlayRef, _animationMode?: string | undefined, focusMonitor?: FocusMonitor);
191+
protected _actionSectionCount: number;
187192
_animationsEnabled: boolean;
188193
_animationStateChanged: EventEmitter<LegacyDialogAnimationEvent>;
189194
// (undocumented)
@@ -196,6 +201,7 @@ export class MatDialogContainer extends CdkDialogContainer<MatDialogConfig> impl
196201
ngOnDestroy(): void;
197202
protected _openAnimationDone(totalTime: number): void;
198203
_startExitAnimation(): void;
204+
_updateActionSectionCount(delta: number): void;
199205
// (undocumented)
200206
static ɵcmp: i0.ɵɵComponentDeclaration<MatDialogContainer, "mat-dialog-container", never, {}, {}, never, never, true, never>;
201207
// (undocumented)
@@ -253,18 +259,17 @@ export enum MatDialogState {
253259
}
254260

255261
// @public
256-
export class MatDialogTitle implements OnInit, OnDestroy {
257-
constructor(_dialogRef: MatDialogRef<any>, _elementRef: ElementRef<HTMLElement>, _dialog: MatDialog);
262+
export class MatDialogTitle extends MatDialogLayoutSection {
258263
// (undocumented)
259264
id: string;
260265
// (undocumented)
261-
ngOnDestroy(): void;
266+
protected _onAdd(): void;
262267
// (undocumented)
263-
ngOnInit(): void;
268+
protected _onRemove(): void;
264269
// (undocumented)
265270
static ɵdir: i0.ɵɵDirectiveDeclaration<MatDialogTitle, "[mat-dialog-title], [matDialogTitle]", ["matDialogTitle"], { "id": { "alias": "id"; "required": false; }; }, {}, never, never, true, never>;
266271
// (undocumented)
267-
static ɵfac: i0.ɵɵFactoryDeclaration<MatDialogTitle, [{ optional: true; }, null, null]>;
272+
static ɵfac: i0.ɵɵFactoryDeclaration<MatDialogTitle, never>;
268273
}
269274

270275
// (No @packageDocumentation comment for this package)

0 commit comments

Comments
 (0)