@gez/date-time-kit
Version:
386 lines (369 loc) • 12.6 kB
text/typescript
import {
type DateTimeGranularity,
debounce,
getCurrentTzOffset,
granHelper
} from '../../utils';
import { type Weeks, weekKey } from '../calendar';
import type { Ele as PeriodSelectorEle } from '../period-selector';
import { Ele as PopoverEle, type EventMap as PopoverEvent } from '../popover';
import {
clearupPopEleAttrSync2Parent,
isPopoverAttrKey,
parentPopAttrSync2PopEle,
popEleAttrSync2Parent,
popoverAttrKeys,
type reExportPopoverAttrs
} from '../popover/attr-sync-helper';
import {
type BaseAttrs,
type BaseEmits,
type Emit2EventMap,
UiBase
} from '../web-component-base';
import styleStr from './index.css';
import html, { utcText } from './index.html';
import {
type DataLimit,
type GenPeriodTimesOptions,
type PeriodTimeInfo,
type QuickGenPeriodTimesOptions,
type QuickKey,
UTCInfo2LocaleInfo,
genPeriodTimes,
localeInfo2UTCInfo,
quickGenPeriodTime,
quickGenPeriodTimeInfo,
quickGenPeriodTimes,
quickKeys
} from './quick-key';
export {
type QuickKey,
type DataLimit,
type GenPeriodTimesOptions,
type QuickGenPeriodTimesOptions,
type PeriodTimeInfo,
type Weeks,
genPeriodTimes,
quickGenPeriodTime,
quickGenPeriodTimes,
quickGenPeriodTimeInfo,
localeInfo2UTCInfo,
UTCInfo2LocaleInfo,
quickKeys,
weekKey
};
export const granularityList = granHelper.dateTime.list;
export type Granularity = DateTimeGranularity;
export type Attrs = BaseAttrs &
reExportPopoverAttrs & {
/**
* Timezone in minutes. For example: UTC+05:45 => `-345`, UTC-01:00 => `60`.
*
* @default
* new Date().getTimezoneOffset() // locale timezone in minutes
*/
'tz-offset'?: number;
/**
* Set which day of the week is the first day.
* @type `'sun' | 'mon' | 'tue' | 'wed' | 'thu' | 'fri' | 'sat'`
* @default 'sun'
*/
'week-start-at'?: Weeks;
/**
* Quick selection key.
*
* @default 'all'
*/
'quick-key'?: QuickKey;
/**
* Start locale time of the quick selection. Only works in custom mode.
*/
'start-time'?: string | number | '';
/**
* End locale time of the quick selection. Only works in custom mode.
*/
'end-time'?: string | number | '';
/**
* 选择器的粒度,表示最小可选的时间单位。默认为 millisecond。
* 例如设置为 'minute',则表示只能选择到分钟,秒和毫秒将被忽略。忽略的时间单位视情况重置为 0 或 23 或 59 或 999。
*/
'min-granularity'?: Granularity;
/**
* Exclude some quick selection options.
*
* @example
* ```ts
* exclude-field="last7Days, last30Days, timezone"
* ```
*/
'exclude-field'?: (QuickKey | 'timezone')[];
};
export interface Emits extends BaseEmits {
'time-changed': PeriodTimeInfo;
'open-change': boolean;
}
export type EventMap = Emit2EventMap<Emits>;
/**
* 快速选择下拉选项
*/
export class Ele extends UiBase<Attrs, Emits> {
public static readonly tagName = 'dt-quick-selector' as const;
protected static _style = styleStr;
protected static _template = html;
static get observedAttributes(): string[] {
return [
...(super.observedAttributes as (keyof BaseAttrs)[]),
'tz-offset',
'week-start-at',
'quick-key',
'start-time',
'end-time',
'min-granularity',
'exclude-field',
...popoverAttrKeys
] satisfies (keyof Attrs)[];
}
public get tzOffset() {
return +this._getAttr('tz-offset', '' + getCurrentTzOffset());
}
public set tzOffset(v: number) {
if (!Number.isSafeInteger(v)) return;
this.setAttribute('tz-offset', '' + v);
}
public get quickKey() {
return this._getAttr('quick-key', 'all');
}
public set quickKey(val: QuickKey) {
if (!quickKeys.includes(val)) return;
this.setAttribute('quick-key', val);
}
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 startTime() {
const v = this._getAttr('start-time', '');
if (v === '') return '';
return new Date(Number.isNaN(+v) ? v : +v);
}
public set startTime(val: number | string | Date) {
if (val === '') {
this.removeAttribute('start-time');
return;
}
const v = new Date(val);
if (Number.isNaN(+v)) return;
this.setAttribute('start-time', +v + '');
}
public get endTime() {
const v = this._getAttr('end-time', '' + this.startTime);
if (v === '') return '';
return new Date(Number.isNaN(+v) ? v : +v);
}
public set endTime(val: number | string | Date) {
if (val === '') {
this.removeAttribute('end-time');
return;
}
const v = new Date(val);
if (Number.isNaN(+v)) return;
this.setAttribute('end-time', +v + '');
}
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);
}
public get excludeField() {
const v = this._getAttr('exclude-field', '') || '';
if (v === '') return [];
return (v as string).split(',').map((i) => i.trim()) as (
| QuickKey
| 'timezone'
)[];
}
public set excludeField(v: (QuickKey | 'timezone')[]) {
if (!Array.isArray(v) || v.length === 0) {
this.removeAttribute('exclude-field');
return;
}
const arr = v.filter(
(i) => quickKeys.includes(i as QuickKey) || i === 'timezone'
);
this.setAttribute('exclude-field', arr.join(','));
}
get _staticEls() {
return {
...super._staticEls,
periodSelector: this.$0<PeriodSelectorEle>`dt-period-selector`!,
popover: this.$0<PopoverEle>`dt-popover`!
} as const;
}
public connectedCallback() {
if (!super.connectedCallback()) return;
this._renderTz();
this._updateRadio();
this._updatePeriodSelector();
const { _els } = this;
this._bindEvt(_els.popover)('open-change', this._onPopoverChange);
this._bindEvt`.tz-trigger`('click', this._onTzTriggerClick);
this._bindEvt`.custom-trigger`('click', this._onCustomTriggerClick);
this._bindEvt`.menu.tz .title svg`('click', this._onBackBtnClick);
this._bindEvt`.menu.custom .title svg`('click', this._onBackBtnClick);
this._bindEvt`.menu`('change', this._onRadioChange);
this._bindEvt`#reset`('click', this._updatePeriodSelector);
this._bindEvt`#done`('click', this._onDoneBtnClick);
popEleAttrSync2Parent(this, this._els.popover);
}
public disconnectedCallback() {
clearupPopEleAttrSync2Parent(this);
return super.disconnectedCallback();
}
protected _onAttrChanged(
name: string,
oldValue: string | null,
newValue: string | null
) {
super._onAttrChanged(name, oldValue, newValue);
if (isPopoverAttrKey(name)) {
parentPopAttrSync2PopEle(
name,
oldValue,
newValue,
this._els.popover
);
return;
}
if (name === 'tz-offset') {
this._renderTz();
this._dispatchTimeChangeEvent();
}
if (name === 'quick-key') {
this._updateRadio();
this._dispatchTimeChangeEvent();
}
if (name === 'week-start-at') {
this._updatePeriodSelector();
}
if (name === 'start-time' || name === 'end-time') {
if (this.quickKey !== 'custom') return;
this._updatePeriodSelector();
}
}
private _updatePeriodSelector = super._genRenderFn(() => {
this._els.periodSelector.weekStartAt = this.weekStartAt;
if (this.$0`.menu.custom`?.style.display === 'none') return;
const ele = this._els.periodSelector;
const startTime = this.startTime;
const endTime = this.endTime;
if (startTime !== '' && endTime !== '') {
ele.timeStart = startTime;
ele.timeEnd = endTime;
} else {
const defaultPeriod = this.quickGenPeriodTime('last30Days');
ele.timeStart = defaultPeriod.start;
ele.timeEnd = defaultPeriod.end;
}
ele.minGranularity = this.minGranularity;
ele.showCalendarDatePoint();
});
private _renderTz = super._genRenderFn(() => {
const tzOffset = this.tzOffset;
this.$<HTMLInputElement>`input[name="tz"]`.forEach((radio) => {
radio.checked = +radio.value === tzOffset;
});
this.$0`.tz-trigger bdo`!.textContent = utcText(tzOffset);
});
private _updateRadio = super._genRenderFn(() => {
const quickKey = this.quickKey;
this
.$0<HTMLInputElement>`input[name="radio"][value="${quickKey}"]`!.checked =
true;
});
private _onPopoverChange = (e: PopoverEvent['open-change']) => {
if (!(e.target instanceof PopoverEle)) return;
if (e.detail === false) {
this._showMenu('top');
}
};
private _showMenu(type: 'top' | 'tz' | 'custom') {
this.$`.menu`.forEach((menu) =>
menu.classList.contains(type)
? (menu.slot = 'pop')
: menu.removeAttribute('slot')
);
if (type === 'custom') {
this._updatePeriodSelector();
}
}
private _onTzTriggerClick = () => this._showMenu('tz');
private _onCustomTriggerClick = (e: Event) => {
e.preventDefault();
this._showMenu('custom');
};
private _onBackBtnClick = () => this._showMenu('top');
private _dispatchTimeChangeEvent = debounce(() => {
const quickKey = this.quickKey;
if (quickKey !== 'custom') {
const t = this.quickGenPeriodTimeInfo(quickKey);
this.dispatchEvent('time-changed', t, true);
return;
}
this.dispatchEvent(
'time-changed',
{
tzOffset: this.tzOffset,
start: this.startTime as Date,
end: this.endTime as Date,
type: 'custom'
},
true
);
});
private _onRadioChange = (e: Event) => {
if (!(e.target instanceof HTMLInputElement)) return;
if (e.target.type !== 'radio') return;
const { name, value } = e.target;
if (name === 'radio') {
const v = value as QuickKey;
if (v === 'custom') return;
this.quickKey = v;
} else if (name === 'tz') {
this.tzOffset = +value;
}
};
private _onDoneBtnClick = (_e: Event) => {
const selector = this._els.periodSelector;
selector.abortSelecting();
this._showMenu('top');
let { timeStart, timeEnd } = selector;
if (timeStart > timeEnd) [timeStart, timeEnd] = [timeEnd, timeStart];
this.startTime = timeStart;
this.endTime = timeEnd;
this.quickKey = 'custom';
this._dispatchTimeChangeEvent();
};
public readonly genPeriodTimes = (options: GenPeriodTimesOptions) =>
genPeriodTimes({ weekStartAt: this.weekStartAt, ...options });
public readonly quickGenPeriodTimes = <T extends DataLimit = DataLimit>(
periods: T[]
) => quickGenPeriodTimes({ weekStartAt: this.weekStartAt, periods });
public readonly quickGenPeriodTime = <T extends DataLimit = DataLimit>(
period: T
) => quickGenPeriodTime(period, { weekStartAt: this.weekStartAt });
public readonly quickGenPeriodTimeInfo = <T extends DataLimit = DataLimit>(
type: T
) =>
quickGenPeriodTimeInfo(
type,
{ weekStartAt: this.weekStartAt },
this.tzOffset
);
}
Ele.define();