diff --git a/apps/client/src/ee/base/components/row-detail-modal/fields/detail-field.tsx b/apps/client/src/ee/base/components/row-detail-modal/fields/detail-field.tsx index c3e8d8e60..5e2b0ad08 100644 --- a/apps/client/src/ee/base/components/row-detail-modal/fields/detail-field.tsx +++ b/apps/client/src/ee/base/components/row-detail-modal/fields/detail-field.tsx @@ -18,6 +18,7 @@ export type FieldProps = { rowId: string; readOnly: boolean; onChange: (value: unknown) => void; + onEditingChange?: (editing: boolean) => void; }; type FieldShellProps = { @@ -99,9 +100,10 @@ type DetailFieldProps = { row: IBaseRow; readOnly: boolean; onUpdate: (propertyId: string, value: unknown) => void; + onEditingChange: (editing: boolean) => void; }; -export function DetailField({ property, row, readOnly, onUpdate }: DetailFieldProps) { +export function DetailField({ property, row, readOnly, onUpdate, onEditingChange }: DetailFieldProps) { const descriptor = getDescriptor(property.type); const value = descriptor?.systemAccessor ? descriptor.systemAccessor(row) @@ -112,6 +114,7 @@ export function DetailField({ property, row, readOnly, onUpdate }: DetailFieldPr rowId: row.id, readOnly, onChange: (next: unknown) => onUpdate(property.id, next), + onEditingChange }; switch (property.type) { diff --git a/apps/client/src/ee/base/components/row-detail-modal/fields/field-long-text.tsx b/apps/client/src/ee/base/components/row-detail-modal/fields/field-long-text.tsx index 89501ed65..7fd11e8a0 100644 --- a/apps/client/src/ee/base/components/row-detail-modal/fields/field-long-text.tsx +++ b/apps/client/src/ee/base/components/row-detail-modal/fields/field-long-text.tsx @@ -9,7 +9,13 @@ const normalize = (s: string) => { return trimmed.length ? trimmed : null; }; -export function FieldLongText({ property, value, readOnly, onChange }: FieldProps) { +export function FieldLongText({ + property, + value, + readOnly, + onChange, + onEditingChange, +}: FieldProps) { const text = toText(value); const [draft, setDraft] = useState(text); const [focused, setFocused] = useState(false); @@ -23,6 +29,7 @@ export function FieldLongText({ property, value, readOnly, onChange }: FieldProp const commit = () => { setFocused(false); + onEditingChange?.(false); if (cancelRef.current) { cancelRef.current = false; setDraft(text); @@ -50,7 +57,10 @@ export function FieldLongText({ property, value, readOnly, onChange }: FieldProp className={classes.fieldTextarea} classNames={{ input: classes.fieldTextareaInput }} value={draft} - onFocus={() => setFocused(true)} + onFocus={() => { + setFocused(true); + onEditingChange?.(true); + }} onChange={(e) => setDraft(e.currentTarget.value)} onBlur={commit} onKeyDown={(e) => { diff --git a/apps/client/src/ee/base/components/row-detail-modal/fields/field-number.tsx b/apps/client/src/ee/base/components/row-detail-modal/fields/field-number.tsx index b9495b375..62ab2255f 100644 --- a/apps/client/src/ee/base/components/row-detail-modal/fields/field-number.tsx +++ b/apps/client/src/ee/base/components/row-detail-modal/fields/field-number.tsx @@ -11,7 +11,13 @@ import classes from "@/ee/base/styles/row-detail-modal.module.css"; const toDraft = (value: unknown) => typeof value === "number" ? String(value) : ""; -export function FieldNumber({ property, value, readOnly, onChange }: FieldProps) { +export function FieldNumber({ + property, + value, + readOnly, + onChange, + onEditingChange, +}: FieldProps) { const typeOptions = property.typeOptions as NumberTypeOptions | undefined; const numValue = typeof value === "number" ? value : null; const [draft, setDraft] = useState(toDraft(value)); @@ -36,6 +42,7 @@ export function FieldNumber({ property, value, readOnly, onChange }: FieldProps) const commit = () => { setFocused(false); + onEditingChange?.(false); if (cancelRef.current) { cancelRef.current = false; setDraft(toDraft(value)); @@ -54,6 +61,7 @@ export function FieldNumber({ property, value, readOnly, onChange }: FieldProps) onFocus={() => { setDraft(toDraft(value)); setFocused(true); + onEditingChange?.(true); }} onChange={(e) => { const v = e.target.value; diff --git a/apps/client/src/ee/base/components/row-detail-modal/fields/field-text.tsx b/apps/client/src/ee/base/components/row-detail-modal/fields/field-text.tsx index e211563a9..dbf1abbdf 100644 --- a/apps/client/src/ee/base/components/row-detail-modal/fields/field-text.tsx +++ b/apps/client/src/ee/base/components/row-detail-modal/fields/field-text.tsx @@ -5,7 +5,13 @@ import classes from "@/ee/base/styles/row-detail-modal.module.css"; const toText = (value: unknown) => (typeof value === "string" ? value : ""); -export function FieldText({ property, value, readOnly, onChange }: FieldProps) { +export function FieldText({ + property, + value, + readOnly, + onChange, + onEditingChange, +}: FieldProps) { const text = toText(value); const [draft, setDraft] = useState(text); const [focused, setFocused] = useState(false); @@ -20,6 +26,7 @@ export function FieldText({ property, value, readOnly, onChange }: FieldProps) { const commit = () => { setFocused(false); + onEditingChange?.(false); if (cancelRef.current) { cancelRef.current = false; setDraft(text); @@ -54,7 +61,10 @@ export function FieldText({ property, value, readOnly, onChange }: FieldProps) { className={classes.fieldInput} value={draft} maxLength={1000} - onFocus={() => setFocused(true)} + onFocus={() => { + setFocused(true); + onEditingChange?.(true); + }} onChange={(e) => setDraft(e.currentTarget.value)} onBlur={commit} onKeyDown={(e) => { diff --git a/apps/client/src/ee/base/components/row-detail-modal/property-row.tsx b/apps/client/src/ee/base/components/row-detail-modal/property-row.tsx index d825d3c03..8658f6db4 100644 --- a/apps/client/src/ee/base/components/row-detail-modal/property-row.tsx +++ b/apps/client/src/ee/base/components/row-detail-modal/property-row.tsx @@ -17,6 +17,7 @@ type PropertyRowProps = { onMenuOpenChange: (opened: boolean) => void; onMenuDirtyChange: (dirty: boolean) => void; onUpdate: (propertyId: string, value: unknown) => void; + onEditingChange?: (editing: boolean) => void; autoFocusValue?: boolean; onAutoFocused?: () => void; }; @@ -29,6 +30,7 @@ export function PropertyRow({ onMenuOpenChange, onMenuDirtyChange, onUpdate, + onEditingChange, autoFocusValue, onAutoFocused, }: PropertyRowProps) { @@ -112,6 +114,7 @@ export function PropertyRow({ row={row} readOnly={!canEdit} onUpdate={onUpdate} + onEditingChange={onEditingChange} /> ); diff --git a/apps/client/src/ee/base/components/row-detail-modal/row-detail-modal.tsx b/apps/client/src/ee/base/components/row-detail-modal/row-detail-modal.tsx index 7d1cfdce9..d43d8c6bc 100644 --- a/apps/client/src/ee/base/components/row-detail-modal/row-detail-modal.tsx +++ b/apps/client/src/ee/base/components/row-detail-modal/row-detail-modal.tsx @@ -75,6 +75,7 @@ export function RowDetailModal({ const isSaving = updateRowMutation.isPending; const opened = !!openRowId; + const [editingField, setEditingField] = useState(false); // One field menu open at a time, mirroring the grid header's semantics. // The shared closeRequest atom asks an open dirty PropertyMenuContent to @@ -90,6 +91,7 @@ export function RowDetailModal({ useEffect(() => { setOpenMenuId(null); menuDirtyRef.current = false; + setEditingField(false); }, [openRowId]); const handleMenuDirtyChange = useCallback((dirty: boolean) => { @@ -293,7 +295,7 @@ export function RowDetailModal({ row={row} primaryProperty={primaryProperty} canEdit={canEdit} - onClose={onClose} + onEditingChange={setEditingField} onCommit={(value) => { if (!primaryProperty) return; updateRowMutation.mutate({ @@ -317,6 +319,7 @@ export function RowDetailModal({ autoFocusValue={property.id === newPropertyId} onAutoFocused={clearNewProperty} menuOpened={openMenuId === property.id} + onEditingChange={setEditingField} onMenuOpenChange={(nextOpened) => handleMenuOpenChange(property.id, nextOpened) } @@ -367,16 +370,38 @@ export function RowDetailModal({ ) : null}