UNPKG

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
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 */ @Injectable() 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; } }