UNPKG

@3mo/date-time-fields

Version:

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

268 lines (258 loc) 8.64 kB
import { __decorate } from "tslib"; import { cache, css, html, live, property, style, bind, state, query, ifDefined } from '@a11d/lit'; import { InputFieldComponent } from '@3mo/field'; import { FieldDateTimePrecision } from './FieldDateTimePrecision.js'; /** * @attr open - Whether the date picker is open * @attr pickerHidden - Hide the date picker * @attr shortcutReferenceDate - The date to use as a reference for shortcuts * @attr precision - The precision of the date picker. Defaults to 'minute' */ export class FieldDateTimeBase extends InputFieldComponent { constructor() { super(...arguments); this.open = false; this.pickerHidden = false; this.shortcutReferenceDate = new DateTime; this.precision = FieldDateTimePrecision.Minute; this.navigationDate = new DateTime(); this.handleLanguageChange = () => { this.navigationDate = new DateTime(this.navigationDate); }; this.calendarIconButtonIcon = 'today'; } connected() { Localizer.languages.change.subscribe(this.handleLanguageChange); } disconnected() { Localizer.languages.change.unsubscribe(this.handleLanguageChange); } get formatOptions() { return { calendar: this.selectedDate?.calendarId, timeZone: this.selectedDate?.timeZoneId, ...this.precision.formatOptions, }; } valueUpdated() { super.valueUpdated(); this.resetNavigationDate(); } handleChange(value, e) { super.handleChange(value, e); this.inputStringValue = this.valueToInputValue(value); } get isActive() { return super.isActive || this.open; } get placeholder() { return ''; } static get styles() { return css ` ${super.styles} :host { position: relative; } :host([disabled]) { pointer-events: none; } mo-field { anchor-name: --mo-field-date-time; } mo-popover { position-anchor: --mo-field-date-time; position-visibility: anchors-visible; background: var(--mo-color-background); box-shadow: var(--mo-shadow); border-radius: var(--mo-border-radius); color: var(--mo-color-foreground); font-size: 0.875em; } #selector { min-height: 175px; } .timezone { padding: 0.4rem; font-size: small; text-align: center; font-weight: 500; color: var(--mo-color-gray); } input::placeholder { color: transparent; } mo-field[active]:not([dense]) input::placeholder { color: var(--mo-color-gray); } #presets { background-color: var(--mo-color-transparent-gray-1); mo-list-item { padding-block: 0.5rem; min-height: auto; opacity: 0.9; } } `; } get template() { return html ` ${super.template} ${this.popoverTemplate} `; } get endSlotTemplate() { return html ` ${super.endSlotTemplate} ${this.clearIconButtonTemplate} ${this.calendarIconButtonTemplate} `; } get inputTemplate() { return html ` <input part='input' ?readonly=${this.readonly} ?required=${this.required} ?disabled=${this.disabled} placeholder=${this.placeholder} .value=${live(this.inputStringValue || '')} @input=${(e) => this.handleInput(this.inputValueToValue(this.inputElement.value || ''), e)} @change=${(e) => this.handleChange(this.inputValueToValue(this.inputElement.value || ''), e)} > `; } get clearIconButtonTemplate() { const clear = (e) => { e.stopPropagation(); this.handleInput(undefined); this.handleChange(undefined); }; return !this.value || !this.focusController.focused ? html.nothing : html ` <mo-icon-button tabindex='-1' dense slot='end' icon='cancel' @click=${clear} ${style({ color: 'var(--mo-color-gray)', fontSize: '20px', cursor: 'pointer', userSelect: 'none', marginBlockStart: '2.75px', marginInlineEnd: '5px' })} ></mo-icon-button> `; } get calendarIconButtonTemplate() { return this.pickerHidden ? html.nothing : html ` <mo-icon tabindex='-1' slot='end' icon=${this.calendarIconButtonIcon} ${style({ color: this.isActive ? 'var(--mo-color-accent)' : 'var(--mo-color-gray)', fontSize: '22px', marginBlockStart: '2px', cursor: 'pointer', userSelect: 'none' })} ></mo-icon> `; } get popoverTemplate() { return this.pickerHidden ? html.nothing : html ` <mo-popover tabindex='-1' .anchor=${this} target='field' ?open=${bind(this, 'open', { sourceUpdated: () => setTimeout(() => this.calendar?.setNavigatingValue(this.navigationDate)) })} > ${cache(!this.open ? html.nothing : html ` <mo-flex direction='horizontal'> ${this.presetsTemplate === html.nothing ? html.nothing : html ` <mo-flex id='presets'> ${this.presetsTemplate} </mo-flex> `} ${this.popoverSelectionTemplate} </mo-flex> `)} </mo-popover> `; } get popoverSelectionTemplate() { return html ` <mo-flex id='selector' direction='horizontal' style='height: 100%; flex: 1'> ${this.dateTemplate} ${this.timeTemplate} </mo-flex> `; } getPresetTemplate(label, value) { const handlePresetClick = () => { const v = value(); this.handleChange(v); this.calendar?.setNavigatingValue(v instanceof DateTimeRange ? v.start : v instanceof DateTime ? v : undefined, 'smooth'); }; return html `<mo-list-item @click=${handlePresetClick}>${label}</mo-list-item>`; } get dateTemplate() { return this.calendarTemplate; } get calendarTemplate() { return html ` <mo-calendar .precision=${this.precision > FieldDateTimePrecision.Day ? FieldDateTimePrecision.Day : this.precision} .value=${this.calendarValue} @dateClick=${(e) => this.handleSelectedDateChange(e.detail, this.precision)} ></mo-calendar> `; } get timeTemplate() { return this.precision <= FieldDateTimePrecision.Day ? html.nothing : html ` <mo-flex gap='0.5rem' style='border-inline-start: 1px solid var(--mo-color-transparent-gray-3)'> <mo-flex direction='horizontal' style='flex: 1'> ${this.hourListTemplate} ${this.minuteListTemplate} ${this.secondListTemplate} </mo-flex> <div class='timezone' title=${ifDefined(this.navigationDate?.formatToParts({ timeZoneName: 'long' }).find(x => x.type === 'timeZoneName')?.value)}> ${this.navigationDate?.formatToParts({ timeZoneName: 'shortOffset' }).find(x => x.type === 'timeZoneName')?.value} </div> </mo-flex> `; } get hourListTemplate() { return this.precision < FieldDateTimePrecision.Hour ? html.nothing : html ` <mo-hour-list style='flex: 1' .navigationDate=${bind(this, 'navigationDate')} .value=${this.selectedDate} @change=${(e) => this.handleSelectedDateChange(e.detail, FieldDateTimePrecision.Hour)} ></mo-hour-list> `; } get minuteListTemplate() { return this.precision < FieldDateTimePrecision.Minute ? html.nothing : html ` <mo-minute-list style='flex: 1' .navigationDate=${bind(this, 'navigationDate')} .value=${this.selectedDate} @change=${(e) => this.handleSelectedDateChange(e.detail, FieldDateTimePrecision.Minute)} ></mo-minute-list> `; } get secondListTemplate() { return this.precision < FieldDateTimePrecision.Second ? html.nothing : html ` <mo-second-list style='flex: 1' .navigationDate=${bind(this, 'navigationDate')} .value=${this.selectedDate} @change=${(e) => this.handleSelectedDateChange(e.detail, FieldDateTimePrecision.Second)} ></mo-second-list> `; } handleSelectedDateChange(date, precision) { date; precision; this.change.dispatch(this.value); this.requestUpdate(); } } __decorate([ property({ type: Boolean, reflect: true }) ], FieldDateTimeBase.prototype, "open", void 0); __decorate([ property({ type: Boolean }) ], FieldDateTimeBase.prototype, "pickerHidden", void 0); __decorate([ property({ type: Object }) ], FieldDateTimeBase.prototype, "shortcutReferenceDate", void 0); __decorate([ property({ type: String, converter: value => FieldDateTimePrecision.parse(value || undefined) }) ], FieldDateTimeBase.prototype, "precision", void 0); __decorate([ state() ], FieldDateTimeBase.prototype, "navigationDate", void 0); __decorate([ query('mo-calendar') ], FieldDateTimeBase.prototype, "calendar", void 0);