"use client"; import type { RefObject } from "react"; import { useCallback, useLayoutEffect, useRef, useSyncExternalStore } from "react"; import { getLayoutRowCount, getLogicalItemStyle, getLogicalTrackSize } from "~/components/board/layout"; import type { TransactionalGridState } from "./dnd"; import type { GridInteractionStore } from "./grid-interaction-store"; import { clearGridPreviewGeometry, createGridPreviewDomState, syncGridPreviewGeometry } from "./grid-preview-geometry"; import type { SectionGridPlacement } from "./use-grid-layout-actions"; export interface GridInteraction { activeId: string; sourceGridId: string; targetGridId: string | null; targetPlacement: SectionGridPlacement | null; state: TransactionalGridState; mode: "drag" | "resize"; valid: boolean; previewRevision: number; } interface GridPreviewLayerProps { sectionId: string; rowCount: number; maxRowCount: number | null; placements: readonly SectionGridPlacement[]; gridRef: RefObject; entryElements: ReadonlyMap; interactionStore: GridInteractionStore; } export const GridPreviewLayer = ({ sectionId, rowCount, maxRowCount, placements, gridRef, entryElements, interactionStore, }: GridPreviewLayerProps) => { const subscribe = useCallback( (listener: () => void) => interactionStore.subscribeGrid(sectionId, listener), [interactionStore, sectionId], ); const getSnapshot = useCallback(() => interactionStore.getGridSnapshot(sectionId), [interactionStore, sectionId]); const interaction = useSyncExternalStore(subscribe, getSnapshot, getSnapshot); const previewGrid = interaction?.state.grids.find((grid) => grid.id === sectionId); const previewStateRef = useRef(createGridPreviewDomState()); const placementsRef = useRef(placements); const isDragging = interaction?.mode === "drag"; const isTargetGrid = isDragging && interaction.targetGridId === sectionId; let targetPlacement: SectionGridPlacement | null = null; if (interaction?.valid && interaction.targetGridId === sectionId) { targetPlacement = interaction.targetPlacement; } const growsDuringDrag = isDragging && (isTargetGrid || (interaction.targetGridId === null && interaction.sourceGridId === sectionId)); const previewRowCount = getLayoutRowCount(previewGrid?.placements ?? placements); let renderedRowCount = rowCount; if (maxRowCount === null && interaction) { renderedRowCount = Math.max(rowCount, previewRowCount); if (growsDuringDrag) renderedRowCount += 1; } useLayoutEffect(() => { placementsRef.current = placements; previewStateRef.current = syncGridPreviewGeometry({ elements: entryElements, placements, previewPlacements: previewGrid?.placements ?? null, activeId: interaction?.activeId ?? null, mode: interaction?.mode ?? null, previous: previewStateRef.current, }); const grid = gridRef.current; if (!grid) return; grid.dataset.dndDropTarget = String(isTargetGrid); if (isTargetGrid) grid.dataset.dndDropValid = String(targetPlacement !== null); else delete grid.dataset.dndDropValid; if (interaction?.targetGridId === sectionId) { grid.dataset.dndPreviewRevision = String(interaction.previewRevision); } else { delete grid.dataset.dndPreviewRevision; } grid.style.height = `${getLogicalTrackSize(renderedRowCount)}px`; const viewport = grid.parentElement; if (viewport?.hasAttribute("data-section-id")) { viewport.style.setProperty("--board-grid-drag-height", `${getLogicalTrackSize(renderedRowCount)}px`); } }, [ entryElements, gridRef, interaction, isTargetGrid, placements, previewGrid?.placements, renderedRowCount, sectionId, targetPlacement, ]); useLayoutEffect( () => () => { clearGridPreviewGeometry(entryElements, placementsRef.current, previewStateRef.current); const grid = gridRef.current; if (!grid) return; grid.removeAttribute("data-dnd-drop-target"); grid.removeAttribute("data-dnd-drop-valid"); grid.removeAttribute("data-dnd-preview-revision"); grid.parentElement?.style.removeProperty("--board-grid-drag-height"); }, [entryElements, gridRef], ); if (!targetPlacement) return null; return (