Files
buzz-sheet/components/admin/extra-sheet-cell.tsx
T
gunshiz 35f9dc40ba
CI / Verify (push) Successful in 2m20s
CI / Build immutable images and deploy (push) Successful in 2m57s
feat : i dont even know what am i doing right now
2026-10-10 04:33:08 +07:00

109 lines
4.6 KiB
TypeScript

"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<string, ExtraSheetRange>; 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<ExtraSheetCell>) {
const context = useContext(ExtraSheetCellContext)!;
const { row, column, active, selected, dragging, mode, data, DataViewer, setCellDimensions } = props;
const rootRef = useRef<HTMLTableCellElement>(null);
const contentRef = useRef<HTMLDivElement>(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 (
<td
ref={rootRef}
className={cn("Spreadsheet__cell", merge && "extra-sheet-merge-slot")}
data-sheet-row={row}
data-sheet-column={column}
style={{ verticalAlign: data?.verticalAlignment ?? "top" }}
tabIndex={covered ? -1 : 0}
aria-hidden={covered || undefined}
aria-rowspan={!covered && merge ? merge.end.row - row + 1 : undefined}
aria-colspan={!covered && merge ? merge.end.column - column + 1 : undefined}
onMouseDown={(event) => {
// 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 ? (
<>
<div aria-hidden="true" style={{ minHeight: covered ? 36 : size.spacer }} />
{!covered ? (
<div
className="extra-sheet-merged-cell"
style={{
width: size.width || undefined, minHeight: size.height || 36,
backgroundColor: data?.backgroundColor,
justifyContent: data?.verticalAlignment === "middle" ? "center" : data?.verticalAlignment === "bottom" ? "flex-end" : "flex-start",
}}
>
<div ref={contentRef}>
<DataViewer row={row} column={column} cell={data} evaluatedCell={props.evaluatedData} setCellData={props.setCellData} />
</div>
</div>
) : null}
</>
) : (
<DataViewer row={row} column={column} cell={data} evaluatedCell={props.evaluatedData} setCellData={props.setCellData} />
)}
</td>
);
}