/** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.dev/license */ import {Injectable, InjectionToken, Optional, SkipSelf, FactoryProvider} from '@angular/core'; import {DateAdapter} from '@angular/material/core'; import {DateRange} from './date-selection-model'; /** Injection token used to customize the date range selection behavior. */ export const MAT_DATE_RANGE_SELECTION_STRATEGY = new InjectionToken< MatDateRangeSelectionStrategy >('MAT_DATE_RANGE_SELECTION_STRATEGY'); /** Object that can be provided in order to customize the date range selection behavior. */ export interface MatDateRangeSelectionStrategy { /** * Called when the user has finished selecting a value. * @param date Date that was selected. Will be null if the user cleared the selection. * @param currentRange Range that is currently show in the calendar. * @param event DOM event that triggered the selection. Currently only corresponds to a `click` * event, but it may get expanded in the future. */ selectionFinished(date: D | null, currentRange: DateRange, event: Event): DateRange; /** * Called when the user has activated a new date (e.g. by hovering over * it or moving focus) and the calendar tries to display a date range. * * @param activeDate Date that the user has activated. Will be null if the user moved * focus to an element that's no a calendar cell. * @param currentRange Range that is currently shown in the calendar. * @param event DOM event that caused the preview to be changed. Will be either a * `mouseenter`/`mouseleave` or `focus`/`blur` depending on how the user is navigating. */ createPreview(activeDate: D | null, currentRange: DateRange, event: Event): DateRange; /** * Called when the user has dragged a date in the currently selected range to another * date. Returns the date updated range that should result from this interaction. * * @param dateOrigin The date the user started dragging from. * @param originalRange The originally selected date range. * @param newDate The currently targeted date in the drag operation. * @param event DOM event that triggered the updated drag state. Will be * `mouseenter`/`mouseup` or `touchmove`/`touchend` depending on the device type. */ createDrag?( dragOrigin: D, originalRange: DateRange, newDate: D, event: Event, ): DateRange | null; } /** Provides the default date range selection behavior. */ @Injectable() export class DefaultMatCalendarRangeStrategy implements MatDateRangeSelectionStrategy { constructor(private _dateAdapter: DateAdapter) {} selectionFinished(date: D, currentRange: DateRange) { let {start, end} = currentRange; if (start == null) { start = date; } else if (end == null && date && this._dateAdapter.compareDate(date, start) >= 0) { end = date; } else { start = date; end = null; } return new DateRange(start, end); } createPreview(activeDate: D | null, currentRange: DateRange) { let start: D | null = null; let end: D | null = null; if (currentRange.start && !currentRange.end && activeDate) { start = currentRange.start; end = activeDate; } return new DateRange(start, end); } createDrag(dragOrigin: D, originalRange: DateRange, newDate: D) { let start = originalRange.start; let end = originalRange.end; if (!start || !end) { // Can't drag from an incomplete range. return null; } const adapter = this._dateAdapter; const isRange = adapter.compareDate(start, end) !== 0; const diffYears = adapter.getYear(newDate) - adapter.getYear(dragOrigin); const diffMonths = adapter.getMonth(newDate) - adapter.getMonth(dragOrigin); const diffDays = adapter.getDate(newDate) - adapter.getDate(dragOrigin); if (isRange && adapter.sameDate(dragOrigin, originalRange.start)) { start = newDate; if (adapter.compareDate(newDate, end) > 0) { end = adapter.addCalendarYears(end, diffYears); end = adapter.addCalendarMonths(end, diffMonths); end = adapter.addCalendarDays(end, diffDays); } } else if (isRange && adapter.sameDate(dragOrigin, originalRange.end)) { end = newDate; if (adapter.compareDate(newDate, start) < 0) { start = adapter.addCalendarYears(start, diffYears); start = adapter.addCalendarMonths(start, diffMonths); start = adapter.addCalendarDays(start, diffDays); } } else { start = adapter.addCalendarYears(start, diffYears); start = adapter.addCalendarMonths(start, diffMonths); start = adapter.addCalendarDays(start, diffDays); end = adapter.addCalendarYears(end, diffYears); end = adapter.addCalendarMonths(end, diffMonths); end = adapter.addCalendarDays(end, diffDays); } return new DateRange(start, end); } } /** @docs-private */ export function MAT_CALENDAR_RANGE_STRATEGY_PROVIDER_FACTORY( parent: MatDateRangeSelectionStrategy, adapter: DateAdapter, ) { return parent || new DefaultMatCalendarRangeStrategy(adapter); } /** @docs-private */ export const MAT_CALENDAR_RANGE_STRATEGY_PROVIDER: FactoryProvider = { provide: MAT_DATE_RANGE_SELECTION_STRATEGY, deps: [[new Optional(), new SkipSelf(), MAT_DATE_RANGE_SELECTION_STRATEGY], DateAdapter], useFactory: MAT_CALENDAR_RANGE_STRATEGY_PROVIDER_FACTORY, };