fix: drag&drop events on folder headers with stopPropagation, separate root drop zone
This commit is contained in:
@@ -50,18 +50,39 @@ function TreeItem({
|
|||||||
const [expanded, setExpanded] = useState(true);
|
const [expanded, setExpanded] = useState(true);
|
||||||
const [isDragOver, setIsDragOver] = useState(false);
|
const [isDragOver, setIsDragOver] = useState(false);
|
||||||
const isRoot = node.folder === null;
|
const isRoot = node.folder === null;
|
||||||
|
const folderId = node.folder?.id || null;
|
||||||
|
|
||||||
|
// Drop handler for this specific folder - stopPropagation so parent doesn't also fire
|
||||||
|
const handleDrop = (e: React.DragEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
setIsDragOver(false);
|
||||||
|
onDropToFolder(folderId);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDragOver = (e: React.DragEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
setIsDragOver(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDragLeave = (e: React.DragEvent) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
// Only clear if leaving to a non-child element
|
||||||
|
setIsDragOver(false);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div>
|
||||||
onDragOver={(e) => { e.preventDefault(); setIsDragOver(true); }}
|
{/* Folder header - this is the drop target */}
|
||||||
onDragLeave={() => setIsDragOver(false)}
|
|
||||||
onDrop={(e) => { e.preventDefault(); setIsDragOver(false); onDropToFolder(node.folder?.id || null); }}
|
|
||||||
>
|
|
||||||
{!isRoot && (
|
{!isRoot && (
|
||||||
<div
|
<div
|
||||||
|
onDragOver={handleDragOver}
|
||||||
|
onDragLeave={handleDragLeave}
|
||||||
|
onDrop={handleDrop}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'group flex items-center gap-1 px-2 py-1.5 text-sm font-medium rounded-md cursor-pointer',
|
'group flex items-center gap-1 px-2 py-1.5 text-sm font-medium rounded-md cursor-pointer',
|
||||||
isDragOver ? 'bg-primary-100 ring-1 ring-primary-300' : 'text-secondary-700 hover:bg-secondary-100'
|
isDragOver ? 'bg-primary-100 ring-2 ring-primary-400' : 'text-secondary-700 hover:bg-secondary-100'
|
||||||
)}
|
)}
|
||||||
style={{ paddingLeft: depth * 12 + 8 }}
|
style={{ paddingLeft: depth * 12 + 8 }}
|
||||||
onClick={() => setExpanded(!expanded)}
|
onClick={() => setExpanded(!expanded)}
|
||||||
@@ -77,17 +98,19 @@ function TreeItem({
|
|||||||
<button onClick={(e) => { e.stopPropagation(); onDeleteFolder(node.folder!.id); }} className="opacity-0 group-hover:opacity-100 text-secondary-400 hover:text-red-600" title="Löschen">✕</button>
|
<button onClick={(e) => { e.stopPropagation(); onDeleteFolder(node.folder!.id); }} className="opacity-0 group-hover:opacity-100 text-secondary-400 hover:text-red-600" title="Löschen">✕</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Sessions in this folder */}
|
||||||
{(isRoot || expanded) && (
|
{(isRoot || expanded) && (
|
||||||
<div>
|
<div>
|
||||||
{node.sessions.map((session) => (
|
{node.sessions.map((session) => (
|
||||||
<div
|
<div
|
||||||
key={session.id}
|
key={session.id}
|
||||||
draggable
|
draggable
|
||||||
onDragStart={() => onDragSession(session.id)}
|
onDragStart={(e) => { e.stopPropagation(); onDragSession(session.id); }}
|
||||||
onClick={() => onSelectSession(session.id)}
|
onClick={() => onSelectSession(session.id)}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'group flex items-center gap-2 px-3 py-1.5 text-sm cursor-pointer rounded-md',
|
'group flex items-center gap-2 px-3 py-1.5 text-sm cursor-pointer rounded-md',
|
||||||
'hover:bg-secondary-100 active:cursor-grabbing',
|
'hover:bg-secondary-100',
|
||||||
activeSessionId === session.id && 'bg-primary-100 text-primary-700'
|
activeSessionId === session.id && 'bg-primary-100 text-primary-700'
|
||||||
)}
|
)}
|
||||||
style={{ paddingLeft: depth * 12 + 24 }}
|
style={{ paddingLeft: depth * 12 + 24 }}
|
||||||
@@ -101,6 +124,21 @@ function TreeItem({
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Root drop zone - only for root level, separate from sessions */}
|
||||||
|
{isRoot && (
|
||||||
|
<div
|
||||||
|
onDragOver={handleDragOver}
|
||||||
|
onDragLeave={handleDragLeave}
|
||||||
|
onDrop={handleDrop}
|
||||||
|
className={clsx(
|
||||||
|
'min-h-[8px] rounded-md transition-colors',
|
||||||
|
isDragOver && 'bg-primary-50 ring-1 ring-primary-300'
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Child folders */}
|
||||||
{(isRoot || expanded) && node.children.map((child) => (
|
{(isRoot || expanded) && node.children.map((child) => (
|
||||||
<TreeItem
|
<TreeItem
|
||||||
key={child.folder!.id}
|
key={child.folder!.id}
|
||||||
|
|||||||
Reference in New Issue
Block a user