import React, { useCallback } from 'react'; import { createPortal } from 'react-dom'; import { observer } from 'mobx-react-lite'; import type { FiltersMap } from '@wix/bex-core'; import { DropdownLayout, listItemActionBuilder } from '@wix/design-system'; import { DeleteSmall, AddSmall, DuplicateSmall, } from '@wix/wix-ui-icons-common'; import type { EditableTableState, Cell } from '../../state/EditableTable'; export interface EditableTableContextMenuProps { state: EditableTableState; position: { x: number; y: number } | null; cell: Cell | null; menuRef: React.Ref; onClose: () => void; } const MENU_WIDTH = 230; const ITEM_HEIGHT = 36; const DIVIDER_HEIGHT = 9; const PADDING = 16; const OFFSET_X = 4; const OFFSET_Y = 4; function _EditableTableContextMenu({ state, position, cell, menuRef, onClose, }: EditableTableContextMenuProps) { const { cellInteraction, clipboard, addItemDisabled, updateItemDisabled, removeItemDisabled, } = state; const handleCopy = useCallback(async () => { const tsv = clipboard.copy(); try { await navigator.clipboard.writeText(tsv); } catch {} onClose(); }, [clipboard, onClose]); const handleCut = useCallback(async () => { const tsv = clipboard.cut(); try { await navigator.clipboard.writeText(tsv); } catch {} onClose(); }, [clipboard, onClose]); const handlePaste = useCallback(async () => { if (!cellInteraction.focusedCell) { return; } try { const rawData = await navigator.clipboard.readText(); if (!rawData) { onClose(); return; } clipboard.paste(rawData); } catch {} onClose(); }, [cellInteraction, clipboard, onClose]); const handleClear = useCallback(() => { state.clearCells(cellInteraction.selectedCellsList); onClose(); }, [state, cellInteraction, onClose]); if (!position || !cell) { return null; } const selectedCells = cellInteraction.selectedCellsList; const selectedCount = selectedCells.length; const selectedRowKeys = [...new Set(selectedCells.map((c) => c.rowKey))]; const hasMultipleRows = selectedRowKeys.length > 1; const options = []; options.push( listItemActionBuilder({ id: 'copy', title: `Copy`, disabled: selectedCount === 0, }), listItemActionBuilder({ id: 'cut', title: `Cut`, disabled: selectedCount === 0 || !!updateItemDisabled, }), listItemActionBuilder({ id: 'paste', title: `Paste`, disabled: !!updateItemDisabled, }), listItemActionBuilder({ id: 'clear', title: selectedCount > 1 ? `Clear ${selectedCount} Cells` : 'Clear Cell', disabled: selectedCount === 0 || !!updateItemDisabled, }), ); const hasRowOps = !addItemDisabled || !removeItemDisabled; if (hasRowOps) { options.push({ id: 'divider-1', value: '-' }); } if (!addItemDisabled) { options.push( listItemActionBuilder({ id: 'add-above', title: 'Add Row Above', prefixIcon: , }), listItemActionBuilder({ id: 'add-below', title: 'Add Row Below', prefixIcon: , }), listItemActionBuilder({ id: 'duplicate', title: 'Duplicate Row', prefixIcon: , }), ); } if (!removeItemDisabled) { if (!addItemDisabled) { options.push({ id: 'divider-2', value: '-' }); } options.push( listItemActionBuilder({ id: 'delete', title: hasMultipleRows ? `Delete ${selectedRowKeys.length} Rows` : 'Delete Row', prefixIcon: , skin: 'destructive', }), ); } const handleSelect = (option: { id: string | number }) => { switch (option.id) { case 'copy': handleCopy(); break; case 'cut': handleCut(); break; case 'paste': handlePaste(); break; case 'clear': handleClear(); break; case 'add-above': { const rowIdx = state.keyedItems.findIndex( (ki) => ki.key === cell.rowKey, ); const prevKey = rowIdx > 0 ? state.keyedItems[rowIdx - 1].key : undefined; state.addItem(prevKey); onClose(); break; } case 'add-below': state.addItem(cell.rowKey); onClose(); break; case 'duplicate': state.duplicateItem(cell.rowKey); onClose(); break; case 'delete': state.confirmDeleteItems( hasMultipleRows ? selectedRowKeys : [cell.rowKey], ); onClose(); break; } }; const dividerCount = options.filter( (o) => 'value' in o && o.value === '-', ).length; const itemCount = options.length - dividerCount; const estimatedHeight = itemCount * ITEM_HEIGHT + dividerCount * DIVIDER_HEIGHT + PADDING; const x = position.x + OFFSET_X + MENU_WIDTH > window.innerWidth ? position.x - MENU_WIDTH : position.x + OFFSET_X; const y = position.y + OFFSET_Y + estimatedHeight > window.innerHeight ? Math.max(4, position.y - estimatedHeight) : position.y + OFFSET_Y; return createPortal(
e.preventDefault()} >
, document.body, ); } export const EditableTableContextMenu = observer(_EditableTableContextMenu);