igniteui-angular-sovn
Version:
Ignite UI for Angular is a dependency-free Angular toolkit for building modern web apps
194 lines (179 loc) • 6.86 kB
text/typescript
import { Injectable } from '@angular/core';
import { IgxDayItemComponent } from './day-item.component';
import { IgxDaysViewComponent } from './days-view.component';
import { ScrollMonth } from '../calendar';
enum Direction {
Up = 'ArrowUp',
Down = 'ArrowDown',
Left = 'ArrowLeft',
Right = 'ArrowRight',
}
const ARROW = 'Arrow';
/** @hidden */
export class IgxDaysViewNavigationService {
public monthView: IgxDaysViewComponent;
/**
* Implements kb navigation in all MoveDirections. nextDate and nextMonthView naming convention is used for both previous/next
*
* @hidden
*/
public focusNextDate(target: HTMLElement, key: string, nextView = false) {
if (target.childElementCount === 0) {
target = target.parentElement;
}
if (key.indexOf('Arrow') === -1) {
key = ARROW.concat(key);
}
const monthView = this.monthView;
const node = monthView.dates.find((date) => date.nativeElement === target);
let dates = monthView.dates.toArray();
let day: IgxDayItemComponent; let step; let i; let nextDate: Date;
const index = dates.indexOf(node);
if (!node) {
return;
}
// focus item in current month
switch (key) {
case Direction.Left: {
step = -1;
nextDate = this.timedelta(node.date.date, step);
for (i = index; i > 0; i--) {
day = nextView ? dates[i] : dates[i - 1];
nextDate = day.date.date;
if (day.date.isPrevMonth) {
break;
}
if (day && day.isFocusable) {
day.nativeElement.focus();
return;
}
}
break;
}
case Direction.Right: {
step = 1;
nextDate = this.timedelta(node.date.date, step);
for (i = index; i < dates.length - 1; i++) {
day = nextView ? dates[i] : dates[i + 1];
nextDate = day.date.date;
if (day.date.isNextMonth) {
break;
}
if (day && day.isFocusable) {
day.nativeElement.focus();
return;
}
}
break;
}
case Direction.Up: {
step = -7;
nextDate = this.timedelta(node.date.date, step);
for (i = index; i - 7 > -1; i -= 7) {
day = nextView ? dates[i] : dates[i - 7];
nextDate = day.date.date;
if (day.date.isPrevMonth) {
break;
}
if (day && day.isFocusable) {
day.nativeElement.focus();
return;
}
}
break;
}
case Direction.Down: {
step = 7;
nextDate = this.timedelta(node.date.date, step);
for (i = index; i + 7 < 42; i += 7) {
day = nextView ? dates[i] : dates[i + 7];
nextDate = day.date.date;
if (day.date.isNextMonth) {
break;
}
if (day && day.isFocusable) {
day.nativeElement.focus();
return;
}
}
break;
}
}
// focus item in prev/next visible month
const nextMonthView = step > 0 ? monthView.nextMonthView : monthView.prevMonthView;
if (nextMonthView) {
dates = nextMonthView.dates.toArray();
day = dates.find((item) => item.date.date.getTime() === nextDate.getTime());
if (day && day.isFocusable) {
day.nativeElement.focus();
return;
}
nextMonthView.daysNavService.focusNextDate(day.nativeElement, key);
}
// if iterating in the visible prev/next moths above found a day that is not focusable, ie is disabled, hidden, etc
// then it is needed to recalculate the next day, which is going to be part of the prev/next months
if (day && !day.isFocusable) {
day = dates[i + step];
if (!day) {
nextDate = this.timedelta(node.date.date, step + i - index);
}
}
// focus item in prev/next month, which is currently out of view
let dayIsNextMonth: boolean; // determine what we need to check for next date - if it belongs to prev or next month
if (day) {
dayIsNextMonth = step > 0 ? day.date.isNextMonth : day.date.isPrevMonth;
}
if (monthView.changeDaysView && !nextMonthView && ((day && dayIsNextMonth) || !day)) {
const monthAction = step > 0 ? ScrollMonth.NEXT : ScrollMonth.PREV;
monthView.viewChanging.emit({monthAction, key, nextDate});
}
}
/**
* Focuses first focusable day in the month. Will go to next visible month, if no day in the first month is focusable
*
* @hidden
*/
public focusHomeDate() {
let monthView = this.monthView;
while (!this.focusFirstDay(monthView) && monthView.nextMonthView) {
monthView = monthView.nextMonthView;
}
}
/**
* Focuses last focusable day in the month. Will go to previous visible month, if no day in the first month is focusable
*
* @hidden
*/
public focusEndDate() {
let monthView = this.monthView;
while (!this.focusLastDay(monthView) && monthView.prevMonthView) {
monthView = monthView.prevMonthView;
}
}
private timedelta(date: Date, units: number): Date {
const ret = new Date(date);
ret.setDate(ret.getDate() + units);
return ret;
}
private focusFirstDay(monthView: IgxDaysViewComponent): boolean {
const dates = monthView.dates.filter(d => d.isCurrentMonth);
for (const date of dates) {
if (date.isFocusable) {
date.nativeElement.focus();
return true;
}
}
return false;
}
private focusLastDay(monthView: IgxDaysViewComponent): boolean {
const dates = monthView.dates.filter(d => d.isCurrentMonth);
for (let i = dates.length - 1; i >= 0; i--) {
if (dates[i].isFocusable) {
dates[i].nativeElement.focus();
return true;
}
}
return false;
}
}