Something went wrong

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

Subcolumn Plugins - Lona Docs Log in

Subcolumn Plugins

Subcolumn plugins are child plugins that render inside a parent layout row. The common case is a child row contributing timed items to a calendar row.

Overview

The parent discovers child rows from the sheet tree and asks the child's plugin for a provider:

Calendar row bind
  → row.children()
  → handle.pluginManager.getPlugin(child.type)
  → childPlugin.contentProviders.calendar
  → provider.subcolumnFromRow(sheet, child, parentRow)
  → Calendar layout renders the returned subcolumn

Your plugin is responsible for item selection and cell appearance. The parent is responsible for layout and positioning.

CalendarSubcolumn.Provider

The calendar key on contentProviders uses this contract:

interface Provider<EventItem, FullDayEventItem = unknown> {
  subcolumnFromRow(
    sheet: SheetViewModel.Readable,
    row: SheetRow.Joined.Client,
    parentRow: SheetRow.Joined.Client,
  ): CalendarSubcolumn<EventItem>;

  fullDayEventsFromRow?(
    row: SheetRow.Joined.Client,
    range: NaiveDate.Range,
  ): FullDayEventItem[];
}

subcolumnFromRow() is where you capture row-specific filters or indexes from the child row and return a renderable subcolumn object.

Cell-Based Example

This is the common pattern for timed events or tasks:

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);
      },
    }),
  },
};

Two details matter:

  • rowId on the returned subcolumn is a RowId, so child plugins commonly convert with RowId.fromLocalId(row.id)
  • the provider reads row-specific state from the child row, not from the parent

Span-Based Example

For bars that stretch across dates, return a span renderer instead:

calendar: {
  subcolumnFromRow: (sheet, row, _parentRow) => ({
    rowId: RowId.fromLocalId(row.id),
    renderer: "spans",
    makeSpans: this.makeChartSpans(sheet, row),
    makeLayout: SpanColumn.make,
  }),
},

Use this when the item is better represented as a bar than a timed cell.

Full-Day Items

If the child row also contributes items to the parent's all-day area, implement fullDayEventsFromRow():

calendar: {
  subcolumnFromRow: (_sheet, row, _parentRow) => this.makeTimedSubcolumn(row),

  fullDayEventsFromRow: (row, range) => {
    const idx = row.assertVariant<CalendarListVariant.Type>().eventsIndex;
    return idx?.findFullDayEventsInRange(range) ?? [];
  },
},

The parent can then aggregate full-day items from every qualifying child.

Registration

Register the child plugin normally:

pluginManager.plugin(new TaskListPlugin(taskStore));

There is no separate "subcolumn plugin registry". Participation is determined by the row tree and the presence of contentProviders.calendar.

Discovery From The Parent

This is what the parent side usually looks like:

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

  const subcolumn = provider.subcolumnFromRow(sheet, child, row);
  subcolumns.push(subcolumn);
}

If your child row is nested correctly and its plugin exposes calendar, the parent will discover it automatically.

See Also