Cell overlays
An overlay is a DOM node of yours that the editor places on a cell or a range. The editor keeps it there through scrolling, resizing and sheet switches. You decide what is inside it: an agent’s cursor, a collaborator’s selection, a suggested edit with an accept button.
Overlays are UI only. They are not part of the workbook and are never saved.
Interactive Demo
Section titled “Interactive Demo”Overlays live on the controller. Create an element and hand it to overlays.add with a target. The id it returns is what update and remove take.
import { useRef } from "react";import { SpreadsheetEditor, type SpreadsheetController } from "@grid-is/spreadsheet-editor";import "@grid-is/spreadsheet-editor/style.css";
export default function App() { const controllerRef = useRef<SpreadsheetController>(null);
const addCursor = () => { const element = document.createElement("div"); element.style.border = "2px solid #7c3aed"; controllerRef.current?.overlays.add({ target: "B2", element }); };
return ( <> <button onClick={addCursor}>Mark B2</button> <SpreadsheetEditor model={model} controllerRef={controllerRef} /> </> );}A target is a cell or a range, with or without a sheet name: "B2", "B2:D4", "Sheet2!B2".
The element
Section titled “The element”The editor sets these styles on your element and nothing else:
position,left,top,widthandheight, so the element covers its targetpointer-events, from theinteractiveoption
While its target cannot be shown, the element is taken out of the DOM. It is put back as soon as the target can be shown again.
Your content does not have to fit inside the target. The element covers its cells, and a name tag above a cursor or a card beside a cell can overflow it freely. Overflowing content is clipped at the edges of the grid, so it slides under the headers the same way cells do.
In React, render into the element with a portal:
import { useEffect, useState } from "react";import { createPortal } from "react-dom";
function Cursor({ controllerRef, name }) { const [element] = useState(() => document.createElement("div"));
useEffect(() => { const overlays = controllerRef.current?.overlays; if (!overlays) return; const id = overlays.add({ target: "B2", element }); return () => overlays.remove(id); }, [controllerRef, element]);
return createPortal(<span className="name-tag">{name}</span>, element);}Render the component after the editor has mounted, so the controller is there when the effect runs.
interactive
Section titled “interactive”By default an overlay is a highlight. Clicks, double-clicks and drags go through it to the cells underneath.
Set interactive: true when the overlay has buttons or inputs. Clicks then stop at the overlay and the sheet does not react to them. The mouse wheel scrolls the grid either way.
controllerRef.current?.overlays.add({ target: "C12", element, interactive: true });Clicking a button in an overlay moves keyboard focus to that button. Call focus() to hand it back to the sheet.
Suggested edits
Section titled “Suggested edits”The suggested edit in the demo above is an interactive overlay on the cell. Accept writes the value through the model, removes the overlay and gives focus back to the sheet:
const accept = () => { const ref = new Reference("C12"); model.getWorkbook()?.writeCellData(ref, { v: 1800 }); model.recalculate(); controllerRef.current?.overlays.remove(id); controllerRef.current?.focus(ref.toString());};update
Section titled “update”update(id: string, patch: Partial<SpreadsheetOverlay>): voidChanges an overlay. Use it to move an overlay to another target. An id that does not exist is ignored.
controllerRef.current?.overlays.update(id, { target: "D6" });remove
Section titled “remove”remove(id: string): voidTakes the overlay off the sheet. Your element is left as it is.
controllerRef.current?.overlays.remove(id);all(): Array<SpreadsheetOverlay & { id: string }>Returns every overlay with its id, in the order they were added.
const targets = controllerRef.current?.overlays.all().map((overlay) => overlay.target);clear(): voidRemoves every overlay.
controllerRef.current?.overlays.clear();Good to know
Section titled “Good to know”- Sheets. A target with a sheet name, like
"Sheet2!B2", is out of the DOM while another sheet is open. A target without one, like"B2", shows on whichever sheet is open. - Merged cells. A target is taken as written.
"B2"covers only B2’s own slot, also when B2 is part of a merged cell. Pass the whole range, like"B2:D4", to cover the merged cell. - Hidden rows and columns. An overlay is out of the DOM while its target has no size.
- A new model. Overlays stay when the
modelprop changes, so an overlay you keep updating carries on working. Callclear, or remove the ones you own, when you load another workbook. - Inserted rows and columns. A target stays at its address. Insert a row above an overlay on
B2and it is still onB2. Listen for events and callupdateif it should follow the content.