import { useState, useEffect } from 'react'; import './CustomTagSelector.less'; import { useAppDispatch } from '../../store/hooks'; import { clearSelection } from '../../store/uiSlice'; import { TagAttachment, AggregateTagAttachment } from './tag_utils'; import { Menu, MenuItem, Tooltip } from '@mui/joy'; interface CustomTagSelectorProps { options: string[]; attachments: (TagAttachment | AggregateTagAttachment)[]; onAdd: (value: string) => void; onDelete: (value: string) => void; onClickTag: (value: string) => void; onDoubleClickTag: (value: string) => void; multiSelect?: boolean; hideAddButton?: boolean; proposed?: boolean; } export function CustomTagSelector({ options, attachments, onAdd, onDelete, onClickTag, onDoubleClickTag, multiSelect = false, hideAddButton = false, proposed = false, }: CustomTagSelectorProps) { const [selected, setSelected] = useState(''); const dispatch = useAppDispatch(); const [editMode, setEditMode] = useState(false); const [contextMenu, setContextMenu] = useState<{ top: number; left: number; tagName: string; } | null>(null); useEffect(() => { if (contextMenu) { const handleGlobalClick = () => setContextMenu(null); const handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'Escape') setContextMenu(null); }; const frameId = requestAnimationFrame(() => { window.addEventListener('click', handleGlobalClick); window.addEventListener('contextmenu', handleGlobalClick); window.addEventListener('keydown', handleKeyDown); }); return () => { cancelAnimationFrame(frameId); window.removeEventListener('click', handleGlobalClick); window.removeEventListener('contextmenu', handleGlobalClick); window.removeEventListener('keydown', handleKeyDown); }; } }, [contextMenu]); const handleContextMenu = (event: React.MouseEvent, tagName: string) => { event.preventDefault(); event.stopPropagation(); setContextMenu({ top: event.clientY, left: event.clientX, tagName: tagName, }); }; const handleMenuClose = () => { setContextMenu(null); }; const selectedPills = attachments.map((attachment) => ( { if (!multiSelect) { dispatch(clearSelection()); } setSelected(s == selected ? '' : s); onClickTag(s); }} onDoubleClickTag={(s) => { onDoubleClickTag(s); }} onDelete={(s) => { setSelected(''); onDelete(s); }} onContextMenu={(e) => handleContextMenu(e, attachment.name)} /> )); return (
{selectedPills} ({ top: contextMenu.top, left: contextMenu.left, right: contextMenu.left, bottom: contextMenu.top, width: 0, height: 0, toJSON: () => {}, x: contextMenu.left, y: contextMenu.top, }), } : null } > { if (contextMenu) onDoubleClickTag(contextMenu.tagName); handleMenuClose(); }} > Focus '{contextMenu?.tagName}' { if (contextMenu) onDelete(contextMenu.tagName); handleMenuClose(); }} color="danger" > Remove Tag {editMode ? ( <> { if (!multiSelect) { dispatch(clearSelection()); } }} onKeyUp={(e) => { // clicking a datalist option produces a keydown event of type Event, not KeyboardEvent // https://stackoverflow.com/questions/31901351/how-to-fire-an-event-on-clicking-data-list-option-list if (!(e.nativeEvent instanceof KeyboardEvent) || e.key === 'Enter') { const t = e.target as HTMLInputElement; onAdd(t.value); t.value = ''; } }} onKeyDown={(e) => { if (e.key === 'Escape') { setEditMode(false); } if ( e.key === 'Backspace' && e.target instanceof HTMLInputElement && e.target.value === '' ) { if (selected == attachments[attachments.length - 1].name) { onDelete(attachments[attachments.length - 1].name); } else { setSelected(attachments[attachments.length - 1].name); } } }} onBlur={(e) => { setEditMode(false); }} /> {options?.map((option) => ( ) : hideAddButton ? null : (
{ setEditMode(true); }} > +
)}
); } export function TagPill({ attachment, selected, proposed = false, onClickTag, onDoubleClickTag, onDelete, onContextMenu, }: { attachment: TagAttachment | AggregateTagAttachment; selected: boolean; proposed?: boolean; onClickTag: (name: string) => void; onDoubleClickTag: (name: string) => void; onDelete: (name: string) => void; onContextMenu: (e: React.MouseEvent) => void; }) { const isAutoTag = 'humanEdit' in attachment ? !attachment.humanEdit : false; return (
{ onClickTag(attachment.name); }} onDoubleClick={(e) => { onDoubleClickTag(attachment.name); }} onContextMenu={onContextMenu} onKeyDown={(e) => { if (e.key === 'Backspace' && selected) { onDelete(attachment.name); } }} > {attachment.name}
); }