@gez/date-time-kit
Version:
374 lines (351 loc) • 13.1 kB
text/typescript
import {
type DateTimeGranularity,
closestByEvent,
granHelper
} from '../../utils';
import {
type Ele as CalendarBaseEle,
type EventMap as CalendarBaseEvent,
type Weeks,
weekKey
} from '../calendar';
import {
Ele as HhMmSsMsSelectorEle,
type EventMap as HhMmSsMsSelectorEvent
} from '../hhmmss-ms-list-grp/selector';
import { type BaseAttrs, type BaseEmits, UiBase } from '../web-component-base';
import {
Ele as YyyyMmNavEle,
type EventMap as YyyyMmNavEvent
} from '../yyyymm-nav';
import styleStr from './index.css';
import html from './index.html';
export const granularityList = granHelper.dateTime.list;
export type Granularity = DateTimeGranularity;
export interface Attrs extends BaseAttrs {
/**
* The start time of the calendar display range.
* @type {`string | number`} A value that can be passed to the Date constructor.
* @default Date.now()
*/
'time-start'?: string | number;
/**
* The end time of the calendar display range.
* @type {`string | number`} A value that can be passed to the Date constructor.
* @default 'time-start'
*/
'time-end': string | number;
/**
* 选择器的粒度,表示最小可选的时间单位。默认为 millisecond。
* 例如设置为 'minute',则表示只能选择到分钟,秒和毫秒将被忽略。忽略的时间单位视情况重置为 0 或 23 或 59 或 999。
*/
'min-granularity'?: Granularity;
/**
* Set which day of the week is the first day.
* @type `'sun' | 'mon' | 'tue' | 'wed' | 'thu' | 'fri' | 'sat'`
* @default 'sun'
*/
'week-start-at'?: Weeks;
}
export interface Emits extends BaseEmits {
change: {
oldStartTime: Date;
oldEndTime: Date;
newStartTime: Date;
newEndTime: Date;
};
}
const diffInMonth = (a: Date, b: Date) => {
if (a > b) [a, b] = [b, a];
const aYear = a.getFullYear();
const aMonth = a.getMonth();
const bYear = b.getFullYear();
const bMonth = b.getMonth();
return bYear * 12 + bMonth - (aYear * 12 + aMonth);
};
/**
* 时间段选择器(两个日历)
*
* 存在一个 timeFormatter 方法,可以重写该方法以自定义时分秒毫秒的回显格式。
*/
export class Ele extends UiBase<Attrs, Emits> {
public static readonly tagName = 'dt-period-selector' as const;
protected static _style = styleStr;
protected static _template = html;
static get observedAttributes(): string[] {
return [
...(super.observedAttributes as (keyof BaseAttrs)[]),
'time-start',
'time-end',
'min-granularity',
'week-start-at'
] satisfies (keyof Attrs)[];
}
public get timeStart() {
const v = this._getAttr('time-start', '' + Date.now());
return new Date(Number.isNaN(+v) ? v : +v);
}
public set timeStart(val: number | string | Date) {
const v = new Date(val);
if (Number.isNaN(+v)) return;
this.setAttribute('time-start', +v + '');
}
public get timeEnd() {
const v = this._getAttr('time-end', '' + this.timeStart);
return new Date(Number.isNaN(+v) ? v : +v);
}
public set timeEnd(val: number | string | Date) {
const v = new Date(val);
if (Number.isNaN(+v)) return;
this.setAttribute('time-end', +v + '');
}
public get weekStartAt() {
return this._getAttr('week-start-at', 'sun');
}
public set weekStartAt(val: Weeks) {
if (!weekKey.includes(val)) return;
this.setAttribute('week-start-at', val);
}
public get minGranularity() {
return this._getAttr('min-granularity', 'millisecond');
}
public set minGranularity(val: Granularity) {
if (!granHelper.dateTime.has(val)) return;
this.setAttribute('min-granularity', val);
}
get _staticEls() {
return {
...super._staticEls,
allNav: this.$<YyyyMmNavEle>`dt-yyyymm-nav`!,
startNav: this.$0<YyyyMmNavEle>`.start dt-yyyymm-nav`!,
endNav: this.$0<YyyyMmNavEle>`.end dt-yyyymm-nav`!,
calendars: this.$<CalendarBaseEle>`dt-calendar-base`!,
startCalendar: this.$0<CalendarBaseEle>`.start dt-calendar-base`!,
endCalendar: this.$0<CalendarBaseEle>`.end dt-calendar-base`!,
timeSelectors: this.$<HhMmSsMsSelectorEle>`dt-hhmmss-ms-selector`!,
startTimeSelector: this
.$0<HhMmSsMsSelectorEle>`.start dt-hhmmss-ms-selector`!,
endTimeSelector: this
.$0<HhMmSsMsSelectorEle>`.end dt-hhmmss-ms-selector`!
} as const;
}
// 存放的是结束时间点
private _selectedDate: Date | null = null;
public connectedCallback() {
if (!super.connectedCallback()) return;
const { _els } = this;
this._selectedDate = null;
this._render();
this._bindEvt(_els.allNav)('change', this._onNavChange);
this._bindEvt(_els.allNav)(
'popover-open-change',
this._onNavOpenToggle
);
this._bindEvt(_els.calendars)('select-time', this._onCalendarSelect);
this._bindEvt(_els.calendars)('hover-item', this._onCalendarItemHover);
this._bindEvt(_els.timeSelectors)('open-change', this._stopEvent);
this._bindEvt(_els.timeSelectors)(
'select-time',
this._onTimeSelectorChange
);
}
protected _onScreenSizeChanged(isSmall: boolean) {
super._onScreenSizeChanged(isSmall);
this._render();
}
protected _onAttrChanged(
name: string,
oldValue: string | null,
newValue: string | null
) {
super._onAttrChanged(name, oldValue, newValue);
this._render();
}
private _updateNavCtrlBtn() {
const { _els } = this;
const timeStart = new Date(_els.startNav.millisecond);
const timeEnd = new Date(_els.endNav.millisecond);
const showCtrlBtn = diffInMonth(timeStart, timeEnd) > 1;
const isSmall = this._isSmallScreen;
_els.startNav.showCtrlBtnMonthAdd = _els.endNav.showCtrlBtnMonthSub =
isSmall || showCtrlBtn;
}
private _render = super._genRenderFn(() => {
let timeStart = this.timeStart as Date;
let timeEnd = this.timeEnd as Date;
if (timeStart > timeEnd) [timeStart, timeEnd] = [timeEnd, timeStart];
const { _els } = this;
_els.startCalendar.weekStartAt = _els.endCalendar.weekStartAt =
this.weekStartAt;
_els.startCalendar.timeStart = _els.endCalendar.timeStart = +timeStart;
_els.endCalendar.timeEnd = _els.startCalendar.timeEnd = +timeEnd;
const isSmall = this._isSmallScreen;
_els.startNav.millisecond = _els.startCalendar.showingTime =
!isSmall ||
!this._selectedDate ||
+timeStart !== +this._selectedDate
? +timeStart
: +timeEnd;
if (diffInMonth(timeStart, timeEnd) <= 1) {
const nextMonth = new Date(
timeStart.getFullYear(),
timeStart.getMonth() + 1
);
_els.endCalendar.showingTime = nextMonth;
_els.endNav.millisecond = +nextMonth;
} else {
_els.endCalendar.showingTime = timeEnd;
_els.endNav.millisecond = +timeEnd;
}
if (this.minGranularity === 'day') {
_els.timeSelectors.forEach((e) => (e.style.display = 'none'));
} else if (granHelper.isTimeGran(this.minGranularity)) {
_els.timeSelectors.forEach((e) => (e.style.display = ''));
_els.startTimeSelector.currentTime = timeStart;
_els.endTimeSelector.currentTime = timeEnd;
_els.startTimeSelector.minGranularity =
_els.endTimeSelector.minGranularity = this.minGranularity;
}
this._updateDateEcho();
this._updateNavCtrlBtn();
const dividingLine = this.$0`.start .dividing-line`!;
if (this._isSmallScreen) {
this.$0`.start .time-selector-wrapper`!.appendChild(
this._els.endTimeSelector
);
dividingLine.style.display = '';
} else {
this.$0`.end .time-selector-wrapper`!.appendChild(
this._els.endTimeSelector
);
dividingLine.style.display = 'none';
}
});
private _updateDateEcho() {
let timeStart = this.timeStart as Date;
let timeEnd = this.timeEnd as Date;
if (timeStart > timeEnd) [timeStart, timeEnd] = [timeEnd, timeStart];
const isSmall = this._isSmallScreen;
this.$0`.start-date-echo`!.textContent = this.dateFormatter(
timeStart,
this.minGranularity,
isSmall
);
this.$0`.end-date-echo`!.textContent = this.dateFormatter(
timeEnd,
this.minGranularity,
isSmall
);
this.$0`.start-date-echo-wrapper`!.classList.toggle(
'active',
!this._selectedDate
);
this.$0`.end-date-echo-wrapper`!.classList.toggle(
'active',
!!this._selectedDate
);
}
private _getTimeSelectorMs(type: 'start' | 'end') {
const selector =
type === 'start'
? this._els.startTimeSelector
: this._els.endTimeSelector;
return this.minGranularity === 'day'
? type === 'start'
? 0
: 86399999 // 23:59:59.999
: selector.millisecond;
}
private _updateDatePoint = (datePoint: Date) => {
if (!this._selectedDate) return;
const newDate = new Date(datePoint).setHours(0, 0, 0, 0);
const oldDate = new Date(this._selectedDate).setHours(0, 0, 0, 0);
const setStartDate = (date: number) =>
(this.timeStart = new Date(
date + this._getTimeSelectorMs('start')
));
const setEndDate = (date: number) =>
(this.timeEnd = new Date(date + this._getTimeSelectorMs('end')));
if (newDate === oldDate) {
setStartDate(newDate);
setEndDate(newDate);
} else if (newDate < oldDate) {
setStartDate(newDate);
setEndDate(oldDate);
} else {
setStartDate(oldDate);
setEndDate(newDate);
}
};
private _onCalendarSelect = (e: CalendarBaseEvent['select-time']) => {
e.stopPropagation();
if (this._selectedDate === null) {
const newTimePoint = new Date(
+e.detail + this._getTimeSelectorMs('start')
);
this._selectedDate = newTimePoint;
this.timeStart = newTimePoint;
} else {
this._updateDatePoint(e.detail);
this._selectedDate = null;
}
};
private _onCalendarItemHover = (e: CalendarBaseEvent['hover-item']) => {
e.stopPropagation();
this._updateDatePoint(e.detail);
};
public abortSelecting() {
if (!this._selectedDate) return;
this._selectedDate = null;
this._render();
}
private _onNavChange = (e: YyyyMmNavEvent['change']) => {
e.stopPropagation();
const wrapper = closestByEvent(e, '.wrapper');
if (!wrapper) return;
const { newTime } = e.detail;
if (wrapper.classList.contains('start')) {
this._els.startCalendar.showingTime = +newTime;
} else {
this._els.endCalendar.showingTime = +newTime;
}
this._updateNavCtrlBtn();
};
private _onNavOpenToggle = (e: YyyyMmNavEvent['popover-open-change']) => {
if (!(e.target instanceof YyyyMmNavEle)) return;
e.stopPropagation();
e.target.nextElementSibling?.classList.toggle('hide', e.detail);
};
private _onTimeSelectorChange = (
e: HhMmSsMsSelectorEvent['select-time']
) => {
if (!(e.target instanceof HhMmSsMsSelectorEle)) return;
e.stopPropagation();
const type = e.target.dataset.type;
if (type === 'start') {
this.timeStart = e.detail;
} else if (type === 'end') {
this.timeEnd = e.detail;
}
};
public showCalendarDatePoint() {
this._render();
}
public dateFormatter = (
time: Date,
minGranularity: Granularity,
isSmall: boolean
) => time.toLocaleDateString('en-GB');
// + (isSmall && minGranularity !== 'day'
// ? ' ' + this.timeFormatter(time, minGranularity)
// : '');
public get timeFormatter() {
return this._els.startTimeSelector.timeFormatter;
}
public set timeFormatter(fn: HhMmSsMsSelectorEle['timeFormatter']) {
if (typeof fn !== 'function') return;
this._els.startTimeSelector.timeFormatter = fn;
this._els.endTimeSelector.timeFormatter = fn;
}
}
Ele.define();