@colixsystems/widget-sdk 0.266.0 → 0.267.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
@@ -86,7 +86,15 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
86
86
 
87
87
  ## Status
88
88
 
89
- `v0.266.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**.
89
+ `v0.267.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**.
90
+
91
+ ### What's new in 0.267.0 (contract 1.217.0)
92
+
93
+ **Every text field can raise the right keyboard.** The `TextInput` primitive (and `<TextField>`, which passes every `TextInput` prop through) now documents `keyboardType` — `"email-address"`, `"phone-pad"`, `"url"`, `"number-pad"`, `"decimal-pad"` — plus `autoCapitalize`, `autoCorrect`, `autoComplete` and `secureTextEntry`. They already worked on both hosts (react-native-web turns `keyboardType` into the input's `type` / `inputMode`); now the contract and the AI widget agent say to set them. A `useDatastoreSchema` column's `text_format` (`"EMAIL"` | `"PHONE"` | `"URL"` | a JSON format | `null`) is typed on `DatastoreSchemaColumn`, so a typed widget can pick the keyboard from it.
94
+
95
+ ### What's new in 0.266.1 (contract 1.216.0)
96
+
97
+ **An open `<Modal>` or `<Overlay>` holds the page still on the web.** Scrolling past the end of a dialog's body, or over a dialog with nothing to scroll, used to scroll the page behind it. The page now stays put until the dialog closes, as it already did in the phone app. Nothing changes for a widget. An anchored `<Menu>`, `<Popover>` or `<Tooltip>` still closes when the page scrolls under it.
90
98
 
91
99
  ### What's new in 0.266.0 (contract 1.216.0)
92
100
 
package/dist/contract.cjs CHANGED
@@ -2028,13 +2028,16 @@ const HOOKS = [
2028
2028
  "widget can resolve a stored columnId to the column's name / dataType / " +
2029
2029
  "relation target at runtime (e.g. a form that renders inputs by column " +
2030
2030
  "type). Returns { schema, loading, error, refetch } where schema is " +
2031
- "{ id, name, columns: [{ id, name, dataType, required, relationType, " +
2032
- "targetTableId, isIdentification }] } (null until loaded). Reads only the " +
2031
+ "{ id, name, columns: [{ id, name, data_type, required, relation_type, " +
2032
+ "target_table_id, is_identification, text_format }] } (null until " +
2033
+ "loaded; snake_case as the server sends it). `text_format` is " +
2034
+ "\"EMAIL\" | \"PHONE\" | \"URL\" (or a JSON format, or null) and names the " +
2035
+ "keyboard a field for that column should raise. Reads only the " +
2033
2036
  "structure, never row data; gated by the same datastore.read ACL as " +
2034
2037
  "record reads, so a public-grant table resolves for anonymous visitors.",
2035
2038
  returnShape: {
2036
2039
  schema:
2037
- "{ id, name, columns: [{ id, name, dataType, required, relationType, targetTableId, isIdentification }] } | null",
2040
+ "{ id, name, columns: [{ id, name, data_type, required, relation_type, target_table_id, is_identification, text_format }] } | null",
2038
2041
  loading: "boolean",
2039
2042
  error: "DatastoreError | null",
2040
2043
  refetch: "() => Promise<void>",
@@ -3248,7 +3251,7 @@ const PRIMITIVES = [
3248
3251
  {
3249
3252
  name: "TextInput",
3250
3253
  description:
3251
- "Single-line or multi-line text field. Controlled — pass `value` + `onChangeText`. Set `multiline` for a textarea-style field (`numberOfLines` controls visible rows). See https://reactnative.dev/docs/textinput.",
3254
+ "Single-line or multi-line text field. Controlled — pass `value` + `onChangeText`. Set `multiline` for a textarea-style field (`numberOfLines` controls visible rows). Pick the on-screen keyboard from what the field holds — the default alphabetic keyboard is wrong for most structured values: `keyboardType=\"email-address\"` for an email, `\"phone-pad\"` for a phone number, `\"url\"` for a link, `\"number-pad\"` for whole digits (a card or code), `\"decimal-pad\"` for an amount. For an email, link, username or code also pass `autoCapitalize=\"none\"` and `autoCorrect={false}`, so the keyboard never rewrites the value; `autoComplete` (\"email\", \"tel\", \"name\", \"one-time-code\") lets the device offer a saved value, and `secureTextEntry` masks a password. The same props reach the web: react-native-web turns `keyboardType` into the input's `type` / `inputMode`. A column's declared format (`text_format` on a `useDatastoreSchema` column: \"EMAIL\" | \"PHONE\" | \"URL\") names the keyboard for a field bound to it. See https://reactnative.dev/docs/textinput.",
3252
3255
  rnComponent: "TextInput",
3253
3256
  docsUrl: "https://reactnative.dev/docs/textinput",
3254
3257
  },
@@ -3607,7 +3610,7 @@ const PRIMITIVES = [
3607
3610
  {
3608
3611
  name: "TextField",
3609
3612
  description:
3610
- 'Themed text-entry field. `<TextField value={name} onChangeText={setName} placeholder="Your name" accessibilityLabel="Name" />` — every `TextInput` prop works, and its background, text colour, border, corner radius and height come from the app\'s input look (Design > Components > Inputs), so a widget\'s fields match the built-in forms. `multiline` grows the field to a ceiling and then scrolls inside it. Override one field with `background`, `textColor`, `borderColor`, `radius` or `height`; `style` is merged last. `label` draws a label above the field and names it for a screen reader, `helper` a hint below, `error` a message in the helper\'s place with a danger border (`true` marks it without a message), `disabled` makes it read-only and dimmed; `containerStyle` styles the box around a labelled field, or around the field and its icons. An icon goes INSIDE the field, never beside it: `leadingIcon` / `trailingIcon` take a lucide name (`<TextField trailingIcon="map-pin" … />`), and `onTrailingPress` with a `trailingAccessibilityLabel` makes the trailing one a button (a clear control, "use my location"). Never wrap a field in a bordered row of your own to put an icon or a button beside it: the field draws its own border, so that shows a box inside a box. Use it by default instead of styling a bare `TextInput`; a field it cannot draw (a one-time code split into digit boxes, an amount with a stepper inside its border) is your own build that keeps a label, the input colours of the theme and an error message.',
3613
+ 'Themed text-entry field. `<TextField value={name} onChangeText={setName} placeholder="Your name" accessibilityLabel="Name" />` — every `TextInput` prop works (set `keyboardType` from what the field holds: `"email-address"`, `"phone-pad"`, `"url"`, `"number-pad"`, `"decimal-pad"`), and its background, text colour, border, corner radius and height come from the app\'s input look (Design > Components > Inputs), so a widget\'s fields match the built-in forms. `multiline` grows the field to a ceiling and then scrolls inside it. Override one field with `background`, `textColor`, `borderColor`, `radius` or `height`; `style` is merged last. `label` draws a label above the field and names it for a screen reader, `helper` a hint below, `error` a message in the helper\'s place with a danger border (`true` marks it without a message), `disabled` makes it read-only and dimmed; `containerStyle` styles the box around a labelled field, or around the field and its icons. An icon goes INSIDE the field, never beside it: `leadingIcon` / `trailingIcon` take a lucide name (`<TextField trailingIcon="map-pin" … />`), and `onTrailingPress` with a `trailingAccessibilityLabel` makes the trailing one a button (a clear control, "use my location"). Never wrap a field in a bordered row of your own to put an icon or a button beside it: the field draws its own border, so that shows a box inside a box. Use it by default instead of styling a bare `TextInput`; a field it cannot draw (a one-time code split into digit boxes, an amount with a stepper inside its border) is your own build that keeps a label, the input colours of the theme and an error message.',
3611
3614
  rnComponent: null,
3612
3615
  docsUrl: null,
3613
3616
  },
@@ -6466,7 +6469,10 @@ const CONTRACT = deepFreeze({
6466
6469
  // `useUsers().invite` takes `send_invitation` (false stores the invite
6467
6470
  // unsent), invite rows carry `sent_at`, `not_sent` is a status, and
6468
6471
  // `useInvites().resend` performs the first send.
6469
- version: "1.216.0",
6472
+ // 1.217.0: additive (sc-4747) — `TextInput` documents `keyboardType`,
6473
+ // `autoCapitalize`, `autoCorrect`, `autoComplete`, `secureTextEntry`;
6474
+ // `useDatastoreSchema` columns document `text_format` (snake_case wire).
6475
+ version: "1.217.0",
6470
6476
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
6471
6477
  hooks: HOOKS,
6472
6478
  primitives: PRIMITIVES,
package/dist/contract.js CHANGED
@@ -2025,13 +2025,16 @@ const HOOKS = [
2025
2025
  "widget can resolve a stored columnId to the column's name / dataType / " +
2026
2026
  "relation target at runtime (e.g. a form that renders inputs by column " +
2027
2027
  "type). Returns { schema, loading, error, refetch } where schema is " +
2028
- "{ id, name, columns: [{ id, name, dataType, required, relationType, " +
2029
- "targetTableId, isIdentification }] } (null until loaded). Reads only the " +
2028
+ "{ id, name, columns: [{ id, name, data_type, required, relation_type, " +
2029
+ "target_table_id, is_identification, text_format }] } (null until " +
2030
+ "loaded; snake_case as the server sends it). `text_format` is " +
2031
+ "\"EMAIL\" | \"PHONE\" | \"URL\" (or a JSON format, or null) and names the " +
2032
+ "keyboard a field for that column should raise. Reads only the " +
2030
2033
  "structure, never row data; gated by the same datastore.read ACL as " +
2031
2034
  "record reads, so a public-grant table resolves for anonymous visitors.",
2032
2035
  returnShape: {
2033
2036
  schema:
2034
- "{ id, name, columns: [{ id, name, dataType, required, relationType, targetTableId, isIdentification }] } | null",
2037
+ "{ id, name, columns: [{ id, name, data_type, required, relation_type, target_table_id, is_identification, text_format }] } | null",
2035
2038
  loading: "boolean",
2036
2039
  error: "DatastoreError | null",
2037
2040
  refetch: "() => Promise<void>",
@@ -3245,7 +3248,7 @@ const PRIMITIVES = [
3245
3248
  {
3246
3249
  name: "TextInput",
3247
3250
  description:
3248
- "Single-line or multi-line text field. Controlled — pass `value` + `onChangeText`. Set `multiline` for a textarea-style field (`numberOfLines` controls visible rows). See https://reactnative.dev/docs/textinput.",
3251
+ "Single-line or multi-line text field. Controlled — pass `value` + `onChangeText`. Set `multiline` for a textarea-style field (`numberOfLines` controls visible rows). Pick the on-screen keyboard from what the field holds — the default alphabetic keyboard is wrong for most structured values: `keyboardType=\"email-address\"` for an email, `\"phone-pad\"` for a phone number, `\"url\"` for a link, `\"number-pad\"` for whole digits (a card or code), `\"decimal-pad\"` for an amount. For an email, link, username or code also pass `autoCapitalize=\"none\"` and `autoCorrect={false}`, so the keyboard never rewrites the value; `autoComplete` (\"email\", \"tel\", \"name\", \"one-time-code\") lets the device offer a saved value, and `secureTextEntry` masks a password. The same props reach the web: react-native-web turns `keyboardType` into the input's `type` / `inputMode`. A column's declared format (`text_format` on a `useDatastoreSchema` column: \"EMAIL\" | \"PHONE\" | \"URL\") names the keyboard for a field bound to it. See https://reactnative.dev/docs/textinput.",
3249
3252
  rnComponent: "TextInput",
3250
3253
  docsUrl: "https://reactnative.dev/docs/textinput",
3251
3254
  },
@@ -3604,7 +3607,7 @@ const PRIMITIVES = [
3604
3607
  {
3605
3608
  name: "TextField",
3606
3609
  description:
3607
- 'Themed text-entry field. `<TextField value={name} onChangeText={setName} placeholder="Your name" accessibilityLabel="Name" />` — every `TextInput` prop works, and its background, text colour, border, corner radius and height come from the app\'s input look (Design > Components > Inputs), so a widget\'s fields match the built-in forms. `multiline` grows the field to a ceiling and then scrolls inside it. Override one field with `background`, `textColor`, `borderColor`, `radius` or `height`; `style` is merged last. `label` draws a label above the field and names it for a screen reader, `helper` a hint below, `error` a message in the helper\'s place with a danger border (`true` marks it without a message), `disabled` makes it read-only and dimmed; `containerStyle` styles the box around a labelled field, or around the field and its icons. An icon goes INSIDE the field, never beside it: `leadingIcon` / `trailingIcon` take a lucide name (`<TextField trailingIcon="map-pin" … />`), and `onTrailingPress` with a `trailingAccessibilityLabel` makes the trailing one a button (a clear control, "use my location"). Never wrap a field in a bordered row of your own to put an icon or a button beside it: the field draws its own border, so that shows a box inside a box. Use it by default instead of styling a bare `TextInput`; a field it cannot draw (a one-time code split into digit boxes, an amount with a stepper inside its border) is your own build that keeps a label, the input colours of the theme and an error message.',
3610
+ 'Themed text-entry field. `<TextField value={name} onChangeText={setName} placeholder="Your name" accessibilityLabel="Name" />` — every `TextInput` prop works (set `keyboardType` from what the field holds: `"email-address"`, `"phone-pad"`, `"url"`, `"number-pad"`, `"decimal-pad"`), and its background, text colour, border, corner radius and height come from the app\'s input look (Design > Components > Inputs), so a widget\'s fields match the built-in forms. `multiline` grows the field to a ceiling and then scrolls inside it. Override one field with `background`, `textColor`, `borderColor`, `radius` or `height`; `style` is merged last. `label` draws a label above the field and names it for a screen reader, `helper` a hint below, `error` a message in the helper\'s place with a danger border (`true` marks it without a message), `disabled` makes it read-only and dimmed; `containerStyle` styles the box around a labelled field, or around the field and its icons. An icon goes INSIDE the field, never beside it: `leadingIcon` / `trailingIcon` take a lucide name (`<TextField trailingIcon="map-pin" … />`), and `onTrailingPress` with a `trailingAccessibilityLabel` makes the trailing one a button (a clear control, "use my location"). Never wrap a field in a bordered row of your own to put an icon or a button beside it: the field draws its own border, so that shows a box inside a box. Use it by default instead of styling a bare `TextInput`; a field it cannot draw (a one-time code split into digit boxes, an amount with a stepper inside its border) is your own build that keeps a label, the input colours of the theme and an error message.',
3608
3611
  rnComponent: null,
3609
3612
  docsUrl: null,
3610
3613
  },
@@ -6463,7 +6466,10 @@ const CONTRACT = deepFreeze({
6463
6466
  // `useUsers().invite` takes `send_invitation` (false stores the invite
6464
6467
  // unsent), invite rows carry `sent_at`, `not_sent` is a status, and
6465
6468
  // `useInvites().resend` performs the first send.
6466
- version: "1.216.0",
6469
+ // 1.217.0: additive (sc-4747) — `TextInput` documents `keyboardType`,
6470
+ // `autoCapitalize`, `autoCorrect`, `autoComplete`, `secureTextEntry`;
6471
+ // `useDatastoreSchema` columns document `text_format` (snake_case wire).
6472
+ version: "1.217.0",
6467
6473
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
6468
6474
  hooks: HOOKS,
6469
6475
  primitives: PRIMITIVES,
package/dist/hooks.js CHANGED
@@ -3165,9 +3165,9 @@ export function useDatastoreRecord(table, recordId, options) {
3165
3165
 
3166
3166
  /**
3167
3167
  * Stateful table-schema resolver hook. Returns { schema, loading, error,
3168
- * refetch } where `schema` is `{ id, name, columns: [{ id, name, dataType,
3169
- * required, relationType, targetTableId, isIdentification }] }` (`null` until
3170
- * loaded).
3168
+ * refetch } where `schema` is `{ id, name, columns: [{ id, name, data_type,
3169
+ * required, relation_type, target_table_id, is_identification, text_format }] }`
3170
+ * (`null` until loaded) — the server's snake_case, verbatim.
3171
3171
  *
3172
3172
  * The host's datastore client exposes `schema(table)` which resolves to the
3173
3173
  * table's structural metadata — columns and their types, NOT row data. Use it
package/dist/index.d.ts CHANGED
@@ -1667,6 +1667,19 @@ export interface DatastoreSchemaColumn {
1667
1667
  copy_via_column_id?: string | null;
1668
1668
  /** Set on a copy the server keeps: the related table's column it copies. */
1669
1669
  copy_of_column_id?: string | null;
1670
+ /**
1671
+ * STRING / TEXT columns only — the value's declared format. EMAIL, PHONE and
1672
+ * URL name the keyboard a field for this column should raise
1673
+ * (`keyboardType` "email-address" / "phone-pad" / "url").
1674
+ */
1675
+ text_format?:
1676
+ | "EMAIL"
1677
+ | "PHONE"
1678
+ | "URL"
1679
+ | "JSON_ARRAY"
1680
+ | "JSON_OBJECT"
1681
+ | "JSON_VALUE"
1682
+ | null;
1670
1683
  }
1671
1684
 
1672
1685
  export interface DatastoreSchema {
@@ -82,6 +82,9 @@ const VALID_TYPES = new Set([
82
82
  "object", "array",
83
83
  ]);
84
84
 
85
+ /** The propertySchema field types, for documentation generated from this list. */
86
+ export const PROPERTY_TYPES = Object.freeze([...VALID_TYPES]);
87
+
85
88
  function isPlainObject(v) {
86
89
  return v !== null && typeof v === "object" && !Array.isArray(v);
87
90
  }
package/dist/web-modal.js CHANGED
@@ -59,6 +59,32 @@ function useCloseOnOutsideScroll(container, enabled, onCloseRef) {
59
59
  }, [container, enabled, onCloseRef]);
60
60
  }
61
61
 
62
+ // A dialog owns the screen, as on native: the page beneath must not
63
+ // scroll, even once the dialog's own body runs out of scroll.
64
+ let pageScrollLocks = 0;
65
+ let unlockedRootStyle = null;
66
+
67
+ function usePageScrollLock(enabled) {
68
+ React.useEffect(() => {
69
+ if (!enabled) return undefined;
70
+ const root = document.documentElement;
71
+ if (pageScrollLocks++ === 0) {
72
+ unlockedRootStyle = {
73
+ overflow: root.style.overflow,
74
+ scrollbarGutter: root.style.scrollbarGutter,
75
+ };
76
+ // Keep a visible scrollbar's space, so the page does not shift sideways.
77
+ if (window.innerWidth > root.clientWidth) root.style.scrollbarGutter = "stable";
78
+ root.style.overflow = "hidden";
79
+ }
80
+ return () => {
81
+ if (--pageScrollLocks > 0) return;
82
+ root.style.overflow = unlockedRootStyle.overflow;
83
+ root.style.scrollbarGutter = unlockedRootStyle.scrollbarGutter;
84
+ };
85
+ }, [enabled]);
86
+ }
87
+
62
88
  function tabStops(dialog) {
63
89
  return Array.from(dialog.querySelectorAll(FOCUSABLE)).filter(
64
90
  (node) => node.getAttribute("aria-hidden") !== "true",
@@ -147,6 +173,8 @@ function ModalLayer({
147
173
  }, [visible]);
148
174
  const container = useBodyContainer(visible);
149
175
  useCloseOnOutsideScroll(container, closesOnOutsideScroll, onCloseRef);
176
+ // An anchored panel closes on a scroll beneath instead.
177
+ usePageScrollLock(Boolean(container) && !closesOnOutsideScroll);
150
178
 
151
179
  React.useEffect(() => {
152
180
  const dialog = dialogRef.current;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@colixsystems/widget-sdk",
3
- "version": "0.266.0",
3
+ "version": "0.267.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
@@ -2025,13 +2025,16 @@ const HOOKS = [
2025
2025
  "widget can resolve a stored columnId to the column's name / dataType / " +
2026
2026
  "relation target at runtime (e.g. a form that renders inputs by column " +
2027
2027
  "type). Returns { schema, loading, error, refetch } where schema is " +
2028
- "{ id, name, columns: [{ id, name, dataType, required, relationType, " +
2029
- "targetTableId, isIdentification }] } (null until loaded). Reads only the " +
2028
+ "{ id, name, columns: [{ id, name, data_type, required, relation_type, " +
2029
+ "target_table_id, is_identification, text_format }] } (null until " +
2030
+ "loaded; snake_case as the server sends it). `text_format` is " +
2031
+ "\"EMAIL\" | \"PHONE\" | \"URL\" (or a JSON format, or null) and names the " +
2032
+ "keyboard a field for that column should raise. Reads only the " +
2030
2033
  "structure, never row data; gated by the same datastore.read ACL as " +
2031
2034
  "record reads, so a public-grant table resolves for anonymous visitors.",
2032
2035
  returnShape: {
2033
2036
  schema:
2034
- "{ id, name, columns: [{ id, name, dataType, required, relationType, targetTableId, isIdentification }] } | null",
2037
+ "{ id, name, columns: [{ id, name, data_type, required, relation_type, target_table_id, is_identification, text_format }] } | null",
2035
2038
  loading: "boolean",
2036
2039
  error: "DatastoreError | null",
2037
2040
  refetch: "() => Promise<void>",
@@ -3245,7 +3248,7 @@ const PRIMITIVES = [
3245
3248
  {
3246
3249
  name: "TextInput",
3247
3250
  description:
3248
- "Single-line or multi-line text field. Controlled — pass `value` + `onChangeText`. Set `multiline` for a textarea-style field (`numberOfLines` controls visible rows). See https://reactnative.dev/docs/textinput.",
3251
+ "Single-line or multi-line text field. Controlled — pass `value` + `onChangeText`. Set `multiline` for a textarea-style field (`numberOfLines` controls visible rows). Pick the on-screen keyboard from what the field holds — the default alphabetic keyboard is wrong for most structured values: `keyboardType=\"email-address\"` for an email, `\"phone-pad\"` for a phone number, `\"url\"` for a link, `\"number-pad\"` for whole digits (a card or code), `\"decimal-pad\"` for an amount. For an email, link, username or code also pass `autoCapitalize=\"none\"` and `autoCorrect={false}`, so the keyboard never rewrites the value; `autoComplete` (\"email\", \"tel\", \"name\", \"one-time-code\") lets the device offer a saved value, and `secureTextEntry` masks a password. The same props reach the web: react-native-web turns `keyboardType` into the input's `type` / `inputMode`. A column's declared format (`text_format` on a `useDatastoreSchema` column: \"EMAIL\" | \"PHONE\" | \"URL\") names the keyboard for a field bound to it. See https://reactnative.dev/docs/textinput.",
3249
3252
  rnComponent: "TextInput",
3250
3253
  docsUrl: "https://reactnative.dev/docs/textinput",
3251
3254
  },
@@ -3604,7 +3607,7 @@ const PRIMITIVES = [
3604
3607
  {
3605
3608
  name: "TextField",
3606
3609
  description:
3607
- 'Themed text-entry field. `<TextField value={name} onChangeText={setName} placeholder="Your name" accessibilityLabel="Name" />` — every `TextInput` prop works, and its background, text colour, border, corner radius and height come from the app\'s input look (Design > Components > Inputs), so a widget\'s fields match the built-in forms. `multiline` grows the field to a ceiling and then scrolls inside it. Override one field with `background`, `textColor`, `borderColor`, `radius` or `height`; `style` is merged last. `label` draws a label above the field and names it for a screen reader, `helper` a hint below, `error` a message in the helper\'s place with a danger border (`true` marks it without a message), `disabled` makes it read-only and dimmed; `containerStyle` styles the box around a labelled field, or around the field and its icons. An icon goes INSIDE the field, never beside it: `leadingIcon` / `trailingIcon` take a lucide name (`<TextField trailingIcon="map-pin" … />`), and `onTrailingPress` with a `trailingAccessibilityLabel` makes the trailing one a button (a clear control, "use my location"). Never wrap a field in a bordered row of your own to put an icon or a button beside it: the field draws its own border, so that shows a box inside a box. Use it by default instead of styling a bare `TextInput`; a field it cannot draw (a one-time code split into digit boxes, an amount with a stepper inside its border) is your own build that keeps a label, the input colours of the theme and an error message.',
3610
+ 'Themed text-entry field. `<TextField value={name} onChangeText={setName} placeholder="Your name" accessibilityLabel="Name" />` — every `TextInput` prop works (set `keyboardType` from what the field holds: `"email-address"`, `"phone-pad"`, `"url"`, `"number-pad"`, `"decimal-pad"`), and its background, text colour, border, corner radius and height come from the app\'s input look (Design > Components > Inputs), so a widget\'s fields match the built-in forms. `multiline` grows the field to a ceiling and then scrolls inside it. Override one field with `background`, `textColor`, `borderColor`, `radius` or `height`; `style` is merged last. `label` draws a label above the field and names it for a screen reader, `helper` a hint below, `error` a message in the helper\'s place with a danger border (`true` marks it without a message), `disabled` makes it read-only and dimmed; `containerStyle` styles the box around a labelled field, or around the field and its icons. An icon goes INSIDE the field, never beside it: `leadingIcon` / `trailingIcon` take a lucide name (`<TextField trailingIcon="map-pin" … />`), and `onTrailingPress` with a `trailingAccessibilityLabel` makes the trailing one a button (a clear control, "use my location"). Never wrap a field in a bordered row of your own to put an icon or a button beside it: the field draws its own border, so that shows a box inside a box. Use it by default instead of styling a bare `TextInput`; a field it cannot draw (a one-time code split into digit boxes, an amount with a stepper inside its border) is your own build that keeps a label, the input colours of the theme and an error message.',
3608
3611
  rnComponent: null,
3609
3612
  docsUrl: null,
3610
3613
  },
@@ -6463,7 +6466,10 @@ const CONTRACT = deepFreeze({
6463
6466
  // `useUsers().invite` takes `send_invitation` (false stores the invite
6464
6467
  // unsent), invite rows carry `sent_at`, `not_sent` is a status, and
6465
6468
  // `useInvites().resend` performs the first send.
6466
- version: "1.216.0",
6469
+ // 1.217.0: additive (sc-4747) — `TextInput` documents `keyboardType`,
6470
+ // `autoCapitalize`, `autoCorrect`, `autoComplete`, `secureTextEntry`;
6471
+ // `useDatastoreSchema` columns document `text_format` (snake_case wire).
6472
+ version: "1.217.0",
6467
6473
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
6468
6474
  hooks: HOOKS,
6469
6475
  primitives: PRIMITIVES,