import React, { useState, useEffect, useRef } from 'react'; export interface CommitInputProps extends Omit, 'onChange' | 'value' | 'type'> { value: string | number; onCommit: (val: string) => void; /** number 类型在编辑时用文本框,提交时由外部校验 min/max */ type?: 'text' | 'number'; } /** * 失焦/回车提交型输入框:编辑过程中不应用 HTML min/max 限制,便于清空后重输。 */ export const CommitInput: React.FC = ({ value, onCommit, type = 'text', min: _min, max: _max, step: _step, onFocus, onBlur, onKeyDown, ...props }) => { const [localVal, setLocalVal] = useState(String(value)); const focusedRef = useRef(false); const localValRef = useRef(localVal); const valueRef = useRef(value); const onCommitRef = useRef(onCommit); localValRef.current = localVal; valueRef.current = value; onCommitRef.current = onCommit; useEffect(() => { if (!focusedRef.current) { setLocalVal(String(value)); } }, [value]); useEffect(() => { return () => { if ( focusedRef.current && localValRef.current !== String(valueRef.current) ) { onCommitRef.current(localValRef.current); } }; }, []); const commitIfChanged = () => { if (localValRef.current !== String(valueRef.current)) { onCommitRef.current(localValRef.current); } }; const handleBlur = (e: React.FocusEvent) => { focusedRef.current = false; onBlur?.(e); commitIfChanged(); }; const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter') { (e.target as HTMLInputElement).blur(); } onKeyDown?.(e); }; const inputType = type === 'number' ? 'text' : type; return ( setLocalVal(e.target.value)} onFocus={(e) => { focusedRef.current = true; onFocus?.(e); }} onBlur={handleBlur} onKeyDown={handleKeyDown} /> ); }; export interface CommitTextareaProps extends Omit, 'onChange' | 'value'> { value: string; onCommit: (val: string) => void; } /** 失焦提交型多行输入框 */ export const CommitTextarea: React.FC = ({ value, onCommit, onFocus, onBlur, onKeyDown, ...props }) => { const [localVal, setLocalVal] = useState(value); const focusedRef = useRef(false); const localValRef = useRef(localVal); const valueRef = useRef(value); const onCommitRef = useRef(onCommit); localValRef.current = localVal; valueRef.current = value; onCommitRef.current = onCommit; useEffect(() => { if (!focusedRef.current) { setLocalVal(value); } }, [value]); useEffect(() => { return () => { if (focusedRef.current && localValRef.current !== valueRef.current) { onCommitRef.current(localValRef.current); } }; }, []); const commitIfChanged = () => { if (localValRef.current !== valueRef.current) { onCommitRef.current(localValRef.current); } }; const handleBlur = (e: React.FocusEvent) => { focusedRef.current = false; onBlur?.(e); commitIfChanged(); }; const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) { (e.target as HTMLTextAreaElement).blur(); } onKeyDown?.(e); }; return (