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 render content within a parent plugin's grid. A calendar-list plugin renders events inside a calendar row. A task-list plugin could render tasks in the same grid.

Overview

The parent plugin (e.g., calendar) discovers its children from the row tree and calls each child's subcolumnProvider to get rendering info. The child decides what to render. The parent decides where.

CalendarSheetPlugin.bindCell()
  → row.children()                        // discover child rows
  → childPlugin.subcolumnProvider          // ask each child for rendering info
    .subcolumnFromRow(sheet, childRow)
  → CalendarDaysRow renders each subcolumn // parent positions child content

Declaring a Subcolumn Provider

Add a subcolumnProvider to your plugin. The provider returns a CalendarSubcolumn that tells the parent how to render your data:

export class TaskListPlugin implements RenderPlugin<HTMLElement, HTMLElement> {
  readonly id = "task-list";
  readonly icon = taskIconTemplate;

  subcolumnProvider: CalendarSubcolumn.Provider<TaskItem> = {
    subcolumnFromRow(
      sheet: SheetViewModel,
      row: SheetRow.Joined.Client,
      parentRow: SheetRow.Joined.Client,
    ): CalendarSubcolumn<TaskItem> {
      return {
        rowId: RowId.fromLocalId(row.id),
        renderer: "cells",
        width: null,  // use parent's default column width

        events(range: DateTime.Range<Utc>): TaskItem[] {
          // Return items visible in this time range
          return taskStore.getTasksInRange(row.id, range);
        },

        gestureItem(item: TaskItem): Option<GestureItem> {
          // Wrap item for the gesture system
          return new TaskGestureItem(item);
        },

        bindCell(
          $cell: CalendarColumnCell.Basic,
          item: TaskItem,
          context: CalendarSubcolumn.CellBindContext,
        ): void {
          // Render one item in a calendar cell
          $cell.style.backgroundColor = item.color;
          $cell.textContent = item.title;
        },
      };
    },
  };

  // ... standard RenderPlugin methods for standalone rendering ...
}

Cell vs Span Rendering

Subcolumns declare their renderer type:

TypeUse whenExample
"cells"Items have a start time and duration within a dayTimed events, tasks
"spans"Items span multiple days as horizontal barsMulti-day events
// Span-based subcolumn
{
  renderer: "spans",
  makeSpans(windowed, tzr): Promise<Span[]> {
    return [{
      id: "task-1",
      dateRange: new NaiveDate.Range(start, end),
      label: "Sprint review",
      color: "var(--blue)",
    }];
  },
  makeLayout(): HTMLElement {
    return SpanLayout.make();
  },
}

Providing Full-Day Items

If your subcolumn has items that should appear in the all-day row, implement fullDayEventsFromRow:

subcolumnProvider: CalendarSubcolumn.Provider<TaskItem, FullDayTaskItem> = {
  subcolumnFromRow(sheet, row, parentRow) { /* ... */ },

  fullDayEventsFromRow(row, range: NaiveDate.Range): FullDayTaskItem[] {
    return taskStore.getFullDayTasks(row.id, range);
  },
};

The parent's all-day plugin aggregates full-day items from all child subcolumns.

How the Parent Discovers Children

The parent calls row.children() on its own row to get child rows, then looks up each child's plugin:

// Inside CalendarSheetPlugin.getSubcolumns()
for (const child of row.children()) {
  const childPlugin = pluginManager.getPlugin(child.type);
  const subcolumn = childPlugin.subcolumnProvider?.subcolumnFromRow(sheet, child, row);
  if (subcolumn) subcolumns.push(subcolumn);
}

Your plugin is discovered automatically — no registration beyond the standard pluginManager.register().

See also