@colixsystems/widget-sdk 0.249.0 → 0.250.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -84,7 +84,11 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
84
84
 
85
85
  ## Status
86
86
 
87
- `v0.249.0` — pre-publish. The package surface (types, function names, export paths) is the v1 contract; runtime behaviour for some hooks is stubbed (each hook documents what's wired and what isn't). It is **not yet published to npm**.
87
+ `v0.250.0` — pre-publish. The package surface (types, function names, export paths) is the v1 contract; runtime behaviour for some hooks is stubbed (each hook documents what's wired and what isn't). It is **not yet published to npm**.
88
+
89
+ ### What's new in 0.250.0 (contract 1.204.0)
90
+
91
+ **`<DataTable showHeader={false}>`** drops the wide layout's header row (and its sticky index) for a table that needs no column labels; sorting then lives only in the card layouts' sort menu. The built-in Data List now draws through `<DataTable>`, and its "Show column headers" setting maps to this prop.
88
92
 
89
93
  ### What's new in 0.249.0 (contract 1.203.0)
90
94
 
@@ -3151,7 +3155,7 @@ import { defineWidget, validateManifest, useDatastoreQuery, Text, View } from "@
3151
3155
  - `parseMarkdown`, `markdownToPlainText`, `parseMarkdownImage`, `formatMarkdownImage`, `stripHtmlToMarkdown`, `MARKDOWN_IMAGE_SIZES` — the same grammar as pure helpers, for content you inspect rather than render: the block list, a marker-free projection (list previews, search, a11y labels), the one-line image form read and written, a legacy-HTML row normalised to markdown on read, and the closed size list (`small` | `medium` | `large` | `full`).
3152
3156
  - `isSafeMarkdownImageSrc`, `isHttpImageSrc`, `normaliseMarkdownImageSize`, `readMarkdownAlt` — the image half of that grammar, for a widget rendering images itself via `renderImage`. `isSafeMarkdownImageSrc` is the same allowlist the parser applies (an `http(s)` URL or a filestore id; a `javascript:` scheme is refused), so the widget and the parser cannot disagree about which src is renderable.
3153
3157
  - `List`, `ListRow`, `ListHeader`, `EmptyState` — the themed list building blocks. `<ListRow title subtitle leading trailing onPress selected divider>` draws one record row from the theme's `list` scope (padding, height, gap, radius, background, divider); `<List>` spaces its rows and hands overrides down; `<ListHeader>` labels a table's columns on the rows' own inset; `<EmptyState icon title message>` is the "nothing here" panel. Use them by default for record rows and a table's header; build your own row when it needs what `ListRow` cannot do (a swipe-to-delete row, a row that expands in place).
3154
- - `DataTable` — a list or table of records laid out for its own width, on both hosts. `<DataTable columns rows>` is stacked label/value cards when narrow, one card per row at medium and aligned columns under a sticky header when wide, built from `List` / `ListRow` / `ListHeader` and styled by the theme's `list` scope. A column is `{ key, label, flex, align, minWidth, sortable, render }`; `renderCell`, `onRowPress`, `keyField` (default `"id"`), `loading` and `emptyTitle` / `emptyMessage`, and `maxHeight` (480) complete it. A sortable header is a 44px button named by its column and described by its sort, and a change of sort is announced once; with `onSortChange` the widget orders the rows (a server sort) and the table draws them as given, otherwise it sorts them itself (a `Date` as a date). A controlled `sort` without `onSortChange` is display-only, its headers plain labels. Use it by default for records; editable cells, grouped rows, resizable columns or a pivot are yours to build from `ListHeader` + `ListRow`.
3158
+ - `DataTable` — a list or table of records laid out for its own width, on both hosts. `<DataTable columns rows>` is stacked label/value cards when narrow, one card per row at medium and aligned columns under a sticky header when wide, built from `List` / `ListRow` / `ListHeader` and styled by the theme's `list` scope. A column is `{ key, label, flex, align, minWidth, sortable, render }`; `renderCell`, `onRowPress`, `keyField` (default `"id"`), `loading` and `emptyTitle` / `emptyMessage`, `maxHeight` (480) and `showHeader` (`false` drops the wide header row) complete it. A sortable header is a 44px button named by its column and described by its sort, and a change of sort is announced once; with `onSortChange` the widget orders the rows (a server sort) and the table draws them as given, otherwise it sorts them itself (a `Date` as a date). A controlled `sort` without `onSortChange` is display-only, its headers plain labels. Use it by default for records; editable cells, grouped rows, resizable columns or a pivot are yours to build from `ListHeader` + `ListRow`.
3155
3159
  - `Badge`, `Avatar`, `Progress`, `Stat`, `Divider` — the themed status pill, person picture, completion bar or ring, headline figure and rule the built-in widgets of the same names render through. Reach for them inside a record row instead of hand-rolling a pill or a circle from `View`.
3156
3160
  - `Grid` — balanced lines of cells sized from the grid's own measured width, on both hosts. `<Grid minCellWidth gap balance>`: `balance` `"cards"` (default) fits as many cells to a line as `minCellWidth` allows (240 when unset), fills every line but the last and pads that one with empty cells; `"tiles"` spreads the remainder and stretches each line (5 at 4 per line are 3 + 2), with `maxColumns` or the width tier (4 / 3 / 2) as the ceiling. `columns` fixes the count per line; `cell={{ min, max }}` with `square` makes definite-size cells (calendar days, seats, swatches) in a centred block. It measures an inner box with no padding, and `gap` is a theme spacing name or a number. Each child keeps its own look, and a child with `flex: 1` fills its cell's height. Use it by default for card grids, tile rows and capped cells; masonry or spanning cells are yours to lay out with `useContainerWidth()`.
3157
3161
  - `Form`, `FieldRow`, `FormSection`, `FormActions`, `FormField` — a form laid out in width tiers from its own measured width, on both hosts: `<Form columns maxWidth gap>` puts its fields one to a line when narrow, two at medium and up to three when wide (`columns` is the wide ceiling), capped near 1000px and centred (`maxWidth={null}` for none). A `<FormField>`, or a `<TextField>`, `<Select>`, `<RadioGroup>`, `<PeoplePicker>` or `<FileUpload>` with its own label, is one field; a multiline field, `<FormField span="full">`, `<FieldRow>` (short related fields on one line, stacked when narrow), `<FormSection title description>` and `<FormActions>` (Save / Cancel in normal flow, secondary first and primary last, a trailing row or a full-width stack when narrow) take the whole line. `<FormField label required helper error>` draws the form controls' label, required mark, helper and error round any control and names it for a screen reader; a control that frames its own label is handed them instead, so one label is drawn. `required` (also on `TextField`, `Select`, `RadioGroup`, `PeoplePicker`, `FileUpload`) draws the mark and adds "Required" to the spoken name; it does not validate. Use it by default; a multi-step wizard, a form beside a live preview or fields laid over a picture are yours to lay out with `useContainerWidth()`.
package/dist/contract.cjs CHANGED
@@ -3364,7 +3364,7 @@ const PRIMITIVES = [
3364
3364
  {
3365
3365
  name: "DataTable",
3366
3366
  description:
3367
- 'A list of records laid out for its own width. `<DataTable columns={[{ key: "name", label: "Name", flex: 2, sortable: true }, { key: "status", label: "Status", render: (row) => <Badge label={row.status} /> }, { key: "total", label: "Total", align: "right" }]} rows={data} loading={loading} onRowPress={(row) => open(row.id)} />` — use it by default for a list or table of records (datastore rows): it is stacked label/value cards when narrow, one card per row at medium (the first column as the title, the rest on one line), and aligned columns under a sticky header when wide, from its own measured width, so it needs no tier code of yours. When the table needs what it cannot do (editable cells, grouped or collapsible rows, resizable columns, a pivot), build your own from `<ListHeader>` over a `<List>` of `<ListRow>`s with the same `flex` per column. Columns: `key`, `label`, `flex` (default 1, the same in the header and the rows), `align` ("left" / "center" / "right"), `minWidth`, `sortable`, `render(row, column)` (a string or number becomes the cell\'s text). Props: `columns`, `rows`, `keyField` (default "id"), `renderCell(row, column)` (every cell without its own `render`; return `undefined` for the default), `onRowPress(row)`, `sort` / `defaultSort` (`{ key, direction: "asc" | "desc" }`), `onSortChange(sort)`, `maxHeight` (the rows\' scroll box, default 480), `loading` (placeholder rows while `rows` is empty), `emptyTitle`, `emptyMessage` (an empty state, "No results" by default), `sortLabels` (`{ sort, ascending, descending }` in the widget\'s language), the Lists overrides (`rowPadding`, `gap`, …), `style`, `testID`, `accessibilityLabel`. A sortable header is a 44px button named by its column and described by its sort, and a change of sort is announced once; the card layouts offer the same sort from a menu. With `onSortChange` you order the rows (re-query with the new sort) and the table draws `rows` as given; without it the table sorts them itself (a `Date` value sorts as a date). A controlled `sort` without `onSortChange` is display-only: the rows follow it and the headers are plain labels. The sticky header is filled with the rows\' Lists background, else the theme surface. It is styled by Design > Components > Lists (and Cards for the card layouts).',
3367
+ 'A list of records laid out for its own width. `<DataTable columns={[{ key: "name", label: "Name", flex: 2, sortable: true }, { key: "status", label: "Status", render: (row) => <Badge label={row.status} /> }, { key: "total", label: "Total", align: "right" }]} rows={data} loading={loading} onRowPress={(row) => open(row.id)} />` — use it by default for a list or table of records (datastore rows): it is stacked label/value cards when narrow, one card per row at medium (the first column as the title, the rest on one line), and aligned columns under a sticky header when wide, from its own measured width, so it needs no tier code of yours. When the table needs what it cannot do (editable cells, grouped or collapsible rows, resizable columns, a pivot), build your own from `<ListHeader>` over a `<List>` of `<ListRow>`s with the same `flex` per column. Columns: `key`, `label`, `flex` (default 1, the same in the header and the rows), `align` ("left" / "center" / "right"), `minWidth`, `sortable`, `render(row, column)` (a string or number becomes the cell\'s text). Props: `columns`, `rows`, `keyField` (default "id"), `renderCell(row, column)` (every cell without its own `render`; return `undefined` for the default), `onRowPress(row)`, `sort` / `defaultSort` (`{ key, direction: "asc" | "desc" }`), `onSortChange(sort)`, `maxHeight` (the rows\' scroll box, default 480), `showHeader` (default true; `false` drops the wide layout\'s header row), `loading` (placeholder rows while `rows` is empty), `emptyTitle`, `emptyMessage` (an empty state, "No results" by default), `sortLabels` (`{ sort, ascending, descending }` in the widget\'s language), the Lists overrides (`rowPadding`, `gap`, …), `style`, `testID`, `accessibilityLabel`. A sortable header is a 44px button named by its column and described by its sort, and a change of sort is announced once; the card layouts offer the same sort from a menu. With `onSortChange` you order the rows (re-query with the new sort) and the table draws `rows` as given; without it the table sorts them itself (a `Date` value sorts as a date). A controlled `sort` without `onSortChange` is display-only: the rows follow it and the headers are plain labels. The sticky header is filled with the rows\' Lists background, else the theme surface. It is styled by Design > Components > Lists (and Cards for the card layouts).',
3368
3368
  rnComponent: null,
3369
3369
  docsUrl: null,
3370
3370
  },
@@ -6330,7 +6330,9 @@ const CONTRACT = deepFreeze({
6330
6330
  // `trailingIcon`, `onTrailingPress`, `trailingAccessibilityLabel` and the
6331
6331
  // slots' testIDs; `<SearchField>` draws through them. New warnings
6332
6332
  // `field-in-bordered-row`, `field-trailing-press-unnamed`.
6333
- version: "1.203.0",
6333
+ // 1.204.0: additive (sc-12244) — `<DataTable showHeader>` (default true);
6334
+ // false drops the wide layout's header row and its sticky index.
6335
+ version: "1.204.0",
6334
6336
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
6335
6337
  hooks: HOOKS,
6336
6338
  primitives: PRIMITIVES,
package/dist/contract.js CHANGED
@@ -3364,7 +3364,7 @@ const PRIMITIVES = [
3364
3364
  {
3365
3365
  name: "DataTable",
3366
3366
  description:
3367
- 'A list of records laid out for its own width. `<DataTable columns={[{ key: "name", label: "Name", flex: 2, sortable: true }, { key: "status", label: "Status", render: (row) => <Badge label={row.status} /> }, { key: "total", label: "Total", align: "right" }]} rows={data} loading={loading} onRowPress={(row) => open(row.id)} />` — use it by default for a list or table of records (datastore rows): it is stacked label/value cards when narrow, one card per row at medium (the first column as the title, the rest on one line), and aligned columns under a sticky header when wide, from its own measured width, so it needs no tier code of yours. When the table needs what it cannot do (editable cells, grouped or collapsible rows, resizable columns, a pivot), build your own from `<ListHeader>` over a `<List>` of `<ListRow>`s with the same `flex` per column. Columns: `key`, `label`, `flex` (default 1, the same in the header and the rows), `align` ("left" / "center" / "right"), `minWidth`, `sortable`, `render(row, column)` (a string or number becomes the cell\'s text). Props: `columns`, `rows`, `keyField` (default "id"), `renderCell(row, column)` (every cell without its own `render`; return `undefined` for the default), `onRowPress(row)`, `sort` / `defaultSort` (`{ key, direction: "asc" | "desc" }`), `onSortChange(sort)`, `maxHeight` (the rows\' scroll box, default 480), `loading` (placeholder rows while `rows` is empty), `emptyTitle`, `emptyMessage` (an empty state, "No results" by default), `sortLabels` (`{ sort, ascending, descending }` in the widget\'s language), the Lists overrides (`rowPadding`, `gap`, …), `style`, `testID`, `accessibilityLabel`. A sortable header is a 44px button named by its column and described by its sort, and a change of sort is announced once; the card layouts offer the same sort from a menu. With `onSortChange` you order the rows (re-query with the new sort) and the table draws `rows` as given; without it the table sorts them itself (a `Date` value sorts as a date). A controlled `sort` without `onSortChange` is display-only: the rows follow it and the headers are plain labels. The sticky header is filled with the rows\' Lists background, else the theme surface. It is styled by Design > Components > Lists (and Cards for the card layouts).',
3367
+ 'A list of records laid out for its own width. `<DataTable columns={[{ key: "name", label: "Name", flex: 2, sortable: true }, { key: "status", label: "Status", render: (row) => <Badge label={row.status} /> }, { key: "total", label: "Total", align: "right" }]} rows={data} loading={loading} onRowPress={(row) => open(row.id)} />` — use it by default for a list or table of records (datastore rows): it is stacked label/value cards when narrow, one card per row at medium (the first column as the title, the rest on one line), and aligned columns under a sticky header when wide, from its own measured width, so it needs no tier code of yours. When the table needs what it cannot do (editable cells, grouped or collapsible rows, resizable columns, a pivot), build your own from `<ListHeader>` over a `<List>` of `<ListRow>`s with the same `flex` per column. Columns: `key`, `label`, `flex` (default 1, the same in the header and the rows), `align` ("left" / "center" / "right"), `minWidth`, `sortable`, `render(row, column)` (a string or number becomes the cell\'s text). Props: `columns`, `rows`, `keyField` (default "id"), `renderCell(row, column)` (every cell without its own `render`; return `undefined` for the default), `onRowPress(row)`, `sort` / `defaultSort` (`{ key, direction: "asc" | "desc" }`), `onSortChange(sort)`, `maxHeight` (the rows\' scroll box, default 480), `showHeader` (default true; `false` drops the wide layout\'s header row), `loading` (placeholder rows while `rows` is empty), `emptyTitle`, `emptyMessage` (an empty state, "No results" by default), `sortLabels` (`{ sort, ascending, descending }` in the widget\'s language), the Lists overrides (`rowPadding`, `gap`, …), `style`, `testID`, `accessibilityLabel`. A sortable header is a 44px button named by its column and described by its sort, and a change of sort is announced once; the card layouts offer the same sort from a menu. With `onSortChange` you order the rows (re-query with the new sort) and the table draws `rows` as given; without it the table sorts them itself (a `Date` value sorts as a date). A controlled `sort` without `onSortChange` is display-only: the rows follow it and the headers are plain labels. The sticky header is filled with the rows\' Lists background, else the theme surface. It is styled by Design > Components > Lists (and Cards for the card layouts).',
3368
3368
  rnComponent: null,
3369
3369
  docsUrl: null,
3370
3370
  },
@@ -6326,7 +6326,9 @@ const CONTRACT = deepFreeze({
6326
6326
  // `trailingIcon`, `onTrailingPress`, `trailingAccessibilityLabel` and the
6327
6327
  // slots' testIDs; `<SearchField>` draws through them. New warnings
6328
6328
  // `field-in-bordered-row`, `field-trailing-press-unnamed`.
6329
- version: "1.203.0",
6329
+ // 1.204.0: additive (sc-12244) — `<DataTable showHeader>` (default true);
6330
+ // false drops the wide layout's header row and its sticky index.
6331
+ version: "1.204.0",
6330
6332
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
6331
6333
  hooks: HOOKS,
6332
6334
  primitives: PRIMITIVES,
@@ -294,35 +294,40 @@ export function makeDataTable(rn, parts) {
294
294
  return resolveListTokens(theme, overrides).background || theme.colors.surface;
295
295
  }
296
296
 
297
+ function columnsHeader(table, theme) {
298
+ return h(
299
+ ListHeader,
300
+ {
301
+ ...table.overrides,
302
+ style: { backgroundColor: headerFill(theme, table.overrides) },
303
+ testID: testId(table.testID, "header"),
304
+ },
305
+ ...table.columns.map((column) =>
306
+ h(HeaderCell, {
307
+ key: column.key,
308
+ column,
309
+ sort: table.sort,
310
+ onSort: table.onSort,
311
+ labels: table.labels,
312
+ testID: table.testID,
313
+ }),
314
+ ),
315
+ );
316
+ }
317
+
297
318
  // Inside the page's scroller; Android scrolls only the outer one without it.
298
319
  function ColumnsLayout({ table }) {
299
320
  const theme = useTableTheme();
321
+ const header = table.showHeader ? columnsHeader(table, theme) : null;
300
322
  return h(
301
323
  ScrollView,
302
324
  {
303
325
  style: { maxHeight: table.maxHeight },
304
326
  nestedScrollEnabled: true,
305
- stickyHeaderIndices: [0],
327
+ stickyHeaderIndices: header ? [0] : undefined,
306
328
  testID: testId(table.testID, "scroll"),
307
329
  },
308
- h(
309
- ListHeader,
310
- {
311
- ...table.overrides,
312
- style: { backgroundColor: headerFill(theme, table.overrides) },
313
- testID: testId(table.testID, "header"),
314
- },
315
- ...table.columns.map((column) =>
316
- h(HeaderCell, {
317
- key: column.key,
318
- column,
319
- sort: table.sort,
320
- onSort: table.onSort,
321
- labels: table.labels,
322
- testID: table.testID,
323
- }),
324
- ),
325
- ),
330
+ header,
326
331
  bodyOf(table, "columns"),
327
332
  );
328
333
  }
@@ -366,6 +371,7 @@ export function makeDataTable(rn, parts) {
366
371
  emptyTitle: props.emptyTitle,
367
372
  emptyMessage: props.emptyMessage ?? (props.emptyTitle ? undefined : emptyFallback),
368
373
  maxHeight: resolveMaxHeight(props.maxHeight),
374
+ showHeader: props.showHeader !== false,
369
375
  overrides: listOverrides(props),
370
376
  testID,
371
377
  };
package/dist/index.d.ts CHANGED
@@ -4076,6 +4076,8 @@ export const DataTable: <Row = Record<string, any>>(
4076
4076
  onSortChange?: (sort: DataTableSort) => void;
4077
4077
  /** The rows' scroll box height, in pixels. Defaults to 480. */
4078
4078
  maxHeight?: number;
4079
+ /** `false` drops the wide layout's header row. Defaults to true. */
4080
+ showHeader?: boolean;
4079
4081
  /** Placeholder rows while `rows` is still empty. */
4080
4082
  loading?: boolean;
4081
4083
  /** The empty state's title and message; the message defaults to "No results". */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@colixsystems/widget-sdk",
3
- "version": "0.249.0",
3
+ "version": "0.250.0",
4
4
  "description": "Common widget interface for AppStudio. Implements WidgetManifest, WidgetContext, property schema, and helper hooks.",
5
5
  "homepage": "https://github.com/Colix-AB/AppStudio",
6
6
  "type": "module",
package/src/contract.js CHANGED
@@ -3364,7 +3364,7 @@ const PRIMITIVES = [
3364
3364
  {
3365
3365
  name: "DataTable",
3366
3366
  description:
3367
- 'A list of records laid out for its own width. `<DataTable columns={[{ key: "name", label: "Name", flex: 2, sortable: true }, { key: "status", label: "Status", render: (row) => <Badge label={row.status} /> }, { key: "total", label: "Total", align: "right" }]} rows={data} loading={loading} onRowPress={(row) => open(row.id)} />` — use it by default for a list or table of records (datastore rows): it is stacked label/value cards when narrow, one card per row at medium (the first column as the title, the rest on one line), and aligned columns under a sticky header when wide, from its own measured width, so it needs no tier code of yours. When the table needs what it cannot do (editable cells, grouped or collapsible rows, resizable columns, a pivot), build your own from `<ListHeader>` over a `<List>` of `<ListRow>`s with the same `flex` per column. Columns: `key`, `label`, `flex` (default 1, the same in the header and the rows), `align` ("left" / "center" / "right"), `minWidth`, `sortable`, `render(row, column)` (a string or number becomes the cell\'s text). Props: `columns`, `rows`, `keyField` (default "id"), `renderCell(row, column)` (every cell without its own `render`; return `undefined` for the default), `onRowPress(row)`, `sort` / `defaultSort` (`{ key, direction: "asc" | "desc" }`), `onSortChange(sort)`, `maxHeight` (the rows\' scroll box, default 480), `loading` (placeholder rows while `rows` is empty), `emptyTitle`, `emptyMessage` (an empty state, "No results" by default), `sortLabels` (`{ sort, ascending, descending }` in the widget\'s language), the Lists overrides (`rowPadding`, `gap`, …), `style`, `testID`, `accessibilityLabel`. A sortable header is a 44px button named by its column and described by its sort, and a change of sort is announced once; the card layouts offer the same sort from a menu. With `onSortChange` you order the rows (re-query with the new sort) and the table draws `rows` as given; without it the table sorts them itself (a `Date` value sorts as a date). A controlled `sort` without `onSortChange` is display-only: the rows follow it and the headers are plain labels. The sticky header is filled with the rows\' Lists background, else the theme surface. It is styled by Design > Components > Lists (and Cards for the card layouts).',
3367
+ 'A list of records laid out for its own width. `<DataTable columns={[{ key: "name", label: "Name", flex: 2, sortable: true }, { key: "status", label: "Status", render: (row) => <Badge label={row.status} /> }, { key: "total", label: "Total", align: "right" }]} rows={data} loading={loading} onRowPress={(row) => open(row.id)} />` — use it by default for a list or table of records (datastore rows): it is stacked label/value cards when narrow, one card per row at medium (the first column as the title, the rest on one line), and aligned columns under a sticky header when wide, from its own measured width, so it needs no tier code of yours. When the table needs what it cannot do (editable cells, grouped or collapsible rows, resizable columns, a pivot), build your own from `<ListHeader>` over a `<List>` of `<ListRow>`s with the same `flex` per column. Columns: `key`, `label`, `flex` (default 1, the same in the header and the rows), `align` ("left" / "center" / "right"), `minWidth`, `sortable`, `render(row, column)` (a string or number becomes the cell\'s text). Props: `columns`, `rows`, `keyField` (default "id"), `renderCell(row, column)` (every cell without its own `render`; return `undefined` for the default), `onRowPress(row)`, `sort` / `defaultSort` (`{ key, direction: "asc" | "desc" }`), `onSortChange(sort)`, `maxHeight` (the rows\' scroll box, default 480), `showHeader` (default true; `false` drops the wide layout\'s header row), `loading` (placeholder rows while `rows` is empty), `emptyTitle`, `emptyMessage` (an empty state, "No results" by default), `sortLabels` (`{ sort, ascending, descending }` in the widget\'s language), the Lists overrides (`rowPadding`, `gap`, …), `style`, `testID`, `accessibilityLabel`. A sortable header is a 44px button named by its column and described by its sort, and a change of sort is announced once; the card layouts offer the same sort from a menu. With `onSortChange` you order the rows (re-query with the new sort) and the table draws `rows` as given; without it the table sorts them itself (a `Date` value sorts as a date). A controlled `sort` without `onSortChange` is display-only: the rows follow it and the headers are plain labels. The sticky header is filled with the rows\' Lists background, else the theme surface. It is styled by Design > Components > Lists (and Cards for the card layouts).',
3368
3368
  rnComponent: null,
3369
3369
  docsUrl: null,
3370
3370
  },
@@ -6326,7 +6326,9 @@ const CONTRACT = deepFreeze({
6326
6326
  // `trailingIcon`, `onTrailingPress`, `trailingAccessibilityLabel` and the
6327
6327
  // slots' testIDs; `<SearchField>` draws through them. New warnings
6328
6328
  // `field-in-bordered-row`, `field-trailing-press-unnamed`.
6329
- version: "1.203.0",
6329
+ // 1.204.0: additive (sc-12244) — `<DataTable showHeader>` (default true);
6330
+ // false drops the wide layout's header row and its sticky index.
6331
+ version: "1.204.0",
6330
6332
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
6331
6333
  hooks: HOOKS,
6332
6334
  primitives: PRIMITIVES,