@object-ui/fields 17.1.0 → 17.3.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.
Files changed (171) hide show
  1. package/CHANGELOG.md +786 -0
  2. package/dist/FieldEditWidget.d.ts +2 -2
  3. package/dist/__tests__/cell-truncation.test.d.ts +0 -0
  4. package/dist/__tests__/field-carrier-sdui.test.d.ts +0 -0
  5. package/dist/__tests__/spec-symbol-batch7.test.d.ts +8 -0
  6. package/dist/__tests__/widget-aria-invalid-e2e.test.d.ts +0 -0
  7. package/dist/__tests__/widget-aria-invalid-registry-e2e.test.d.ts +0 -0
  8. package/dist/__tests__/widget-dom-leak-e2e.test.d.ts +0 -0
  9. package/dist/__tests__/widget-props-contract.test.d.ts +8 -0
  10. package/dist/index.cjs +5 -5
  11. package/dist/index.d.ts +4 -0
  12. package/dist/index.js +2882 -2569
  13. package/dist/widgets/AddressField.d.ts +2 -2
  14. package/dist/widgets/AddressField.uniqueIds.test.d.ts +0 -0
  15. package/dist/widgets/AutoNumberField.d.ts +2 -2
  16. package/dist/widgets/AvatarField.d.ts +2 -2
  17. package/dist/widgets/BooleanField.d.ts +2 -2
  18. package/dist/widgets/CapabilityMultiSelectField.d.ts +2 -2
  19. package/dist/widgets/CharacterCount.d.ts +56 -0
  20. package/dist/widgets/CheckboxesField.d.ts +2 -2
  21. package/dist/widgets/CodeField.d.ts +2 -2
  22. package/dist/widgets/ColorField.d.ts +2 -2
  23. package/dist/widgets/CurrencyField.d.ts +2 -2
  24. package/dist/widgets/DateField.d.ts +2 -2
  25. package/dist/widgets/DateTimeField.d.ts +2 -2
  26. package/dist/widgets/EmailField.d.ts +2 -2
  27. package/dist/widgets/FileField.d.ts +2 -2
  28. package/dist/widgets/FilterConditionField.d.ts +2 -2
  29. package/dist/widgets/FormulaField.d.ts +2 -2
  30. package/dist/widgets/FullscreenFieldEditor.d.ts +155 -0
  31. package/dist/widgets/GeolocationField.d.ts +2 -2
  32. package/dist/widgets/GeolocationField.uniqueIds.test.d.ts +0 -0
  33. package/dist/widgets/GridField.d.ts +2 -2
  34. package/dist/widgets/ImageField.d.ts +2 -2
  35. package/dist/widgets/LocationField.d.ts +2 -2
  36. package/dist/widgets/LookupField.d.ts +2 -2
  37. package/dist/widgets/LookupField.gateHintLabel.test.d.ts +8 -0
  38. package/dist/widgets/MasterDetailField.d.ts +2 -2
  39. package/dist/widgets/MultiSelectField.d.ts +2 -2
  40. package/dist/widgets/NumberField.d.ts +2 -2
  41. package/dist/widgets/ObjectField.d.ts +2 -2
  42. package/dist/widgets/ObjectRefField.d.ts +2 -2
  43. package/dist/widgets/OptionsEmptyState.d.ts +45 -0
  44. package/dist/widgets/OptionsEmptyState.no-provider.test.d.ts +0 -0
  45. package/dist/widgets/OptionsEmptyState.test.d.ts +0 -0
  46. package/dist/widgets/PasswordField.d.ts +2 -2
  47. package/dist/widgets/PercentField.d.ts +2 -2
  48. package/dist/widgets/PhoneField.d.ts +2 -2
  49. package/dist/widgets/QRCodeField.d.ts +2 -2
  50. package/dist/widgets/RadioField.d.ts +2 -2
  51. package/dist/widgets/RatingField.d.ts +2 -2
  52. package/dist/widgets/RecipientPickerField.d.ts +2 -2
  53. package/dist/widgets/RecordPickerDialog.d.ts +17 -1
  54. package/dist/widgets/RecordPickerDialog.filterOptions.test.d.ts +8 -0
  55. package/dist/widgets/RecordPickerDialog.selectLabel.test.d.ts +8 -0
  56. package/dist/widgets/RichTextField.d.ts +20 -2
  57. package/dist/widgets/SelectField.d.ts +2 -2
  58. package/dist/widgets/SignatureField.d.ts +2 -2
  59. package/dist/widgets/SliderField.d.ts +2 -2
  60. package/dist/widgets/SummaryField.d.ts +2 -2
  61. package/dist/widgets/TagsField.d.ts +2 -2
  62. package/dist/widgets/TagsField.placeholder.no-provider.test.d.ts +0 -0
  63. package/dist/widgets/TagsField.placeholder.test.d.ts +0 -0
  64. package/dist/widgets/TextAreaField.d.ts +45 -6
  65. package/dist/widgets/TextField.d.ts +2 -2
  66. package/dist/widgets/TimeField.d.ts +2 -2
  67. package/dist/widgets/UrlField.d.ts +2 -2
  68. package/dist/widgets/UserField.d.ts +2 -2
  69. package/dist/widgets/VectorField.d.ts +2 -2
  70. package/dist/widgets/__tests__/FullscreenFieldEditor.i18n.test.d.ts +0 -0
  71. package/dist/widgets/__tests__/FullscreenFieldEditor.no-provider.test.d.ts +0 -0
  72. package/dist/widgets/__tests__/RichTextField.fullscreenDisabled.test.d.ts +0 -0
  73. package/dist/widgets/__tests__/RichTextField.mobileFullscreen.test.d.ts +0 -0
  74. package/dist/widgets/__tests__/TextAreaField.characterCount.announcements.test.d.ts +0 -0
  75. package/dist/widgets/__tests__/TextAreaField.characterCount.i18n.test.d.ts +0 -0
  76. package/dist/widgets/__tests__/TextAreaField.characterCount.no-provider.test.d.ts +0 -0
  77. package/dist/widgets/__tests__/TextAreaField.fullscreenCharacterCount.i18n.test.d.ts +0 -0
  78. package/dist/widgets/__tests__/TextAreaField.fullscreenCharacterCount.test.d.ts +0 -0
  79. package/dist/widgets/__tests__/TextAreaField.fullscreenDisabled.test.d.ts +0 -0
  80. package/dist/widgets/__tests__/TextAreaField.mobileFullscreen.test.d.ts +0 -0
  81. package/dist/widgets/file-value.d.ts +20 -33
  82. package/dist/widgets/toDomProps.d.ts +91 -0
  83. package/dist/widgets/types.d.ts +215 -4
  84. package/dist/withFieldCarrier.d.ts +56 -0
  85. package/package.json +13 -13
  86. package/src/FieldEditWidget.tsx +6 -4
  87. package/src/__tests__/cell-truncation.test.tsx +96 -0
  88. package/src/__tests__/field-carrier-sdui.test.tsx +169 -0
  89. package/src/__tests__/spec-symbol-batch7.test.ts +126 -0
  90. package/src/__tests__/validation-feedback.test.tsx +22 -13
  91. package/src/__tests__/widget-aria-invalid-e2e.test.tsx +265 -0
  92. package/src/__tests__/widget-aria-invalid-registry-e2e.test.tsx +338 -0
  93. package/src/__tests__/widget-dom-leak-e2e.test.tsx +598 -0
  94. package/src/__tests__/widget-props-contract.test.tsx +142 -0
  95. package/src/complex-widgets.test.tsx +3 -3
  96. package/src/datetime-widgets.test.tsx +2 -2
  97. package/src/index.tsx +77 -27
  98. package/src/widgets/AddressField.tsx +32 -13
  99. package/src/widgets/AddressField.uniqueIds.test.tsx +92 -0
  100. package/src/widgets/AutoNumberField.tsx +2 -2
  101. package/src/widgets/AvatarField.tsx +10 -3
  102. package/src/widgets/BooleanField.tsx +5 -5
  103. package/src/widgets/CapabilityMultiSelectField.tsx +4 -4
  104. package/src/widgets/CharacterCount.tsx +263 -0
  105. package/src/widgets/CheckboxesField.tsx +35 -18
  106. package/src/widgets/CodeField.tsx +10 -3
  107. package/src/widgets/ColorField.tsx +12 -3
  108. package/src/widgets/CurrencyField.tsx +6 -5
  109. package/src/widgets/DateField.tsx +4 -4
  110. package/src/widgets/DateTimeField.tsx +4 -4
  111. package/src/widgets/EmailField.tsx +6 -6
  112. package/src/widgets/FileField.tsx +14 -4
  113. package/src/widgets/FilterConditionField.tsx +14 -3
  114. package/src/widgets/FormulaField.tsx +3 -3
  115. package/src/widgets/FullscreenFieldEditor.tsx +271 -0
  116. package/src/widgets/GeolocationField.tsx +22 -7
  117. package/src/widgets/GeolocationField.uniqueIds.test.tsx +88 -0
  118. package/src/widgets/GridField.tsx +4 -4
  119. package/src/widgets/ImageField.tsx +10 -3
  120. package/src/widgets/LocationField.tsx +10 -3
  121. package/src/widgets/LookupField.gateHintLabel.test.tsx +135 -0
  122. package/src/widgets/LookupField.tsx +50 -15
  123. package/src/widgets/MasterDetailField.tsx +3 -3
  124. package/src/widgets/MultiSelectField.tsx +34 -18
  125. package/src/widgets/NumberField.tsx +5 -5
  126. package/src/widgets/ObjectField.tsx +18 -9
  127. package/src/widgets/ObjectRefField.tsx +14 -4
  128. package/src/widgets/OptionsEmptyState.no-provider.test.tsx +88 -0
  129. package/src/widgets/OptionsEmptyState.test.tsx +169 -0
  130. package/src/widgets/OptionsEmptyState.tsx +83 -0
  131. package/src/widgets/PasswordField.tsx +5 -5
  132. package/src/widgets/PeoplePicker.tsx +7 -1
  133. package/src/widgets/PercentField.tsx +6 -5
  134. package/src/widgets/PhoneField.tsx +6 -6
  135. package/src/widgets/QRCodeField.tsx +10 -3
  136. package/src/widgets/RadioField.tsx +28 -18
  137. package/src/widgets/RatingField.tsx +19 -4
  138. package/src/widgets/RecipientPickerField.tsx +22 -4
  139. package/src/widgets/RecordPickerDialog.filterOptions.test.tsx +611 -0
  140. package/src/widgets/RecordPickerDialog.selectLabel.test.tsx +112 -0
  141. package/src/widgets/RecordPickerDialog.tsx +173 -8
  142. package/src/widgets/RichTextField.tsx +152 -25
  143. package/src/widgets/SelectField.tsx +53 -22
  144. package/src/widgets/SignatureField.tsx +2 -2
  145. package/src/widgets/SliderField.tsx +3 -3
  146. package/src/widgets/SummaryField.tsx +3 -3
  147. package/src/widgets/TagsField.placeholder.no-provider.test.tsx +68 -0
  148. package/src/widgets/TagsField.placeholder.test.tsx +95 -0
  149. package/src/widgets/TagsField.tsx +25 -4
  150. package/src/widgets/TextAreaField.tsx +172 -94
  151. package/src/widgets/TextField.tsx +5 -5
  152. package/src/widgets/TimeField.tsx +4 -4
  153. package/src/widgets/UrlField.tsx +6 -6
  154. package/src/widgets/UserField.tsx +3 -3
  155. package/src/widgets/VectorField.tsx +3 -3
  156. package/src/widgets/__tests__/FullscreenFieldEditor.i18n.test.tsx +248 -0
  157. package/src/widgets/__tests__/FullscreenFieldEditor.no-provider.test.tsx +116 -0
  158. package/src/widgets/__tests__/RichTextField.fullscreenDisabled.test.tsx +244 -0
  159. package/src/widgets/__tests__/RichTextField.mobileFullscreen.test.tsx +195 -0
  160. package/src/widgets/__tests__/TextAreaField.characterCount.announcements.test.tsx +417 -0
  161. package/src/widgets/__tests__/TextAreaField.characterCount.i18n.test.tsx +287 -0
  162. package/src/widgets/__tests__/TextAreaField.characterCount.no-provider.test.tsx +160 -0
  163. package/src/widgets/__tests__/TextAreaField.fullscreenCharacterCount.i18n.test.tsx +161 -0
  164. package/src/widgets/__tests__/TextAreaField.fullscreenCharacterCount.test.tsx +346 -0
  165. package/src/widgets/__tests__/TextAreaField.fullscreenDisabled.test.tsx +246 -0
  166. package/src/widgets/__tests__/TextAreaField.mobileFullscreen.test.tsx +175 -0
  167. package/src/widgets/file-value.ts +21 -5
  168. package/src/widgets/toDomProps.ts +175 -0
  169. package/src/widgets/types.ts +230 -7
  170. package/src/widgets/useFieldTranslation.ts +60 -0
  171. package/src/withFieldCarrier.tsx +75 -0
@@ -1,7 +1,7 @@
1
1
  import { default as React } from 'react';
2
- import { FieldWidgetProps } from './types';
2
+ import { FieldWidgetComponentProps } from './types';
3
3
  /**
4
4
  * QR Code field widget - generates QR codes from text
5
5
  * Uses a simple SVG-based QR code generator
6
6
  */
7
- export declare function QRCodeField({ value, onChange, field, readonly, ...props }: FieldWidgetProps<string>): React.JSX.Element;
7
+ export declare function QRCodeField({ value, onChange, field, readonly, error, ...props }: FieldWidgetComponentProps<string>): React.JSX.Element;
@@ -1,5 +1,5 @@
1
1
  import { default as React } from 'react';
2
- import { FieldWidgetProps } from './types';
2
+ import { FieldWidgetComponentProps } from './types';
3
3
  /**
4
4
  * RadioField - choose exactly one value from a fixed option set, rendered as a
5
5
  * radio group. The stored value is the selected option value (string). Used for
@@ -12,4 +12,4 @@ import { FieldWidgetProps } from './types';
12
12
  * first" hint while a dependency is empty, and a value no longer offered (parent
13
13
  * changed / predicate flipped) is cleared.
14
14
  */
15
- export declare function RadioField({ value, onChange, field, readonly, className, schema, dependentValues, dependsOn: dependsOnProp, emptyHint: _emptyHint, dataSource: _dataSource, ...props }: FieldWidgetProps<string>): React.JSX.Element;
15
+ export declare function RadioField({ value, onChange, field, readonly, className, dependentValues, dependsOn: dependsOnProp, emptyHint, dataSource: _dataSource, error, ...props }: FieldWidgetComponentProps<string>): React.JSX.Element;
@@ -1,7 +1,7 @@
1
1
  import { default as React } from 'react';
2
- import { FieldWidgetProps } from './types';
2
+ import { FieldWidgetComponentProps } from './types';
3
3
  /**
4
4
  * Rating field widget - provides a star rating input
5
5
  * Supports numeric values from 0 to max (default 5)
6
6
  */
7
- export declare function RatingField({ value, onChange, field, readonly, className, ...props }: FieldWidgetProps<number>): React.JSX.Element;
7
+ export declare function RatingField({ value, onChange, field, readonly, className, error, ...props }: FieldWidgetComponentProps<number>): React.JSX.Element;
@@ -1,4 +1,4 @@
1
1
  import { default as React } from 'react';
2
- import { FieldWidgetProps } from './types';
3
- export declare function RecipientPickerField({ value, onChange, readonly, className, ...props }: FieldWidgetProps<string>): React.JSX.Element;
2
+ import { FieldWidgetComponentProps } from './types';
3
+ export declare function RecipientPickerField({ value, onChange, readonly, className, error, ...props }: FieldWidgetComponentProps<string>): React.JSX.Element;
4
4
  export default RecipientPickerField;
@@ -140,6 +140,22 @@ export interface RecordPickerDialogProps {
140
140
  * Typically pass `getCellRenderer` from @object-ui/fields.
141
141
  */
142
142
  cellRenderer?: CellRendererResolver;
143
+ /**
144
+ * The referenced object's schema `fields` map (field name → field
145
+ * definition). When provided, cell renderers receive the FULL field
146
+ * metadata — `options`, `currency`, `scale`, `precision`, `format`,
147
+ * `reference_to`, … — exactly like the list view enriches its columns from
148
+ * the object schema. Without it a `select` column falls back to
149
+ * title-casing the raw stored value instead of resolving the option label
150
+ * (#3333: `manufacturing` rendered as "Manufacturing" instead of the
151
+ * authored option label).
152
+ *
153
+ * The filter bar reads the same map: a `select` filter column with no
154
+ * authored `options` takes them from the schema field here, so the filter
155
+ * panel's dropdown offers exactly the options the table cells render
156
+ * (#3336 — it used to open empty, leaving the field unfilterable).
157
+ */
158
+ fieldsMeta?: Record<string, any>;
143
159
  /**
144
160
  * Filter bar column definitions.
145
161
  * When provided, shows an inline filter bar below the search input.
@@ -185,4 +201,4 @@ export interface RecordPickerDialogProps {
185
201
  * states, and single/multi-select. Responsive: mobile-friendly width
186
202
  * via Tailwind breakpoints.
187
203
  */
188
- export declare function RecordPickerDialog({ open, onOpenChange, title, multiple, dataSource, objectName, columns: columnsProp, displayField, titleFormat, idField, pageSize, value, onSelect, onSelectRecords, lookupFilters, baseFilter, cellRenderer, filterColumns, renderFilterBar, renderGrid, }: RecordPickerDialogProps): React.JSX.Element;
204
+ export declare function RecordPickerDialog({ open, onOpenChange, title, multiple, dataSource, objectName, columns: columnsProp, displayField, titleFormat, idField, pageSize, value, onSelect, onSelectRecords, lookupFilters, baseFilter, cellRenderer, fieldsMeta, filterColumns, renderFilterBar, renderGrid, }: RecordPickerDialogProps): React.JSX.Element;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * ObjectUI
3
+ * Copyright (c) 2024-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+ export {};
@@ -0,0 +1,8 @@
1
+ /**
2
+ * ObjectUI
3
+ * Copyright (c) 2024-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+ export {};
@@ -1,8 +1,26 @@
1
1
  import { default as React } from 'react';
2
- import { FieldWidgetProps } from './types';
2
+ import { FieldWidgetComponentProps } from './types';
3
3
  /**
4
4
  * Rich text field with markdown/HTML support
5
5
  * For now, this is a simple textarea. A full implementation would use
6
6
  * a rich text editor like TipTap, Lexical, or Slate.
7
+ *
8
+ * Reached by forms as `field:markdown` and `field:html`; both resolve here.
9
+ *
10
+ * ## Fullscreen editing (objectui#3301)
11
+ *
12
+ * `ObjectForm` stamps `mobile_fullscreen` onto the metadata of every long-text
13
+ * field — textarea AND rich-text — when `ObjectFormSchema.mobile
14
+ * .fullscreenLongText` is set, and `ObjectFormSchema.mobile`'s own JSDoc has
15
+ * always promised "textarea/rich-text get an expand button". This widget never
16
+ * read the flag, so for `field:markdown` / `field:html` that promise did
17
+ * nothing at all: the producer stamped, and no consumer existed.
18
+ *
19
+ * The flag is read off `field` and nowhere else — the single metadata carrier
20
+ * since objectui#3233 — matching `TextAreaField` read for read, so a
21
+ * misspelled flag stays inert in both widgets rather than being caught by a
22
+ * tolerant fallback in one of them. The affordance and dialog themselves come
23
+ * from the shared `FullscreenFieldEditor`, so one form-level setting keeps
24
+ * producing one behaviour across both widgets.
7
25
  */
8
- export declare function RichTextField({ value, onChange, field, readonly, errorMessage, ...props }: FieldWidgetProps<string>): React.JSX.Element;
26
+ export declare function RichTextField({ value, onChange, field, readonly, error, ...props }: FieldWidgetComponentProps<string>): React.JSX.Element;
@@ -1,5 +1,5 @@
1
1
  import { default as React } from 'react';
2
- import { FieldWidgetProps } from './types';
2
+ import { FieldWidgetComponentProps } from './types';
3
3
  /**
4
4
  * SelectField - dropdown selection widget.
5
5
  *
@@ -15,4 +15,4 @@ import { FieldWidgetProps } from './types';
15
15
  * the shared {@link useCascadingOptions} hook (#2715), so single and multi stay
16
16
  * in lockstep.
17
17
  */
18
- export declare function SelectField(props: FieldWidgetProps<any>): React.JSX.Element;
18
+ export declare function SelectField(props: FieldWidgetComponentProps<any>): React.JSX.Element;
@@ -1,7 +1,7 @@
1
1
  import { default as React } from 'react';
2
- import { FieldWidgetProps } from './types';
2
+ import { FieldWidgetComponentProps } from './types';
3
3
  /**
4
4
  * Signature field widget - provides a signature pad for capturing signatures
5
5
  * Outputs signature as a base64 PNG image
6
6
  */
7
- export declare function SignatureField({ value, onChange, readonly }: FieldWidgetProps<string>): React.JSX.Element;
7
+ export declare function SignatureField({ value, onChange, readonly }: FieldWidgetComponentProps<string>): React.JSX.Element;
@@ -1,7 +1,7 @@
1
1
  import { default as React } from 'react';
2
- import { FieldWidgetProps } from './types';
2
+ import { FieldWidgetComponentProps } from './types';
3
3
  /**
4
4
  * Slider field widget - provides a range slider input
5
5
  * Supports numeric values with configurable min, max, and step
6
6
  */
7
- export declare function SliderField({ value, onChange, field, readonly, ...props }: FieldWidgetProps<number>): React.JSX.Element;
7
+ export declare function SliderField({ value, onChange, field, readonly, ...props }: FieldWidgetComponentProps<number>): React.JSX.Element;
@@ -1,7 +1,7 @@
1
1
  import { default as React } from 'react';
2
- import { FieldWidgetProps } from './types';
2
+ import { FieldWidgetComponentProps } from './types';
3
3
  /**
4
4
  * SummaryField - Read-only aggregation field
5
5
  * Values are aggregated from related records and cannot be edited
6
6
  */
7
- export declare function SummaryField({ value, field, ...props }: FieldWidgetProps<any>): React.JSX.Element;
7
+ export declare function SummaryField({ value, field, ...props }: FieldWidgetComponentProps<any>): React.JSX.Element;
@@ -1,8 +1,8 @@
1
1
  import { default as React } from 'react';
2
- import { FieldWidgetProps } from './types';
2
+ import { FieldWidgetComponentProps } from './types';
3
3
  /**
4
4
  * TagsField - free-form list of string tags. Type a value and press Enter (or
5
5
  * comma) to add it; click a tag's × to remove it. The stored value is a
6
6
  * string[]. Used for the `tags` field type.
7
7
  */
8
- export declare function TagsField({ value, onChange, field, readonly, className, ...props }: FieldWidgetProps<string[]>): React.JSX.Element;
8
+ export declare function TagsField({ value, onChange, field, readonly, className, error, ...props }: FieldWidgetComponentProps<string[]>): React.JSX.Element;
File without changes
@@ -1,12 +1,51 @@
1
1
  import { default as React } from 'react';
2
- import { FieldWidgetProps } from './types';
2
+ import { FieldWidgetComponentProps } from './types';
3
3
  /**
4
4
  * TextAreaField - Multi-line text input widget
5
5
  * Supports configurable row count and preserves whitespace in readonly mode.
6
6
  *
7
- * Mobile UX (round 3): when the host form passes `mobileFullscreen` (or the
8
- * field schema sets `mobile_fullscreen: true`), an "expand" affordance opens
9
- * a fullscreen edit dialog — much easier on phones than tapping a 4-row
10
- * textarea trapped between other fields.
7
+ * Mobile UX (round 3): when the FIELD METADATA carries `mobile_fullscreen:
8
+ * true`, an "expand" affordance opens a fullscreen edit dialog — much easier
9
+ * on phones than tapping a 4-row textarea trapped between other fields.
10
+ *
11
+ * That flag has exactly one producer: `ObjectForm` stamps it onto every
12
+ * long-text field when `ObjectFormSchema.mobile.fullscreenLongText` is set
13
+ * (`plugin-form/src/ObjectForm.tsx`). It reaches this widget on `field` — the
14
+ * single metadata carrier since objectui#3233. A `SchemaRenderer`-hosted node
15
+ * arrives there too: the registry adapter (`withFieldCarrier`) maps the SDUI
16
+ * `schema` node onto `field` before the widget sees it.
17
+ *
18
+ * The affordance, the dialog and the draft/commit semantics live in the shared
19
+ * `FullscreenFieldEditor` — the same producer stamps the same flag on
20
+ * rich-text fields, and `RichTextField` renders it from there too
21
+ * (objectui#3301). Only the EDITOR differs per widget; here it is a
22
+ * full-height `Textarea`.
23
+ *
24
+ * There is deliberately NO widget-prop override. A `mobileFullscreen`
25
+ * (camelCase) prop was read here and written by nobody in the repo, and the
26
+ * snake_case `mobile_fullscreen` prop cannot arrive either: the form renderer
27
+ * strips both `mobile_fullscreen` and `fullscreen` from the props it forwards
28
+ * to registered widgets (`stripRegisteredFieldProps` in
29
+ * `components/src/renderers/form/form.tsx`). Reading keys nobody produces
30
+ * documented a contract that never held and invited the next author to pass a
31
+ * silently-ignored prop, so the reads are gone (objectui#3232). If a host
32
+ * override is ever genuinely needed, declare ONE key on
33
+ * `FieldWidgetComponentProps`, stop stripping it, and have a host pass it.
34
+ *
35
+ * ## The character counter (objectui#3408, shared across surfaces in #3417)
36
+ *
37
+ * Both editing surfaces below — the inline `<Textarea>` and the fullscreen
38
+ * dialog's — render the SAME `CharacterCount`, which owns the three-node
39
+ * GOV.UK shape (decorative digits / accessible description / gated status
40
+ * region) and the debounce and threshold behind it. Until #3417 the fullscreen
41
+ * footer had its own hand-written copy that was digits and nothing else, which
42
+ * is what a second copy of a UI always costs: it drifts, and nothing reports
43
+ * the drift.
44
+ *
45
+ * This widget's remaining job is the part only it can do — mint the two
46
+ * description ids and name each one in the `aria-describedby` of the control
47
+ * it describes. The surfaces differ in exactly one behaviour, and it is
48
+ * declared rather than implied: `announceNearLimit`. Inline announces
49
+ * (unchanged since #3408); the fullscreen dialog does not.
11
50
  */
12
- export declare function TextAreaField({ value, onChange, field, readonly, errorMessage, ...props }: FieldWidgetProps<string>): React.JSX.Element;
51
+ export declare function TextAreaField({ value, onChange, field, readonly, error, ...props }: FieldWidgetComponentProps<string>): React.JSX.Element;
@@ -1,7 +1,7 @@
1
1
  import { default as React } from 'react';
2
- import { FieldWidgetProps } from './types';
2
+ import { FieldWidgetComponentProps } from './types';
3
3
  /**
4
4
  * TextField - Standard single-line or multi-line text input
5
5
  * Automatically renders as a textarea when rows are configured in field metadata
6
6
  */
7
- export declare function TextField({ value, onChange, field, readonly, ...props }: FieldWidgetProps<string>): React.JSX.Element;
7
+ export declare function TextField({ value, onChange, field, readonly, ...props }: FieldWidgetComponentProps<string>): React.JSX.Element;
@@ -1,7 +1,7 @@
1
1
  import { default as React } from 'react';
2
- import { FieldWidgetProps } from './types';
2
+ import { FieldWidgetComponentProps } from './types';
3
3
  /**
4
4
  * TimeField - Time picker input widget
5
5
  * Uses native time input for hour and minute selection
6
6
  */
7
- export declare function TimeField({ value, onChange, field, readonly, ...props }: FieldWidgetProps<string>): React.JSX.Element;
7
+ export declare function TimeField({ value, onChange, field, readonly, ...props }: FieldWidgetComponentProps<string>): React.JSX.Element;
@@ -1,7 +1,7 @@
1
1
  import { default as React } from 'react';
2
- import { FieldWidgetProps } from './types';
2
+ import { FieldWidgetComponentProps } from './types';
3
3
  /**
4
4
  * UrlField - URL input with clickable link in readonly mode
5
5
  * Validates URLs to only render http/https links for security
6
6
  */
7
- export declare function UrlField({ value, onChange, field, readonly, errorMessage, ...props }: FieldWidgetProps<string>): React.JSX.Element;
7
+ export declare function UrlField({ value, onChange, field, readonly, error, ...props }: FieldWidgetComponentProps<string>): React.JSX.Element;
@@ -1,3 +1,3 @@
1
1
  import { default as React } from 'react';
2
- import { FieldWidgetProps } from './types';
3
- export declare function UserField(props: FieldWidgetProps<any>): React.JSX.Element;
2
+ import { FieldWidgetComponentProps } from './types';
3
+ export declare function UserField(props: FieldWidgetComponentProps<any>): React.JSX.Element;
@@ -1,7 +1,7 @@
1
1
  import { default as React } from 'react';
2
- import { FieldWidgetProps } from './types';
2
+ import { FieldWidgetComponentProps } from './types';
3
3
  /**
4
4
  * VectorField - Vector/embedding data display
5
5
  * Shows vector embeddings in a read-only format
6
6
  */
7
- export declare function VectorField({ value, field, ...props }: FieldWidgetProps<number[]>): React.JSX.Element;
7
+ export declare function VectorField({ value, field, ...props }: FieldWidgetComponentProps<number[]>): React.JSX.Element;
@@ -1,33 +1,4 @@
1
- /**
2
- * File field value shapes (ObjectStack ADR-0104 D3 wave 2).
3
- *
4
- * A `file`/`image`/`avatar`/`video`/`audio` field value reaches the UI in one
5
- * of three forms, and every widget that renders one has to cope with all
6
- * three — so the rules live here once rather than being re-derived per widget.
7
- *
8
- * 1. **Reference** — a bare `sys_file` id string. The form the backend stores
9
- * once file-as-reference is adopted.
10
- * 2. **Expanded** — `{ id, name, size, mimeType, url }`, what the read path
11
- * returns after resolving a stored reference. Note `mimeType`: this comes
12
- * from the platform's spec-owned `FileValueSchema` and is **camelCase**.
13
- * 3. **Legacy inline blob** — `{ file_id?, name, original_name, size,
14
- * mime_type, url }`, the pre-reference shape this package used to build
15
- * itself, with **snake_case** `mime_type`.
16
- *
17
- * That casing split is the whole reason this module exists: a widget reading
18
- * only `mime_type` silently stops recognising images the moment the backend
19
- * starts returning the expanded form, and the failure looks like "thumbnails
20
- * disappeared" rather than anything pointing at a value shape.
21
- *
22
- * ## Submitting
23
- *
24
- * When the upload adapter surfaced a `fileId`, the widget submits the **bare
25
- * id** — the reference form. When it did not (the object-URL fallback adapter,
26
- * an older backend), it submits the legacy blob unchanged. So the same build
27
- * works against a backend that has adopted references and one that has not.
28
- * Action params already POST a bare fileId; this brings record field values
29
- * onto the same contract.
30
- */
1
+ import { isFileIdToken } from '@objectstack/spec/data';
31
2
  /** A file value normalised for rendering, whatever form it arrived in. */
32
3
  export interface FileValueView {
33
4
  /** `sys_file` id, when the value carries one. */
@@ -43,10 +14,26 @@ export interface FileValueView {
43
14
  }
44
15
  /**
45
16
  * A minted file id: uuid/nanoid-shaped, and crucially not a URL — a URL always
46
- * carries `:`, `/` or `.`, so it can never match. Mirrors the platform's
47
- * `isFileIdToken`, which is the arbiter on the server side of the same question.
17
+ * carries `:`, `/` or `.`, so it can never match.
18
+ *
19
+ * This IS the platform's arbiter now, not a mirror of it (objectui#3161,
20
+ * objectstack#4115 ledger batch 7). The declaration that stood here said it
21
+ * "mirrors the platform's `isFileIdToken`" while being a character-for-character
22
+ * copy of that function's body under that function's own name — the shape
23
+ * objectui#3003 argued about and objectui#3169 caught again in
24
+ * `isAggregatedViewContainer`: every value test and every behaviour test passes
25
+ * against a faithful copy, so reference identity is the only check that can tell
26
+ * a re-export from a fork. It is asserted in
27
+ * `__tests__/spec-symbol-batch7.test.ts`.
28
+ *
29
+ * Why it matters that this one is shared rather than duplicated: the regex is a
30
+ * WIRE decision. Widening it server-side (say, ids grow past 64 chars) while a
31
+ * copy here keeps the old bound turns every new id into "not a reference", and
32
+ * the widget then submits the legacy inline blob against a backend that expects
33
+ * a reference. That failure surfaces as a broken thumbnail, nowhere near a
34
+ * regex.
48
35
  */
49
- export declare function isFileIdToken(value: unknown): value is string;
36
+ export { isFileIdToken };
50
37
  /**
51
38
  * The `sys_file` id a value refers to, or `undefined`.
52
39
  *
@@ -0,0 +1,91 @@
1
+ /**
2
+ * ObjectUI
3
+ * Copyright (c) 2024-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+ /**
9
+ * The RUNTIME EXECUTOR of the "DOM pass-through" section of
10
+ * {@link FieldWidgetComponentProps} (objectui#3291).
11
+ *
12
+ * ## The defect
13
+ *
14
+ * Field widgets forwarded their leftover props to a control with a bare
15
+ * spread — `const { inputType, ...domProps } = props as any; <Input
16
+ * {...domProps} />`. Whatever a host handed the widget therefore became a DOM
17
+ * attribute. Measured on `origin/main`, a real form + a real widget:
18
+ *
19
+ * ```
20
+ * <input placeholder="PH-f" zzcanary="CANARY-STR" zzcanaryobj="[object Object]"
21
+ * zzcanarynum="42" id="…" type="text" value="" name="f">
22
+ * ```
23
+ *
24
+ * The `[object Object]` this issue is named for is an authored field-config
25
+ * key being `String()`-ed onto an attribute. React 19 does not warn: an
26
+ * all-lowercase unknown attribute is passed through in complete silence, which
27
+ * is why this survived so long.
28
+ *
29
+ * ## Why a whitelist, and not a list of keys to drop
30
+ *
31
+ * The biggest leak source is not any NAMED renderer prop — it is the open tail
32
+ * of author-supplied keys. The form renderer destructures a fixed set of known
33
+ * keys and forwards `...fieldProps` verbatim
34
+ * (`components/src/renderers/form/form.tsx`), and `SchemaRenderer` is wider
35
+ * still: it spreads the whole authored node as props and has no strip layer at
36
+ * all, so on the SDUI path a widget's own spread is the ONLY line of defence.
37
+ *
38
+ * A blacklist enumerating today's renderer-only keys (`error`, `emptyHint`,
39
+ * `dataSource`, `dependentValues`, `dependsOn`, `options`, `inputType`, …)
40
+ * would pass every one of the canaries above and would not stop the next
41
+ * authored key either. Only "keep the known-safe set, drop the rest" closes
42
+ * it, which is what this function does.
43
+ *
44
+ * ## Declared = enforced
45
+ *
46
+ * {@link FieldWidgetComponentProps} already DECLARES the closed set of keys a
47
+ * widget may receive, and {@link FieldWidgetDomProps} names the subset that may
48
+ * legitimately reach a DOM element (objectui#3221). Until now that was a
49
+ * type-level claim a widget could violate at runtime simply by spreading. This
50
+ * function is that declaration's executable form, and TWO compile-time
51
+ * assertions below bind them in both directions — forwarding an undeclared key
52
+ * and declaring an unforwarded DOM key are each a compile error. Neither
53
+ * direction can drift silently.
54
+ *
55
+ * ## Deliberately NOT forwarded
56
+ *
57
+ * `role` and other HTML global attributes are absent because the contract does
58
+ * not declare them. They reached the DOM before this change only through the
59
+ * open spread. If a field node should be able to author one, DECLARE it on
60
+ * `FieldWidgetComponentProps` first and add it here — do not reopen the spread
61
+ * (AGENTS.md #0.1: fix the contract, never widen the consumer).
62
+ */
63
+ declare const DOM_PASS_THROUGH_KEYS: readonly ["id", "name", "autoFocus", "tabIndex", "onBlur", "onFocus", "onClick", "className", "disabled"];
64
+ type DomPassThroughKey = (typeof DOM_PASS_THROUGH_KEYS)[number];
65
+ /** The subset of `P` this helper forwards, with each key's declared type. */
66
+ export type DomProps<P> = Pick<P, Extract<keyof P, DomPassThroughKey>> & {
67
+ [K in Extract<keyof P, `data-${string}` | `aria-${string}`>]: P[K];
68
+ };
69
+ /**
70
+ * Keep only what may legitimately become a DOM attribute, and drop everything
71
+ * else — renderer plumbing, and any extra key an author put on the field
72
+ * config or SDUI node.
73
+ *
74
+ * Replaces the bare `{...props}` spread in every field widget that renders a
75
+ * host element:
76
+ *
77
+ * ```tsx
78
+ * // before — forwards whatever it was handed
79
+ * const { inputType, ...domProps } = props as any;
80
+ * return <Input {...domProps} … />;
81
+ *
82
+ * // after
83
+ * return <Input {...toDomProps(props)} … />;
84
+ * ```
85
+ *
86
+ * Semantic props a widget INTERPRETS (`value`, `onChange`, `field`,
87
+ * `readonly`, `error`, …) are read from `props` as before; this function only
88
+ * governs what gets spread.
89
+ */
90
+ export declare function toDomProps<P extends object>(props: P): DomProps<P>;
91
+ export {};