import React, { useCallback, useRef } from 'react'; import { observer } from 'mobx-react-lite'; import type { FiltersMap } from '@wix/bex-core'; import { Tooltip } from '@wix/design-system'; import { StatusWarningFilledSmall } from '@wix/wix-ui-icons-common'; import { st, classes } from './EditableCell.st.css.js'; import type { EditableTableState } from '../../state/EditableTable'; import { useCellFocusAndEditing } from './useCellFocusAndEditing'; import { Focusable } from './types'; export interface EditableCellProps { state: EditableTableState; rowKey: string; columnId: string; } function _EditableCell({ state, rowKey, columnId, }: EditableCellProps) { const { cellInteraction, clipboard } = state; const cellRef = useRef(null); const editInputRef = useRef(); const column = state._columnMap.get(columnId); const keyedItem = state._getKeyedItem(rowKey); const resolved = column ? state.resolveCellTypeByColumnId(column.id) : null; const value = state.getCellDisplayValue(rowKey, columnId); const cellValidation = state.getCellValidation(rowKey, columnId); const isFocused = cellInteraction.isCellFocused(rowKey, columnId); const isEditing = cellInteraction.isCellEditing(rowKey, columnId); const isSelected = cellInteraction.isCellSelected(rowKey, columnId); const selectionBorder = cellInteraction.getSelectionBorder(rowKey, columnId); const copiedBorder = clipboard.getCopiedBorder(rowKey, columnId); const isEditable = state.isCellEditable(rowKey, columnId); const handleClick = useCallback( (e: React.MouseEvent) => { e.stopPropagation(); state.handleCellClick(rowKey, columnId, e.shiftKey); }, [state, rowKey, columnId], ); const handleDoubleClick = useCallback( (e: React.MouseEvent) => { e.stopPropagation(); e.preventDefault(); state.handleCellDoubleClick(rowKey, columnId); }, [state, rowKey, columnId], ); // Auto-start edit if onChange fires before explicit startEdit (e.g., onOptionMarked // in select). startEdit resets editingValue, so it must come before setEditingValue. // For production cell types, consider the per-callback withEnterEdit pattern from cms-web. const handleChange = useCallback( (newValue: any) => { if (!cellInteraction.isEditing) { cellInteraction.startEdit(rowKey, columnId, 'enter'); } cellInteraction.setEditingValue(newValue); }, [cellInteraction, rowKey, columnId], ); const handleCommit = useCallback( () => cellInteraction.commitEdit(), [cellInteraction], ); const handleCancel = useCallback( () => cellInteraction.cancelEdit(), [cellInteraction], ); const handleStartEdit = useCallback(() => { if (!cellInteraction.isEditing) { cellInteraction.startEdit(rowKey, columnId); } }, [cellInteraction, rowKey, columnId]); const cellKeyboard = useCellFocusAndEditing({ state, rowKey, columnId, cellRef, editInputRef, }); if (!column || !keyedItem || !resolved) { return null; } const { ViewComponent, EditComponent } = resolved; const showValidationIndicator = !cellValidation.valid && !isEditing; const showEdit = (isEditing || (isFocused && isEditable && !state.updateItemDisabled && (!!resolved.toggleValue || !!resolved.showEditWhenFocused))) && EditComponent; const cellContent = showEdit ? ( ) : ( ); // The cell type's EditWrapper (if any) stays mounted across the edit↔view // swap, so it can own behavior that must outlive the inline editor. const EditWrapper = resolved.EditWrapper; return (
{showValidationIndicator && ( )} {EditWrapper ? ( {cellContent} ) : ( cellContent )}
); } export const EditableCell = observer(_EditableCell);