# `Plushie.Widget.Table`
[🔗](https://github.com/plushie-ui/plushie-elixir/blob/v0.7.2/lib/plushie/widget/table.ex#L182)

Data table with children-based rows for efficient diffing.

Tables support two ways to populate rows:

**Rich composition** using `table_row` and `cell` children for
custom cell content (buttons, icons, any widget):

    table "users", columns: cols do
      for user <- users do
        table_row user.id do
          cell "name", text(user.name)
          cell "actions", button("del", "Delete")
        end
      end
    end

**Data shorthand** using the `rows:` prop for text-only tables:

    table "users", columns: cols, rows: data

These two approaches are **mutually exclusive**: setting `rows:`
and providing do-block children in the same table raises an error.

## Props

| Name | Type | Default | Description |
|------|------|---------|-------------|
| `columns` | `[map()]` | `nil` | Column definitions. Each map requires a `:key` (string or atom, consistent across all columns). Optional: `:label`, `:width`, `:sortable`, `:align`. |
| `rows` | `[map()]` | `nil` | Data shorthand for text-only tables. List of maps with keys matching column `:key` values. Each map should include an `:id` key for stable row identity. |
| `header` | `boolean()` | `nil` | Show header row. Default: true. |
| `width` | `:fill \| :shrink \| {:fill_portion, pos_integer()} \| number()` | `nil` | Table width. Default: fill. |
| `height` | `:fill \| :shrink \| {:fill_portion, pos_integer()} \| number()` | `nil` | Table height. Wraps in a scrollable when set. |
| `padding` | `number() \| {number(), number()} \| %Plushie.Type.Padding{}` | `nil` | Cell internal padding. |
| `sort_by` | `String.t() \| atom()` | `nil` | Key of the currently sorted column. |
| `sort_order` | `:asc \| :desc` | `nil` | Current sort direction: `:asc` or `:desc`. |
| `separator` | `number()` | `nil` | Divider line thickness in pixels. Set to 0.0 to hide. |
| `separator_color` | `String.t()` | `nil` | Divider line color. |
| `header_text_size` | `number()` | `nil` | Text size for auto-generated header labels. |
| `row_text_size` | `number()` | `nil` | Text size for data shorthand auto-generated cells. |
| `event_rate` | `integer()` | `nil` | Max events per second for coalescable events. |
| `a11y` | `%Plushie.Type.A11y{} \| map() \| keyword()` | `%{role: :table}` | Accessibility annotations. |

## Events

| Event | Type | Description |
|-------|------|-------------|
| `:sort` | `value: String.t() \| atom()` | Column key when a sortable header is clicked. App handles reordering. |

# `option`

```elixir
@type option() ::
  (((((((((((({:columns, [map()]} | {:rows, [map()]}) | {:header, boolean()})
            | {:width,
               :fill | :shrink | {:fill_portion, pos_integer()} | number()})
           | {:height,
              :fill | :shrink | {:fill_portion, pos_integer()} | number()})
          | {:padding,
             number()
             | {number(), number()}
             | %Plushie.Type.Padding{
                 bottom: term(),
                 left: term(),
                 right: term(),
                 top: term()
               }})
         | {:sort_by, String.t() | atom()})
        | {:sort_order, :asc | :desc})
       | {:separator, number()})
      | {:separator_color, atom() | String.t() | map()})
     | {:header_text_size, number()})
    | {:row_text_size, number()})
   | {:event_rate, integer()})
  | {:a11y,
     %Plushie.Type.A11y{
       active_descendant: term(),
       busy: term(),
       described_by: term(),
       description: term(),
       disabled: term(),
       error_message: term(),
       expanded: term(),
       has_popup: term(),
       hidden: term(),
       invalid: term(),
       label: term(),
       label_from: term(),
       labelled_by: term(),
       level: term(),
       live: term(),
       mnemonic: term(),
       modal: term(),
       orientation: term(),
       position_in_set: term(),
       radio_group: term(),
       read_only: term(),
       required: term(),
       role: term(),
       selected: term(),
       size_of_set: term(),
       toggled: term(),
       value: term()
     }
     | map()
     | keyword()}
```

# `t`

```elixir
@type t() :: %Plushie.Widget.Table{
  a11y:
    (%Plushie.Type.A11y{
       active_descendant: term(),
       busy: term(),
       described_by: term(),
       description: term(),
       disabled: term(),
       error_message: term(),
       expanded: term(),
       has_popup: term(),
       hidden: term(),
       invalid: term(),
       label: term(),
       label_from: term(),
       labelled_by: term(),
       level: term(),
       live: term(),
       mnemonic: term(),
       modal: term(),
       orientation: term(),
       position_in_set: term(),
       radio_group: term(),
       read_only: term(),
       required: term(),
       role: term(),
       selected: term(),
       size_of_set: term(),
       toggled: term(),
       value: term()
     }
     | map()
     | keyword())
    | Plushie.Animation.Transition.t()
    | Plushie.Animation.Spring.t()
    | Plushie.Animation.Sequence.t()
    | nil,
  children: [Plushie.Widget.ui_node()],
  columns:
    [map()]
    | Plushie.Animation.Transition.t()
    | Plushie.Animation.Spring.t()
    | Plushie.Animation.Sequence.t()
    | nil,
  event_rate:
    integer()
    | Plushie.Animation.Transition.t()
    | Plushie.Animation.Spring.t()
    | Plushie.Animation.Sequence.t()
    | nil,
  header:
    boolean()
    | Plushie.Animation.Transition.t()
    | Plushie.Animation.Spring.t()
    | Plushie.Animation.Sequence.t()
    | nil,
  header_text_size:
    number()
    | Plushie.Animation.Transition.t()
    | Plushie.Animation.Spring.t()
    | Plushie.Animation.Sequence.t()
    | nil,
  height:
    (:fill | :shrink | {:fill_portion, pos_integer()} | number())
    | Plushie.Animation.Transition.t()
    | Plushie.Animation.Spring.t()
    | Plushie.Animation.Sequence.t()
    | nil,
  id: String.t(),
  padding:
    (number()
     | {number(), number()}
     | %Plushie.Type.Padding{
         bottom: term(),
         left: term(),
         right: term(),
         top: term()
       })
    | Plushie.Animation.Transition.t()
    | Plushie.Animation.Spring.t()
    | Plushie.Animation.Sequence.t()
    | nil,
  row_text_size:
    number()
    | Plushie.Animation.Transition.t()
    | Plushie.Animation.Spring.t()
    | Plushie.Animation.Sequence.t()
    | nil,
  rows:
    [map()]
    | Plushie.Animation.Transition.t()
    | Plushie.Animation.Spring.t()
    | Plushie.Animation.Sequence.t()
    | nil,
  separator:
    number()
    | Plushie.Animation.Transition.t()
    | Plushie.Animation.Spring.t()
    | Plushie.Animation.Sequence.t()
    | nil,
  separator_color:
    String.t()
    | Plushie.Animation.Transition.t()
    | Plushie.Animation.Spring.t()
    | Plushie.Animation.Sequence.t()
    | nil,
  sort_by:
    (String.t() | atom())
    | Plushie.Animation.Transition.t()
    | Plushie.Animation.Spring.t()
    | Plushie.Animation.Sequence.t()
    | nil,
  sort_order:
    (:asc | :desc)
    | Plushie.Animation.Transition.t()
    | Plushie.Animation.Spring.t()
    | Plushie.Animation.Sequence.t()
    | nil,
  width:
    (:fill | :shrink | {:fill_portion, pos_integer()} | number())
    | Plushie.Animation.Transition.t()
    | Plushie.Animation.Spring.t()
    | Plushie.Animation.Sequence.t()
    | nil
}
```

# `a11y`

```elixir
@spec a11y(
  widget :: t(),
  value ::
    (%Plushie.Type.A11y{
       active_descendant: term(),
       busy: term(),
       described_by: term(),
       description: term(),
       disabled: term(),
       error_message: term(),
       expanded: term(),
       has_popup: term(),
       hidden: term(),
       invalid: term(),
       label: term(),
       label_from: term(),
       labelled_by: term(),
       level: term(),
       live: term(),
       mnemonic: term(),
       modal: term(),
       orientation: term(),
       position_in_set: term(),
       radio_group: term(),
       read_only: term(),
       required: term(),
       role: term(),
       selected: term(),
       size_of_set: term(),
       toggled: term(),
       value: term()
     }
     | map()
     | keyword())
    | nil
) :: t()
```

Accessibility annotations.

Accepts `%Plushie.Type.A11y{} | map() | keyword()`.

# `build`

```elixir
@spec build(widget :: t()) :: Plushie.Widget.ui_node()
```

Converts this widget struct to a `ui_node()` map.

# `columns`

```elixir
@spec columns(widget :: t(), value :: [map()] | nil) :: t()
```

Column definitions. Each map requires a `:key` (string or atom, consistent across all columns). Optional: `:label`, `:width`, `:sortable`, `:align`.

Accepts `[map()]`.

# `event_rate`

```elixir
@spec event_rate(widget :: t(), value :: integer() | nil) :: t()
```

Max events per second for coalescable events.

Accepts `integer()`.

# `extend`

```elixir
@spec extend(widget :: t(), children :: [Plushie.Widget.child()]) :: t()
```

Appends multiple children to the widget.

# `header`

```elixir
@spec header(widget :: t(), value :: boolean() | nil) :: t()
```

Show header row. Default: true.

Accepts `boolean()`.

# `header_text_size`

```elixir
@spec header_text_size(widget :: t(), value :: number() | nil) :: t()
```

Text size for auto-generated header labels.

Accepts `number()`.

# `height`

```elixir
@spec height(
  widget :: t(),
  value :: (:fill | :shrink | {:fill_portion, pos_integer()} | number()) | nil
) :: t()
```

Table height. Wraps in a scrollable when set.

Accepts `:fill | :shrink | {:fill_portion, pos_integer()} | number()`.

# `new`

```elixir
@spec new(id :: String.t(), opts :: [option()]) :: t()
```

Creates a new widget struct with the given ID and keyword options.

# `padding`

```elixir
@spec padding(
  widget :: t(),
  value ::
    (number()
     | {number(), number()}
     | %Plushie.Type.Padding{
         bottom: term(),
         left: term(),
         right: term(),
         top: term()
       })
    | nil
) :: t()
```

Cell internal padding.

Accepts `number() | {number(), number()} | %Plushie.Type.Padding{}`.

# `push`

```elixir
@spec push(widget :: t(), child :: Plushie.Widget.child()) :: t()
```

Appends a child to the widget.

# `row_text_size`

```elixir
@spec row_text_size(widget :: t(), value :: number() | nil) :: t()
```

Text size for data shorthand auto-generated cells.

Accepts `number()`.

# `rows`

```elixir
@spec rows(widget :: t(), value :: [map()] | nil) :: t()
```

Data shorthand for text-only tables. List of maps with keys matching column `:key` values. Each map should include an `:id` key for stable row identity.

Accepts `[map()]`.

# `separator`

```elixir
@spec separator(widget :: t(), value :: number() | nil) :: t()
```

Divider line thickness in pixels. Set to 0.0 to hide.

Accepts `number()`.

# `separator_color`

```elixir
@spec separator_color(widget :: t(), value :: (atom() | String.t() | map()) | nil) ::
  t()
```

Divider line color.

Accepts `String.t()`.

# `sort_by`

```elixir
@spec sort_by(widget :: t(), value :: (String.t() | atom()) | nil) :: t()
```

Key of the currently sorted column.

Accepts `String.t() | atom()`.

# `sort_order`

```elixir
@spec sort_order(widget :: t(), value :: (:asc | :desc) | nil) :: t()
```

Current sort direction: `:asc` or `:desc`.

Accepts `:asc | :desc`.

# `table`
*macro* 

Creates a `:table` widget.

Shorthand for `new/2`. Import this macro to use the widget name
directly in view functions:

    import Plushie.Widget.Table, only: [table: 2]

    table("my-id", prop: value)

# `width`

```elixir
@spec width(
  widget :: t(),
  value :: (:fill | :shrink | {:fill_portion, pos_integer()} | number()) | nil
) ::
  t()
```

Table width. Default: fill.

Accepts `:fill | :shrink | {:fill_portion, pos_integer()} | number()`.

# `with_options`

```elixir
@spec with_options(widget :: t(), opts :: [option()]) :: t()
```

Applies keyword options to an existing widget struct.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
