Thank you for being patient! We're working hard on resolving the issue
Layout plugins render shared coordinate systems. Child plugins contribute
content into those layouts through contentProviders.
The split is simple:
Two built-in patterns matter most:
calendar row
├── calendar-list child
├── tasks child
└── data child
timeline row
├── calendar-list child
└── tasks child
At render time, the parent walks row.children(), asks each child's plugin for
the relevant provider, and then renders those providers in its own coordinate
system.
ContentProvidersThis is the public shape exposed by RenderPlugin:
type ContentProviders = {
calendar?: CalendarSubcolumn.Provider<any>;
calendarMonth?: CalendarSubcolumn.Provider<any>;
timeline?: TimelinePlugin.SubcolumnProvider<any>;
};
In practice, most child plugins today implement one or both of:
| Key | Used by | Returns |
|---|---|---|
calendar | daily and weekly calendar layouts | CalendarSubcolumn.Provider<T> |
timeline | all-day timeline layout | TimelinePlugin.SubcolumnProvider<T> |
calendarMonth exists on the public type, but the common integrations are
calendar and timeline.
A calendar provider turns a child row into a CalendarSubcolumn.
import { RowId } from "@tento-lona";
import { CalendarSubcolumn } from "@tento-lona/sheets-ui";
contentProviders: ContentProviders = {
calendar: {
subcolumnFromRow: (_sheet, row, _parentRow) => ({
rowId: RowId.fromLocalId(row.id),
renderer: "cells",
events: (range) =>
row.assertVariant<TasksVariant.Type>().tasksIndex?.findAllInRange(range) ?? [],
gestureItem: (_item) => null,
bindCell: ($cell, item, { windowed, currentTime }) => {
TasksPluginBase.bindTaskCellContent($cell, item, windowed, currentTime);
},
}),
},
};
The returned subcolumn can be:
| Renderer | Use when |
|---|---|
"cells" | items have start times and durations inside the day |
"spans" | items should render as bars across the date range |
A timeline provider has one extra step. The timeline key returns a
SubcolumnProvider, and that object produces a TimelineContentProvider
capturing the child row's filters.
contentProviders: ContentProviders = {
timeline: {
subcolumnFromRow: (row, _parentRow) => this.makeTimelineProvider(row),
},
};
makeTimelineProvider(
row: SheetRow.Joined.Client,
): TimelineContentProvider.Provider<EventItemFullDay> {
return {
items: (range) =>
row.assertVariant<CalendarListVariant.Type>().eventsIndex
?.findFullDayEventsInRange(range) ?? [],
loadColumns: async (columns) => {
await this.store.ensureLoaded(row.id, columns);
},
makeCell: () => AllDayEventsCell.make(),
bindCell: ($cell, item, today) => {
($cell as AllDayEventsCell).bind(item, today);
},
gestureItem: (_item) => null,
dateRange: (item) => item.time,
itemId: (item) => item.id,
};
}
Use timeline when the layout is stacking date spans, not timed day cells.
Register the child plugin normally:
pluginManager.plugin(new TaskListPlugin(taskStore));
pluginManager.plugin(new CalendarListPlugin(calendarStore));
No special registration is needed for layout participation. The parent finds child providers from the row tree.
Calendar-style parents typically do this:
for (const child of row.children()) {
const plugin = handle.pluginManager.getPlugin(child.type);
const provider = plugin.contentProviders?.calendar;
if (!provider) continue;
const subcolumn = provider.subcolumnFromRow(sheet, child, row);
subcolumns.push(subcolumn);
}
Timeline parents do the same thing with contentProviders.timeline.
This matters for SDK consumers because the composition point is the row tree. If a child row is nested under the layout row and its plugin exposes the right provider key, it participates automatically.
| Goal | Provider key |
|---|---|
| Timed items inside the daily or weekly calendar grid | calendar |
| Full-day and date-range bars in a timeline row | timeline |
| Month-specific calendar participation | calendarMonth |
RenderPlugin contract