import { useRef, useEffect, KeyboardEvent, useState } from 'react' interface TextInputProps { value: string onInput: (value: string) => void onKeyDown: (e: TextInputKeyDownEvent) => void className?: string placeholder?: string } export interface TextInputKeyDownEvent { key: string ctrlKey: boolean preventDefault: () => void } export default function FancyTextEditor({ value: _value, onInput, onKeyDown, className: extraClasses = '', placeholder = '', }: TextInputProps) { const divRef = useRef(null) const [hasFocus, setHasFocus] = useState(false) // the contenteditable likes to slip in newlines at the bottom of our innerText // which makes it bad to check for empty string because it might be "\n" // so we just trim it upfront and then fogeddaboudit const value = _value.trim() // The funky mechanics here are to stop the cursor from jumping back the start. // It probably will have the cursor jump to the start if anything changes programmatically, // which is probably unnecessary anyway useEffect(() => { const div = divRef.current if (div == null) { return } if (!value && !hasFocus) { div.innerText = placeholder } else if (div.innerText !== value) { div.innerText = value } }, [hasFocus, placeholder, value]) useEffect(() => { const div = divRef.current! if (div == null) { return } const inputListener = () => { onInput(div.innerText) } const blurListener = () => { setHasFocus(false) if (!value) { div.innerText = placeholder } } const focusListener = () => { setHasFocus(true) div.innerText = value } div.addEventListener('input', inputListener) div.addEventListener('blur', blurListener) div.addEventListener('focus', focusListener) return () => { div.removeEventListener('focus', focusListener) div.removeEventListener('blur', blurListener) div.removeEventListener('input', inputListener) } }, [onInput, placeholder, value]) const handleKeyDown = (e: KeyboardEvent) => { onKeyDown(e) } const textColour = value ? 'text-gray-900' : 'text-gray-500' return (
) }