Skip to content

Commit

Permalink
fix(date-picker): better denote selected date
Browse files Browse the repository at this point in the history
- include selected date in button label
- add aria-selected to date in calendar

fixes VPAT-662

Signed-off-by: Ashley Ryan <[email protected]>
  • Loading branch information
Ashley Ryan authored and steve-haar committed Mar 11, 2022
1 parent 05050bd commit 11a75f6
Show file tree
Hide file tree
Showing 7 changed files with 73 additions and 15 deletions.
7 changes: 5 additions & 2 deletions golden/clr-angular.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,8 @@ export interface ClrCommonStrings {
datepickerSelectMonthText: string;
datepickerSelectYearText: string;
datepickerToggle: string;
datepickerToggleChangeDateLabel: string;
datepickerToggleChooseDateLabel: string;
delete?: string;
detailExpandableAriaLabel: string;
detailPaneEnd: string;
Expand Down Expand Up @@ -1032,10 +1034,11 @@ export declare class ClrDateContainer extends ClrAbstractContainer implements Af
protected ngControlService: NgControlService;
get open(): boolean;
get popoverPosition(): ClrPopoverPosition;
constructor(toggleService: ClrPopoverToggleService, dateNavigationService: DateNavigationService, datepickerEnabledService: DatepickerEnabledService, dateFormControlService: DateFormControlService, commonStrings: ClrCommonStringsService, focusService: FocusService, viewManagerService: ViewManagerService, controlClassService: ControlClassService, layoutService: LayoutService, ngControlService: NgControlService, ifControlStateService: IfControlStateService);
protected renderer: Renderer2;
constructor(renderer: Renderer2, toggleService: ClrPopoverToggleService, dateNavigationService: DateNavigationService, datepickerEnabledService: DatepickerEnabledService, dateFormControlService: DateFormControlService, dateIOService: DateIOService, commonStrings: ClrCommonStringsService, focusService: FocusService, viewManagerService: ViewManagerService, controlClassService: ControlClassService, layoutService: LayoutService, ngControlService: NgControlService, ifControlStateService: IfControlStateService);
ngAfterViewInit(): void;
static ɵcmp: i0.ɵɵComponentDeclaration<ClrDateContainer, "clr-date-container", never, { "clrPosition": "clrPosition"; }, {}, never, ["label", "[clrDate]", "clr-control-helper", "clr-control-error", "clr-control-success"]>;
static ɵfac: i0.ɵɵFactoryDeclaration<ClrDateContainer, [null, null, null, null, null, null, null, null, { optional: true; }, null, null]>;
static ɵfac: i0.ɵɵFactoryDeclaration<ClrDateContainer, [null, null, null, null, null, null, null, null, null, null, { optional: true; }, null, null]>;
}

export declare class ClrDateInput extends WrappedFormControl<ClrDateContainer> implements OnInit, AfterViewInit, OnDestroy {
Expand Down
20 changes: 13 additions & 7 deletions projects/angular/src/forms/datepicker/date-container.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016-2021 VMware, Inc. All Rights Reserved.
* Copyright (c) 2016-2022 VMware, Inc. All Rights Reserved.
* This software is released under MIT license.
* The full license information can be found in LICENSE in the root directory of this project.
*/
Expand Down Expand Up @@ -30,6 +30,7 @@ import { ClrPopoverEventsService } from '../../utils/popover/providers/popover-e
import { ClrPopoverPositionService } from '../../utils/popover/providers/popover-position.service';
import { ViewManagerService } from './providers/view-manager.service';
import { IfControlStateService, CONTROL_STATE } from '../common/if-control-state/if-control-state.service';
import { DayModel } from './model/day.model';

const DATEPICKER_PROVIDERS: any[] = [
ClrPopoverEventsService,
Expand All @@ -54,6 +55,7 @@ export default function () {
let context: TestContext<ClrDateContainer, TestComponent>;
let enabledService: MockDatepickerEnabledService;
let dateFormControlService: DateFormControlService;
let dateNavigationService: DateNavigationService;
let toggleService: ClrPopoverToggleService;
let container: any;

Expand All @@ -72,6 +74,7 @@ export default function () {
enabledService = context.getClarityProvider(DatepickerEnabledService) as MockDatepickerEnabledService;
dateFormControlService = context.getClarityProvider(DateFormControlService);
toggleService = context.getClarityProvider(ClrPopoverToggleService);
dateNavigationService = context.getClarityProvider(DateNavigationService);
container = context.clarityDirective;
});

Expand Down Expand Up @@ -163,14 +166,17 @@ export default function () {
expect(context.clarityElement.className).toContain('clr-form-control-disabled');
});

it('has an accessible title on the calendar toggle button', () => {
it('has an accessible title and aria-label on the calendar toggle button', async () => {
const toggleButton: HTMLButtonElement = context.clarityElement.querySelector('.clr-input-group-icon-action');
expect(toggleButton.title).toEqual('Toggle datepicker');
});
expect(toggleButton.title).toEqual('Choose date');
expect(toggleButton.attributes['aria-label'].value).toEqual('Choose date');

it('has an accessible aria-label on the calendar toggle button', () => {
const toggleButton: HTMLButtonElement = context.clarityElement.querySelector('.clr-input-group-icon-action');
expect(toggleButton.attributes['aria-label'].value).toEqual('Toggle datepicker');
dateNavigationService.notifySelectedDayChanged(new DayModel(2022, 1, 1));
context.detectChanges();
await context.fixture.whenStable();

expect(toggleButton.title).toEqual('Change date, 02/01/2022');
expect(toggleButton.attributes['aria-label'].value).toEqual('Change date, 02/01/2022');
});

it('supports clrPosition option', () => {
Expand Down
43 changes: 39 additions & 4 deletions projects/angular/src/forms/datepicker/date-container.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
/*
* Copyright (c) 2016-2021 VMware, Inc. All Rights Reserved.
* Copyright (c) 2016-2022 VMware, Inc. All Rights Reserved.
* This software is released under MIT license.
* The full license information can be found in LICENSE in the root directory of this project.
*/
import { Component, Optional, ViewChild, ElementRef, Input, AfterViewInit } from '@angular/core';
import { Component, Optional, ViewChild, ElementRef, Input, AfterViewInit, Renderer2 } from '@angular/core';
import { startWith } from 'rxjs/operators';

import { ClrPopoverToggleService } from '../../utils/popover/providers/popover-toggle.service';
import { ControlClassService } from '../common/providers/control-class.service';
Expand All @@ -12,6 +13,7 @@ import { FocusService } from '../common/providers/focus.service';
import { LayoutService } from '../common/providers/layout.service';
import { NgControlService } from '../common/providers/ng-control.service';

import { DayModel } from './model/day.model';
import { DateFormControlService } from './providers/date-form-control.service';
import { DateIOService } from './providers/date-io.service';
import { DateNavigationService } from './providers/date-navigation.service';
Expand Down Expand Up @@ -40,8 +42,6 @@ import { ClrAbstractContainer } from '../common/abstract-container';
type="button"
clrPopoverOpenCloseButton
class="clr-input-group-icon-action"
[attr.title]="commonStrings.keys.datepickerToggle"
[attr.aria-label]="commonStrings.keys.datepickerToggle"
[disabled]="isInputDateDisabled"
*ngIf="isEnabled"
>
Expand Down Expand Up @@ -119,10 +119,12 @@ export class ClrDateContainer extends ClrAbstractContainer implements AfterViewI
}

constructor(
protected renderer: Renderer2,
private toggleService: ClrPopoverToggleService,
private dateNavigationService: DateNavigationService,
private datepickerEnabledService: DatepickerEnabledService,
private dateFormControlService: DateFormControlService,
private dateIOService: DateIOService,
public commonStrings: ClrCommonStringsService,
private focusService: FocusService,
private viewManagerService: ViewManagerService,
Expand Down Expand Up @@ -156,6 +158,8 @@ export class ClrDateContainer extends ClrAbstractContainer implements AfterViewI
}
})
);

this.subscriptions.push(this.listenForDateChanges());
}

/**
Expand All @@ -175,6 +179,37 @@ export class ClrDateContainer extends ClrAbstractContainer implements AfterViewI
);
}

/**
* Return the label for the toggle button.
* If there's a selected date, the date is included in the label.
*/
private getToggleButtonLabel(day: DayModel): string {
if (day) {
const formattedDate = this.dateIOService.toLocaleDisplayFormatString(day.toDate());

return (
this.commonStrings.parse(this.commonStrings.keys.datepickerToggleChangeDateLabel, {
SELECTED_DATE: formattedDate,
}) || this.commonStrings.keys.datepickerToggle
);
}
return this.commonStrings.keys.datepickerToggleChooseDateLabel || this.commonStrings.keys.datepickerToggle;
}

private listenForDateChanges() {
// because date-input.ts initializes the input in ngAfterViewInit,
// using a databound attribute to change the button labels results in ExpressionChangedAfterItHasBeenCheckedError.
// so instead, update the attribute directly on the element
return this.dateNavigationService.selectedDayChange
.pipe(startWith(this.dateNavigationService.selectedDay))
.subscribe(day => {
const label = this.getToggleButtonLabel(day);
const toggleEl = this.toggleButton.nativeElement;
this.renderer.setAttribute(toggleEl, 'aria-label', label);
this.renderer.setAttribute(toggleEl, 'title', label);
});
}

/**
* Processes the user input and Initializes the Calendar everytime the datepicker popover is open.
*/
Expand Down
11 changes: 10 additions & 1 deletion projects/angular/src/forms/datepicker/day.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016-2021 VMware, Inc. All Rights Reserved.
* Copyright (c) 2016-2022 VMware, Inc. All Rights Reserved.
* This software is released under MIT license.
* The full license information can be found in LICENSE in the root directory of this project.
*/
Expand Down Expand Up @@ -129,6 +129,15 @@ export default function () {
expect(dayBtn.attributes['aria-label'].value).toEqual(dvm.dayModel.toDateString());
});

it('sets aria-selected when the date is selected', () => {
const button: HTMLButtonElement = context.clarityElement.children[0];
expect(button.attributes['aria-selected'].value).toBe('false');
context.testComponent.dayView.isSelected = true;

context.detectChanges();
expect(button.attributes['aria-selected'].value).toBe('true');
});

it('updates the focusable date when a button is focused', () => {
spyOn(context.clarityDirective, 'onDayViewFocus');
const button: HTMLButtonElement = context.clarityElement.children[0];
Expand Down
3 changes: 2 additions & 1 deletion projects/angular/src/forms/datepicker/day.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016-2021 VMware, Inc. All Rights Reserved.
* Copyright (c) 2016-2022 VMware, Inc. All Rights Reserved.
* This software is released under MIT license.
* The full license information can be found in LICENSE in the root directory of this project.
*/
Expand Down Expand Up @@ -27,6 +27,7 @@ import { DateNavigationService } from './providers/date-navigation.service';
(click)="selectDay()"
(focus)="onDayViewFocus()"
[attr.aria-label]="dayString"
[attr.aria-selected]="dayView.isSelected"
>
{{ dayView.dayModel.date }}
</button>
Expand Down
2 changes: 2 additions & 0 deletions projects/angular/src/utils/i18n/common-strings.default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ export const commonStringsDefault: ClrCommonStrings = {
// Date Picker
datepickerDialogLabel: 'Choose date',
datepickerToggle: 'Toggle datepicker',
datepickerToggleChooseDateLabel: 'Choose date',
datepickerToggleChangeDateLabel: 'Change date, {SELECTED_DATE}',
datepickerPreviousMonth: 'Previous month',
datepickerCurrentMonth: 'Current month',
datepickerNextMonth: 'Next month',
Expand Down
2 changes: 2 additions & 0 deletions projects/angular/src/utils/i18n/common-strings.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,8 @@ export interface ClrCommonStrings {
*/
datepickerDialogLabel: string;
datepickerToggle: string;
datepickerToggleChooseDateLabel: string;
datepickerToggleChangeDateLabel: string;
datepickerPreviousMonth: string;
datepickerCurrentMonth: string;
datepickerNextMonth: string;
Expand Down

0 comments on commit 11a75f6

Please sign in to comment.