Skip to content

Block Kit: no tabular input element, so a plugin block cannot capture a table whose columns are the editor's choice #2522

Description

@virafb

Summary

A plugin block can capture a list of rows, but not a table. repeater is the
closest element, and its row shape is fixed by the plugin at declaration time,
so a block built around it models one particular table rather than tables. A
plugin that wants to hand editors a designed data table therefore has to ship
the table as opaque HTML, which is exactly the content a block API is meant to
open up.

Verified against @emdash-cms/blocks@0.32.0 and @emdash-cms/admin@0.32.0,
re-checked unchanged in 0.33.0 (current latest on npm).

Where

Block Kit has a table, but it is a block, not an element: read-only
rows with plugin-declared columns and a page_action_id for pagination,
intended for plugin admin pages.

interface TableColumn { key: string; label: string; format?: ; sortable?: boolean }
// TableBlock: { columns: TableColumn[]; rows: Array<Record<string, unknown>>; page_action_id: string; … }

A Portable Text block's editing modal renders fields, which are elements,
and the element list in @emdash-cms/blocks src/validation.ts is:

button, text_input, number_input, select, toggle, secret_input,
checkbox, radio, date_input, combobox, repeater, media_picker

Nothing there is tabular. (BlockKitField in the admin renders six of those
twelve; the rest fall to Unknown field type. Noted in #2307, not this issue.)

Why repeater is not the answer

repeater gives the editor control of the rows and the plugin control of
the columns. Declaring four text_input sub-fields produces a four-column
table forever: an editor with a three-column table gets a stray empty column
they cannot remove, and an editor with five has nowhere to put the fifth. The
column count is content, not schema, and the block cannot express that.

Why the editor's own table node is not the answer either

The prose editor has a Table node, so an editor can insert a table into the
surrounding prose. But it lives outside the plugin block, so the plugin has no
say in how it renders and gets none of its data. A designed table that carries
plugin-owned classes, a caption convention or a render component cannot be built
on it. (It also has open limits of its own: #2242, #2336.)

Reproducing

  1. Register a Portable Text block with a repeater of four text_input
    sub-fields, intending it as a data table.
  2. Insert it and fill three columns. The fourth is empty in every row and there
    is no way to remove it.
  3. There is no declaration that lets the editor add a fifth.

Suggested shape

A table_input element whose columns are editor-managed, with headers as
values rather than as schema:

{ type: "table_input", action_id: "data", label: "Table",
  min_columns?: number, max_columns?: number,
  min_rows?: number, max_rows?: number,
  column_label?: string }        // e.g. "Column" → "Add Column"

storing something a renderer can consume directly:

{ "columns": ["Metric", "Before", "After"],
  "rows": [["Time to first draft", "3 days", "2 hours"]] }

That reuses the repeater's interaction model (add, remove, reorder) on a second
axis, and it keeps the plugin in charge of rendering while leaving the shape of
the data to the editor.

If a full element is too much, a narrower version would still unblock most of
this: let a repeater declare that its sub-field set is editor-extensible, so
columns can be added at authoring time from a single declared sub-field type.

Happy to send a PR if you would like a particular shape.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions