/** @jsxImportSource react */ import { Culture } from "../../ui/Culture"; import { offFocusOut, oneFocusOut } from "../../ui/FocusManager"; import { enableCultureSensitiveFormatting } from "../../ui/Format"; import type { Instance } from "../../ui/Instance"; import type { RenderingContext } from "../../ui/RenderingContext"; import { VDOM, Widget } from "../../ui/Widget"; import { parseDateInvariant } from "../../util"; import { KeyCode } from "../../util/KeyCode"; import { WheelComponent } from "./Wheel"; enableCultureSensitiveFormatting(); export class DateTimePicker extends Widget { declare public size: number; declare public segment: string; declare public autoFocus?: boolean; declare public showSeconds?: boolean; declare public encoding?: (date: Date) => string; declare public onFocusOut?: string | ((instance: Instance) => void); declare public onSelect?: string | ((e: React.KeyboardEvent, instance: Instance, date: Date) => void); declare baseClass: string; declareData(...args: Record[]): void { return super.declareData(...args, { value: undefined, }); } render(context: RenderingContext, instance: Instance, key: string): React.ReactNode { return ( ); } } DateTimePicker.prototype.baseClass = "datetimepicker"; DateTimePicker.prototype.styled = true; DateTimePicker.prototype.size = 3; DateTimePicker.prototype.autoFocus = false; DateTimePicker.prototype.segment = "datetime"; DateTimePicker.prototype.showSeconds = false; interface DateTimePickerComponentProps { instance: Instance; data: Record; size: number; segment: string; } interface DateTimePickerComponentState { date: Date; activeWheel: string | null; } class DateTimePickerComponent extends VDOM.Component { el!: HTMLDivElement; declare wheels: Record; keyDownPipes: Record void>; constructor(props: DateTimePickerComponentProps) { super(props); let date = props.data.value ? parseDateInvariant(props.data.value as string | number | Date) : new Date(); if (isNaN(date.getTime())) date = new Date(); this.state = { date: date, activeWheel: null, }; let { widget } = props.instance; let pickerWidget = widget as DateTimePicker; this.handleChange = this.handleChange.bind(this); this.onFocus = this.onFocus.bind(this); this.onBlur = this.onBlur.bind(this); this.onKeyDown = this.onKeyDown.bind(this); let showDate = props.segment.indexOf("date") !== -1; let showTime = props.segment.indexOf("time") !== -1; this.wheels = { year: showDate, month: showDate, date: showDate, hours: showTime, minutes: showTime, seconds: showTime && !!pickerWidget.showSeconds, }; this.keyDownPipes = {}; } UNSAFE_componentWillReceiveProps(props: DateTimePickerComponentProps): void { let date = props.data.value ? parseDateInvariant(props.data.value as string | number | Date) : new Date(); if (isNaN(date.getTime())) date = new Date(); this.setState({ date }); } setDateComponent(date: Date, component: string, value: number): Date { let v = new Date(date); switch (component) { case "year": v.setFullYear(value); break; case "month": v.setMonth(value); break; case "date": v.setDate(value); break; case "hours": v.setHours(value); break; case "minutes": v.setMinutes(value); break; case "seconds": v.setSeconds(value); break; } return v; } handleChange(): void { let { widget } = this.props.instance; let pickerWidget = widget as DateTimePicker; let encode = pickerWidget.encoding || Culture.getDefaultDateEncoding(); this.props.instance.set("value", encode!(this.state.date)); } render(): React.ReactNode { let { instance, data, size } = this.props; let { widget } = instance; let { CSS, baseClass } = widget; let pickerWidget = widget as DateTimePicker; let date = this.state.date; let culture = Culture.getDateTimeCulture(); let monthNames = culture.getMonthNames("short"); let years = []; for (let y = 1970; y <= 2050; y++) years.push({y}); let days = []; let start = new Date(date.getFullYear(), date.getMonth(), 1); while (start.getMonth() === date.getMonth()) { let day = start.getDate(); days.push({day < 10 ? "0" + day : day}); start.setDate(start.getDate() + 1); } let hours = []; for (let h = 0; h < 24; h++) { hours.push({h < 10 ? "0" + h : h}); } let minutes = []; for (let m = 0; m < 60; m++) { minutes.push({m < 10 ? "0" + m : m}); } return (
{ this.el = el!; }} className={data.classNames as string} onFocus={this.onFocus} onBlur={this.onBlur} onKeyDown={this.onKeyDown} > {this.wheels.year && ( { this.setState( (state) => ({ date: this.setDateComponent(this.state.date, "year", newIndex + 1970), }), this.handleChange, ); }} onPipeKeyDown={(kd) => { this.keyDownPipes["year"] = kd; }} onMouseDown={() => { this.setState({ activeWheel: "year" }); }} > {years} )} {this.wheels.year && this.wheels.month && -} {this.wheels.month && ( { this.setState( (state) => ({ date: this.setDateComponent(this.state.date, "month", newIndex), }), this.handleChange, ); }} onPipeKeyDown={(kd) => { this.keyDownPipes["month"] = kd; }} onMouseDown={() => { this.setState({ activeWheel: "month" }); }} > {monthNames.map((m: string, i: number) => ( {m} ))} )} {this.wheels.month && this.wheels.date && -} {this.wheels.date && ( { this.setState( (state) => ({ date: this.setDateComponent(this.state.date, "date", newIndex + 1), }), this.handleChange, ); }} onPipeKeyDown={(kd) => { this.keyDownPipes["date"] = kd; }} onMouseDown={() => { this.setState({ activeWheel: "date" }); }} > {days} )} {this.wheels.hours && this.wheels.year && } {this.wheels.hours && ( { this.setState( (state) => ({ date: this.setDateComponent(this.state.date, "hours", newIndex), }), this.handleChange, ); }} onPipeKeyDown={(kd) => { this.keyDownPipes["hours"] = kd; }} onMouseDown={() => { this.setState({ activeWheel: "hours" }); }} > {hours} )} {this.wheels.hours && this.wheels.minutes && :} {this.wheels.minutes && ( { this.setState( (state) => ({ date: this.setDateComponent(this.state.date, "minutes", newIndex), }), this.handleChange, ); }} onPipeKeyDown={(kd) => { this.keyDownPipes["minutes"] = kd; }} onMouseDown={() => { this.setState({ activeWheel: "minutes" }); }} > {minutes} )} {this.wheels.minutes && this.wheels.seconds && :} {this.wheels.seconds && ( { this.setState( (state) => ({ date: this.setDateComponent(this.state.date, "seconds", newIndex), }), this.handleChange, ); }} onPipeKeyDown={(kd) => { this.keyDownPipes["seconds"] = kd; }} onMouseDown={() => { this.setState({ activeWheel: "seconds" }); }} > {minutes} )}
); } componentDidMount(): void { let { widget } = this.props.instance; let pickerWidget = widget as DateTimePicker; if (pickerWidget.autoFocus) this.el.focus(); } componentWillUnmount(): void { offFocusOut(this); } onFocus(): void { oneFocusOut(this, this.el, this.onFocusOut.bind(this)); if (!this.state.activeWheel) { let firstWheel: string | null = null; for (let wheel in this.wheels) { if (this.wheels[wheel]) { firstWheel = wheel; break; } } this.setState({ activeWheel: firstWheel, }); } } onFocusOut(): void { let { instance } = this.props; let { widget } = instance; let pickerWidget = widget as DateTimePicker; if (pickerWidget.onFocusOut) instance.invoke("onFocusOut", null, instance); } onBlur(): void { this.setState({ activeWheel: null, }); } onKeyDown(e: React.KeyboardEvent): void { let tmp: string | null = null; let { instance } = this.props; let { widget } = instance; let pickerWidget = widget as DateTimePicker; switch (e.keyCode) { case KeyCode.right: e.preventDefault(); for (let wheel in this.wheels) { if (this.wheels[wheel]) { if (tmp === this.state.activeWheel) { this.setState({ activeWheel: wheel }); break; } tmp = wheel; } } break; case KeyCode.left: e.preventDefault(); for (let wheel in this.wheels) { if (this.wheels[wheel]) { if (wheel === this.state.activeWheel && tmp) { this.setState({ activeWheel: tmp }); break; } tmp = wheel; } } break; case KeyCode.enter: e.preventDefault(); if (pickerWidget.onSelect) instance.invoke("onSelect", e, instance, this.state.date); break; default: let kdp = this.keyDownPipes[this.state.activeWheel!]; if (kdp) kdp(e); break; } } }