0

I'm using hello-pangea/dnd to have a drag and drop area, I want to be able to drag and drop sections to reorder them in a list.

However, I don't want to just have a handle, I want to be able to drag them from anywhere in the div. I'm able to go that, but to add something else to it, I would like to have an interactive area dedicated to something else in that div, that wouldn't be affected by the drag and drop.

To illustrate my issue, I would like to be able to drag and drop by clicking and dragging the collapsible-header area dragging area

But to also exclude the svg from the drag and drop area, if I interact with the svg, I don't want to drag the whole entire section to be moved, I want to be able to move around the blue handles on the svg, area I want to exclude from dnd

I tried different solutions but haven't been successful.

my issue, I can't interact with the svg without dragging the section

Here is my code:

import { DraggableProvidedDragHandleProps } from "@hello-pangea/dnd"; import CollapsibleSection from "../collapsibleSection"; export interface StatsSectionProps { isExpanded: boolean; onToggle: () => void; dragHandleProps?: DraggableProvidedDragHandleProps; } export const StatsSection: React.FC<StatsSectionProps> = ({ isExpanded, onToggle, dragHandleProps }) => ( <CollapsibleSection header={<h3>Stats</h3>} isExpanded={isExpanded} onToggle={onToggle} dragHandleProps={dragHandleProps}> <div className="stats-info"> <p>Statistical information will be displayed here.</p> </div> </CollapsibleSection> ); 
export interface SpanSectionProps { isExpanded: boolean; onToggle: () => void; timePeriod: { totalDays: number; monthsAndDays: string; yearsMonthsDays: string; xAxisLabels: string; startDate: string; endDate: string; }; dragHandleProps?: DraggableProvidedDragHandleProps; onDateChange: (startDate: string, endDate: string) => void; } export const SpanSection: React.FC<SpanSectionProps> = ({isExpanded, onToggle, timePeriod, dragHandleProps, onDateChange}) => { const [localTimePeriod, setLocalTimePeriod] = useState(timePeriod); useEffect(() => { setLocalTimePeriod(timePeriod); }, [timePeriod]); const handleDateChange = (startDate: string, endDate: string) => { setLocalTimePeriod(prev => ({ ...prev, startDate, endDate })); onDateChange(startDate, endDate); }; return ( <div className="span-section" style={{ width: '100%' }}> <CollapsibleSection header={ <TimelineAxis fixedStartDate={timePeriod.startDate} fixedEndDate={timePeriod.endDate} initialStartDate={localTimePeriod.startDate} initialEndDate={localTimePeriod.endDate} onDateChange={handleDateChange} /> } isExpanded={isExpanded} onToggle={onToggle} dragHandleProps={dragHandleProps} > <div className="time-period-info"> <p>Start date: {localTimePeriod.startDate}</p> <p>End date: {localTimePeriod.endDate}</p> <p>Total days: {localTimePeriod.totalDays}</p> <p>Months and days: {localTimePeriod.monthsAndDays}</p> <p>Years, months, and days: {localTimePeriod.yearsMonthsDays}</p> <p>X axis labels: {localTimePeriod.xAxisLabels}</p> </div> </CollapsibleSection> </div> ); }; 
import React, { useRef, useEffect, useState, useCallback } from 'react'; import * as d3 from 'd3'; interface TimelineAxisProps { fixedStartDate: string; fixedEndDate: string; initialStartDate: string; initialEndDate: string; onDateChange: (startDate: string, endDate: string) => void; } const TimelineAxis: React.FC<TimelineAxisProps> = ({ fixedStartDate, fixedEndDate, initialStartDate, initialEndDate, onDateChange }) => { const [width, setWidth] = useState(0); const svgRef = useRef<SVGSVGElement>(null); const [startDate, setStartDate] = useState(initialStartDate); const [endDate, setEndDate] = useState(initialEndDate); const parseDate = d3.timeParse('%d/%m/%Y'); const formatDate = d3.timeFormat('%d/%m/%Y'); const updateWidth = useCallback(() => { if (svgRef.current) { const newWidth = svgRef.current.getBoundingClientRect().width; setWidth(newWidth); } }, []); useEffect(() => { updateWidth(); window.addEventListener('resize', updateWidth); return () => window.removeEventListener('resize', updateWidth); }, [updateWidth]); const fixedStart = parseDate(fixedStartDate); const fixedEnd = parseDate(fixedEndDate); const start = parseDate(startDate); const end = parseDate(endDate); if (!fixedStart || !fixedEnd || !start || !end) return null; const years = d3.timeYear.range(fixedStart, d3.timeYear.offset(fixedEnd, 1)); const scale = d3.scaleTime() .domain([fixedStart, fixedEnd]) .range([30, width - 30]); const getVisibleYears = (years: Date[], scale: d3.ScaleTime<number, number>): Date[] => { if (years.length <= 1) return years; const totalSpace = Math.abs(scale(years[years.length - 1]) - scale(years[0])); const averageSpace = totalSpace / (years.length - 1); const minSpaceBetweenLabels = 50; let step; if (averageSpace >= minSpaceBetweenLabels) { step = 1; } else if (averageSpace * 2 >= minSpaceBetweenLabels) { step = 2; } else if (averageSpace * 3 >= minSpaceBetweenLabels) { step = 3; } else { step = 4; } return years.filter((_, index) => index % step === 0); }; const visibleYears = getVisibleYears(years, scale); const handleDrag = (isStart: boolean) => (event: React.MouseEvent<SVGCircleElement>) => { event.preventDefault(); event.stopPropagation(); const svg = svgRef.current; if (!svg) return; const startDrag = (e: MouseEvent) => { e.preventDefault(); const mouseX = e.clientX - svg.getBoundingClientRect().left; const date = scale.invert(mouseX); const formattedDate = formatDate(date); if (isStart) { if (date >= fixedStart && date < end) { setStartDate(formattedDate); onDateChange(formattedDate, endDate); } } else { if (date <= fixedEnd && date > start) { setEndDate(formattedDate); onDateChange(startDate, formattedDate); } } }; const stopDrag = () => { document.removeEventListener('mousemove', startDrag); document.removeEventListener('mouseup', stopDrag); }; document.addEventListener('mousemove', startDrag); document.addEventListener('mouseup', stopDrag); }; const handleSvgClick = (event: React.MouseEvent<SVGSVGElement>) => { event.stopPropagation(); }; return ( <svg ref={svgRef} width="100%" height="60" onClick={handleSvgClick}> <line x1="30" y1="30" x2={width - 30} y2="30" stroke="currentColor" /> {visibleYears.map((year, index) => ( <g key={index} transform={`translate(${scale(year)}, 0)`}> <line x1="0" y1="25" x2="0" y2="35" stroke="currentColor" /> <text x="0" y="20" textAnchor="middle" fontSize="12">{year.getFullYear()}</text> </g> ))} <circle cx={scale(start)} cy="30" r="8" fill="var(--primary-color, blue)" cursor="ew-resize" onMouseDown={handleDrag(true)} /> <circle cx={scale(end)} cy="30" r="8" fill="var(--primary-color, blue)" cursor="ew-resize" onMouseDown={handleDrag(false)} /> <text x={scale(start)} y="50" textAnchor="middle" fontSize="12">{startDate}</text> <text x={scale(end)} y="50" textAnchor="middle" fontSize="12">{endDate}</text> </svg> ); }; export default TimelineAxis; 
interface SectionsProps { variables: Omit<VariablesSectionProps, 'dragHandleProps'>; span: Omit<SpanSectionProps, 'dragHandleProps'>; predictions: Omit<PredictionsSectionProps, 'dragHandleProps'>; stats: Omit<StatsSectionProps, 'dragHandleProps'>; } type SectionKey = keyof SectionsProps; const DraggableSections: React.FC<{ sectionsProps: SectionsProps }> = ({ sectionsProps }) => { const [sections, setSections] = useState<Array<{ id: SectionKey; component: React.ComponentType<any> }>>([ { id: 'span', component: SpanSection }, { id: 'stats', component: StatsSection }, ]); const onDragEnd = (result: any) => { if (!result.destination) return; const items = Array.from(sections); const [reorderedItem] = items.splice(result.source.index, 1); items.splice(result.destination.index, 0, reorderedItem); setSections(items); }; return ( <DragDropContext onDragEnd={onDragEnd}> <Droppable droppableId="sections"> {(provided) => ( <div {...provided.droppableProps} ref={provided.innerRef}> {sections.map((section, index) => { const SectionComponent = section.component; return ( <Draggable key={section.id} draggableId={section.id} index={index}> {(provided, snapshot) => ( <div ref={provided.innerRef} {...provided.draggableProps} className={`draggable-section ${snapshot.isDragging ? 'is-dragging' : ''}`}> <SectionComponent {...sectionsProps[section.id]} dragHandleProps={provided.dragHandleProps} /> </div> )} </Draggable> ); })} {provided.placeholder} </div> )} </Droppable> </DragDropContext> ); }; export default DraggableSections; 

I haven't been able to find a working solution so far. Would you have any idea/suggestion on how to approach or solve this?

1 Answer 1

0
import React from "react"; import { DragDropContext, Draggable, Droppable, DropResult } from "@hello-pangea/dnd"; const App = () => { const items = [ { id: "1", content: "Draggable Item 1", isDraggable: true }, { id: "2", content: "Draggable Item 2", isDraggable: false }, { id: "3", content: "Draggable Item 3", isDraggable: true }, ]; const [list, setList] = React.useState(items); const onDragEnd = (result: DropResult) => { const { destination, source } = result; // If no destination, return if (!destination) return; // Reorder items const reorderedItems = Array.from(list); const [removed] = reorderedItems.splice(source.index, 1); reorderedItems.splice(destination.index, 0, removed); setList(reorderedItems); }; return ( <DragDropContext onDragEnd={onDragEnd}> <Droppable droppableId="droppable-area"> {(provided) => ( <div {...provided.droppableProps} ref={provided.innerRef} style={{ padding: 16, width: 300, border: "1px solid lightgray", borderRadius: 4, }} > {list.map((item, index) => ( <Draggable key={item.id} draggableId={item.id} index={index} isDragDisabled={!item.isDraggable} // Disable dragging based on the item property > {(provided) => ( <div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps} style={{ padding: 16, margin: "4px 0", backgroundColor: item.isDraggable ? "#fff" : "#ddd", border: "1px solid lightgray", borderRadius: 4, cursor: item.isDraggable ? "grab" : "not-allowed", ...provided.draggableProps.style, }} > {item.content} </div> )} </Draggable> ))} {provided.placeholder} </div> )} </Droppable> </DragDropContext> ); }; export default App; 
Sign up to request clarification or add additional context in comments.

1 Comment

Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.