/**
* Generates the drag-and-drop wrapper components source code (dnd-kit based).
*
* One file exports all primitives:
* - TqDragArea: DndContext boundary; tracks which droppable zone each
* draggable was dropped into
* - TqDraggable: draggable wrapper; when dropped into a zone it renders
* into that zone's DOM node via a portal (its React subtree
* never re-parents, so children/state keep working)
* - TqDroppable: drop zone; registers its DOM node with the area and
* highlights while a draggable hovers it
* - TqSortable: self-contained sortable list; reorders its direct
* children on drag (vertical or horizontal)
* - TqSortableItem: plain styling wrapper for a sortable entry
*
* React 17 compatible on purpose: no useId / useSyncExternalStore — projects
* only get React 18 when other primitives (e.g. the calendar) require it.
* dnd-kit is SSR-safe, so plain imports are fine in Next.js pages.
*/
export const generateDragDropComponentCode = (): string => {
return `import React, {
createContext,
useCallback,
useContext,
useEffect,
useMemo,
useRef,
useState,
} from 'react'
import { createPortal } from 'react-dom'
import {
DndContext,
PointerSensor,
closestCenter,
useDraggable,
useDroppable,
useSensor,
useSensors,
} from '@dnd-kit/core'
import {
SortableContext,
arrayMove,
horizontalListSortingStrategy,
useSortable,
verticalListSortingStrategy,
} from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
const DragAreaContext = createContext(null)
// Stable auto-ids without React 18's useId. Ids never reach the DOM (dnd-kit
// keeps them in its internal registry), so server/client counters don't need
// to match.
let autoIdCounter = 0
const useStableId = (prefix, explicitId) => {
const idRef = useRef(null)
if (idRef.current === null) {
autoIdCounter += 1
idRef.current = prefix + '-auto-' + autoIdCounter
}
return explicitId || idRef.current
}
// Distance constraint keeps clicks on buttons/links inside draggables working.
const useDefaultSensors = () =>
useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 4 } }))
export const TqDragArea = ({ children, ...rest }) => {
const [assignments, setAssignments] = useState({})
const [zoneNodes, setZoneNodes] = useState({})
const sensors = useDefaultSensors()
const registerZone = useCallback((id, node) => {
setZoneNodes((prev) => {
if (prev[id] === node) {
return prev
}
const next = { ...prev }
if (node) {
next[id] = node
} else {
delete next[id]
}
return next
})
}, [])
const handleDragEnd = useCallback(({ active, over }) => {
if (!over) {
return
}
setAssignments((prev) => ({ ...prev, [active.id]: over.id }))
}, [])
const contextValue = useMemo(
() => ({ assignments, zoneNodes, registerZone }),
[assignments, zoneNodes, registerZone]
)
return (