diff --git a/lib/ui/css/grid.scss b/lib/ui/css/grid.scss index 01c381b..762c4be 100644 --- a/lib/ui/css/grid.scss +++ b/lib/ui/css/grid.scss @@ -348,7 +348,7 @@ } } - .ui-grid-date-cell { + .ui-date-cell { line-height: 22px; box-sizing: border-box; padding: var(--spacing-cell); diff --git a/lib/ui/date.d.ts b/lib/ui/date.d.ts new file mode 100644 index 0000000..557062e --- /dev/null +++ b/lib/ui/date.d.ts @@ -0,0 +1,71 @@ +/** + * 创建日期选择框 + * @param min 最小可选日期 + * @param max 最大可选日期 + * @returns 返回创建的日期选择框 + */ +export function createDateInput(min?: string, max?: string): HTMLInputElement; + +/** + * 格式化日期字符串 + * @param date 要格式化的日期值

+ * 支持以下几种数据类型

+ * `"2024-01-26"`
+ * `"1/26/2024"`
+ * `"638418240000000000"`
+ * `new Date('2024-01-26')`
+ * @returns 格式化为 M/d/yyyy 的日期字符串 + */ +export function formatDate(date: Date | number | string): string; + +/** + * 设置显示日期 + * @param element 要设置显示日期的元素 + * @param val 日期值,支持格式参见 {@linkcode formatDate} + */ +export function setDateValue(element: HTMLElement, val: Date | number | string): void; + +/** + * 从日期选择框获取日期值 + * @param element 要获取的日期选择框 + * @param formatter 自定义格式化函数,传入参数为 `Date` 类型 + * @returns 默认返回日期 `ticks` 的字符串 + */ +export function getDateValue(element: HTMLInputElement, formatter?: (date: Date) => string): string; + +/** 日期选择框类 */ +export class DateSelector { + /** + * 日期发生变化时触发的事件 + * @param date 日期值,或者经自定义参数中格式化函数格式后的值 + */ + onDateChanged?: (date: Date | any) => void; + + /** + * 日期选择框构造函数 + * @param opts 日期选项参数 + */ + constructor(opts: { + /** 父容器元素,可以为 `string` 作为选择器 */ + parent: HTMLElement | string, + /** 最小可选择日期 */ + minDate?: string, + /** 最大可选择日期 */ + maxDate?: string, + /** + * 自定义格式化函数,用于获取日期值时调用 + * @param date 日期值 + * @returns 返回格式化的值 + */ + valueFormatter?: (date: Date) => any + }); + + get enabled(): boolean; + set enabled(flag: boolean); + + get value(): Date | any; + set value(val: Date | number | string); + + set minDate(date: string); + set maxDate(date: string); +} \ No newline at end of file diff --git a/lib/ui/date.js b/lib/ui/date.js new file mode 100644 index 0000000..e01375a --- /dev/null +++ b/lib/ui/date.js @@ -0,0 +1,153 @@ +import { createElement } from "../functions"; + +export function createDateInput(min, max) { + const date = createElement('input', 'ui-date-cell'); + date.required = true; + date.type = 'date'; + if (min != null) { + date.min = min; + } + if (max != null) { + date.max = max; + } + return date; +} + +function toDateValue(dt) { + if (isNaN(dt)) { + return ''; + } + const month = String(dt.getMonth() + 1).padStart(2, '0'); + const date = String(dt.getDate()).padStart(2, '0'); + return `${dt.getFullYear()}-${month}-${date}`; +} + +function resolveDate(s) { + if (s instanceof Date) { + return s; + } + const ticks = Number(s); + if (!isNaN(ticks) && ticks > 0) { + return new Date((ticks - 621355968e9) / 1e4); + } + return new Date(s); +} + +export function formatDate(date) { + date = resolveDate(date); + if (date instanceof Date && !isNaN(date)) { + return `${date.getMonth() + 1}/${date.getDate()}/${date.getFullYear()}`; + } + return ''; +} + +export function setDateValue(element, val) { + if (element.tagName === 'INPUT') { + if (val === '') { + element.value = ''; + } else if (isNaN(val)) { + if (/^\d{4}-\d{2}-\d{2}$/.test(val)) { + element.value = val; + } else if (/^\d{1,2}\/\d{1,2}\/\d{4}$/.test(val)) { + element.value = toDateValue(new Date(val)); + } else { + element.value = ''; + } + } else { + if (!(val instanceof Date)) { + val = new Date((val - 621355968e9) / 1e4); + } + element.value = toDateValue(val); + } + } else { + element.innerText = formatDate(val); + } +} + +export function getDateValue(element, formatter) { + const date = element?.valueAsDate; + if (date instanceof Date && !isNaN(date)) { + const year = date.getFullYear(); + if (year < 1900 || year > 9999) { + return ''; + } + if (typeof formatter === 'function') { + return formatter(date); + } + return String(date.getTime() * 1e4 + 621355968e9); + } + return ''; +} + +export class DateSelector { + _var = { + parent: null, + options: null + }; + + onDateChanged; + + constructor(opts) { + opts ??= {}; + if (typeof opts.parent === 'string') { + opts.parent = document.querySelector(opts.parent); + } + if (!(opts.parent instanceof HTMLElement)) { + throw new Error('no specified parent.'); + } + this._var.options = opts; + this._var.parent = opts.parent; + + const el = createDateInput(opts.minDate, opts.maxDate); + el.addEventListener('blur', e => { + const date = this._getDate(e.target.valueAsDate); + if (date == null) { + e.target.value = ''; + } + if (typeof this.onDateChanged === 'function') { + this.onDateChanged(date); + } + }); + this._var.el = el; + parent.appendChild(el); + } + + get enabled() { return !this._var.el.disabled } + set enabled(flag) { + this._var.el.disabled = flag === false; + } + + get value() { return this._getDate(this._var.el.valueAsDate) } + set value(val) { + setDateValue(this._var.el, val); + } + + /** + * @param {string} date + */ + set minDate(date) { + this._var.el.min = date; + this._var.options.minDate = date; + } + /** + * @param {string} date + */ + set maxDate(date) { + this._var.el.max = date; + this._var.options.maxDate = date; + } + + _getDate(date) { + if (date instanceof Date && !isNaN(date)) { + const year = date.getFullYear(); + if (year < 1900 || year > 9999) { + return null; + } + if (typeof this._var.options.valueFormatter === 'function') { + return this._var.options.valueFormatter(date); + } + return date; + } + return null; + } +} \ No newline at end of file diff --git a/lib/ui/grid/column.js b/lib/ui/grid/column.js index db5fd3d..8a00887 100644 --- a/lib/ui/grid/column.js +++ b/lib/ui/grid/column.js @@ -1,11 +1,11 @@ import { global } from "../../utility"; -// import { nullOrEmpty } from "../../utility/strings"; import { createElement } from "../../functions"; import { createIcon } from "../icon"; import { createCheckbox } from "../checkbox"; // import { setTooltip } from "../tooltip"; import { Dropdown } from "../dropdown"; import { convertCssStyle } from "../extension"; +import { createDateInput, formatDate, setDateValue, getDateValue } from "../date"; export class GridColumn { static create() { @@ -296,87 +296,25 @@ export class GridDateColumn extends GridColumn { if (enabled === false) { return super.create(); } - const date = createElement('input', 'ui-grid-date-cell'); - date.required = true; - date.type = 'date'; - if (col.dateMin != null) { - date.min = col.dateMin; - } - if (col.dateMax != null) { - date.max = col.dateMax; - } + const date = createDateInput(col.dateMin, col.dateMax); // date.addEventListener('change', trigger); date.addEventListener('blur', trigger); return date; } static setValue(element, val) { - if (element.tagName === 'INPUT') { - if (val === '') { - element.value = ''; - } else if (isNaN(val)) { - if (/^\d{4}-\d{2}-\d{2}$/.test(val)) { - element.value = val; - } else if (/^\d{1,2}\/\d{1,2}\/\d{4}$/.test(val)) { - element.value = this._toDateValue(new Date(val)); - } else { - element.value = ''; - } - } else { - if (!(val instanceof Date)) { - val = new Date((val - 621355968e9) / 1e4); - } - element.value = this._toDateValue(val); - } - } else { - element.innerText = this.formatDate(val); - } + setDateValue(element, val); } static getValue(e, col) { - const date = e.target?.valueAsDate; - if (date instanceof Date && !isNaN(date)) { - const year = date.getFullYear(); - if (year < 1900 || year > 9999) { - return ''; - } - if (typeof col.dateValueFormatter === 'function') { - return col.dateValueFormatter(date); - } - return String(date.getTime() * 1e4 + 621355968e9); - } - return ''; + return getDateValue(e.target, col.dateValueFormatter); } static setEnabled(element, enabled) { element.disabled = enabled === false; } - static _toDateValue(dt) { - if (isNaN(dt)) { - return ''; - } - const month = String(dt.getMonth() + 1).padStart(2, '0'); - const date = String(dt.getDate()).padStart(2, '0'); - return `${dt.getFullYear()}-${month}-${date}`; - } - - static _resolveDate(s) { - if (s instanceof Date) { - return s; - } - const ticks = Number(s); - if (!isNaN(ticks) && ticks > 0) { - return new Date((ticks - 621355968e9) / 1e4); - } - return new Date(s); - } - static formatDate(date) { - date = this._resolveDate(date); - if (date instanceof Date && !isNaN(date)) { - return `${date.getMonth() + 1}/${date.getDate()}/${date.getFullYear()}`; - } - return ''; + return formatDate(date); } } \ No newline at end of file