Skip to content
SumOfficeSumOfficeSumOffice

Embedding kit: structure, events, menu

A DOM-free access model, structural projections of the workbook, click and selection events, context-menu contributions, and commands instead of direct writes.

Status Item What it means
Exists now The operations contract in the core Reading structure, cells, and objects, typed change operations.
Exists now Handling actions in the surface Hit-testing, clicks and menu invocation, selection, and internal semantic events.
Needs to be formalized The public embedding kit Stable methods and events, adding your own menu items, permissions, and versioning.
  • Read — find out the workbook’s contents, the selection, a cell, an object.
  • Events — respond to user actions.
  • Commands — safely change the workbook.
  • Interface extension — add your own commands and menu items.

Not the document’s DOM, but structural projections and commands

Section titled “Not the document’s DOM, but structural projections and commands”

The spreadsheet in SumSheet isn’t laid out on the page as an element tree. It’s drawn on a canvas, and only the visible part of the workbook is held in memory — so the cell or object you need may not exist in the markup at all, even while the user sees it. You need to reach the logical structure through a set of calls.

Item What it gives you Where the data comes from
WorkbookSession The workbook and session identifiers, the revision, an unsaved-changes flag, the active sheet, and the available formats. The core session.
WorkbookInfo Sheets with stable identifiers, their names, visibility, and which sheet is active. A projection from the core.
Selection The sheet, the selected range, the active cell, and the identifiers of the selected objects. Hit-testing in the surface, plus identifiers from the core.
CellDetails The stored value, the displayed value, the formula, the type, the format, input validation, a comment, and a link. The read_cell_details operation.
RangeSnapshot A bounded range: values, formulas, styles, and related data. A request to the core with the range’s boundaries stated explicitly.
ObjectInventory Shapes, charts, images, controls, their anchoring, stacking order, and available actions. An object inventory from the file.
ContextTarget What’s under the pointer: a cell, a table, a chart, a shape, a header. Semantic hit-testing.

This is the proposed naming for the public calls — these names don’t exist in the current contract.

const session = await F1.open({
mount, documentId, bytes, mode: "edit"
});
const workbook = await session.getWorkbookInfo();
const selection = await session.getSelection();
const cell = await session.getCellDetails(selection.activeCell);

How to get sheets, a range, a table, and a graphic object

Section titled “How to get sheets, a range, a table, and a graphic object”
Task Proposed call What comes back
Get the sheets session.getWorkbookInfo() The list of sheets, with identifiers, names, visibility, and order.
Read a cell session.getCellDetails(...) The stored and displayed value, the formula, the type, the style, and related data.
Read a range session.getRangeSnapshot(...) The values, formulas, and styles of the requested range — without the whole workbook.
Look up a table session.getTableAt(...) The table’s identifier, its range, columns, filters, totals, and available actions.
Get the objects session.getObjects(...) An object inventory with identifiers, types, anchoring, stacking order, and a mutability flag.
Look up the object under a point session.hitTest(...) What’s under the pointer: the object kind and its stable identifier.

Example: find a chart and rename it through a command

const objects = await session.getObjects({ sheetId });
const chart = objects.find(x => x.type === "chart");
if (chart?.capabilities.rename) {
await session.execute({
operation: "renameObject",
objectId: chart.stableId,
name: "Shipping plan",
expectedRevision: session.revision
});
}

What the core’s projection already hands over now

Section titled “What the core’s projection already hands over now”

Opening the workbook already returns the active sheet, the list of sheets, the visible area, a cell preview, table details, and an object inventory. For every object, you get a stable identifier, the sheet, the type, the name, anchoring, relationships with other parts, stacking order, visibility, locking, and the list of allowed changes. For an individual cell, there’s the read_cell_details operation.

Event When it arrives Main fields
selectionChanged The selection changed, and it’s confirmed. the new and previous selection, the source of the change, the revision.
cellActivated A single click or tap made the cell active. the cell reference, the range, the modifier keys pressed, the source.
cellDoubleClicked A double-click on a cell — after determining exactly what was hit. the cell reference, the edit intent, the modifier keys.
objectSelected A shape, chart, image, or control is selected. the stable identifier, the object type, anchoring, available actions.
objectDoubleClicked A double-click on a graphic object. the object itself, the default action, the modifier keys.
contextMenuOpening A right-click, or a long-press on a phone. the object under the pointer, the selection, the built-in menu items, available actions.
workbookChanged The core applied the change. the new revision, which operation, what changed, an unsaved-changes flag.
operationRefused The core refused — before anything changed. the refusal code and text, the object, the current revision.

Subscribing to semantic events

const unsubscribe = session.on("cellDoubleClicked", async event => {
const details = await session.getCellDetails(event.cellRef);
hostPanel.open({ documentId, cell: details });
});
session.on("objectSelected", event => {
propertiesPanel.show(event.target);
});
// When closing the integration:
unsubscribe();

pointer or touch → hit-testing → selection confirmation → semantic event → default action

How to change the menu depending on the selected element

Section titled “How to change the menu depending on the selected element”

You don’t search the markup for an internal menu element and insert a button into it. Instead, you register a menu item provider: before showing the menu, SumSheet determines what’s under the pointer and asks you for additional items.

ContextTarget.kind Example context data
cell / range sheetId, address, valueType, formula, validation, comment, hyperlink, protected.
table tableId, name, range, columnId, totals/filter state, mutation capabilities.
chart objectId, chartType, source range, anchor, editable capabilities.
shape / image objectId, anchor, zOrder, groupId, metadata, supported mutations.
rowHeader / columnHeader track index, hidden state, size, protection.
sheetTab sheetId, name, visibility, active/grouped state.
canvas sheetId, pointer coordinates, no semantic object.

Example menu contribution for a table row

session.contextMenu.registerProvider(async context => {
if (context.target.kind !== "table") return [];
return [{
id: "partner.open-shipment-card",
label: "Open shipment card",
icon: "external-link",
enabled: Boolean(context.target.rowKey),
group: "partner",
order: 20,
run: () => host.openCard(context.target.rowKey)
}];
});
  • An item has its own name with your prefix, a translatable label, an optional icon, a group, its order within the group, and a rule for when it’s enabled or visible.

  • The item provider gets a safe snapshot of the context, not the internal markup and not the whole workbook.

  • Built-in commands stay SumSheet’s own; you add your own items and, in the agreed spots, replace the ones that are allowed.

  • If your item provider takes too long to think, the wait cuts off: the menu must never hang because of your back end.

Commands instead of writing directly into the structure

Section titled “Commands instead of writing directly into the structure”

Every operation that changes the document goes through the operations contract in the core. That’s how behavior stays the same in the browser, in the desktop app, and on the server, and how the change history stays consistent.

Step What happens
1. Assemble the intent The operation name, a stable reference to the object or range, the parameters, and the expected revision.
2. Check access Read-only mode, sheet protection, declared capabilities, the source file’s format, and the session’s rules.
3. Pass it to the core The same contract — through the browser build, the desktop processor, or the server service.
4. Apply it whole On success, the revision and the history change; on refusal, the workbook stays untouched — there’s no such thing as half an edit.
5. Hand back the result A new projection and a workbook-change event — or a refusal event.

Example of an edit with revision checking

const result = await session.execute({
operation: "setCellValue",
target: { sheetId, row: 12, col: 4 },
value: { kind: "string", value: "Delivered" },
expectedRevision: session.revision
});
if (!result.ok && result.code.endsWith("-stale-revision")) {
await session.refreshProjection();
}
// DON'T: this bypasses Rust, the history, and saving.
document.querySelector("[data-cell='E13']").textContent = "Delivered";
session.workbook.sheets[0].cells[12][4].value = "Delivered";

Documentation assistant

Answers are assembled from the documentation and may be inaccurate — check the sources.