Something went wrong

Thank you for being patient! We're working hard on resolving the issue

Layout Plugins & Content Providers - Lona Docs Log in

Layout Plugins & Content Providers

Layout plugins render shared coordinate systems. Child plugins contribute content into those layouts through contentProviders.

The split is simple:

  • the parent layout decides where content goes
  • the child plugin decides what items exist and how each item renders

Mental Model

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.

ContentProviders

This 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:

KeyUsed byReturns
calendardaily and weekly calendar layoutsCalendarSubcolumn.Provider<T>
timelineall-day timeline layoutTimelinePlugin.SubcolumnProvider<T>

calendarMonth exists on the public type, but the common integrations are calendar and timeline.

Calendar Providers

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:

RendererUse when
"cells"items have start times and durations inside the day
"spans"items should render as bars across the date range

Timeline Providers

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.

Registering A Child Plugin

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.

How Parents Discover Providers

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.

Choosing The Right Provider

GoalProvider key
Timed items inside the daily or weekly calendar gridcalendar
Full-day and date-range bars in a timeline rowtimeline
Month-specific calendar participationcalendarMonth

See Also