"use client"; import { createContext, useContext, useLayoutEffect, useRef, useState } from "react"; import type { CellComponentProps } from "react-spreadsheet"; import { type ExtraSheetCell, type ExtraSheetRange, } from "@/lib/guides/extra-sheet"; import { cn } from "@/lib/utils"; export const ExtraSheetCellContext = createContext<{ merges: Map; layoutVersion: number } | null>(null); export function sheetCellElement(table: HTMLTableElement, row: number, column: number): HTMLTableCellElement | null { return table.querySelector(`[data-sheet-row="${row}"][data-sheet-column="${column}"]`); } export function sheetRangeRect(table: HTMLTableElement, range: ExtraSheetRange) { const first = sheetCellElement(table, range.start.row, range.start.column); const last = sheetCellElement(table, range.end.row, range.end.column); if (!first || !last) return null; return { top: first.offsetTop, left: first.offsetLeft, width: last.offsetLeft + last.offsetWidth - first.offsetLeft, height: last.offsetTop + last.offsetHeight - first.offsetTop, }; } // Keep physical cells for react-spreadsheet's per-axis measurements. Only the // anchor draws the merged content; covered cells remain empty layout slots. export function ExtraSheetCellView(props: CellComponentProps) { const context = useContext(ExtraSheetCellContext)!; const { row, column, active, selected, dragging, mode, data, DataViewer, setCellDimensions } = props; const rootRef = useRef(null); const contentRef = useRef(null); const merge = context.merges.get(`${row}:${column}`); const covered = !!merge && (merge.start.row !== row || merge.start.column !== column); const [size, setSize] = useState({ width: 0, height: 0, spacer: 36 }); useLayoutEffect(() => { const root = rootRef.current; if (!root) return; const table = root.closest("table"); if (merge && !covered && table) { const rect = sheetRangeRect(table, merge); if (rect) { const contentHeight = contentRef.current?.scrollHeight ?? 36; const spacer = Math.max(36, contentHeight - (rect.height - root.offsetHeight)); setSize((previous) => previous.width === rect.width && previous.height === rect.height && previous.spacer === spacer ? previous : { width: rect.width, height: rect.height, spacer }); } } if (selected || active) { setCellDimensions({ row, column }, { top: root.offsetTop, left: root.offsetLeft, width: root.offsetWidth, height: root.offsetHeight, }); } if (active && mode === "view" && !covered) root.focus(); }, [row, column, active, selected, mode, covered, merge, data, context, setCellDimensions, size.spacer]); const target = merge?.start ?? { row, column }; return ( { // Activation focuses the anchor. Browser default focus can otherwise // blur the grid after the previous editor unmounts. event.preventDefault(); if (mode !== "view") return; if (event.shiftKey) props.select(target); else props.activate(target); }} onMouseOver={() => { if (dragging) props.select(target); }} > {merge ? ( <>