UNPKG

@3mo/date-time-fields

Version:

Date time fields let people select dates, date-ranges, and times.

409 lines (384 loc) 14.5 kB
var Calendar_1; import { __decorate } from "tslib"; import { Component, css, component, html, property, event, repeat, state, unsafeCSS } from '@a11d/lit'; import { hasChanged } from '@a11d/equals'; import { CalendarDatesController } from './CalendarDatesController.js'; import { FieldDateTimePrecision } from '../FieldDateTimePrecision.js'; /** * @fires dateClick - Dispatched when a date is clicked, with the clicked date as detail. */ let Calendar = Calendar_1 = class Calendar extends Component { constructor() { super(...arguments); this.includeWeek = false; this.datesController = new CalendarDatesController(this); this.view = FieldDateTimePrecision.Day; this.handleItemClick = (date, precision) => { return () => { if (this.precision === precision) { if (this.isDisabled(date, precision)) { return; } this.dateClick.dispatch(date); this.setNavigatingValue(date, 'smooth'); } else { const nextView = this.view !== precision ? precision : this.view === FieldDateTimePrecision.Year ? FieldDateTimePrecision.Month : this.view === FieldDateTimePrecision.Month ? FieldDateTimePrecision.Day : this.view; this.setView(nextView, date); } }; }; } setView(view, navigationDate = this.datesController.navigationDate) { this.view = view; this.setNavigatingValue(navigationDate); } get navigationDate() { return this.datesController.navigationDate; } async setNavigatingValue(date, behavior = 'instant') { this.datesController.disableObservers = true; this.datesController.navigationDate = date; await this.updateComplete; await new Promise(r => setTimeout(r, 10)); this.renderRoot.querySelector(`.${this.view}[data-navigating]`) ?.scrollIntoView({ block: 'center', behavior }); await new Promise(r => setTimeout(r, behavior === 'instant' ? 10 : 100)); this.datesController.disableObservers = false; } static get styles() { const year = unsafeCSS(FieldDateTimePrecision.Year.key); const month = unsafeCSS(FieldDateTimePrecision.Month.key); const week = unsafeCSS(FieldDateTimePrecision.Week.key); const day = unsafeCSS(FieldDateTimePrecision.Day.key); return css ` :host { --mo-calendar-item-size: 2.25rem; flex: 1; } .scroller { height: min(450px, 100vh); display: grid; position: relative; scrollbar-width: none; overflow-x: hidden; scroll-behavior: smooth; &[data-view=${week}], &[data-view=${day}] { grid-template-columns: repeat(1, 1fr); } &[data-view=${month}] { grid-template-columns: repeat(3, 1fr); } &[data-view=${year}] { grid-template-columns: repeat(5, 1fr); } } .year, .month, .day { display: flex; align-items: center; justify-content: center; text-align: center; font-weight: 500; user-select: none; } .year, .month, .week[data-view=${week}], .day:not([data-view=${week}]) { transition: 0.2s; border-radius: var(--mo-border-radius); &:hover { background: var(--mo-color-transparent-gray-3); } &[data-now] { outline: 2px dashed var(--mo-color-gray-transparent); } &[data-start], &[data-end] { background: var(--mo-color-accent-transparent); opacity: 1; color: color-mix(in srgb, var(--mo-color-accent), var(--mo-color-foreground)) !important; } &[data-in-range] { background: color-mix(in srgb, var(--mo-color-accent), transparent 92%); } &[data-disabled] { opacity: 0.38; pointer-events: none; } } /* Headings */ .year[data-view=${month}], .month[data-view=${week}], .month[data-view=${day}] { font-size: 1.125rem; font-weight: 500; height: 2rem; padding: 0.5rem; } /* Selection */ .year[data-view=${year}], .month[data-view=${month}], .week[data-view=${week}], .day[data-view=${day}] { opacity: 0.875; font-size: 0.94rem; cursor: pointer; height: var(--mo-calendar-item-size); } .year { &[data-view=${week}], &[data-view=${day}] { display: none; } &:not([data-view=${year}]) { grid-column: -1 / 1; border-block-start: 1px solid var(--mo-color-transparent-gray-3); } padding-inline: 0.5rem; } .month-container { width: 100%; position: relative; &[data-view=${week}], &[data-view=${day}] { &::before { content: ' '; position: absolute; display: inline-block; width: 1px; height: 100%; inset-block: 0; background: var(--mo-color-transparent-gray-3); } &::after { content: ' '; position: absolute; display: inline-block; height: 1px; width: 100%; inset-inline: 0; background: var(--mo-color-transparent-gray-3); } } & > mo-grid { padding-inline: 0.5rem; padding-block-end: 0.5rem; margin-block-start: -0.375rem; } &[data-view=${month}] { & > mo-grid { display: none; } } } .weekdays { text-align: center; opacity: 0.5; font-size: 0.75rem; font-weight: 500; grid-column: -1 / 1; user-select: none; span { display: flex; align-items: center; justify-content: center; } } .month { padding-inline: 0.5rem; } .week { grid-column: 1 / -1; } .week-number { opacity: 0.5; display: flex; align-items: center; justify-content: center; } .day { width: var(--mo-calendar-item-size); height: var(--mo-calendar-item-size); } `; } get includeWeekNumber() { return this.includeWeek || FieldDateTimePrecision.Week === this.precision; } get columns() { return [ !this.includeWeekNumber ? undefined : '[week-number] var(--mo-calendar-item-size)', ...CalendarDatesController.sampleWeek.map(day => `[${this.getColumnName(day)}] var(--mo-calendar-item-size)`), ].join(' '); } getColumnName(date) { return `day-${date.dayOfWeek}`; } get template() { return html ` <div class='scroller' data-view=${this.view.key}> ${repeat(this.datesController.data, d => d.toString(), d => this.getYearTemplate(d))} </div> `; } getYearTemplate(date) { return html ` ${date.dayOfYear !== 1 ? html.nothing : html ` <div class='year' role='button' data-view=${this.view.key} ?data-navigating=${this.isNavigating(date, FieldDateTimePrecision.Year)} ?data-now=${this.isNow(date, FieldDateTimePrecision.Year)} ?data-start=${this.isStart(date, FieldDateTimePrecision.Year)} ?data-end=${this.isEnd(date, FieldDateTimePrecision.Year)} ?data-in-range=${this.isInRange(date, FieldDateTimePrecision.Year)} ?data-disabled=${this.isDisabled(date, FieldDateTimePrecision.Year)} @click=${this.handleItemClick(date, FieldDateTimePrecision.Year)} ${this.datesController.observerIntersectionNavigation(date, FieldDateTimePrecision.Month, FieldDateTimePrecision.Year)} > ${date.format({ year: 'numeric' })} </div> `} ${this.view < FieldDateTimePrecision.Month || date.day !== 1 ? html.nothing : this.getMonthTemplate(date)} `; } static get weekDaysTemplate() { return html ` <mo-grid class='weekdays' columns='subgrid'> ${CalendarDatesController.sampleWeek.map((day, index) => { const { narrow, short, long } = { narrow: day.format({ weekday: 'narrow' }), short: day.format({ weekday: 'short' }), long: day.format({ weekday: 'long' }), }; return html ` <span style='grid-column: day-${index + 1}' title=${long}> ${long === short ? narrow : short} </span> `; })} </mo-grid> `; } getMonthTemplate(date) { return html ` <mo-flex class='month-container' data-view=${this.view.key}> <div class='month' role='button' data-view=${this.view.key} ?data-navigating=${this.isNavigating(date, FieldDateTimePrecision.Month)} ?data-now=${this.isNow(date, FieldDateTimePrecision.Month)} ?data-start=${this.isStart(date, FieldDateTimePrecision.Month)} ?data-end=${this.isEnd(date, FieldDateTimePrecision.Month)} ?data-in-range=${this.isInRange(date, FieldDateTimePrecision.Month)} ?data-disabled=${this.isDisabled(date, FieldDateTimePrecision.Month)} @click=${this.handleItemClick(date, FieldDateTimePrecision.Month)} ${this.datesController.observerIntersectionNavigation(date, FieldDateTimePrecision.Day)} > ${date.format(this.view > FieldDateTimePrecision.Month ? { year: 'numeric', month: 'long' } : { month: 'long' })} </div> ${this.view < FieldDateTimePrecision.Week ? html.nothing : html ` <mo-grid justifyContent='center' autoRows='var(--mo-calendar-item-size)' columns=${this.view > FieldDateTimePrecision.Month ? this.columns : 'auto'}> ${Calendar_1.weekDaysTemplate} ${this.datesController.data.filter(d => d.year === date.year && d.month === date.month).map((day, _, month) => this.getWeekTemplate(day, month))} </mo-grid> `} </mo-flex> `; } getWeekTemplate(date, month) { if (date.yearOfWeek === undefined || date.weekOfYear === undefined) { return this.getDayTemplate(date); } if (date !== month.find(d => d.weekOfYear === date.weekOfYear && d.yearOfWeek === date.yearOfWeek)) { return html.nothing; } return html ` <mo-grid class='week' columns='subgrid' data-view=${this.view.key} ?data-navigating=${this.isNavigating(date, FieldDateTimePrecision.Week)} ?data-now=${this.isNow(date, FieldDateTimePrecision.Week)} ?data-start=${this.isStart(date, FieldDateTimePrecision.Week)} ?data-end=${this.isEnd(date, FieldDateTimePrecision.Week)} ?data-in-range=${this.isInRange(date, FieldDateTimePrecision.Week)} ?data-disabled=${this.isDisabled(date, FieldDateTimePrecision.Week)} @click=${this.precision === FieldDateTimePrecision.Day ? html.nothing : this.handleItemClick(date, FieldDateTimePrecision.Week)} ${this.datesController.observerIntersectionNavigation(date, FieldDateTimePrecision.Week)} > ${month.filter(d => d.weekOfYear === date.weekOfYear && d.yearOfWeek === date.yearOfWeek).map(day => this.getDayTemplate(day))} </mo-grid> `; } getDayTemplate(day) { return html ` ${this.includeWeekNumber === false || day.dayOfWeek !== 1 ? html.nothing : html ` <div class='week-number' style='grid-column: week-number'>${day.weekOfYear?.format()}</div> `} <div tabindex='0' role='button' class='day' style='grid-column: ${this.getColumnName(day)}' data-view=${this.view.key} ?data-navigating=${this.isNavigating(day, FieldDateTimePrecision.Day)} ?data-now=${this.isNow(day, FieldDateTimePrecision.Day)} ?data-start=${this.isStart(day, FieldDateTimePrecision.Day)} ?data-end=${this.isEnd(day, FieldDateTimePrecision.Day)} ?data-in-range=${this.isInRange(day, FieldDateTimePrecision.Day)} ?data-disabled=${this.isDisabled(day, FieldDateTimePrecision.Day)} @click=${this.precision === FieldDateTimePrecision.Week ? html.nothing : this.handleItemClick(day, FieldDateTimePrecision.Day)} > ${day.format({ day: 'numeric' })} </div> `; } isDisabled(date, precision) { return false || (!!this.min && precision.isSmallerThan(date, this.min) && !precision.equals(date, this.min)) || (!!this.max && precision.isSmallerThan(this.max, date) && !precision.equals(this.max, date)) || (this.dateDisabled?.(date) ?? false); } isNavigating(date, precision) { if (this.view !== precision) { return false; } if (precision === FieldDateTimePrecision.Week) { return this.navigationDate.yearOfWeek === date.yearOfWeek && this.navigationDate.weekOfYear === date.weekOfYear; } return this.navigationDate.year === date.year && (precision < FieldDateTimePrecision.Month || this.navigationDate.month === date.month) && (precision < FieldDateTimePrecision.Day || this.navigationDate.day === date.day); } isNow(date, precision) { return this.view === precision && precision.equals(date, CalendarDatesController.today); } isStart(date, precision) { return precision === this.precision && !!this.value?.start && precision.equals(date, this.value?.start); } isEnd(date, precision) { return precision === this.precision && !!this.value?.end && precision.equals(date, this.value?.end); } isInRange(date, precision) { return precision === this.precision && !!this.value?.start && !!this.value?.end && precision.isSmallerThan(this.value.start, date) && precision.isSmallerThan(date, this.value.end); } }; __decorate([ event() ], Calendar.prototype, "dateClick", void 0); __decorate([ property({ type: Object }) ], Calendar.prototype, "value", void 0); __decorate([ property({ type: Object, updated() { this.setView(this.precision); } }) ], Calendar.prototype, "precision", void 0); __decorate([ property({ type: Boolean, reflect: true }) ], Calendar.prototype, "includeWeek", void 0); __decorate([ property({ type: Object }) ], Calendar.prototype, "min", void 0); __decorate([ property({ type: Object }) ], Calendar.prototype, "max", void 0); __decorate([ property({ type: Object, hasChanged }) ], Calendar.prototype, "dateDisabled", void 0); __decorate([ state() ], Calendar.prototype, "view", void 0); Calendar = Calendar_1 = __decorate([ component('mo-calendar') ], Calendar); export { Calendar };