Skip to content

Commit

Permalink
feat(datepicker): allow configuring weekday width
Browse files Browse the repository at this point in the history
- introduces new `@Input() weekday: boolean | TranslationWidth` in `NgbDatepicker` and `NgbInputDatepicker`
- introduces new `getWeekdayLabel(date, width)` in `NgbDatepickerI18n`

Fixes ng-bootstrap#2516
  • Loading branch information
maxokorokov committed Mar 8, 2021
1 parent f2b52d9 commit 70297f3
Show file tree
Hide file tree
Showing 14 changed files with 226 additions and 69 deletions.
2 changes: 2 additions & 0 deletions src/datepicker/datepicker-config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {Injectable, TemplateRef} from '@angular/core';
import {TranslationWidth} from '@angular/common';
import {DayTemplateContext} from './datepicker-day-template-context';
import {NgbDateStruct} from './ngb-date-struct';

Expand All @@ -23,4 +24,5 @@ export class NgbDatepickerConfig {
showWeekdays = true;
showWeekNumbers = false;
startDate: {year: number, month: number};
weekdays: TranslationWidth | boolean = TranslationWidth.Short;
}
9 changes: 8 additions & 1 deletion src/datepicker/datepicker-i18n.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,20 @@ describe('ngb-datepicker-i18n-default', () => {
expect(i18n.getMonthFullName(13)).toBe('');
});

it('should return weekday name', () => {
it('should return weekday short name', () => {
expect(i18n.getWeekdayShortName(0)).toBe('');
expect(i18n.getWeekdayShortName(1)).toBe('Mo');
expect(i18n.getWeekdayShortName(7)).toBe('Su');
expect(i18n.getWeekdayShortName(8)).toBe('');
});

it('should return weekday label', () => {
expect(i18n.getWeekdayLabel(0)).toBe('');
expect(i18n.getWeekdayLabel(1)).toBe('Mo');
expect(i18n.getWeekdayLabel(7)).toBe('Su');
expect(i18n.getWeekdayLabel(8)).toBe('');
});

it('should generate aria label for a date',
() => { expect(i18n.getDayAriaLabel(new NgbDate(2010, 10, 8))).toBe('Friday, October 8, 2010'); });

Expand Down
24 changes: 18 additions & 6 deletions src/datepicker/datepicker-i18n.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {Inject, Injectable, LOCALE_ID} from '@angular/core';
import {FormStyle, getLocaleDayNames, getLocaleMonthNames, TranslationWidth, formatDate} from '@angular/common';
import {formatDate, FormStyle, getLocaleDayNames, getLocaleMonthNames, TranslationWidth} from '@angular/common';
import {NgbDateStruct} from './ngb-date-struct';

export function NGB_DATEPICKER_18N_FACTORY(locale) {
Expand All @@ -25,9 +25,18 @@ export abstract class NgbDatepickerI18n {
* Returns the short weekday name to display in the heading of the month view.
*
* With default calendar we use ISO 8601: 'weekday' is 1=Mon ... 7=Sun.
*
* @deprecated 9.1.0, use 'getWeekdayLabel' instead
*/
abstract getWeekdayShortName(weekday: number): string;

/**
* Returns the weekday label using specified width
*
* @since 9.1.0
*/
getWeekdayLabel(weekday: number, width?: TranslationWidth): string { return this.getWeekdayShortName(weekday); }

/**
* Returns the short month name to display in the date picker navigation.
*
Expand Down Expand Up @@ -88,21 +97,24 @@ export abstract class NgbDatepickerI18n {
*/
@Injectable()
export class NgbDatepickerI18nDefault extends NgbDatepickerI18n {
private _weekdaysShort: readonly string[];
private _monthsShort: readonly string[];
private _monthsFull: readonly string[];

constructor(@Inject(LOCALE_ID) private _locale: string) {
super();

const weekdaysStartingOnSunday = getLocaleDayNames(_locale, FormStyle.Standalone, TranslationWidth.Short);
this._weekdaysShort = weekdaysStartingOnSunday.map((day, index) => weekdaysStartingOnSunday[(index + 1) % 7]);

this._monthsShort = getLocaleMonthNames(_locale, FormStyle.Standalone, TranslationWidth.Abbreviated);
this._monthsFull = getLocaleMonthNames(_locale, FormStyle.Standalone, TranslationWidth.Wide);
}

getWeekdayShortName(weekday: number): string { return this._weekdaysShort[weekday - 1] || ''; }
getWeekdayShortName(weekday: number): string { return this.getWeekdayLabel(weekday, TranslationWidth.Short); }

getWeekdayLabel(weekday: number, width?: TranslationWidth): string {
const weekdaysStartingOnSunday =
getLocaleDayNames(this._locale, FormStyle.Standalone, width === undefined ? TranslationWidth.Short : width);
const weekdays = weekdaysStartingOnSunday.map((day, index) => weekdaysStartingOnSunday[(index + 1) % 7]);
return weekdays[weekday - 1] || '';
}

getMonthShortName(month: number): string { return this._monthsShort[month - 1] || ''; }

Expand Down
11 changes: 11 additions & 0 deletions src/datepicker/datepicker-input.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -747,6 +747,17 @@ describe('NgbInputDatepicker', () => {
expect(dp.showWeekdays).toBeTruthy();
});

it('should propagate the "weekdays" option', () => {
const fixture = createTestCmpt(`<input ngbDatepicker [weekdays]="false">`);
const dpInput = fixture.debugElement.query(By.directive(NgbInputDatepicker)).injector.get(NgbInputDatepicker);

dpInput.open();
fixture.detectChanges();

const dp = fixture.debugElement.query(By.css('ngb-datepicker')).injector.get(NgbDatepicker);
expect(dp.weekdays).toBeFalse();
});

it('should propagate the "showWeekNumbers" option', () => {
const fixture = createTestCmpt(`<input ngbDatepicker [showWeekNumbers]="true">`);
const dpInput = fixture.debugElement.query(By.directive(NgbInputDatepicker)).injector.get(NgbInputDatepicker);
Expand Down
27 changes: 24 additions & 3 deletions src/datepicker/datepicker-input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
TemplateRef,
ViewContainerRef
} from '@angular/core';
import {DOCUMENT} from '@angular/common';
import {DOCUMENT, TranslationWidth} from '@angular/common';
import {
AbstractControl,
ControlValueAccessor,
Expand Down Expand Up @@ -69,12 +69,14 @@ export class NgbInputDatepicker implements OnChanges,
static ngAcceptInputType_disabled: boolean | '';
static ngAcceptInputType_navigation: string;
static ngAcceptInputType_outsideDays: string;
static ngAcceptInputType_weekdays: boolean | number;

private _cRef: ComponentRef<NgbDatepicker>| null = null;
private _disabled = false;
private _elWithFocus: HTMLElement | null = null;
private _model: NgbDate | null = null;
private _inputValue: string;
private _showWeekdays: boolean;
private _zoneSubscription: any;

/**
Expand Down Expand Up @@ -197,8 +199,16 @@ export class NgbInputDatepicker implements OnChanges,

/**
* If `true`, weekdays will be displayed.
*
* @deprecated 9.1.0, please use 'weekdays' instead
*/
@Input() showWeekdays: boolean;
@Input()
set showWeekdays(weekdays: boolean) {
this.weekdays = weekdays;
this._showWeekdays = weekdays;
}

get showWeekdays(): boolean { return this._showWeekdays; }

/**
* If `true`, week numbers will be displayed.
Expand Down Expand Up @@ -231,6 +241,17 @@ export class NgbInputDatepicker implements OnChanges,
*/
@Input() positionTarget: string | HTMLElement;

/**
* The way weekdays should be displayed.
*
* * `true` - weekdays are displayed using default width
* * `false` - weekdays are not displayed
* * `TranslationWidth` - weekdays are displayed using specified width
*
* @since 9.1.0
*/
@Input() weekdays: TranslationWidth | boolean;

/**
* An event emitted when user selects a date using keyboard or mouse.
*
Expand Down Expand Up @@ -453,7 +474,7 @@ export class NgbInputDatepicker implements OnChanges,

private _applyDatepickerInputs(datepickerInstance: NgbDatepicker): void {
['dayTemplate', 'dayTemplateData', 'displayMonths', 'firstDayOfWeek', 'footerTemplate', 'markDisabled', 'minDate',
'maxDate', 'navigation', 'outsideDays', 'showNavigation', 'showWeekdays', 'showWeekNumbers']
'maxDate', 'navigation', 'outsideDays', 'showNavigation', 'showWeekNumbers', 'weekdays']
.forEach((optionName: string) => {
if (this[optionName] !== undefined) {
datepickerInstance[optionName] = this[optionName];
Expand Down
23 changes: 1 addition & 22 deletions src/datepicker/datepicker-month.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,6 @@ const createTestComponent = () => createGenericTestComponent(
`,
TestComponent) as ComponentFixture<TestComponent>;

function getWeekdays(element: HTMLElement): HTMLElement[] {
return <HTMLElement[]>Array.from(element.querySelectorAll('.ngb-dp-weekday'));
}

function getWeekNumbers(element: HTMLElement): HTMLElement[] {
return <HTMLElement[]>Array.from(element.querySelectorAll('.ngb-dp-week-number'));
}
Expand All @@ -37,11 +33,6 @@ function getDates(element: HTMLElement): HTMLElement[] {
return <HTMLElement[]>Array.from(element.querySelectorAll('.ngb-dp-day'));
}

function expectWeekdays(element: HTMLElement, weekdays: string[]) {
const result = getWeekdays(element).map(td => td.innerText.trim());
expect(result).toEqual(weekdays);
}

function expectWeekNumbers(element: HTMLElement, weeknumbers: string[]) {
const result = getWeekNumbers(element).map(td => td.innerText.trim());
expect(result).toEqual(weeknumbers);
Expand All @@ -60,7 +51,7 @@ class MockDatepickerService extends NgbDatepickerService {
lastDate: new NgbDate(2016, 8, 31),
year: 2016,
number: 8,
weekdays: [1, 2],
weekdays: ['Mo', 'Tu'],
weeks: [
// month: 7, 8
{
Expand Down Expand Up @@ -241,18 +232,6 @@ describe('ngb-datepicker-month', () => {
});
});

it('should show/hide weekdays', () => {
const fixture = createTestComponent();
fixture.componentInstance.showWeekNumbers = false;
fixture.detectChanges();

expectWeekdays(fixture.nativeElement, ['Mo', 'Tu']);

fixture.componentInstance.showWeekdays = false;
fixture.detectChanges();
expectWeekdays(fixture.nativeElement, []);
});

it('should show/hide week numbers', () => {
const fixture = createTestComponent();

Expand Down
6 changes: 2 additions & 4 deletions src/datepicker/datepicker-month.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,9 @@ import {NgbDateStruct} from './ngb-date-struct';
encapsulation: ViewEncapsulation.None,
styleUrls: ['./datepicker-month.scss'],
template: `
<div *ngIf="datepicker.showWeekdays" class="ngb-dp-week ngb-dp-weekdays" role="row">
<div *ngIf="viewModel.weekdays.length > 0" class="ngb-dp-week ngb-dp-weekdays" role="row">
<div *ngIf="datepicker.showWeekNumbers" class="ngb-dp-weekday ngb-dp-showweek"></div>
<div *ngFor="let w of viewModel.weekdays" class="ngb-dp-weekday small" role="columnheader">
{{ i18n.getWeekdayShortName(w) }}
</div>
<div *ngFor="let weekday of viewModel.weekdays" class="ngb-dp-weekday small" role="columnheader">{{ weekday }}</div>
</div>
<ng-template ngFor let-week [ngForOf]="viewModel.weeks">
<div *ngIf="!week.collapsed" class="ngb-dp-week" role="row">
Expand Down
30 changes: 27 additions & 3 deletions src/datepicker/datepicker-service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {NgbDate} from './ngb-date';
import {Subscription} from 'rxjs';
import {DatepickerViewModel} from './datepicker-view-model';
import {NgbDatepickerI18n, NgbDatepickerI18nDefault} from './datepicker-i18n';
import {TranslationWidth} from '@angular/common';

describe('ngb-datepicker-service', () => {

Expand Down Expand Up @@ -232,18 +233,18 @@ describe('ngb-datepicker-service', () => {
it(`should generate a month with firstDayOfWeek=1 by default`, () => {
service.focus(new NgbDate(2017, 5, 5));
expect(model.months.length).toBe(1);
expect(model.months[0].weekdays[0]).toBe(1);
expect(model.months[0].weekdays[0]).toBe('Mo');
});

it(`should generate weeks starting with 'firstDayOfWeek'`, () => {
service.set({firstDayOfWeek: 2});
service.focus(new NgbDate(2017, 5, 5));
expect(model.months.length).toBe(1);
expect(model.months[0].weekdays[0]).toBe(2);
expect(model.months[0].weekdays[0]).toBe('Tu');

service.set({firstDayOfWeek: 4});
expect(model.months.length).toBe(1);
expect(model.months[0].weekdays[0]).toBe(4);
expect(model.months[0].weekdays[0]).toBe('Th');
});

it(`should update months when 'firstDayOfWeek' changes`, () => {
Expand All @@ -262,6 +263,29 @@ describe('ngb-datepicker-service', () => {
});
});

describe(`weekday`, () => {

it(`should update visibility and width correctly`, () => {
// default values
service.focus(new NgbDate(2017, 5, 1));
expect(model.weekdayWidth).toBe(TranslationWidth.Short);
expect(model.weekdaysVisible).toBeTrue();
expect(model.months[0].weekdays).toEqual(['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su']);

// Specific width
service.set({weekdays: TranslationWidth.Narrow});
expect(model.weekdayWidth).toBe(TranslationWidth.Narrow);
expect(model.weekdaysVisible).toBeTrue();
expect(model.months[0].weekdays).toEqual(['M', 'T', 'W', 'T', 'F', 'S', 'S']);

// false
service.set({weekdays: false});
expect(model.weekdayWidth).toBe(TranslationWidth.Short);
expect(model.weekdaysVisible).toBeFalse();
expect(model.months[0].weekdays).toEqual([]);
});
});

describe(`displayMonths`, () => {

it(`should emit only positive numeric 'displayMonths' values`, () => {
Expand Down
46 changes: 34 additions & 12 deletions src/datepicker/datepicker-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,33 @@ import {
buildMonths,
checkDateInRange,
checkMinBeforeMax,
generateSelectBoxMonths,
generateSelectBoxYears,
isChangedDate,
isChangedMonth,
isDateSelectable,
generateSelectBoxYears,
generateSelectBoxMonths,
prevMonthDisabled,
nextMonthDisabled
nextMonthDisabled,
prevMonthDisabled
} from './datepicker-tools';

import {filter} from 'rxjs/operators';
import {NgbDatepickerI18n} from './datepicker-i18n';

export interface DatepickerServiceInputs extends Partial<
Required<Pick<DatepickerViewModel, 'dayTemplateData' | 'displayMonths' | 'disabled' | 'firstDayOfWeek' |
'focusVisible' | 'markDisabled' | 'maxDate' | 'minDate' | 'navigation' | 'outsideDays'>>> {}
import {TranslationWidth} from '@angular/common';


export type DatepickerServiceInputs = Partial<{
dayTemplateData: NgbDayTemplateData,
displayMonths: number,
disabled: boolean,
firstDayOfWeek: number,
focusVisible: boolean,
markDisabled: NgbMarkDisabled,
maxDate: NgbDate | null,
minDate: NgbDate | null,
navigation: 'select' | 'arrows' | 'none',
outsideDays: 'visible' | 'collapsed' | 'hidden',
weekdays: TranslationWidth | boolean
}>;

@Injectable()
export class NgbDatepickerService {
Expand Down Expand Up @@ -61,13 +73,13 @@ export class NgbDatepickerService {
return {markDisabled};
}
},
maxDate: (date: NgbDate) => {
maxDate: (date: NgbDate | null) => {
const maxDate = this.toValidDate(date, null);
if (isChangedDate(this._state.maxDate, maxDate)) {
return {maxDate};
}
},
minDate: (date: NgbDate) => {
minDate: (date: NgbDate | null) => {
const minDate = this.toValidDate(date, null);
if (isChangedDate(this._state.minDate, minDate)) {
return {minDate};
Expand All @@ -82,6 +94,13 @@ export class NgbDatepickerService {
if (this._state.outsideDays !== outsideDays) {
return {outsideDays};
}
},
weekdays: (weekdays: boolean | TranslationWidth) => {
const weekdayWidth = weekdays === true || weekdays === false ? TranslationWidth.Short : weekdays;
const weekdaysVisible = weekdays === true || weekdays === false ? weekdays : true;
if (this._state.weekdayWidth !== weekdayWidth || this._state.weekdaysVisible !== weekdaysVisible) {
return {weekdayWidth, weekdaysVisible};
}
}
};

Expand All @@ -107,7 +126,9 @@ export class NgbDatepickerService {
prevDisabled: false,
nextDisabled: false,
selectedDate: null,
selectBoxes: {years: [], months: []}
selectBoxes: {years: [], months: []},
weekdayWidth: TranslationWidth.Short,
weekdaysVisible: true
};

get model$(): Observable<DatepickerViewModel> { return this._model$.pipe(filter(model => model.months.length > 0)); }
Expand Down Expand Up @@ -270,7 +291,8 @@ export class NgbDatepickerService {
// rebuilding months
if (startDate) {
const forceRebuild = 'dayTemplateData' in patch || 'firstDayOfWeek' in patch || 'markDisabled' in patch ||
'minDate' in patch || 'maxDate' in patch || 'disabled' in patch || 'outsideDays' in patch;
'minDate' in patch || 'maxDate' in patch || 'disabled' in patch || 'outsideDays' in patch ||
'weekdaysVisible' in patch;

const months = buildMonths(this._calendar, startDate, state, this._i18n, forceRebuild);

Expand Down
Loading

0 comments on commit 70297f3

Please sign in to comment.