Build your own feature plugin

The pattern: tableFeatures() is an open registry. Library features (rowSortingFeature, columnFilteringFeature, ...) are nothing more than objects with three hooks. Your own feature plugs into the same surface.

When

You want to inject behavior that applies to every row or column - a computed accent class, an audit fingerprint, an inline diff badge, a per-row policy gate - without forking the grid component or writing the same cell: boilerplate on every column def.

The contract

A feature is an object with three optional hooks:

type TableFeature = {
  /** Initial slice of state the engine merges into its store. */
  getInitialState?: () => Record<string, unknown>
  /** Default values for the options bag the consumer passes through. */
  getDefaultOptions?: <TData>() => Record<string, unknown>
  /** Runs once when the table is created. Wrap api methods here. */
  createTable?: <TFeatures extends TableFeatures, TData extends RowData>(table: {
    options: Record<string, unknown>
    getRowModel: () => { rows: Array<{ original: TData }> }
  }) => void
}

Drop the unused hooks. A read-only decorator only needs createTable.

How

This 25-line plugin writes a computed __rowAccent string onto every row in the row model. A user-supplied function decides the accent:

import { tableFeatures, rowSortingFeature, type RowData, type TableFeatures } from 'sv-grid-community'

type RowAccentFn<TData extends RowData> = (row: TData) => string | undefined

const rowAccentFeature = {
  getInitialState: () => ({}),
  getDefaultOptions: <TData extends RowData>() =>
    ({ rowAccent: undefined as RowAccentFn<TData> | undefined }),
  createTable: <TFeatures extends TableFeatures, TData extends RowData>(table: {
    options: { rowAccent?: RowAccentFn<TData> }
    getRowModel: () => { rows: Array<{ original: TData }> }
  }) => {
    // Wrap the row-model accessor so every row carries the computed
    // accent. Cheap O(visibleRows) per render.
    const orig = table.getRowModel.bind(table)
    table.getRowModel = () => {
      const model = orig()
      const fn = table.options.rowAccent
      if (!fn) return model
      for (const row of model.rows) {
        (row.original as Record<string, unknown>).__rowAccent =
          fn(row.original as TData)
      }
      return model
    }
  },
} as const

Wire it up

Register the feature and supply rowClass to surface the accent in CSS:

<script lang="ts">
  import { SvGrid, tableFeatures, rowSortingFeature } from 'sv-grid-community'

  type Deal = { company: string; health: number; __rowAccent?: string }

  const features = tableFeatures({ rowSortingFeature, rowAccentFeature })
  const deals: Deal[] = [/* ... */]

  function rowAccent(d: Deal): string | undefined {
    if (d.health >= 0.75) return 'good'
    if (d.health <  0.40) return 'bad'
    return 'warn'
  }
  function rowClass(ctx: { row: Deal; rowIndex: number }): string {
    const a = rowAccent(ctx.row)
    return a ? `accent-${a}` : ''
  }
</script>

<SvGrid {features} {columns} data={deals} {rowClass} />

<style>
  /* rowClass lands on the <tr>; <td> cells paint their own bg over it,
     so tint the cells via a descendant selector. */
  :global(.accent-good td.sv-grid-cell) { background: rgba(34,197,94,0.14) !important; }
  :global(.accent-warn td.sv-grid-cell) { background: rgba(245,158,11,0.16) !important; }
  :global(.accent-bad  td.sv-grid-cell) { background: rgba(239,68,68,0.18) !important; }
</style>

Trade-offs

See also