import * as React from "react"; import { classList, ControlProps } from "../util"; import { Button } from "./Button"; import { FocusList } from "./FocusList"; export interface InputProps extends ControlProps { inputClassName?: string; groupClassName?: string; initialValue?: string; label?: string; title?: string; placeholder?: string; icon?: string; iconTitle?: string; disabled?: boolean; type?: string; readOnly?: boolean; autoComplete?: boolean; selectOnClick?: boolean; treatSpaceAsEnter?: boolean; handleInputRef?: React.RefObject | ((ref: HTMLInputElement) => void); preserveValueOnBlur?: boolean; options?: pxt.Map; filter?: string; onChange?: (newValue: string) => void; onEnterKey?: (value: string) => void; onIconClick?: (value: string) => void; onFocus?: (value: string) => void; onBlur?: (value: string) => void; onOptionSelected?: (value: string) => void; } const sanitizeForDomId = (text: string | undefined) => { if (!text) { return ""; } const sanitized = text.replace(/[^a-zA-Z0-9_-]/g, "-"); return sanitized || "option"; }; export const Input = (props: InputProps) => { const { id, className, inputClassName, groupClassName, role, ariaHidden, ariaLabel, initialValue, label, title, placeholder, icon, iconTitle, disabled, type, readOnly, autoComplete, selectOnClick, onChange, onEnterKey, onIconClick, onFocus, onBlur, onOptionSelected, handleInputRef, preserveValueOnBlur = true, options } = props; const [value, setValue] = React.useState(initialValue || ""); const [expanded, setExpanded] = React.useState(false); const [filter] = React.useState(props.filter ? new RegExp(props.filter) : undefined); const optionValues = React.useMemo(() => (options ? Object.values(options) : []), [options]); let container: HTMLDivElement; React.useEffect(() => { setValue(initialValue || ""); }, [initialValue]); const handleContainerRef = (ref: HTMLDivElement) => { if (!ref) return; container = ref; } const clickHandler = (evt: React.MouseEvent) => { if (selectOnClick) { (evt.target as any).select() } if (options && !expanded) { setExpanded(true); } } const changeHandler = (e: React.ChangeEvent) => { let newValue = (e.target as any).value; if (newValue && filter) { newValue = newValue.match(filter)?.join("") || ""; } if (!readOnly && (value !== newValue)) { setValue(newValue); } if (onChange) { onChange(newValue); } } const keyDownHandler = (e: React.KeyboardEvent) => { const charCode = (typeof e.which == "number") ? e.which : e.keyCode; if (charCode === /*enter*/13 || props.treatSpaceAsEnter && charCode === /*space*/32) { if (onEnterKey) { e.preventDefault(); onEnterKey(value); } } else if (options && e.key === "ArrowDown") { if (expanded) { document.getElementById(getDropdownOptionId(optionValues[0]))?.focus(); } else { expandButtonClickHandler(); } e.preventDefault(); e.stopPropagation(); } else if (options && expanded && e.key === "ArrowUp") { document.getElementById(getDropdownOptionId(optionValues[optionValues.length - 1]))?.focus(); e.preventDefault(); e.stopPropagation(); } } const captureEscapeKey = (e: React.KeyboardEvent) => { if (e.code !== "Escape") return; (e.target as HTMLElement).blur(); expandButtonClickHandler(); document.getElementById(id)?.focus(); e.stopPropagation(); e.preventDefault(); } const iconClickHandler = () => { if (onIconClick) onIconClick(value); } const expandButtonClickHandler = () => { if (options) { setExpanded(!expanded); } } const focusHandler = () => { if (onFocus) { onFocus(value); } } const blurHandler = () => { if (onBlur) { onBlur(value); } if (!preserveValueOnBlur) { setValue(""); } } const containerBlurHandler = (e: React.FocusEvent) => { if (expanded && !container.contains(e.relatedTarget as HTMLElement)) { setExpanded(false); } } const optionClickHandler = (option: string) => { setExpanded(false); const value = options[option]; setValue(value); if (onOptionSelected) { onOptionSelected(value); } document.getElementById(id)?.focus(); } const getDropdownOptionId = (option: string) => { if (!optionValues.length) { return undefined; } const index = optionValues.indexOf(option); if (index === -1) { return undefined; } const sanitized = sanitizeForDomId(option); return `dropdown-item-${index}-${sanitized}`; } return (
{label && }
{icon && (onIconClick ?
{expanded &&
    { Object.keys(options).map(option =>
  • )}
}
); }