Something went wrong

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

Parent Plugins - Lona Docs Log in

Parent Plugins

Parent plugins host child providers in a shared layout. The built-in calendar and timeline rows are the main examples.

What A Parent Plugin Does

A parent plugin:

  1. walks row.children()
  2. resolves each child's plugin from handle.pluginManager
  3. asks that plugin for a layout-specific provider
  4. renders the returned providers in its own coordinate system

The parent owns layout, stacking, and menu positioning. The child owns item selection and item rendering.

Discovering Calendar Children

Daily and weekly calendar layouts usually collect contentProviders.calendar:

bindCell(
  $cell: CalendarSheetColumn,
  { sheet, row, handle }: BindCellContext,
): void {
  const subcolumns = this.getSubcolumns(sheet, row, handle.pluginManager);
  $cell.bind({ subcolumns });
}

private getSubcolumns(
  sheet: SheetViewModel.Readable,
  row: SheetRow.Joined.Client,
  pluginManager: SheetDelegate.PluginManager,
): CalendarSubcolumn<unknown>[] {
  const result: CalendarSubcolumn<unknown>[] = [];

  for (const child of row.children()) {
    const plugin = pluginManager.getPlugin(child.type);
    const provider = plugin.contentProviders?.calendar;
    if (!provider) continue;

    result.push(provider.subcolumnFromRow(sheet, child, row));
  }

  return result;
}

This is the key extension point for SDK consumers: adding a new child row type usually means adding a new child plugin, not editing the parent layout.

Aggregating Full-Day Items

Calendar parents often need full-day items from their children. That comes from fullDayEventsFromRow() on the child's calendar provider:

private fullDayItems(
  row: SheetRow.Joined.Client,
  range: NaiveDate.Range,
  pluginManager: SheetDelegate.PluginManager,
): EventItemFullDay[] {
  const deduped = new Map<string, EventItemFullDay>();

  for (const child of row.children()) {
    const plugin = pluginManager.getPlugin(child.type);
    const provider = plugin.contentProviders?.calendar;
    const items = provider?.fullDayEventsFromRow?.(child, range) ?? [];

    for (const item of items) {
      deduped.set(item.inner.id, item);
    }
  }

  return Array.from(deduped.values());
}

This lets calendar-list, tasks, and other child types contribute to the same all-day surface.

Discovering Timeline Children

Timeline parents do the same thing with contentProviders.timeline:

const providers: TimelineContentProvider.Provider<unknown>[] = [];

for (const child of row.children()) {
  const plugin = handle.pluginManager.getPlugin(child.type);
  const provider = plugin.contentProviders?.timeline?.subcolumnFromRow(
    child,
    row,
  );
  if (provider) providers.push(provider);
}

Each returned provider supplies items(), dateRange(), itemId(), makeCell(), and bindCell() so the parent can stack and render spans.

Positioning Menus And Overlays

Because the parent owns the coordinate system, it also owns the positioning handles passed into child-facing UI:

handle.popover.reveal(
  "task-details",
  $details,
  { x: column.withSubindex(0), y: row.id },
  { offsetY: 48 },
);
handle.fixedChild.reveal(
  "timeline-span",
  $span,
  { x: column.withSubindex(0), y: row.id },
  { inset: { left: 0, top: 4 }, widthBehavior: "fill" },
);

This is why parent plugins are about more than rendering. They are also the boundary between item data and on-screen coordinates.

Adding A New Child Type

To make a new child row participate in an existing parent layout:

  1. implement a RenderPlugin for the child row type
  2. add contentProviders.calendar or contentProviders.timeline
  3. ensure rows of that type appear under the parent row in the sheet tree

The parent layout code usually does not need to change.

See Also