Skip to content

Commit c5311ae

Browse files
author
Josh Soref (ChatGPT)
committed
fix(material/datepicker): remove label from internal calendar view
OpenAI Codex (v0.156.1) model: GPT-6-Sol medium Prompt: > Try to fix angular#33763. Don't make the mistakes made in angular#33844. Response: > I made a local fix for angular#33763 (angular#33763). The month and year views no longer render a second label inside the grid. The shared calendar body no longer creates the extra label row, and the old label inputs, styling, tokens, and API golden entries are removed. The leading spacer cell remains to preserve weekday column alignment for screen readers. > I added regression checks for the visible calendar and for grids with zero and nonzero first-row offsets. git diff --check passes. I couldn’t run the test suite because this checkout has no installed dependencies or test runner. Prompt: > in src/material/datepicker/calendar-body.html preserve the previous style of `</td>` on its own line. Prompt: > why did you remove MAT_DATE_FORMATS from src/material/datepicker/month- view.spec.ts? Reponse: > MAT_DATE_FORMATS was used in that spec only by the custom month-label test. I removed the test because the fix removes the in-grid month label it checked; that left the import unused. The month view still uses MAT_DATE_FORMATS to format date cells’ accessible names.
1 parent 0c41bc7 commit c5311ae

14 files changed

Lines changed: 36 additions & 141 deletions

‎goldens/material/datepicker/index.api.md‎

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -197,8 +197,6 @@ export class MatCalendarBody<D = any> implements OnChanges, OnDestroy, AfterView
197197
_isRangeEnd(value: number): boolean;
198198
_isRangeStart(value: number): boolean;
199199
_isSelected(value: number): boolean;
200-
label: string;
201-
labelMinRequiredCells: number;
202200
// (undocumented)
203201
ngAfterViewChecked(): void;
204202
// (undocumented)
@@ -218,7 +216,7 @@ export class MatCalendarBody<D = any> implements OnChanges, OnDestroy, AfterView
218216
todayValue: number;
219217
_trackRow: (row: MatCalendarCell[]) => MatCalendarCell<any>[];
220218
// (undocumented)
221-
static ɵcmp: i0.ɵɵComponentDeclaration<MatCalendarBody<any>, "[mat-calendar-body]", ["matCalendarBody"], { "label": { "alias": "label"; "required": false; }; "rows": { "alias": "rows"; "required": false; }; "todayValue": { "alias": "todayValue"; "required": false; }; "startValue": { "alias": "startValue"; "required": false; }; "endValue": { "alias": "endValue"; "required": false; }; "labelMinRequiredCells": { "alias": "labelMinRequiredCells"; "required": false; }; "numCols": { "alias": "numCols"; "required": false; }; "activeCell": { "alias": "activeCell"; "required": false; }; "isRange": { "alias": "isRange"; "required": false; }; "cellAspectRatio": { "alias": "cellAspectRatio"; "required": false; }; "comparisonStart": { "alias": "comparisonStart"; "required": false; }; "comparisonEnd": { "alias": "comparisonEnd"; "required": false; }; "previewStart": { "alias": "previewStart"; "required": false; }; "previewEnd": { "alias": "previewEnd"; "required": false; }; "startDateAccessibleName": { "alias": "startDateAccessibleName"; "required": false; }; "endDateAccessibleName": { "alias": "endDateAccessibleName"; "required": false; }; }, { "selectedValueChange": "selectedValueChange"; "previewChange": "previewChange"; "activeDateChange": "activeDateChange"; "dragStarted": "dragStarted"; "dragEnded": "dragEnded"; }, never, never, true, never>;
219+
static ɵcmp: i0.ɵɵComponentDeclaration<MatCalendarBody<any>, "[mat-calendar-body]", ["matCalendarBody"], { "rows": { "alias": "rows"; "required": false; }; "todayValue": { "alias": "todayValue"; "required": false; }; "startValue": { "alias": "startValue"; "required": false; }; "endValue": { "alias": "endValue"; "required": false; }; "numCols": { "alias": "numCols"; "required": false; }; "activeCell": { "alias": "activeCell"; "required": false; }; "isRange": { "alias": "isRange"; "required": false; }; "cellAspectRatio": { "alias": "cellAspectRatio"; "required": false; }; "comparisonStart": { "alias": "comparisonStart"; "required": false; }; "comparisonEnd": { "alias": "comparisonEnd"; "required": false; }; "previewStart": { "alias": "previewStart"; "required": false; }; "previewEnd": { "alias": "previewEnd"; "required": false; }; "startDateAccessibleName": { "alias": "startDateAccessibleName"; "required": false; }; "endDateAccessibleName": { "alias": "endDateAccessibleName"; "required": false; }; }, { "selectedValueChange": "selectedValueChange"; "previewChange": "previewChange"; "activeDateChange": "activeDateChange"; "dragStarted": "dragStarted"; "dragEnded": "dragEnded"; }, never, never, true, never>;
222220
// (undocumented)
223221
static ɵfac: i0.ɵɵFactoryDeclaration<MatCalendarBody<any>, never>;
224222
}
@@ -688,7 +686,6 @@ export class MatMonthView<D> implements AfterContentInit, OnChanges, OnDestroy {
688686
set maxDate(value: D | null);
689687
get minDate(): D | null;
690688
set minDate(value: D | null);
691-
_monthLabel: i0.WritableSignal<string>;
692689
// (undocumented)
693690
ngAfterContentInit(): void;
694691
// (undocumented)
@@ -841,7 +838,6 @@ export class MatYearView<D> implements AfterContentInit, OnDestroy {
841838
_selectedMonth: i0.WritableSignal<number | null>;
842839
_todayMonth: i0.WritableSignal<number | null>;
843840
_updateActiveDate(event: MatCalendarUserEvent<number>): void;
844-
_yearLabel: i0.WritableSignal<string>;
845841
// (undocumented)
846842
static ɵcmp: i0.ɵɵComponentDeclaration<MatYearView<any>, "mat-year-view", ["matYearView"], { "activeDate": { "alias": "activeDate"; "required": false; }; "selected": { "alias": "selected"; "required": false; }; "minDate": { "alias": "minDate"; "required": false; }; "maxDate": { "alias": "maxDate"; "required": false; }; "dateFilter": { "alias": "dateFilter"; "required": false; }; "dateClass": { "alias": "dateClass"; "required": false; }; }, { "selectedChange": "selectedChange"; "monthSelected": "monthSelected"; "activeDateChange": "activeDateChange"; }, never, never, true, never>;
847843
// (undocumented)

‎src/material/datepicker/_m2-datepicker.scss‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@
2222
// differences. We should look into introducing the other tokens in a follow-up.
2323
datepicker-calendar-text-font: map.get($system, body-large-font),
2424
datepicker-calendar-text-size: 13px,
25-
datepicker-calendar-body-label-text-size: map.get($system, label-small-size),
26-
datepicker-calendar-body-label-text-weight: map.get($system, label-small-weight),
2725
datepicker-calendar-period-button-text-size: map.get($system, label-small-size),
2826
datepicker-calendar-period-button-text-weight: map.get($system, label-small-weight),
2927
datepicker-calendar-header-text-size: 11px,
@@ -59,7 +57,6 @@
5957

6058
datepicker-toggle-active-state-icon-color: map.get($system, primary),
6159
datepicker-toggle-icon-color: map.get($system, on-surface-variant),
62-
datepicker-calendar-body-label-text-color: map.get($system, on-surface-variant),
6360
datepicker-calendar-period-button-text-color: map.get($system, on-surface),
6461
datepicker-calendar-period-button-icon-color: map.get($system, on-surface-variant),
6562
datepicker-calendar-navigation-button-icon-color: map.get($system, on-surface-variant),

‎src/material/datepicker/_m3-datepicker.scss‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
datepicker-calendar-container-touch-shape: map.get($system, corner-extra-large),
2020
),
2121
color: (
22-
datepicker-calendar-body-label-text-color: map.get($system, on-surface),
2322
datepicker-calendar-container-background-color: map.get($system, surface-container-high),
2423
datepicker-calendar-container-text-color: map.get($system, on-surface),
2524
datepicker-calendar-date-disabled-state-text-color:
@@ -60,8 +59,6 @@
6059
datepicker-toggle-icon-color: map.get($system, on-surface-variant),
6160
),
6261
typography: (
63-
datepicker-calendar-body-label-text-size: map.get($system, title-small-size),
64-
datepicker-calendar-body-label-text-weight: map.get($system, title-small-weight),
6562
datepicker-calendar-header-text-size: map.get($system, title-small-size),
6663
datepicker-calendar-header-text-weight: map.get($system, title-small-weight),
6764
datepicker-calendar-period-button-text-size: map.get($system, title-small-size),

‎src/material/datepicker/calendar-body.html‎

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,3 @@
1-
<!--
2-
If there's not enough space in the first row, create a separate label row. We mark this row as
3-
aria-hidden because we don't want it to be read out as one of the weeks in the month.
4-
-->
5-
@if (_firstRowOffset < labelMinRequiredCells) {
6-
<tr aria-hidden="true">
7-
<td class="mat-calendar-body-label"
8-
[attr.colspan]="numCols"
9-
[style.paddingTop]="_cellPadding"
10-
[style.paddingBottom]="_cellPadding">
11-
{{label}}
12-
</td>
13-
</tr>
14-
}
15-
161
<!-- Create the first row separately so we can include a special spacer cell. -->
172
@for (row of rows; track _trackRow(row); let rowIndex = $index) {
183
<tr role="row">
@@ -24,11 +9,10 @@
249
-->
2510
@if (rowIndex === 0 && _firstRowOffset) {
2611
<td
27-
class="mat-calendar-body-label"
12+
class="mat-calendar-body-offset"
2813
[attr.colspan]="_firstRowOffset"
2914
[style.paddingTop]="_cellPadding"
3015
[style.paddingBottom]="_cellPadding">
31-
{{_firstRowOffset >= labelMinRequiredCells ? label : ''}}
3216
</td>
3317
}
3418
<!--

‎src/material/datepicker/calendar-body.scss‎

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,9 @@
1-
@use 'sass:math';
21
@use '@angular/cdk';
32

43
@use '../core/style/button-common';
54
@use '../core/tokens/token-utils';
65
@use './m3-datepicker';
76

8-
$calendar-body-label-padding-start: 5% !default;
9-
// We don't want the label to jump around when we switch between month and year views, so we use
10-
// the same amount of padding regardless of the number of columns. We align the header label with
11-
// the one third mark of the first cell, this was chosen somewhat arbitrarily to make it look
12-
// roughly like the mock. Half way is too far since the cell text is center aligned.
13-
$calendar-body-label-side-padding: math.div(33%, 7) !default;
147
$calendar-body-cell-min-size: 32px !default;
158
$calendar-body-cell-content-margin: 5% !default;
169
$calendar-body-cell-content-border-width: 1px !default;
@@ -51,16 +44,9 @@ $fallbacks: m3-datepicker.get-tokens();
5144
}
5245
}
5346

54-
.mat-calendar-body-label {
47+
.mat-calendar-body-offset {
5548
height: 0;
5649
line-height: 0;
57-
text-align: start;
58-
padding-left: $calendar-body-label-side-padding;
59-
padding-right: $calendar-body-label-side-padding;
60-
61-
font-size: token-utils.slot(datepicker-calendar-body-label-text-size, $fallbacks);
62-
font-weight: token-utils.slot(datepicker-calendar-body-label-text-weight, $fallbacks);
63-
color: token-utils.slot(datepicker-calendar-body-label-text-color, $fallbacks);
6450
}
6551

6652
// Label that is not rendered and removed from the accessibility tree.

‎src/material/datepicker/calendar-body.spec.ts‎

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ describe('MatCalendarBody', () => {
1414
let testComponent: StandardCalendarBody;
1515
let calendarBodyNativeElement: Element;
1616
let rowEls: Element[];
17-
let labelEls: Element[];
17+
let offsetEls: Element[];
1818
let cellEls: Element[];
1919

2020
function refreshElementLists() {
2121
rowEls = Array.from(calendarBodyNativeElement.querySelectorAll('tr'));
22-
labelEls = Array.from(calendarBodyNativeElement.querySelectorAll('.mat-calendar-body-label'));
22+
offsetEls = Array.from(calendarBodyNativeElement.querySelectorAll('.mat-calendar-body-offset'));
2323
cellEls = Array.from(calendarBodyNativeElement.querySelectorAll('.mat-calendar-body-cell'));
2424
}
2525

@@ -35,8 +35,8 @@ describe('MatCalendarBody', () => {
3535
});
3636

3737
it('creates body', () => {
38-
expect(rowEls.length).toBe(3);
39-
expect(labelEls.length).toBe(1);
38+
expect(rowEls.length).toBe(2);
39+
expect(offsetEls.length).toBe(0);
4040
expect(cellEls.length).toBe(14);
4141
});
4242

@@ -98,21 +98,22 @@ describe('MatCalendarBody', () => {
9898
.toBe(cellEls.length - 1);
9999
});
100100

101-
it('places label in first row if space is available', () => {
102-
testComponent.rows[0] = testComponent.rows[0].slice(3);
103-
testComponent.rows = testComponent.rows.slice();
104-
fixture.changeDetectorRef.markForCheck();
105-
fixture.detectChanges();
106-
refreshElementLists();
107-
108-
expect(rowEls.length).toBe(2);
109-
expect(labelEls.length).toBe(1);
110-
expect(cellEls.length).toBe(11);
111-
expect(rowEls[0].firstElementChild!.classList)
112-
.withContext('first cell should be the label')
113-
.toContain('mat-calendar-body-label');
114-
expect(labelEls[0].getAttribute('colspan')).toBe('3');
115-
});
101+
for (const offset of [1, 3]) {
102+
it(`keeps an empty first-row spacer for an offset of ${offset}`, () => {
103+
testComponent.rows[0] = testComponent.rows[0].slice(offset);
104+
testComponent.rows = testComponent.rows.slice();
105+
fixture.changeDetectorRef.markForCheck();
106+
fixture.detectChanges();
107+
refreshElementLists();
108+
109+
expect(rowEls.length).toBe(2);
110+
expect(offsetEls.length).toBe(1);
111+
expect(cellEls.length).toBe(14 - offset);
112+
expect(rowEls[0].firstElementChild).toBe(offsetEls[0]);
113+
expect(offsetEls[0].getAttribute('colspan')).toBe(`${offset}`);
114+
expect(offsetEls[0].textContent!.trim()).toBe('');
115+
});
116+
}
116117

117118
it('cell should be selected on click', () => {
118119
const todayElement = calendarBodyNativeElement.querySelector(
@@ -699,12 +700,10 @@ describe('MatCalendarBody', () => {
699700
@Component({
700701
template: `
701702
<table mat-calendar-body
702-
[label]="label"
703703
[rows]="rows"
704704
[todayValue]="todayValue"
705705
[startValue]="selectedValue"
706706
[endValue]="selectedValue"
707-
[labelMinRequiredCells]="labelMinRequiredCells"
708707
[numCols]="numCols"
709708
[activeCell]="10"
710709
(selectedValueChange)="onSelect($event)">
@@ -713,11 +712,9 @@ describe('MatCalendarBody', () => {
713712
changeDetection: ChangeDetectionStrategy.Eager,
714713
})
715714
class StandardCalendarBody {
716-
label = 'Jan 2017';
717715
rows = createCalendarCells(2);
718716
todayValue = 3;
719717
selectedValue = 4;
720-
labelMinRequiredCells = 3;
721718
numCols = 7;
722719

723720
onSelect(event: MatCalendarUserEvent<number>) {

‎src/material/datepicker/calendar-body.ts‎

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -114,9 +114,6 @@ export class MatCalendarBody<D = any> implements OnChanges, OnDestroy, AfterView
114114
*/
115115
private _focusActiveCellAfterViewChecked = false;
116116

117-
/** The label for the table. (e.g. "Jan 2017"). */
118-
@Input() label!: string;
119-
120117
/** The cells to display in the table. */
121118
@Input() rows!: MatCalendarCell[][];
122119

@@ -129,9 +126,6 @@ export class MatCalendarBody<D = any> implements OnChanges, OnDestroy, AfterView
129126
/** End value of the selected date range. */
130127
@Input() endValue!: number;
131128

132-
/** The minimum number of free cells needed to fit the label in the first row. */
133-
@Input() labelMinRequiredCells!: number;
134-
135129
/** The number of columns in the table. */
136130
@Input() numCols: number = 7;
137131

‎src/material/datepicker/calendar.spec.ts‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,12 @@ describe('MatCalendar', () => {
7171
expect(calendarInstance.activeDate).toEqual(new Date(2017, JAN, 31));
7272
});
7373

74+
it('shows the month in the header without repeating it in the grid', () => {
75+
expect(periodButton.textContent).toContain('JAN');
76+
expect(calendarElement.querySelector('.mat-calendar-body-label')).toBeNull();
77+
expect(calendarElement.querySelectorAll('tbody tr').length).toBe(5);
78+
});
79+
7480
it('should select date in month view', () => {
7581
let monthCells = calendarElement.querySelectorAll('.mat-calendar-body-cell');
7682
(monthCells[monthCells.length - 1] as HTMLElement).click();

‎src/material/datepicker/month-view.html‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
<tr aria-hidden="true"><th class="mat-calendar-table-header-divider" colspan="7"></th></tr>
1212
</thead>
1313
<tbody mat-calendar-body
14-
[label]="_monthLabel()"
1514
[rows]="_weeks()"
1615
[todayValue]="_todayDate()!"
1716
[startValue]="_rangeStart()!"
@@ -21,7 +20,6 @@
2120
[previewStart]="_previewStart()"
2221
[previewEnd]="_previewEnd()"
2322
[isRange]="_isRange()"
24-
[labelMinRequiredCells]="3"
2523
[activeCell]="_dateAdapter.getDate(activeDate) - 1"
2624
[startDateAccessibleName]="startDateAccessibleName"
2725
[endDateAccessibleName]="endDateAccessibleName"

‎src/material/datepicker/month-view.spec.ts‎

Lines changed: 4 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import {
2323
import {Component, signal, WritableSignal, ChangeDetectionStrategy} from '@angular/core';
2424
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
2525
import {By} from '@angular/platform-browser';
26-
import {MAT_DATE_FORMATS, provideNativeDateAdapter} from '../core';
26+
import {provideNativeDateAdapter} from '../core';
2727
import {DEC, FEB, JAN, MAR, NOV} from '../testing';
2828
import {MatCalendarCellClassFunction, MatCalendarUserEvent} from './calendar-body';
2929
import {
@@ -63,9 +63,9 @@ describe('MatMonthView', () => {
6363
testComponent = fixture.componentInstance;
6464
});
6565

66-
it('has correct month label', () => {
67-
let labelEl = monthViewNativeElement.querySelector('.mat-calendar-body-label')!;
68-
expect(labelEl.innerHTML.trim()).toBe('JAN');
66+
it('does not render a redundant month label or an empty label row', () => {
67+
expect(monthViewNativeElement.querySelector('.mat-calendar-body-label')).toBeNull();
68+
expect(monthViewNativeElement.querySelectorAll('tbody tr').length).toBe(5);
6969
});
7070

7171
it('has 31 days', () => {
@@ -791,47 +791,6 @@ describe('MatMonthView', () => {
791791
});
792792
});
793793
});
794-
795-
describe('month view with custom date formats', () => {
796-
let fixture: ComponentFixture<StandardMonthView>;
797-
let monthViewNativeElement: Element;
798-
799-
beforeEach(waitForAsync(() => {
800-
TestBed.configureTestingModule({
801-
providers: [
802-
provideNativeDateAdapter(),
803-
provideFakeDirectionality('ltr'),
804-
{provide: MAT_DATE_RANGE_SELECTION_STRATEGY, useClass: DefaultMatCalendarRangeStrategy},
805-
{
806-
provide: MAT_DATE_FORMATS,
807-
useValue: {
808-
parse: {
809-
dateInput: null,
810-
},
811-
display: {
812-
dateInput: {year: 'numeric', month: 'numeric', day: 'numeric'},
813-
monthLabel: {year: 'numeric', month: 'short'},
814-
monthYearLabel: {year: 'numeric', month: 'short'},
815-
dateA11yLabel: {year: 'numeric', month: 'long', day: 'numeric'},
816-
monthYearA11yLabel: {year: 'numeric', month: 'long'},
817-
},
818-
},
819-
},
820-
],
821-
});
822-
823-
fixture = TestBed.createComponent(StandardMonthView);
824-
fixture.detectChanges();
825-
826-
let monthViewDebugElement = fixture.debugElement.query(By.directive(MatMonthView))!;
827-
monthViewNativeElement = monthViewDebugElement.nativeElement;
828-
}));
829-
830-
it('has correct month label', () => {
831-
let labelEl = monthViewNativeElement.querySelector('.mat-calendar-body-label')!;
832-
expect(labelEl.innerHTML.trim()).toBe('Jan 2017');
833-
});
834-
});
835794
});
836795

837796
@Component({

0 commit comments

Comments
 (0)