ember-assembly
Version:
A collection of beautiful UI components built by Goods
281 lines (251 loc) • 6.75 kB
text/typescript
import Component from '@glimmer/component';
import moment, { Moment } from 'moment';
import { action, set } from '@ember/object';
import { isNone, isEmpty } from '@ember/utils';
import { tracked } from '@glimmer/tracking';
type CalendarMode = 'single' | 'multiple' | 'range';
interface AsmUiCalendarArgs {
selected: Moment[];
isLoading?: boolean;
mode?: CalendarMode;
center?: Moment;
daysMetadata?: DayMetadata[];
datePath?: string;
rangeStart?: Moment | undefined;
rangeFinish?: Moment | undefined;
onChangeSelection?: (selection: Moment[]) => void;
onChangeRange?: (date: Moment | undefined) => void | undefined;
onChangeCenter?: (date: Moment | undefined) => void | undefined;
}
interface Day {
id: string;
number: string;
date: Moment;
isCurrentMonth: boolean;
isPreviousMonth: boolean;
isNextMonth: boolean;
isToday: boolean;
isSelected: boolean;
isRangeStart: boolean;
isRangeFinish: boolean;
metadata: any;
}
interface DayMetadata {
date: Moment;
metadata: any;
}
export default class AsmUiCalendar extends Component<AsmUiCalendarArgs> {
/**
*
*/
get mode(): CalendarMode {
return this.args.mode ?? 'multiple';
}
/**
*
*/
get center(): Moment {
return this.args.center ?? moment.utc();
}
/**
*
*/
get selected(): Moment[] {
return this.args.selected ?? [];
}
/**
*
*/
get daysMetadata(): DayMetadata[] {
return this.args.daysMetadata ?? [];
}
/**
*
*/
get datePath(): string {
return this.args.datePath ?? 'date';
}
/**
*
*/
get isLoading(): boolean {
return this.args.isLoading ?? false;
}
/*
A local copy of the range start and finish is needed to handle the problem
where the range is only actually selected once a start and finish have been
chosen.
*/
localRangeStart: Moment | undefined = undefined;
localRangeFinish: Moment | undefined = undefined;
/**
*
*/
get startDate(): Moment {
return moment.utc(this.center).startOf('month').startOf('week');
}
/**
*
*/
get finishDate(): Moment {
return moment.utc(this.center).endOf('month').endOf('week');
}
/**
*
*/
get days(): Day[] {
let days: any = [];
let date = this.startDate.clone();
let currentMonth = moment.utc().format('M');
if (!isNone(this.center)) {
currentMonth = this.center.format('M');
}
let formattedSelected = this.selected.map((selected: any) =>
moment(selected).format('YYYY-MM-DD')
);
let dayMetadataIndex = this.daysMetadata.map((dayMetadata: any) => {
let id = moment(dayMetadata[this.datePath]).format('YYYY-MM-DD');
return {
id,
dayMetadata,
};
});
while (date.isBefore(this.finishDate)) {
let id = date.format('YYYY-MM-DD');
let isSelected = false;
let isRangeStart = false;
let isRangeFinish = false;
if (this.mode == 'range') {
let localRangeFinish = this.localRangeFinish;
if (isNone(localRangeFinish)) {
localRangeFinish = this.localRangeStart;
}
isRangeStart =
!isNone(this.localRangeStart) &&
this.localRangeStart.isSame(date, 'day');
isRangeFinish =
!isNone(this.localRangeFinish) &&
this.localRangeFinish.isSame(date, 'day');
isSelected =
date.isSameOrAfter(this.localRangeStart, 'day') &&
date.isSameOrBefore(localRangeFinish, 'day');
} else {
isSelected =
formattedSelected.find((selected: any) => selected == id) !==
undefined;
}
let metadata = null;
if (!isEmpty(dayMetadataIndex)) {
let item = dayMetadataIndex.find((item: any) => {
return item.id == id;
});
if (!isNone(item)) {
metadata = item.dayMetadata;
}
}
days.push({
id: id,
number: date.format('D'),
date: date.clone(),
isCurrentMonth: date.format('M') == currentMonth,
isPreviousMonth: date.isBefore(this.center, 'month'),
isNextMonth: date.isAfter(this.center, 'month'),
isToday: date.isSame(moment.utc(), 'day'),
isSelected,
isRangeStart,
isRangeFinish,
metadata,
});
date.add(1, 'day');
}
return days;
}
/**
*
*/
get weeks() {
let { days } = this;
let weeks: any = [];
let i = 0;
while (days[i]) {
let daysOfWeek = days.slice(i, i + 7);
weeks.push({
/* @ts-ignore */
id: `week-of-${daysOfWeek[0].id}`,
days: daysOfWeek,
missingDays: 7 - daysOfWeek.length,
});
i += 7;
}
return weeks;
}
/**
*
* @param quantity
* @param unit
*/
onMoveCenter(quantity: number, unit: 'day' | 'month' | 'year') {
let center = moment.utc(this.center).add(quantity, unit);
if (!isNone(this.args.onChangeCenter)) {
this.args.onChangeCenter(center);
} else {
//@ts-ignore
set(this, 'center', center);
}
}
onSelectDate(day: Day) {
let selected: Moment[] = [];
if (this.mode == 'single') {
selected = [day.date];
if (!isNone(this.args.onChangeSelection)) {
this.args.onChangeSelection(selected);
}
} else if (this.mode == 'multiple') {
//@ts-ignore
selected = this.selected.map((date) => date.clone());
if (day.isSelected) {
let date = selected.find((date) => date.format() == day.date.format());
if (!isNone(date)) {
//@ts-ignore
selected.removeObject(date);
}
} else {
//@ts-ignore
selected.pushObject(day.date);
}
if (!isNone(this.args.onChangeSelection)) {
this.args.onChangeSelection(selected);
}
} else if (this.mode == 'range') {
let isChangingStart =
isNone(this.localRangeStart) || !isNone(this.localRangeFinish);
if (isChangingStart) {
this.localRangeStart = day.date;
this.localRangeFinish = undefined;
} else {
if (day.date.isBefore(this.localRangeStart)) {
this.localRangeFinish = this.localRangeStart;
this.localRangeStart = day.date;
} else {
this.localRangeFinish = day.date;
}
if (!isNone(this.args.onChangeRange)) {
//@ts-ignore
this.args.onChangeRange(this.localRangeStart, this.localRangeFinish);
}
}
}
}
/**
*
* @param owner
* @param args
*/
constructor(owner: any, args: any) {
super(owner, args);
this.localRangeStart = this.args.rangeStart;
this.localRangeFinish = this.args.rangeFinish;
}
}