@object-ui/fields 17.5.0 → 17.7.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 (249) hide show
  1. package/CHANGELOG.md +6150 -0
  2. package/README.md +108 -8
  3. package/dist/FieldEditWidget.d.ts +70 -8
  4. package/dist/coerceToSafeValue.d.ts +54 -0
  5. package/dist/currency.d.ts +1 -1
  6. package/dist/field-type-alias.d.ts +16 -0
  7. package/dist/index.cjs +5 -5
  8. package/dist/index.css +53 -11
  9. package/dist/index.d.ts +653 -128
  10. package/dist/index.js +4275 -3171
  11. package/dist/widgets/AddressField.d.ts +3 -3
  12. package/dist/widgets/AutoNumberField.d.ts +1 -1
  13. package/dist/widgets/AvatarField.d.ts +20 -4
  14. package/dist/widgets/BooleanField.d.ts +2 -2
  15. package/dist/widgets/CapabilityMultiSelectField.d.ts +1 -1
  16. package/dist/widgets/CheckboxesField.d.ts +1 -1
  17. package/dist/widgets/CodeField.d.ts +1 -1
  18. package/dist/widgets/ColorField.d.ts +1 -1
  19. package/dist/widgets/CurrencyField.d.ts +1 -1
  20. package/dist/widgets/DateField.d.ts +2 -2
  21. package/dist/widgets/DateTimeField.d.ts +2 -2
  22. package/dist/widgets/EmailField.d.ts +1 -1
  23. package/dist/widgets/FileField.d.ts +18 -2
  24. package/dist/widgets/FilterConditionField.d.ts +43 -2
  25. package/dist/widgets/FormulaField.d.ts +1 -1
  26. package/dist/widgets/FullscreenFieldEditor.d.ts +9 -2
  27. package/dist/widgets/GeolocationField.d.ts +10 -4
  28. package/dist/widgets/GridField.d.ts +102 -86
  29. package/dist/widgets/ImageField.d.ts +1 -1
  30. package/dist/widgets/LocationField.d.ts +31 -4
  31. package/dist/widgets/LookupField.d.ts +9 -1
  32. package/dist/widgets/MasterDetailField.d.ts +1 -1
  33. package/dist/widgets/MultiSelectField.d.ts +1 -1
  34. package/dist/widgets/NumberField.d.ts +2 -2
  35. package/dist/widgets/ObjectField.d.ts +10 -3
  36. package/dist/widgets/ObjectRefField.d.ts +1 -1
  37. package/dist/widgets/OptionsEmptyState.d.ts +38 -8
  38. package/dist/widgets/PasswordField.d.ts +15 -1
  39. package/dist/widgets/PeoplePicker.d.ts +17 -2
  40. package/dist/widgets/PercentField.d.ts +3 -2
  41. package/dist/widgets/PersonRow.d.ts +13 -2
  42. package/dist/widgets/PhoneField.d.ts +1 -1
  43. package/dist/widgets/QRCodeField.d.ts +1 -1
  44. package/dist/widgets/RadioField.d.ts +1 -1
  45. package/dist/widgets/RatingField.d.ts +1 -1
  46. package/dist/widgets/RecipientPickerField.d.ts +44 -1
  47. package/dist/widgets/RecordPickerDialog.d.ts +33 -16
  48. package/dist/widgets/RichTextField.d.ts +37 -1
  49. package/dist/widgets/SelectField.d.ts +1 -1
  50. package/dist/widgets/SelectionTray.d.ts +13 -2
  51. package/dist/widgets/SignatureField.d.ts +32 -2
  52. package/dist/widgets/SliderField.d.ts +37 -2
  53. package/dist/widgets/SummaryField.d.ts +1 -1
  54. package/dist/widgets/TagsField.d.ts +1 -1
  55. package/dist/widgets/TextAreaField.d.ts +6 -2
  56. package/dist/widgets/TextField.d.ts +2 -2
  57. package/dist/widgets/TimeField.d.ts +2 -2
  58. package/dist/widgets/UrlField.d.ts +1 -1
  59. package/dist/widgets/UserField.d.ts +1 -1
  60. package/dist/widgets/VectorField.d.ts +1 -1
  61. package/dist/widgets/_cell-renderer-bridge.d.ts +1 -1
  62. package/dist/widgets/address-format.d.ts +27 -7
  63. package/dist/widgets/file-affordance.d.ts +33 -0
  64. package/dist/widgets/file-size-guard.d.ts +5 -23
  65. package/dist/widgets/file-value.d.ts +38 -9
  66. package/dist/widgets/lookupColumnDisplay.d.ts +110 -0
  67. package/dist/widgets/nativeDateValue.d.ts +9 -27
  68. package/dist/widgets/number-format.d.ts +137 -0
  69. package/dist/widgets/numberBadInput.d.ts +135 -0
  70. package/dist/widgets/personDisplay.d.ts +22 -4
  71. package/dist/widgets/richTextDisplay.d.ts +116 -0
  72. package/dist/widgets/toDomProps.d.ts +10 -1
  73. package/dist/widgets/toHostGroupProps.d.ts +172 -21
  74. package/dist/widgets/toHostProps.d.ts +105 -0
  75. package/dist/widgets/types.d.ts +19 -6
  76. package/dist/widgets/upload-error-message.d.ts +6 -0
  77. package/dist/widgets/uploadingScope.d.ts +142 -0
  78. package/dist/widgets/useCascadingOptions.d.ts +3 -2
  79. package/dist/widgets/useRecordQuery.d.ts +4 -2
  80. package/dist/widgets/useUploadingSignal.d.ts +8 -0
  81. package/package.json +11 -13
  82. package/dist/widgets/CharacterCount.d.ts +0 -56
  83. package/src/FieldEditWidget.test.ts +0 -133
  84. package/src/FieldEditWidget.tsx +0 -207
  85. package/src/__tests__/AddressCellRenderer.test.tsx +0 -166
  86. package/src/__tests__/CurrencyField.minorUnits.test.tsx +0 -191
  87. package/src/__tests__/CurrencyField.symbol.test.tsx +0 -198
  88. package/src/__tests__/DateCellRenderer.test.tsx +0 -82
  89. package/src/__tests__/NumberCellRenderer.grouping.test.tsx +0 -146
  90. package/src/__tests__/NumberCellRenderer.test.tsx +0 -60
  91. package/src/__tests__/PercentCellRenderer.locale.test.tsx +0 -152
  92. package/src/__tests__/PercentCellRenderer.test.tsx +0 -115
  93. package/src/__tests__/boolean-label-association-e2e.test.tsx +0 -298
  94. package/src/__tests__/capability-multiselect-retired.test.ts +0 -109
  95. package/src/__tests__/cell-truncation.test.tsx +0 -96
  96. package/src/__tests__/composite-group-label-e2e.test.tsx +0 -436
  97. package/src/__tests__/composite-group-label-readonly-e2e.test.tsx +0 -403
  98. package/src/__tests__/date-formatter-residue-4272.test.ts +0 -120
  99. package/src/__tests__/date-locale-channel.test.tsx +0 -305
  100. package/src/__tests__/field-carrier-sdui.test.tsx +0 -169
  101. package/src/__tests__/formatCurrency.centsDigit.test.tsx +0 -158
  102. package/src/__tests__/formatCurrency.minorUnits.test.tsx +0 -221
  103. package/src/__tests__/group-labelling-declaration.test.ts +0 -148
  104. package/src/__tests__/lookupCellDisplayField.test.tsx +0 -107
  105. package/src/__tests__/lookupCellDisplayName.test.tsx +0 -119
  106. package/src/__tests__/lookupCellRecordLink.test.tsx +0 -171
  107. package/src/__tests__/lookupCellReferenceKey.test.tsx +0 -57
  108. package/src/__tests__/percent-cell-vs-measure-4576.test.ts +0 -115
  109. package/src/__tests__/percent-formatter-locale-4553.test.ts +0 -147
  110. package/src/__tests__/percent-tie-halfup-4590.test.ts +0 -224
  111. package/src/__tests__/registry-collision.test.ts +0 -91
  112. package/src/__tests__/resolveFieldCurrency.test.ts +0 -20
  113. package/src/__tests__/select-label-association-e2e.test.tsx +0 -199
  114. package/src/__tests__/spec-symbol-batch7.test.ts +0 -126
  115. package/src/__tests__/validation-feedback.test.tsx +0 -361
  116. package/src/__tests__/widget-aria-invalid-e2e.test.tsx +0 -265
  117. package/src/__tests__/widget-aria-invalid-registry-e2e.test.tsx +0 -338
  118. package/src/__tests__/widget-dom-leak-e2e.test.tsx +0 -357
  119. package/src/__tests__/widget-props-contract.test.tsx +0 -142
  120. package/src/coerce-safe-value.test.ts +0 -34
  121. package/src/complex-widgets.test.tsx +0 -924
  122. package/src/currency.ts +0 -144
  123. package/src/datetime-widgets.test.tsx +0 -185
  124. package/src/field-type-alias.multiple.test.ts +0 -81
  125. package/src/field-type-alias.ts +0 -156
  126. package/src/field-type-coverage.test.ts +0 -67
  127. package/src/index.css +0 -44
  128. package/src/index.tsx +0 -2745
  129. package/src/standard-widgets.test.tsx +0 -364
  130. package/src/widgets/AddressField.postalCode.test.tsx +0 -257
  131. package/src/widgets/AddressField.tsx +0 -182
  132. package/src/widgets/AddressField.uniqueIds.test.tsx +0 -93
  133. package/src/widgets/AutoNumberField.tsx +0 -18
  134. package/src/widgets/AvatarField.tsx +0 -117
  135. package/src/widgets/BooleanField.tsx +0 -86
  136. package/src/widgets/CapabilityMultiSelectField.test.tsx +0 -168
  137. package/src/widgets/CapabilityMultiSelectField.tsx +0 -241
  138. package/src/widgets/CharacterCount.tsx +0 -263
  139. package/src/widgets/CheckboxesField.cascade.test.tsx +0 -143
  140. package/src/widgets/CheckboxesField.tsx +0 -169
  141. package/src/widgets/CodeField.tsx +0 -41
  142. package/src/widgets/ColorField.tsx +0 -53
  143. package/src/widgets/CurrencyField.tsx +0 -135
  144. package/src/widgets/DateField.tsx +0 -40
  145. package/src/widgets/DateTimeField.tsx +0 -46
  146. package/src/widgets/EmailField.tsx +0 -37
  147. package/src/widgets/FileField.tsx +0 -512
  148. package/src/widgets/FilterConditionField.tsx +0 -454
  149. package/src/widgets/FormulaField.tsx +0 -38
  150. package/src/widgets/FullscreenFieldEditor.tsx +0 -60
  151. package/src/widgets/GeolocationField.tsx +0 -193
  152. package/src/widgets/GeolocationField.uniqueIds.test.tsx +0 -88
  153. package/src/widgets/GridField.test.tsx +0 -632
  154. package/src/widgets/GridField.tsx +0 -1070
  155. package/src/widgets/ImageCropperDialog.tsx +0 -245
  156. package/src/widgets/ImageField.maxSize.test.tsx +0 -154
  157. package/src/widgets/ImageField.tsx +0 -305
  158. package/src/widgets/ImageLightbox.test.tsx +0 -52
  159. package/src/widgets/ImageLightbox.tsx +0 -108
  160. package/src/widgets/LocationField.tsx +0 -57
  161. package/src/widgets/LookupField.dependsOn.test.tsx +0 -233
  162. package/src/widgets/LookupField.gateHintLabel.test.tsx +0 -135
  163. package/src/widgets/LookupField.hydration.test.tsx +0 -136
  164. package/src/widgets/LookupField.idField.test.tsx +0 -61
  165. package/src/widgets/LookupField.tsx +0 -1295
  166. package/src/widgets/MarkdownContent.tsx +0 -19
  167. package/src/widgets/MasterDetailField.tsx +0 -131
  168. package/src/widgets/MultiSelectField.cascade.test.tsx +0 -143
  169. package/src/widgets/MultiSelectField.tsx +0 -196
  170. package/src/widgets/NumberField.tsx +0 -49
  171. package/src/widgets/ObjectField.tsx +0 -94
  172. package/src/widgets/ObjectRefField.tsx +0 -121
  173. package/src/widgets/OptionsEmptyState.no-provider.test.tsx +0 -88
  174. package/src/widgets/OptionsEmptyState.test.tsx +0 -169
  175. package/src/widgets/OptionsEmptyState.tsx +0 -99
  176. package/src/widgets/PasswordField.tsx +0 -49
  177. package/src/widgets/PeoplePicker.test.tsx +0 -360
  178. package/src/widgets/PeoplePicker.tsx +0 -579
  179. package/src/widgets/PeoplePickerComponents.test.tsx +0 -104
  180. package/src/widgets/PercentField.tsx +0 -95
  181. package/src/widgets/PersonRow.tsx +0 -117
  182. package/src/widgets/PhoneField.tsx +0 -37
  183. package/src/widgets/QRCodeField.tsx +0 -103
  184. package/src/widgets/RadioField.cascade.test.tsx +0 -141
  185. package/src/widgets/RadioField.tsx +0 -143
  186. package/src/widgets/RatingField.tsx +0 -94
  187. package/src/widgets/RecipientPickerField.test.tsx +0 -157
  188. package/src/widgets/RecipientPickerField.tsx +0 -188
  189. package/src/widgets/RecordPickerDialog.baseFilterShapes.test.tsx +0 -191
  190. package/src/widgets/RecordPickerDialog.dateLocale.test.tsx +0 -124
  191. package/src/widgets/RecordPickerDialog.filterOptions.test.tsx +0 -611
  192. package/src/widgets/RecordPickerDialog.selectLabel.test.tsx +0 -112
  193. package/src/widgets/RecordPickerDialog.tsx +0 -1365
  194. package/src/widgets/RichTextField.tsx +0 -176
  195. package/src/widgets/SelectField.cascade.test.tsx +0 -138
  196. package/src/widgets/SelectField.tsx +0 -193
  197. package/src/widgets/SelectionTray.tsx +0 -110
  198. package/src/widgets/SignatureField.tsx +0 -140
  199. package/src/widgets/SliderField.tsx +0 -39
  200. package/src/widgets/SummaryField.tsx +0 -31
  201. package/src/widgets/TagsField.placeholder.no-provider.test.tsx +0 -68
  202. package/src/widgets/TagsField.placeholder.test.tsx +0 -95
  203. package/src/widgets/TagsField.tsx +0 -88
  204. package/src/widgets/TextAreaField.tsx +0 -213
  205. package/src/widgets/TextField.tsx +0 -45
  206. package/src/widgets/TimeField.tsx +0 -31
  207. package/src/widgets/UrlField.tsx +0 -46
  208. package/src/widgets/UserField.tsx +0 -59
  209. package/src/widgets/UserFieldPicker.test.tsx +0 -78
  210. package/src/widgets/VectorField.tsx +0 -29
  211. package/src/widgets/__tests__/FilterConditionField.matchAll.test.ts +0 -66
  212. package/src/widgets/__tests__/FilterConditionField.operators.test.ts +0 -166
  213. package/src/widgets/__tests__/FullscreenFieldEditor.i18n.test.tsx +0 -248
  214. package/src/widgets/__tests__/FullscreenFieldEditor.no-provider.test.tsx +0 -116
  215. package/src/widgets/__tests__/RichTextField.fullscreenDisabled.test.tsx +0 -244
  216. package/src/widgets/__tests__/RichTextField.mobileFullscreen.test.tsx +0 -195
  217. package/src/widgets/__tests__/TextAreaField.characterCount.announcements.test.tsx +0 -417
  218. package/src/widgets/__tests__/TextAreaField.characterCount.i18n.test.tsx +0 -287
  219. package/src/widgets/__tests__/TextAreaField.characterCount.no-provider.test.tsx +0 -160
  220. package/src/widgets/__tests__/TextAreaField.fullscreenCharacterCount.i18n.test.tsx +0 -161
  221. package/src/widgets/__tests__/TextAreaField.fullscreenCharacterCount.test.tsx +0 -346
  222. package/src/widgets/__tests__/TextAreaField.fullscreenDisabled.test.tsx +0 -246
  223. package/src/widgets/__tests__/TextAreaField.mobileFullscreen.test.tsx +0 -175
  224. package/src/widgets/_cell-renderer-bridge.ts +0 -19
  225. package/src/widgets/address-format.ts +0 -120
  226. package/src/widgets/deriveLookupColumns.test.ts +0 -112
  227. package/src/widgets/deriveLookupColumns.ts +0 -108
  228. package/src/widgets/file-size-guard.ts +0 -55
  229. package/src/widgets/file-value.test.ts +0 -230
  230. package/src/widgets/file-value.ts +0 -246
  231. package/src/widgets/nativeDateValue.test.ts +0 -115
  232. package/src/widgets/nativeDateValue.ts +0 -112
  233. package/src/widgets/openNativePicker.ts +0 -18
  234. package/src/widgets/optionWidgets.gatedOptions.test.tsx +0 -267
  235. package/src/widgets/optionWidgets.unconfiguredOptions.test.tsx +0 -150
  236. package/src/widgets/personDisplay.test.ts +0 -113
  237. package/src/widgets/personDisplay.ts +0 -92
  238. package/src/widgets/recentLookups.test.ts +0 -45
  239. package/src/widgets/recentLookups.ts +0 -47
  240. package/src/widgets/toDomProps.ts +0 -204
  241. package/src/widgets/toHostGroupProps.ts +0 -102
  242. package/src/widgets/types.ts +0 -242
  243. package/src/widgets/useCascadingOptions.ts +0 -46
  244. package/src/widgets/useFieldTranslation.ts +0 -132
  245. package/src/widgets/useRecordQuery.test.ts +0 -174
  246. package/src/widgets/useRecordQuery.ts +0 -291
  247. package/src/widgets/useUploadingSignal.test.tsx +0 -62
  248. package/src/widgets/useUploadingSignal.ts +0 -20
  249. package/src/withFieldCarrier.tsx +0 -75
@@ -0,0 +1,116 @@
1
+ import { default as React } from 'react';
2
+ import { CellRendererProps } from '../index.js';
3
+ /**
4
+ * Renders `markdown` values as formatted GFM markdown (lazy-loaded, sanitized)
5
+ * instead of the raw markup string.
6
+ *
7
+ * Markdown ONLY. `MarkdownContent` runs react-markdown with no `rehype-raw`, so
8
+ * raw HTML in the string is not parsed and never reaches the DOM — that is this
9
+ * renderer's trust boundary, not an oversight, and it must stay that way
10
+ * (objectui#5452). `richtext` used to be routed here too and rendered as a
11
+ * COMPLETELY EMPTY cell, because a richtext value is entirely HTML and this
12
+ * pipeline drops all of it; see {@link HtmlCellRenderer}, which is where that
13
+ * type belongs. Loosening this pipeline to pass raw HTML through would have
14
+ * "fixed" one type by moving every `markdown` cell's trust boundary.
15
+ *
16
+ * What the cell holds is decided by {@link richTextSource} (objectui#8580):
17
+ * `[]` is the No-value affordance, never a childless container, and an
18
+ * object is the text the string class prints for it, never `[object Object]`.
19
+ */
20
+ export declare function MarkdownCellRenderer({ value }: CellRendererProps): React.ReactElement;
21
+ /**
22
+ * Renders an `html` value as sanitized, formatted HTML instead of raw markup.
23
+ *
24
+ * Also the renderer for `richtext` (objectui#5452). Both types store an HTML
25
+ * string: the spec's `Field.richtext` is documented as "Formatted content with
26
+ * HTML/WYSIWYG", the showcase seed's own specimen is
27
+ * `<p>Rich <strong>text</strong></p>`, and this repo's designer bridge already
28
+ * maps `richtext` onto its `html` type. {@link sanitizeHtml} above removes
29
+ * script/style/iframe/object/embed blocks, inline event handlers and
30
+ * `javascript:` URLs, and touches nothing a rich-text editor legitimately
31
+ * emits — headings, paragraphs, emphasis, lists, links, quotes all survive, so
32
+ * routing `richtext` here restores the content rather than trading a blank cell
33
+ * for a mangled one.
34
+ *
35
+ * What the cell holds is decided by {@link richTextSource} (objectui#8580):
36
+ * `[]` is the No-value affordance, never a childless `prose` container, and
37
+ * an object is the text the string class prints for it. The populated branch
38
+ * stays on {@link sanitizeHtml}: a stored string comes back from
39
+ * `richTextSource` verbatim, so the bytes that reach the sanitiser are the
40
+ * bytes that always did — pinned byte-for-byte in
41
+ * `__tests__/cellRenderers.childlessContainer-8580.test.tsx`.
42
+ */
43
+ export declare function HtmlCellRenderer({ value }: CellRendererProps): React.ReactElement;
44
+ /**
45
+ * Field type → display pipeline, for the three types `RichTextField` serves.
46
+ *
47
+ * THE table, singular. `getCellRenderer`'s standard map spreads it (so the
48
+ * grid, kanban, gallery, related list, dashboard panel and detail read mode
49
+ * resolve through these entries) and `RichTextField`'s readonly branch indexes
50
+ * it (so a readonly form field renders through the very same component). A
51
+ * change here moves every read surface at once; there is no second copy left
52
+ * to forget.
53
+ *
54
+ * `richtext` reads through the HTML renderer — NOT the markdown one, which
55
+ * drops raw HTML and therefore rendered every populated richtext value as a
56
+ * blank cell (objectui#5452).
57
+ */
58
+ export declare const RICH_TEXT_CELL_RENDERERS: {
59
+ readonly markdown: typeof MarkdownCellRenderer;
60
+ readonly html: typeof HtmlCellRenderer;
61
+ readonly richtext: typeof HtmlCellRenderer;
62
+ };
63
+ /** The rich-content field types, i.e. the keys of {@link RICH_TEXT_CELL_RENDERERS}. */
64
+ export type RichTextFieldType = keyof typeof RICH_TEXT_CELL_RENDERERS;
65
+ /**
66
+ * The same three types as a RUNTIME list, DERIVED from THE table rather than
67
+ * spelled a second time — the type-level {@link RichTextFieldType} cannot be
68
+ * enumerated at runtime, and a hand-written array beside it would be exactly
69
+ * the shape this export exists to retire.
70
+ *
71
+ * ## Why this export exists
72
+ *
73
+ * objectui#4831 asked, in its own body, whether the hand-written type lists
74
+ * that keep omitting the third of this widget's three registry keys should
75
+ * become "every type that resolves to the long-text widget". It was answered
76
+ * by adding one literal, and the omission recurred (objectui#4250,
77
+ * objectui#8438). This is that question answered in the other direction: a
78
+ * consumer that spreads this list cannot omit a key, because it never names
79
+ * one. Adding a fourth key to {@link RICH_TEXT_CELL_RENDERERS} extends every
80
+ * such consumer in the same commit that adds the key.
81
+ *
82
+ * ⚠️ It is NOT a general "long text" list and must not be used as one:
83
+ * `textarea` renders a `<Textarea>` too and is deliberately absent, because it
84
+ * is a different widget with its own registry key and its own metadata face.
85
+ * The invariant this list states is narrower and exact — *these are the keys
86
+ * `RichTextField` serves*.
87
+ */
88
+ export declare const RICH_TEXT_FIELD_TYPES: readonly RichTextFieldType[];
89
+ /**
90
+ * The SYNTAX a rich-content type stores, derived from the renderer that type
91
+ * resolves to rather than declared a second time.
92
+ *
93
+ * This is what `RichTextField`'s editor header names ("Format: markdown"), and
94
+ * deriving it from {@link RICH_TEXT_CELL_RENDERERS} is the point: the header
95
+ * used to be computed as `field.format || 'markdown'`, and since `format` is
96
+ * declared on no rich-content field type (only on `date`/`datetime`/`time`/
97
+ * `phone`/`auto_number`), it read `undefined` for every real field and labelled
98
+ * an `html` field "Format: markdown" — a label pointing at a pipeline the value
99
+ * does not go through. Derived, the label cannot disagree with the renderer.
100
+ *
101
+ * `undefined` for a type with no pipeline, rather than a guessed default: the
102
+ * caller has to say what it does with "not a rich-content type" instead of
103
+ * silently getting the markdown answer, which is the bug this replaces.
104
+ */
105
+ export declare function richTextSyntax(fieldType: string): 'markdown' | 'html' | undefined;
106
+ /**
107
+ * The display pipeline a field type reads through, or `undefined` when the
108
+ * type is not one of the three rich-content types.
109
+ *
110
+ * The lookup is a function rather than a raw index so the "not a rich-content
111
+ * type" answer is in the RETURN TYPE. Indexing {@link RICH_TEXT_CELL_RENDERERS}
112
+ * directly hands back a non-optional component for any key the caller casts,
113
+ * so the miss becomes a runtime `undefined` the compiler has been told cannot
114
+ * happen — and a caller that checks for it reads as dead code.
115
+ */
116
+ export declare function richTextCellRenderer(fieldType: string): React.FC<CellRendererProps> | undefined;
@@ -83,7 +83,16 @@ import { DomProps as CoreDomProps } from '@object-ui/core';
83
83
  * the contract it executes. Not two judges.
84
84
  */
85
85
  declare const DOM_PASS_THROUGH_KEYS: readonly ["id", "name", "autoFocus", "tabIndex", "onBlur", "onFocus", "onClick", "className", "disabled"];
86
- type DomPassThroughKey = (typeof DOM_PASS_THROUGH_KEYS)[number];
86
+ /**
87
+ * The keys this helper forwards.
88
+ *
89
+ * Exported so the SIBLING executor — `toHostProps`, which carries the declared
90
+ * NON-DOM keys (`f08bcd9af`) — can subtract this set from the contract and
91
+ * assert that the two together cover every declared key exactly once. Without
92
+ * that subtraction there is no way to state "these keys are handled elsewhere"
93
+ * as a compile-time fact rather than as a comment.
94
+ */
95
+ export type DomPassThroughKey = (typeof DOM_PASS_THROUGH_KEYS)[number];
87
96
  /** The subset of `P` this helper forwards, with each key's declared type. */
88
97
  export type DomProps<P> = CoreDomProps<P, DomPassThroughKey>;
89
98
  /**
@@ -7,19 +7,21 @@
7
7
  */
8
8
  /**
9
9
  * The props that make a group-labelled widget's rendered surface the thing its
10
- * host label actually NAMES, on the surfaces that are not controls
11
- * (objectui#3990).
10
+ * host label actually NAMES — and, where nothing else can carry it, DESCRIBES
11
+ * (objectui#3990, objectui#4005).
12
12
  *
13
13
  * ## What it is for
14
14
  *
15
15
  * A widget declared `labelling: 'group'` (objectui#3961) is told by the form
16
16
  * renderer: your label publishes an `id` and drops its `for`, so name yourself
17
- * by IDREF. The renderer hands down exactly two facts that address the field as
18
- * a WHOLE:
17
+ * by IDREF. The renderer hands down facts that address the field as a WHOLE:
19
18
  *
20
19
  * - `id` — the host's control id (`…-form-item`), the id the label's `for`
21
20
  * used to point at;
22
- * - `aria-labelledby` — the IDREF of that visible label.
21
+ * - `aria-labelledby` — the IDREF of that visible label;
22
+ * - `aria-describedby` — the IDREF of `<FormDescription>`, the field's visible
23
+ * help text. Which surface may consume it is the one thing that is NOT
24
+ * uniform, so it is decided per call site — see {@link HostGroupSurface}.
23
25
  *
24
26
  * Every editable branch already consumes them inside its wider `toDomProps`
25
27
  * spread. The branches that returned EARLY did not — a field-level
@@ -39,19 +41,66 @@
39
41
  * to nothing can at least be swept for, while "a label published an id and
40
42
  * nobody consumed it" looks like healthy markup.
41
43
  *
42
- * ## Why this narrow pair, and not the whole `toDomProps` result
44
+ * ## The description was the other half of the same defect (objectui#4005)
43
45
  *
44
- * Everything else in the DOM pass-through addresses a CONTROL, and a readonly
45
- * display has none: `aria-describedby` / `aria-required` are announced when
46
- * focus lands on something focusable, `disabled` / `tabIndex` / the focus
47
- * handlers only mean something on an interactive element, and `name` is
48
- * DOM-legal on form controls only — on a `div` it is exactly the leak
49
- * objectui#3291 sweeps for. Spreading the full result would also put the host's
50
- * `className` on top of the widget's own, because the widgets whose readonly
51
- * surface is a placeholder or a plain container (`EmptyValue`, `FileField`,
52
- * `AddressField`) keep `className` inside `props`.
46
+ * #3990 landed the `id` + `aria-labelledby` pair above and deliberately stopped
47
+ * there. The reasoning it recorded — a readonly display has no focusable
48
+ * control, and `aria-describedby` is announced when focus lands on something
49
+ * focusable — was the right call for the surface as it stood THEN: before this
50
+ * helper existed, a readonly branch had no role at all, and on a role-less
51
+ * `div` / `span` an `aria-describedby` is as inert as the `aria-labelledby`
52
+ * was. That premise expired the moment #3990 gave those surfaces
53
+ * `role="group"`. A group IS a description carrier under ARIA 1.2: assistive
54
+ * technology announces a group's name and description on entering it, with no
55
+ * focusable control required. So the help text stayed measurably unreachable
56
+ * for exactly as long as it took to have somewhere to put it. Measured on
57
+ * `origin/main` at `c25222758`, a real form, one field per row, counting the
58
+ * elements whose `aria-describedby` names the rendered `<FormDescription>`:
53
59
  *
54
- * ## Why `role` travels with the two keys
60
+ * ```
61
+ * descEl consumers consumerTags
62
+ * address editable desc=YES SET 1 [input]
63
+ * address readonly desc=YES SET 0 []
64
+ * geolocation editable desc=YES SET 1 [input]
65
+ * geolocation readonly desc=YES SET 0 []
66
+ * checkboxes editable desc=YES SET 1 [div[group]]
67
+ * checkboxes readonly desc=YES SET 0 []
68
+ * radio editable desc=YES SET 1 [div[radiogroup]]
69
+ * radio readonly desc=YES SET 0 []
70
+ * rating editable desc=YES SET 1 [div[group]]
71
+ * rating readonly desc=YES SET 0 []
72
+ * file editable desc=YES SET 1 [div[button]]
73
+ * file readonly desc=YES SET 0 []
74
+ * multiselect editable desc=YES SET 1 [div[group]]
75
+ * multiselect readonly desc=YES SET 0 []
76
+ * ```
77
+ *
78
+ * All seven render the `<FormDescription>` in the readonly state — none is
79
+ * exempt for want of one — and all seven left it referenced by nothing.
80
+ *
81
+ * ## What still does NOT come along, and why that is a boundary and not an oversight
82
+ *
83
+ * `aria-invalid` and `aria-required` are CONTROL-channel state: they report
84
+ * what a user's own editing may do wrong, to the element they would edit. A
85
+ * readonly display cannot be edited and cannot be made invalid by the person
86
+ * reading it, so putting either on it announces a state that has no action
87
+ * behind it. Spreading the whole `toDomProps` result would drag both back —
88
+ * along with `disabled` / `tabIndex` / the focus handlers, which only mean
89
+ * something on an interactive element, and `name`, which is DOM-legal on form
90
+ * controls only and on a `div` is exactly the leak objectui#3291 sweeps for.
91
+ * That is what objectui#3291 and objectui#3318 drew the line against, and
92
+ * adding the description does not move it: the description is what the FIELD
93
+ * is, the other two are what the CONTROL is doing. The line is pinned from both
94
+ * sides in `composite-group-label-readonly-e2e.test.tsx` — the description must
95
+ * be there, `aria-invalid` / `aria-required` must not — so neither a later
96
+ * "spread it all" nor a later "take it all back out" can pass quietly.
97
+ *
98
+ * Spreading the full result would also put the host's `className` on top of the
99
+ * widget's own, because the widgets whose readonly surface is a placeholder or
100
+ * a plain container (`EmptyValue`, `FileField`, `AddressField`) keep
101
+ * `className` inside `props`.
102
+ *
103
+ * ## Why `role` travels with the keys
55
104
  *
56
105
  * `aria-labelledby` on a role-less `div` / `span` names NOTHING — `generic`
57
106
  * prohibits an author name — so the pair is inert without a role that supports
@@ -73,19 +122,121 @@ export interface HostGroupProps {
73
122
  id?: string;
74
123
  /** IDREF of the host's visible label. */
75
124
  'aria-labelledby'?: string;
125
+ /**
126
+ * IDREF of the host's visible help text — present only on a surface that
127
+ * renders no input of the field's own. See {@link HostGroupSurface}.
128
+ */
129
+ 'aria-describedby'?: string;
76
130
  /** `'group'` when — and only when — a host named this surface. */
77
131
  role?: 'group';
78
132
  }
79
133
  /**
80
- * Read the two whole-field keys out of a widget's leftover props, plus the role
81
- * that makes them mean something. See {@link HostGroupProps}.
134
+ * WHERE the surface being named sits relative to the field's own inputs. The
135
+ * name is uniform; the DESCRIPTION is not, so every call site has to say which
136
+ * of the two shapes it is, and the compiler makes the next widget author say it
137
+ * too rather than inherit whichever default happened to be there.
138
+ *
139
+ * - `'above-the-inputs'` — a composite's EDITABLE container (`address`,
140
+ * `geolocation`): the field's real inputs are inside it and each already
141
+ * receives `aria-describedby` in its own `toDomProps` spread, straight from
142
+ * `<FormControl>`'s Slot. This surface therefore takes the NAME only.
143
+ * objectui#3318 chose that split deliberately and
144
+ * `composite-group-label-e2e.test.tsx` pins it: the help text is announced
145
+ * when focus reaches the box the user types into, and announcing it a second
146
+ * time on the container that merely wraps those boxes is a double channel of
147
+ * exactly the kind objectui#3290 removed from the required marker.
148
+ *
149
+ * - `'instead-of-the-inputs'` — every readonly branch, and the zero-option box
150
+ * (`OptionsEmptyState`). The field renders no input at all here, so no
151
+ * element inside can carry the description and this surface is the only
152
+ * candidate there is. It takes the name AND the description.
153
+ *
154
+ * Note what the discriminator is NOT about: whether the surface contains
155
+ * anything focusable. `geolocation`'s readonly row holds a "View on map" link
156
+ * — focusable, and correctly announced as itself. It is not an input OF THE
157
+ * FIELD and never receives the field's `aria-describedby`, so that row is still
158
+ * `'instead-of-the-inputs'`.
159
+ */
160
+ export type HostGroupSurface = 'above-the-inputs' | 'instead-of-the-inputs';
161
+ /**
162
+ * Read the whole-field keys out of a widget's leftover props, plus the role
163
+ * that makes them mean something. See {@link HostGroupProps} and
164
+ * {@link HostGroupSurface}.
82
165
  *
83
166
  * Routed through {@link toDomProps} rather than reading `props` directly so the
84
- * pair keeps ONE gate: those keys reach an element only if the DOM pass-through
85
- * whitelist still forwards them, and its two compile-time assertions bind that
167
+ * keys keep ONE gate: they reach an element only if the DOM pass-through
168
+ * whitelist still forwards them, and its compile-time assertions bind that
86
169
  * whitelist to the props contract in both directions.
170
+ *
171
+ * `aria-describedby` is forwarded exactly as the host handed it down, with no
172
+ * check that it resolves — a widget cannot see whether `<FormDescription>`
173
+ * rendered, and a DOM probe for it would be a layout-effect hack around the
174
+ * renderer's own knowledge. That is not a new gap: `<FormControl>`'s Slot emits
175
+ * the IDREF unconditionally, so a field with NO description already hands every
176
+ * editable control an `aria-describedby` pointing at an element that does not
177
+ * exist. Measured on `origin/main` at `c25222758`, `desc=NO`: all seven
178
+ * group-labelled widgets plus the builtin `input` and the single-control
179
+ * `email` carry exactly one such reference, on the same element that would
180
+ * carry a resolving one. A readonly surface answering identically is parity
181
+ * with the editable branch, not a class of its own — and an IDREF that resolves
182
+ * to nothing is inert for assistive technology, where an unconsumed
183
+ * description is simply lost.
87
184
  */
88
185
  export declare function toHostGroupProps(props: {
89
186
  id?: string;
90
187
  'aria-labelledby'?: string;
91
- }): HostGroupProps;
188
+ 'aria-describedby'?: string;
189
+ }, surface: HostGroupSurface): HostGroupProps;
190
+ /**
191
+ * The host keys a LABELABLE surface may consume — the `labelling: 'control'`
192
+ * counterpart of {@link HostGroupProps} (objectui#8803).
193
+ *
194
+ * ## Why a second, smaller bag instead of reusing the group one
195
+ *
196
+ * A widget declared `'control'` is told nothing by the host: its label emits a
197
+ * plain `for` and expects to reach a labelable element on its own. Every
198
+ * editable branch answers that by spreading `toDomProps` onto the control it
199
+ * renders. The branches that return EARLY answer it with nothing — and for the
200
+ * single `SelectField` that is the zero-option box, where the `…-form-item` id
201
+ * reached no element at all and the label's `for` DANGLED.
202
+ *
203
+ * What such a surface needs is therefore the exact complement of the group
204
+ * bag:
205
+ *
206
+ * - `id` — yes. It is the id the `for` points at, and landing it is the whole
207
+ * repair;
208
+ * - `aria-describedby` — yes, for the objectui#4005 reason, unchanged by the
209
+ * surface being labelable: the field renders no input in this state, so
210
+ * this box is the only element the visible help text can be announced on;
211
+ * - `aria-labelledby` — ⛔ NO. The `for` already names this element. Adding an
212
+ * IDREF beside it is the second naming channel objectui#3978 removed;
213
+ * - `role` — ⛔ NO. A labelable element's own semantics are what make the name
214
+ * land; a synthetic role on top is the category error objectui#3991 named.
215
+ *
216
+ * `aria-invalid` / `aria-required` / `name` stay off for the reasons spelled
217
+ * out at length on {@link HostGroupProps}: control-channel state on a surface
218
+ * nobody can edit, and the DOM leak objectui#3291 sweeps for. A closed type,
219
+ * not an open props tail, for that same reason.
220
+ */
221
+ export interface HostControlProps {
222
+ /** The host's control id — the id its label's `for` points at. */
223
+ id?: string;
224
+ /** IDREF of the host's visible help text. */
225
+ 'aria-describedby'?: string;
226
+ }
227
+ /**
228
+ * Read the two whole-field keys a labelable replacement surface may carry. See
229
+ * {@link HostControlProps}.
230
+ *
231
+ * Routed through {@link toDomProps} for the same reason {@link toHostGroupProps}
232
+ * is: the keys keep ONE gate, and its compile-time assertions bind that
233
+ * whitelist to the props contract in both directions.
234
+ *
235
+ * Standalone rendering (the grid's inline cell editor, an action param dialog,
236
+ * a bare SDUI node) hands down neither key, so both values are `undefined`,
237
+ * React emits no attribute, and that markup stays byte-identical.
238
+ */
239
+ export declare function toHostControlProps(props: {
240
+ id?: string;
241
+ 'aria-describedby'?: string;
242
+ }): HostControlProps;
@@ -0,0 +1,105 @@
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 NON-DOM half of {@link FieldWidgetComponentProps}
10
+ * (`f08bcd9af`) — the sibling of `toDomProps`, and deliberately a SEPARATE
11
+ * function rather than more entries in that whitelist.
12
+ *
13
+ * ## The defect
14
+ *
15
+ * objectui#6909 / #7009 closed the DOM half at `FieldEditWidget`: the factory
16
+ * hands the widget `toDomProps(props)`, so `id` / `name` / `tabIndex` /
17
+ * `aria-*` / `data-*` and friends finally arrive. The contract also declares a
18
+ * "Host plumbing" block plus two controlled-input keys the factory neither owns
19
+ * nor routes to the DOM, and NOTHING carried those. A host could pass any of
20
+ * them with no type error and the widget never received it — this package's own
21
+ * first-class defect class, named in `toDomProps.ts`: "a key that type-checks,
22
+ * reads as supported, and silently never reaches the element" (objectui#3290's
23
+ * `aria-required`, objectui#3222's validation slot).
24
+ *
25
+ * Measured on `main` at `71d83a6b1`, the live victim was `error`:
26
+ * `InlineFieldInput` (`@object-ui/plugin-detail`, since PR #7109) already
27
+ * passes `error={error}` to `FieldEditWidget`, and the factory dropped it on
28
+ * the floor — so a control that had failed validation never reported
29
+ * `aria-invalid` and a screen-reader user was never told. The kanban
30
+ * `RequiredFieldsDialog` had the same hole from the other side: it computes the
31
+ * validation state, renders it in red text, and had no way to hand it over.
32
+ *
33
+ * ## Why NOT more keys in `DOM_PASS_THROUGH_KEYS`
34
+ *
35
+ * Because none of them is DOM-legal. That whitelist is deliberately closed and
36
+ * exists precisely to stop renderer plumbing reaching an element — its own
37
+ * header names `error`, `emptyHint`, `dataSource`, `dependentValues` and
38
+ * `dependsOn` as examples of what a blacklist would have failed to catch. Put
39
+ * them there and a `dataSource` adapter becomes `dataSource="[object Object]"`
40
+ * on an `<input>`. They travel as COMPONENT props, which is what they are.
41
+ *
42
+ * ## Declared = enforced, in both directions
43
+ *
44
+ * The three assertions below make the two executors PARTITION the contract:
45
+ * every declared key is DOM-handled, forwarded here, or named as one the
46
+ * factory itself owns. A key added to `FieldWidgetComponentProps` that is none
47
+ * of those is a compile error, so the next non-DOM key cannot repeat this bug.
48
+ *
49
+ * ## Precedence: this is a CONDUIT, and never a second authority
50
+ *
51
+ * Several of these keys have a context-based fallback inside the widget that
52
+ * reads them, so DELIVERING one can change behaviour where before it could not
53
+ * arrive at all. The rule is stated once, here, and this function implements it
54
+ * by doing nothing: it forwards what the host passed and resolves NOTHING.
55
+ * Each key's precedence stays where its single reader already implements and
56
+ * documents it —
57
+ *
58
+ * - `dataSource`: **the explicit prop WINS** over `SchemaRendererContext`.
59
+ * `LookupField` already resolves exactly that order and says so on the line
60
+ * that does it: "explicit prop > field-level > wrapper field >
61
+ * SchemaRendererContext > none". Hosts that pass nothing keep reading the
62
+ * context, so the grid's inline editor is unaffected.
63
+ * - `dependentValues`: the explicit prop is the ONLY channel that can carry a
64
+ * record — the host supplies it, and there is NO context fallback. Both
65
+ * readers (`useCascadingOptions`, and `LookupField`'s own resolver) used to
66
+ * spell a `?? ctx.formValues ?? ctx.data` tail after it, but
67
+ * `SchemaRendererContextType` declares exactly `dataSource` / `debug` /
68
+ * `debugFlags` / `apiFetch` — neither member exists, so that tail was
69
+ * unconditionally `{}` and no host could change it. It was retired under
70
+ * ADR-0049 enforce-or-remove (objectui#7206). Unlike `dataSource` above,
71
+ * DELIVERING this key cannot displace a context value, because there has
72
+ * never been one to displace.
73
+ * - `dependsOn`: the FIELD METADATA wins over the prop — the one documented
74
+ * inversion, stated on the key's own doc comment and implemented as
75
+ * `field?.dependsOn ?? dependsOnProp` in all four option widgets.
76
+ * - `emptyHint`: the host's value wins when supplied (objectui#3231).
77
+ * - `onCreateNew`: the prop wins over `field.onCreateNew`; `onSelectRecord`
78
+ * has no metadata carrier at all, so the prop is its ONLY one.
79
+ *
80
+ * Resolving any of that HERE would give each key a second author — the
81
+ * `field || schema` shape objectui#3233 spent a release removing. One key, one
82
+ * resolver, in the widget that reads it.
83
+ */
84
+ declare const HOST_PLUMBING_KEYS: readonly ["error", "onUploadingChange", "dataSource", "dependentValues", "dependsOn", "dependsOnLabels", "emptyHint", "onSelectRecord", "onCreateNew"];
85
+ type HostPlumbingKey = (typeof HOST_PLUMBING_KEYS)[number];
86
+ /** The subset of `P` this helper forwards, with each key's declared type. */
87
+ export type HostProps<P> = Pick<P, Extract<keyof P, HostPlumbingKey>>;
88
+ /**
89
+ * Keep only the declared NON-DOM keys — the host plumbing a widget interprets
90
+ * as component props — and drop everything else.
91
+ *
92
+ * The companion to `toDomProps`, and used the same way. A host that passes
93
+ * nothing gets an empty object, so the widget's prop set is unchanged:
94
+ *
95
+ * ```tsx
96
+ * <Widget {...toDomProps(props)} {...toHostProps(props)} field={field} … />
97
+ * ```
98
+ *
99
+ * Iterating the props (rather than the key list) mirrors `pickDomProps`, so a
100
+ * key the host did not mention stays ABSENT rather than arriving as
101
+ * `undefined` — which keeps the factory-boundary prop set readable as "what the
102
+ * host actually supplied".
103
+ */
104
+ export declare function toHostProps<P extends object>(props: P): HostProps<P>;
105
+ export {};
@@ -150,16 +150,21 @@ export type FieldWidgetComponentProps<T = any> = {
150
150
  */
151
151
  error?: string;
152
152
  /**
153
- * Upload widgets (`file`/`image`) fire this when their in-progress state
153
+ * Upload widgets (`file`/`image`/`avatar`) fire this when their in-progress state
154
154
  * flips, so a host can block submit until a presigned upload settles. Other
155
155
  * widgets ignore it.
156
156
  */
157
157
  onUploadingChange?: (uploading: boolean) => void;
158
158
  /**
159
159
  * DataSource for widgets that query records (lookup / user / object-ref /
160
- * recipient-picker / grid). Injected by the form renderer for the field
161
- * types that need it, and passed directly by inline-edit hosts. Option
162
- * widgets destructure it purely to keep it off their DOM spread.
160
+ * recipient-picker). Injected by the form renderer for the field types that
161
+ * need it, and passed directly by inline-edit hosts. Option widgets
162
+ * destructure it purely to keep it off their DOM spread.
163
+ *
164
+ * `grid` was listed here with zero consumers (objectui#4814): `GridField.tsx`
165
+ * never reads `dataSource`, and no data-source table has ever contained the
166
+ * key, so the claim described a wiring that did not exist. `owner` is absent
167
+ * for a different reason — the spelling itself is retired (same card).
163
168
  *
164
169
  * Left structural (`unknown`): `@object-ui/fields` must not depend on a
165
170
  * concrete adapter — every consumer narrows it itself.
@@ -168,7 +173,15 @@ export type FieldWidgetComponentProps<T = any> = {
168
173
  /**
169
174
  * Live sibling-field values driving cascading / role-gated options and
170
175
  * dependent lookups (ADR-0058, #2215/#2284). The form renderer passes the
171
- * in-progress record; widgets fall back to `SchemaRendererContext`.
176
+ * in-progress record.
177
+ *
178
+ * ⚠️ This used to add "widgets fall back to `SchemaRendererContext`". They
179
+ * never could: they spelled such a fallback (`?? ctx.formValues ?? ctx.data`)
180
+ * while `SchemaRendererContextType` declares exactly `dataSource` / `debug` /
181
+ * `debugFlags` / `apiFetch`, so neither member could be supplied by any host
182
+ * and the fallback was unconditionally empty. Those reads were retired under
183
+ * ADR-0049 enforce-or-remove (objectui#7206) — this prop is the one channel
184
+ * that carries a record, and a widget reached without it gates.
172
185
  */
173
186
  dependentValues?: Record<string, unknown>;
174
187
  /**
@@ -182,7 +195,7 @@ export type FieldWidgetComponentProps<T = any> = {
182
195
  * sibling field in user-visible copy (the dependent-lookup gate hint).
183
196
  *
184
197
  * Only the form renderer knows a field's label — the widget sees its own
185
- * metadata and a `depends_on` list of API names. Without this map the gate
198
+ * metadata and a `dependsOn` list of API names. Without this map the gate
186
199
  * sentence interpolated the raw API name into every locale, `en` included
187
200
  * ("Select crm_account first"), which is an internal identifier leaking into
188
201
  * the UI, not merely an untranslated word (objectstack#5407). This is the
@@ -0,0 +1,6 @@
1
+ import { TranslateFn } from '@object-ui/i18n';
2
+ /**
3
+ * The translated error row for a pick named `name` whose upload ended in
4
+ * `err`, either kind.
5
+ */
6
+ export declare function uploadErrorMessage(t: TranslateFn, name: string, err: unknown): string;