import { useEffect, useRef, useState } from "react"; import { Popover, Textarea, Group, CloseButton, Tooltip } from "@mantine/core"; import { useDebouncedCallback } from "@mantine/hooks"; import { IBaseProperty } from "@/ee/base/types/base.types"; import { formatLongTextPreview } from "@/ee/base/formatters/cell-formatters"; import cellClasses from "@/ee/base/styles/cells.module.css"; type CellLongTextProps = { value: unknown; property: IBaseProperty; rowId: string; isEditing: boolean; onCommit: (value: unknown) => void; onValueChange: (value: unknown) => void; onCancel: () => void; onTabNavigate?: (shiftKey: boolean) => void; }; const toText = (value: unknown) => (typeof value === "string" ? value : ""); const normalize = (s: string) => { const trimmed = s.trim(); return trimmed.length ? trimmed : null; }; export function CellLongText({ value, isEditing, onCommit, onValueChange, onCancel, onTabNavigate, }: CellLongTextProps) { const [draft, setDraft] = useState(() => toText(value)); const cancelledRef = useRef(false); const committedRef = useRef(false); const wasEditingRef = useRef(false); const textareaRef = useRef(null); // Seed draft and focus on the false->true editing transition only; ignore // value changes mid-edit so the user's typing is not clobbered. useEffect(() => { if (isEditing && !wasEditingRef.current) { cancelledRef.current = false; committedRef.current = false; setDraft(toText(value)); requestAnimationFrame(() => { const el = textareaRef.current; if (!el) return; el.focus(); el.setSelectionRange(el.value.length, el.value.length); }); } wasEditingRef.current = isEditing; }, [isEditing, value]); // Autosave after a typing pause; commit/cancel clear the pending fire so // a closed editor can never write a stale or discarded draft. const debouncedAutosave = useDebouncedCallback(() => { onValueChange(normalize(draft)); }, 10_000); const commit = () => { if (committedRef.current) return; committedRef.current = true; debouncedAutosave.cancel(); onCommit(normalize(draft)); }; const cancel = () => { cancelledRef.current = true; debouncedAutosave.cancel(); onCancel(); }; const preview = formatLongTextPreview(toText(value)); return ( { if (opened) return; // Programmatic close after cancel must not re-commit. if (cancelledRef.current) { cancelledRef.current = false; return; } commit(); }} position="bottom-start" width={320} shadow="md" withinPortal closeOnClickOutside closeOnEscape={false} trapFocus >
{preview ? ( {preview} ) : ( )}
e.stopPropagation()} className={cellClasses.longTextDropdown} > {isEditing && ( <>