@colixsystems/widget-sdk 0.249.0 → 0.251.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,18 @@ 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.251.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.251.0 (contract 1.205.0)
90
+
91
+ **A text property's default is real, editable text in the Studio.** The Properties panel used to show a `string` property's `default` as a grey placeholder, so authors took it for a hint and never edited the text the widget actually rendered. It now shows the default as the field's own value. It is still not written until the author edits, so an untouched field keeps following the manifest. Clearing a field that shows a default stores `""`. Your widget gets an empty string and should render it as empty, so read text props with `??`, not `||`. Two `ui` hints are now typed:
92
+
93
+ - `ui.placeholder` — a grey hint shown while the field is empty.
94
+ - `ui.clearRestoresDefault: true` — clearing brings the default back instead of storing `""`. Use it for a caption that must never render blank, such as a button label.
95
+
96
+ ### What's new in 0.250.0 (contract 1.204.0)
97
+
98
+ **`<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
99
 
89
100
  ### What's new in 0.249.0 (contract 1.203.0)
90
101
 
@@ -2085,7 +2096,7 @@ const style = useWidgetStyle();
2085
2096
  <View style={[styles.card, style.cardBackground && { backgroundColor: style.cardBackground }]}>
2086
2097
  ```
2087
2098
 
2088
- Keep `themeDefault` for a fallback that IS a theme token: it stays display-only (a greyed placeholder in the Style panel) so the field tracks the workspace theme. Use `default` for a literal constant your code genuinely falls back to — that value now reaches `props.style`, so it must match what your code applies.
2099
+ Keep `themeDefault` for a fallback that IS a theme token: it stays display-only (shown in the Style panel field, never written) so the field tracks the workspace theme. Use `default` for a literal constant your code genuinely falls back to — that value now reaches `props.style`, so it must match what your code applies.
2089
2100
 
2090
2101
  ### What's new in 0.117.0 (contract 1.89.0)
2091
2102
 
@@ -2605,7 +2616,7 @@ Docs-only correction of documented host behaviour: no export, type, runtime, or
2605
2616
 
2606
2617
  **`themeDefault` publishes the value a style field falls back to (sc-4164).** An unset `styleSchema` field rendered as an empty box, so an author adjusting `Title size` could not see the size they were changing. `WidgetPropertyDef` now documents the key the Studio has read since sc-1807:
2607
2618
 
2608
- - **`themeDefault: "<dotted path into the resolved widget theme>"`** — e.g. `"typography.sizes.lg"`, `"radii.md"`, `"colors.onSurface"`. The Studio renders it as greyed placeholder text, resolved against the **workspace's** theme, so the hint stays truthful after a rebrand. Prefer it over a literal `default` whenever your fallback is a theme token — which it usually is, since you style from `useTheme()`.
2619
+ - **`themeDefault: "<dotted path into the resolved widget theme>"`** — e.g. `"typography.sizes.lg"`, `"radii.md"`, `"colors.onSurface"`. The Studio shows it in the field, resolved against the **workspace's** theme, so the hint stays truthful after a rebrand. Prefer it over a literal `default` whenever your fallback is a theme token — which it usually is, since you style from `useTheme()`.
2609
2620
  - **A literal `default`** still works and is the right choice for a hard-coded constant your code carries (`default: 1.4` for a line height you wrote yourself).
2610
2621
  - **Both are DISPLAY-ONLY.** Neither is ever written into `props.style`, so a style field keeps its only-when-set contract and continues to inherit the theme. Declare the value your code actually applies — a baseline that disagrees with the render is worse than none.
2611
2622
  - **Optional.** A field whose fallback has no fixed value (a `background` that inherits whatever the container paints) declares neither and renders blank.
@@ -3151,7 +3162,7 @@ import { defineWidget, validateManifest, useDatastoreQuery, Text, View } from "@
3151
3162
  - `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
3163
  - `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
3164
  - `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`.
3165
+ - `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
3166
  - `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
3167
  - `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
3168
  - `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,12 @@ 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
+ // 1.205.0: additive (sc-11840) — a string property's `default` shows as the
6336
+ // field's editable text and clearing stores ""; `ui.placeholder` and
6337
+ // `ui.clearRestoresDefault` are typed.
6338
+ version: "1.205.0",
6334
6339
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
6335
6340
  hooks: HOOKS,
6336
6341
  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,12 @@ 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
+ // 1.205.0: additive (sc-11840) — a string property's `default` shows as the
6332
+ // field's editable text and clearing stores ""; `ui.placeholder` and
6333
+ // `ui.clearRestoresDefault` are typed.
6334
+ version: "1.205.0",
6330
6335
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
6331
6336
  hooks: HOOKS,
6332
6337
  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
@@ -87,8 +87,8 @@ export interface WidgetPropertyDef {
87
87
  // sc-1807 / sc-4164 — the value the widget falls back to when this field is
88
88
  // unset, as a dotted path into the resolved widget theme
89
89
  // (`"typography.sizes.lg"`, `"radii.md"`, `"colors.onSurface"`). The Studio
90
- // shows it as greyed placeholder text so the author can see what they are
91
- // adjusting from; it resolves against the WORKSPACE's theme, so the hint stays
90
+ // shows it in the field (a greyed hint for colours) so the author can see
91
+ // what they are adjusting from; it resolves against the WORKSPACE's theme, so the hint stays
92
92
  // truthful after a rebrand. DISPLAY-ONLY — never written into props.style, so
93
93
  // a styleSchema field keeps its only-when-set contract. Prefer this over a
94
94
  // literal `default` whenever the fallback is a theme token.
@@ -106,6 +106,11 @@ export interface WidgetPropertyDef {
106
106
  // a static `default`). Today: `"tabId"` → seed a unique stable id
107
107
  // for newly added Tab Layout tabs.
108
108
  defaultFactory?: "tabId";
109
+ // string: a grey hint shown while the field is empty.
110
+ placeholder?: string;
111
+ // string: clearing restores `default` instead of storing "" — for a
112
+ // caption that must never render blank (a button label).
113
+ clearRestoresDefault?: boolean;
109
114
  };
110
115
  validation?: { min?: number; max?: number; pattern?: string };
111
116
  }
@@ -4076,6 +4081,8 @@ export const DataTable: <Row = Record<string, any>>(
4076
4081
  onSortChange?: (sort: DataTableSort) => void;
4077
4082
  /** The rows' scroll box height, in pixels. Defaults to 480. */
4078
4083
  maxHeight?: number;
4084
+ /** `false` drops the wide layout's header row. Defaults to true. */
4085
+ showHeader?: boolean;
4079
4086
  /** Placeholder rows while `rows` is still empty. */
4080
4087
  loading?: boolean;
4081
4088
  /** 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.251.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,12 @@ 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
+ // 1.205.0: additive (sc-11840) — a string property's `default` shows as the
6332
+ // field's editable text and clearing stores ""; `ui.placeholder` and
6333
+ // `ui.clearRestoresDefault` are typed.
6334
+ version: "1.205.0",
6330
6335
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
6331
6336
  hooks: HOOKS,
6332
6337
  primitives: PRIMITIVES,