Thank you for being patient! We're working hard on resolving the issue
Plugins are the UI layer for row types. A plugin decides how a row creates cells, binds data, exposes row options, and optionally participates in layout rows such as calendars and timelines.
makeCell() and bindCell()loadDatacontentProvidersimport { LonaIcons, NumberIcon } from "@tento-ui/ui/icons";
import { NumberInput } from "@tento-ui/components/input/number-input";
import {
type BindCellContext,
type RenderPlugin,
} from "@tento-lona/sheets-ui";
import type { SheetDelegate } from "@tento-lona/sheets-ui";
import { SheetRow } from "@tento-lona";
export class NumberSheetPlugin implements RenderPlugin<NumberInput> {
readonly id = "number";
readonly icon: HTMLTemplateElement = LonaIcons.$template(NumberIcon);
readonly loadData = null;
readonly legendFactory = null;
readonly makeRowOptions = null;
optionsInfo(_row: SheetRow.Joined.Client): Option<string> {
return "Store numeric values.";
}
makeCell(): NumberInput {
return NumberInput.make();
}
bindCell(
$cell: NumberInput,
{ row, column, isCurrent }: BindCellContext,
): void {
const day = column.discrete()[0];
const handle = row.cell(day);
handle.onData((cell) => {
$cell.bind(
typeof cell?.value === "number" ? cell.value : null,
{ placeholder: isCurrent ? "Enter a value" : undefined },
);
});
}
onResize(): void {}
onBindRow(_handle: SheetDelegate, _row: SheetRow.Joined.Client): void {}
onViewportColumnsChanged(): void {}
onRenderedColumnsWillChange(): void {}
}
The important point is that bindCell() receives the joined row and the
resolved column. Most plugins read from row.cell(...), a row variant, or a
store keyed by row.id.
Register the plugin instance with the manager. The plugin's id is the row
type key used at render time.
pluginManager.plugin(new NumberSheetPlugin());
pluginManager.plugin(new ColumnTaskPlugin(makeRowOptions, taskItemDelegate));
When the sheet renders a row with type === "tasks", it calls
pluginManager.getPlugin("tasks") and delegates to that plugin.
BindCellContextbindCell() and onResize() receive the same context object:
| Field | Meaning |
|---|---|
sheet | Read-only sheet view model |
row | Joined row for the current cell |
column | Resolved sheet column |
widthPx | Pixel width allocated to the cell |
inThePast | Whether the column is before the current date |
isCurrent | Whether this is the current column |
handle | SheetDelegate for popovers, fixed children, and rebinding |
now | Current date in the column timezone |
Two patterns show up constantly:
const day = column.discrete()[0];
const cellHandle = row.cell(day);
const variant = row.variant as TasksVariant;
const index = variant.tasksIndex;
loadData is optional. Use it when the plugin needs async work to complete
before binding visible cells.
readonly loadData = async (_sheet, row, column) => {
await this.store.ensureLoaded(row.id, column);
};
The third argument is a NaiveDate.Partial, not a viewport range. For more
complex layouts, child providers often do their own loading instead.
Every plugin receives a SheetDelegate through context.handle:
| Handle | Use case |
|---|---|
handle.popover | Scroll-aware popovers anchored to a row and column |
handle.fixedChild | Scroll-aware overlays and spans |
handle.staticChild | Row-local elements positioned by raw pixel offset |
handle.tooltip | Hover tooltips |
handle.rebind | Request row, column, or structure rebinds |
For example:
handle.popover.reveal(
"task-details",
$details,
{ x: column.withSubindex(0), y: row.id },
{ offsetY: 48, context: "task-details" },
);
Use staticChildren() for row-local UI such as banners or headers:
staticChildren(row: SheetRow.Joined.Client): StaticChildSpec[] {
return [{
id: "data-title",
$element: DataTitleHeader.make().bind(row.label ?? "Untitled"),
fillWidth: true,
}];
}
Use fixedChildrenForRow() for items positioned against dates or columns:
async fixedChildrenForRow(
row: SheetRow.Joined.Client,
context: FixedChildRowContext,
): Promise<SheetDelegate.FixedChildSpec[]> {
return [{
id: "task-span",
x: { type: "date-range", startDate, endDate },
inset: { left: 0, top: 0 },
widthBehavior: "fill",
makeElement: () => TaskSpan.make(),
}];
}
If a child row type should render inside a calendar or timeline, add
contentProviders to the plugin:
contentProviders: ContentProviders = {
calendar: this.calendarProvider,
timeline: {
subcolumnFromRow: (row, parentRow) => this.makeTimelineProvider(row),
},
};
See Layout Plugins & Content Providers, Subcolumn Plugins, and Parent Plugins for those contracts.
contentProviders.calendar