import { GenesysDevIcon, GenesysDevIcons } from 'genesys-dev-icons'; import React, { useEffect, useRef, useState } from 'react'; import DxLabel from '../dxlabel/DxLabel'; import { DxTextboxProps } from '..'; import './DxTextbox.scss'; const dateYyyyMmDd = /^\d{4}-\d{2}-\d{2}$/; export default function DxTextbox(props: DxTextboxProps) { const [debounceMs, setDebounceMs] = useState(props.changeDebounceMs || 300); const [value, setValue] = useState(props.initialValue || props.value || ''); const [isFocused, setIsFocused] = useState(false); const [escapePressed, setEscapePressed] = useState(Date.now()); const [step, setStep] = useState(undefined); let [timer, setTimer] = useState(undefined as unknown as ReturnType); // Constructor useEffect(() => { // Register global key bindings document.addEventListener('keydown', globalKeyBindings, false); return () => { document.removeEventListener('keydown', globalKeyBindings, false); }; }, []); // Value prop updated useEffect(() => { //Set initial value of date to avoid invalid format error if (inputType === 'date') { let parsedDate: Date; if (props.initialValue) { parsedDate = parseDate(props.initialValue); } else { parsedDate = parseDate(props.value); } const formattedDate = formatDate(parsedDate?.toISOString()); setValue(formattedDate); // Ignore value changed if initial value was set; they're mutually exclusive } else if (!props.initialValue && inputType !== 'date') { setValue(props.value || ''); } }, [props.value]); // Escape pressed useEffect(() => { if (!isFocused || !props.clearOnEscape) return; setValue(''); inputRef.current?.blur(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [escapePressed]); // Value changed useEffect(() => { if (props.inputType === 'decimal') { // Normalize step setting if (!isNaN(parseFloat(value))) { const match = /\.(.+)/.exec(value); console.log(match); if (match) { const s = `0.${Array.apply(null, Array(match[1].length - 1)) .map(() => '0') .join('')}1`; console.log(s); setStep(s); } } } else if (props.inputType === 'integer') { // Overwrite value as integer to forcibly truncate floating point numbers setValue(parseInt(value).toString()); } // Debounce onChange notification if (!props.onChange) return; clearTimeout(timer); setTimer(setTimeout(() => (props.onChange ? props.onChange(value) : undefined), debounceMs)); // eslint-disable-next-line react-hooks/exhaustive-deps }, [value]); // Update state from props useEffect(() => { setDebounceMs(props.changeDebounceMs || 300); }, [props.changeDebounceMs]); // Normalize inputRef let inputRef; // = useRef(null); if (props.inputRef) inputRef = props.inputRef; else if (props.inputType === 'textarea') inputRef = useRef(null); else inputRef = useRef(null); const hasLabel = props.label && props.label !== ''; // Global key bindings function globalKeyBindings(event: KeyboardEvent) { if (props.onKeyboardEvent) { props.onKeyboardEvent(event); } // Escape - cancel search if (event.key === 'Escape' && props.clearOnEscape) { event.stopPropagation(); event.preventDefault(); setEscapePressed(Date.now()); return; } } //Formats date to fit required HTML format const formatDate = (inputDate: string) => { const date = new Date(inputDate); if (isNaN(date.getTime())) return ''; // Return empty if invalid date // Format as YYYY-MM-DD for HTML date input const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); return `${year}-${month}-${day}`; }; const parseDate = (input: string) => { if (!input) return; let date; if (input.match(dateYyyyMmDd)) { /* * This date format causes the Date constructor to treat it as a UTC date, whereas the other formats are parsed as local dates. * This causes the local timezone offset to be subtracted from the UTC date time, * and the result is that the parsed date is a day behind the date selected. * This block adjusts for the timezone offset to make the dates consistent regardless of the input date string format. */ date = new Date(input); let offset = date.getTimezoneOffset() * 60000; date = new Date(date.getTime() + offset); } else { date = new Date(input); } return isNaN(date.getTime()) ? null : date; }; // Normalize input type let inputType: React.HTMLInputTypeAttribute | undefined = props.inputType; if (inputType === 'integer' || inputType === 'decimal') inputType = 'number'; let component; switch (inputType) { case 'textarea': { component = (