@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
package/README.md CHANGED
@@ -5,7 +5,7 @@ The standard field library and registry for Object UI.
5
5
  ## Features
6
6
 
7
7
  - 📚 **Standard Fields** - Implementation of all ObjectStack protocol fields (Text, Number, Date, Lookup, etc.)
8
- - 🔌 **Plugin System** - `FieldRegistry` allows registering custom renderers or overriding standard ones.
8
+ - 🔌 **Plugin System** - `registerFieldRenderer` registers custom renderers, or overrides standard ones.
9
9
  - 🛠 **Helpers** - Utilities for schema mapping, validation, and expression evaluation.
10
10
 
11
11
  ## Installation
@@ -23,10 +23,15 @@ The Field Registry is the core mechanism that allows decoupling view components
23
23
  You can override standard fields or add new ones:
24
24
 
25
25
  ```tsx
26
- import { registerFieldRenderer } from '@object-ui/fields';
27
- import { MyCustomColorPicker } from './MyCustomColorPicker';
26
+ import { registerFieldRenderer, type CellRendererProps } from '@object-ui/fields';
27
+ import type { FC } from 'react';
28
28
 
29
- // Register a new 'color' field type
29
+ // Your own renderer — anywhere in your app; it takes the standard props bag.
30
+ declare const MyCustomColorPicker: FC<CellRendererProps>;
31
+
32
+ // ⚠️ `color` is a SHIPPED type (`ColorSwatchCellRenderer`), and `getCellRenderer`
33
+ // reads the registry BEFORE the standard map — so this call OVERRIDES the
34
+ // built-in renderer. To ADD a type instead, register a name nothing ships.
30
35
  registerFieldRenderer('color', MyCustomColorPicker);
31
36
  ```
32
37
 
@@ -35,10 +40,16 @@ registerFieldRenderer('color', MyCustomColorPicker);
35
40
  View components use `getCellRenderer` to resolve the correct component for a field type.
36
41
 
37
42
  ```tsx
38
- import { getCellRenderer } from '@object-ui/fields';
39
-
40
- const MyGridCell = ({ field, value }) => {
41
- const Renderer = getCellRenderer(field.type);
43
+ import { getCellRenderer, resolveCellRendererType, type CellRendererProps } from '@object-ui/fields';
44
+
45
+ const MyGridCell = ({ field, value }: CellRendererProps) => {
46
+ // Resolve the renderer KEY first, then the renderer. A field's declared
47
+ // `type` is not always the renderer's key: a textual field carrying a
48
+ // format hint (`Field.text({ format: 'phone' })`) resolves to the richer
49
+ // renderer. Passing `field.type` raw skips that mapping and draws such a
50
+ // column as bare text, silently. Resolving first is never worse: with no
51
+ // format hint the resolver returns the declared `type` unchanged.
52
+ const Renderer = getCellRenderer(resolveCellRendererType(field));
42
53
  return <Renderer field={field} value={value} />;
43
54
  };
44
55
  ```
@@ -55,6 +66,33 @@ Supported types out of the box:
55
66
  - **Media**: `file`, `image`
56
67
  - **System**: `formula`, `summary`, `auto_number`
57
68
 
69
+ ### `type="number"` widgets: what is announced and what is not
70
+
71
+ `NumberField`, `CurrencyField`, `PercentField` and `GeolocationField` all render
72
+ a native `type="number"` input, so the **browser** decides what the box accepts.
73
+ They share one reading of that, in `widgets/numberBadInput.tsx`:
74
+
75
+ - **Announced.** When the browser reports `validity.badInput` — the box is
76
+ holding text it cannot convert, e.g. a typed `1e`, which Chromium keeps
77
+ DISPLAYING while `.value` reads `''` — the control is marked `aria-invalid`
78
+ and draws a `Not saved: …` message, reusing objectui#6716's refusal shape.
79
+ Both a change arm and a blur arm are wired, because pasting into an empty box
80
+ never moves `.value` and so fires no React change event at all. The sentence
81
+ resolves through the `fields.number.badInput` locale key (objectui#8148), so
82
+ it follows the reader's language; the example numeral each box quotes is the
83
+ widget's own and is interpolated rather than translated.
84
+ - ⚠️ **Not announced, and not announceable.** Entries the browser silently
85
+ **truncates**: `1.2.3` stores `1.23`, `0x10` stores `10`. The characters are
86
+ discarded as they arrive, before any handler here runs, so no widget-side
87
+ guard can refuse them. Recovering them would mean abandoning `type="number"`
88
+ and with it the mobile numeric keyboard and the `min`/`max`/`step` spinner
89
+ (objectui#2572).
90
+
91
+ ⛔ Silence therefore means "the browser read *something*", never "the value is
92
+ correct". User-facing wording lives in
93
+ [the fields guide](../../content/docs/guide/fields.md). The measured browser vs
94
+ happy-dom matrix is in `src/__tests__/numberInputBrowserReadings.ts`.
95
+
58
96
  ### Rendering form field widgets outside the form
59
97
 
60
98
  The full widget surface is exported for consumers that render field widgets
@@ -72,6 +110,64 @@ The app-shell `ActionParamDialog` uses these to render declared action params
72
110
  through the exact same widgets as the object form — with a drift test pinning
73
111
  param support ⊇ form support.
74
112
 
113
+ ### Enumerating the CELL renderer registry
114
+
115
+ `listCellRendererTypes()` is the read-side twin of `FORM_FIELD_TYPES`: every
116
+ field type `getCellRenderer` resolves to a renderer **of its own**, as opposed
117
+ to the `TextCellRenderer` fallback every other spelling lands on.
118
+
119
+ ⚠️ It is a **function**, not a frozen constant, and the difference is
120
+ load-bearing. `registerFieldRenderer` is published, so the cell registry can
121
+ grow after this module is evaluated; a constant would be a snapshot taken at
122
+ import time. Consumers that reason about "all registered cell types" — the two
123
+ censuses in `@object-ui/fields` and `@object-ui/plugin-detail` that measure what
124
+ every type draws — reconcile their tables against this reading so a newly
125
+ registered type fails them by name instead of slipping past a frozen
126
+ population (objectui#8734).
127
+
128
+ ### Asking whether a cell is masked
129
+
130
+ `isMaskedFieldType(type)` answers whether a cell of that field type is drawn
131
+ as a mask (`••••••`) instead of its value. It is the read-side twin of
132
+ `isInlineExcludedFieldType()`, and a consumer that acts on a cell's value asks
133
+ it rather than keeping its own list. The detail page's copy affordance in
134
+ `@object-ui/plugin-detail` is one such consumer: it refuses to copy a masked
135
+ row (objectui#8686). `MASKED_FIELD_TYPES` is the declared set behind it,
136
+ `password` and `secret`. The standard cell of each member draws the mask, and
137
+ the predicate answers `true` for it. Like `listCellRendererTypes()`, the
138
+ answer is a live reading of the cell registry taken when you call it, and,
139
+ with nothing overridden at runtime, it agrees with what `getCellRenderer`
140
+ resolves for every type that function lists. To add a masked type, register
141
+ the package's own mask under it with
142
+ `registerFieldRenderer('api_token', getCellRenderer('password'))`, while
143
+ `password` still resolves to the shipped mask. The predicate then answers
144
+ `true` with no change to the declared set, and the detail page refuses to copy
145
+ that row. Overriding a declared type with one of this package's own renderers
146
+ makes the predicate answer `false`, since none of them is the mask, and the
147
+ cell draws what that renderer draws: after
148
+ `registerFieldRenderer('password', TextCellRenderer)` the cell shows the value
149
+ and the detail row copies again. Overriding a declared type with a component
150
+ of your own keeps the declared answer, `true`, because the package cannot
151
+ tell whether an opaque component hides the value, so it errs toward
152
+ withholding it. Your own component under an undeclared type answers `false`.
153
+ Spellings are matched raw, the way `getCellRenderer` looks them up:
154
+ `field:password` renders in the clear and is not masked.
155
+
156
+ The mask itself is exported as `MaskedCellRenderer` (objectui#10657). A table
157
+ that cannot type a column yet, because its object's field types are still
158
+ loading or their read failed, draws that column WITHHELD with it rather than as
159
+ text: `object-grid`, the related list and `object-data-table` do. It is the
160
+ shipped mask, not whatever is registered for `password` at the time, and its
161
+ `field` prop is optional because it reads only whether a value is present.
162
+ `registerFieldRenderer('api_token', MaskedCellRenderer)` masks a type the same
163
+ way the `getCellRenderer('password')` form above does.
164
+
165
+ ```tsx
166
+ import { MaskedCellRenderer } from '@object-ui/fields';
167
+
168
+ const withheldCell = (value: unknown) => <MaskedCellRenderer value={value} />;
169
+ ```
170
+
75
171
  ### File uploads in line-item grids
76
172
 
77
173
  `GridField` (the master-detail line-items grid) supports `type: 'file'` columns:
@@ -111,6 +207,10 @@ never drift). While a `dependsOn` parent is empty the control is gated; a parent
111
207
  change re-filters the list and clears a now-invalid value (scalar `select` /
112
208
  `radio` drop the value; multi-value `multiselect` / `checkboxes` prune just the
113
209
  offered-out entries).
210
+ An option whose `label` is blank (the empty string is a legal label; an absent one is
211
+ not) displays its `value` instead of an empty row, on the editable and the read-only
212
+ path alike — all eight read sites across the four widgets share the one
213
+ `optionDisplayLabel` helper in `@object-ui/core` (objectui#9230).
114
214
  Client-side hiding is UX only — gate authorization-sensitive values on the
115
215
  server too. See
116
216
  [`content/docs/fields/select.mdx`](../../content/docs/fields/select.mdx).
@@ -1,5 +1,5 @@
1
1
  import { default as React } from 'react';
2
- import { FieldWidgetComponentProps } from './widgets/types';
2
+ import { FieldWidgetComponentProps } from './widgets/types.js';
3
3
  /**
4
4
  * Form field types that are deliberately NOT given an inline editor — every
5
5
  * other form widget type must be in {@link EDIT_WIDGETS}. This pairing is
@@ -10,7 +10,21 @@ import { FieldWidgetComponentProps } from './widgets/types';
10
10
  export declare const INLINE_EXCLUDED_FIELD_TYPES: Set<string>;
11
11
  /** Field types whose value is chosen in one discrete gesture (no free typing). */
12
12
  export declare const DISCRETE_EDIT_TYPES: Set<string>;
13
- /** True when a field type has a dedicated in-place edit widget. */
13
+ /**
14
+ * True when a field type has a dedicated in-place edit widget.
15
+ *
16
+ * A RETIRED spelling answers `false` unconditionally, ahead of every table
17
+ * lookup (objectui#4931). This is the gate the retirement was missing: hosts
18
+ * ask this question to decide whether to hand a cell to {@link FieldEditWidget},
19
+ * and while it answered `true` for a retired spelling the whole tombstone was
20
+ * bypassable by delegation — the grid's inline cell editor rendered a working
21
+ * person picker for a field the record form refuses.
22
+ *
23
+ * Keyed on the retirement TABLE rather than on the raw `type`, and checked
24
+ * BEFORE `resolveInlineEditType`, so it holds even if a future retired spelling
25
+ * is still (or again) a key of {@link EDIT_WIDGETS} — closing the class, not
26
+ * one spelling.
27
+ */
14
28
  export declare function hasFieldEditWidget(type: string | undefined): boolean;
15
29
  /**
16
30
  * True when a field type is deliberately NOT inline-editable (alias-aware:
@@ -25,10 +39,58 @@ export declare function isInlineExcludedFieldType(type: string | undefined): boo
25
39
  * `null` for types without a registered widget so the caller can fall back to
26
40
  * a plain editor.
27
41
  *
28
- * `autoFocus` is forwarded because an inline-edit host enters edit mode ON a
29
- * field and expects the caret to land there (objectui#4220 — the detail page's
30
- * delegation): each widget's own `toDomProps` whitelist already carries it onto
31
- * the real focusable control, so nothing here needs to know which element that
32
- * is. A host that passes nothing is unaffected.
42
+ * The host's DOM pass-through set is forwarded WHOLE (objectui#6909). This
43
+ * component's props are `FieldWidgetComponentProps`, so a caller may already
44
+ * pass `id`, `name`, `autoFocus`, `tabIndex`, `onBlur`, `onFocus`, `onClick`,
45
+ * any `aria-*` and any `data-*` with no type error — but the body used to
46
+ * destructure five keys and render the widget with those, so `autoFocus` was
47
+ * the ONLY survivor of the whole block and everything else was silently
48
+ * dropped. That is this package's own first-class defect class, named in
49
+ * `widgets/toDomProps.ts`: a key that type-checks, reads as supported, and
50
+ * silently never reaches the element (objectui#3290's `aria-required`,
51
+ * objectui#3222's validation slot). Forwarding is not a widening: the keys were
52
+ * already declared, and each widget still re-filters through its own
53
+ * `toDomProps` before anything reaches a DOM element.
54
+ *
55
+ * `autoFocus`' original reason survives inside that set — an inline-edit host
56
+ * enters edit mode ON a field and expects the caret to land there
57
+ * (objectui#4220, the detail page's delegation) — and so does its property:
58
+ * each widget's own whitelist carries these onto the real focusable control, so
59
+ * nothing here needs to know which element that is. A host that passes nothing
60
+ * is unaffected.
61
+ *
62
+ * The host's NON-DOM set is forwarded WHOLE too (`f08bcd9af`), through the
63
+ * sibling executor `toHostProps`. The DOM fix left the other half of the
64
+ * contract undelivered: `error`, `onUploadingChange` and the "Host plumbing"
65
+ * block (`dataSource`, `dependentValues`, `dependsOn`, `dependsOnLabels`,
66
+ * `emptyHint`, `onSelectRecord`, `onCreateNew`) still type-checked, read as
67
+ * supported, and never reached the widget. `error` was the live one:
68
+ * `InlineFieldInput` has passed `error={error}` since PR #7109 and this factory
69
+ * dropped it, so a control that had failed validation never reported
70
+ * `aria-invalid`. Forwarding is not a widening — every one of those keys is
71
+ * already declared on `FieldWidgetComponentProps`, the same argument #7009
72
+ * landed on in this file.
73
+ *
74
+ * ⛔ They do NOT go through `toDomProps`. None of them is DOM-legal, and that
75
+ * whitelist is closed for exactly this reason — a `dataSource` adapter routed
76
+ * there becomes `dataSource="[object Object]"` on an `<input>`, the leak the
77
+ * helper exists to prevent. `toHostProps`' direction-3 assertion makes the two
78
+ * sets provably disjoint, so the order of the two spreads below is not a
79
+ * question anyone has to answer again.
80
+ *
81
+ * ## `dataSource` precedence: the explicit prop WINS
82
+ *
83
+ * Delivering `dataSource` can change behaviour where before it could not
84
+ * arrive, because the relational widgets fall back to `SchemaRendererContext`
85
+ * (which the grid already provides). The precedence is therefore STATED rather
86
+ * than left to emerge: **a host's explicit `dataSource` prop wins over the
87
+ * context**. That is not a new decision — `LookupField` already resolves
88
+ * `props.dataSource ?? lookupField?.dataSource ?? fieldMeta?.dataSource ??
89
+ * contextDataSource` and documents that order on the line that does it. This
90
+ * factory is a CONDUIT and resolves nothing: adding a resolution here would
91
+ * give `dataSource` a second author, the `field || schema` shape objectui#3233
92
+ * removed. A host that passes no `dataSource` keeps reading the context exactly
93
+ * as before, so no in-repo host changes behaviour. The full per-key precedence
94
+ * table lives on `toHostProps`, next to the list it governs.
33
95
  */
34
- export declare function FieldEditWidget({ field, value, onChange, readonly, autoFocus, }: FieldWidgetComponentProps<any>): React.ReactElement | null;
96
+ export declare function FieldEditWidget(props: FieldWidgetComponentProps<any>): React.ReactElement | null;
@@ -0,0 +1,54 @@
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
+ * `coerceToSafeValue` — the one answer this package has to "what text does a
10
+ * string-typed cell draw for a value that is not a string?".
11
+ *
12
+ * ## Why this is its own module (objectui#8580)
13
+ *
14
+ * It used to be defined in `./index.tsx`, the barrel, and every text-like cell
15
+ * renderer there reaches it. The rich-content renderers in
16
+ * `./widgets/richTextDisplay.tsx` need the SAME answer — `@objectstack/spec`
17
+ * places `markdown` / `html` / `richtext` in the same value class as `text` /
18
+ * `textarea` / `code` (`STRING_VALUE_TYPES`, "Value is a plain string"), and
19
+ * before objectui#8580 they had a private one: `String(value)`, which drew a
20
+ * childless container for `[]` and the literal `[object Object]` for `{}`
21
+ * where `text` draws the No-value affordance and `[Object]`. That module is
22
+ * imported BY the barrel (`4bb940b6e` extracted it so `RichTextField` could
23
+ * reach the display pipelines without importing the barrel back), so it
24
+ * cannot import the barrel to borrow this helper without re-creating exactly
25
+ * the cycle that extraction removed. The helper therefore lives below both
26
+ * consumers. The barrel re-exports it unchanged; it is part of this package's
27
+ * published surface and nothing about its contract moved.
28
+ */
29
+ /**
30
+ * Coerce a value to a safe primitive for rendering.
31
+ * Handles MongoDB wrapper types ($numberDecimal, $oid, $date), expanded
32
+ * reference objects, and arrays so that no raw object is ever passed as
33
+ * a React child — preventing React error #310.
34
+ *
35
+ * A STRING is returned verbatim, whatever its shape. This helper is reached by
36
+ * every text-like cell (`text`, `textarea`, `code`, `time`, `auto_number`,
37
+ * `qrcode` all register to `TextCellRenderer`), so it may not classify a value
38
+ * by looking at its characters. It used to: any string starting `{`/`[` and
39
+ * ending `}`/`]` was `JSON.parse`d and the result run through the
40
+ * reference-label extraction below, which answers `[Object]` for an object
41
+ * carrying no name/label/externalId/id. That turned the showcase Field Zoo's
42
+ * `code` value `{"ok": true}` into the literal `[Object]`, and `[1, 2, 3]` in a
43
+ * text field into `1, 2, 3` (objectui#7246).
44
+ *
45
+ * Shape is not a type. The reference case the parse was written for
46
+ * (objectui#1426 — an unresolved external-id ref arriving as
47
+ * '{"externalId":"Website Relaunch"}') belongs to reference-TYPED columns and
48
+ * is handled there: `LookupCellRenderer` carries its own JSON-string branch,
49
+ * which resolves the label through the referenced object's schema and links to
50
+ * the record — neither of which this type-blind helper could ever do. Scoped,
51
+ * not dropped; both halves are pinned in
52
+ * `__tests__/textCellJsonText-7246.test.tsx`.
53
+ */
54
+ export declare function coerceToSafeValue(value: unknown): string | number | boolean | null | undefined;
@@ -44,7 +44,7 @@ export declare function currencyFractionDigits(currency: string): number;
44
44
  * Falls back to `currency` itself — the code as given — in the three cases
45
45
  * where ICU cannot answer, because a bare code is a legitimate adornment (it is
46
46
  * what ICU itself returns for KWD/BHD/CHF/ISK, and what
47
- * `formatAmount`'s own bad-code branch renders beside it):
47
+ * `formatCurrency`'s own bad-code branch renders beside it):
48
48
  *
49
49
  * 1. an invalid currency code — `Intl` throws `RangeError: Invalid currency
50
50
  * code`;
@@ -19,6 +19,22 @@ export interface FieldTypeMappingConfig {
19
19
  /** `FieldSchema.multiple` — the field holds zero-or-more values. */
20
20
  multiple?: boolean | null;
21
21
  }
22
+ /**
23
+ * The retirement half of this module — the TOMBSTONE table
24
+ * {@link RETIRED_FIELD_TYPES}, the {@link isRetiredFieldType} gate, and the
25
+ * once-per-spelling reporter — now lives in `@object-ui/core`
26
+ * (`utils/retired-field-types.ts`), hoisted there by objectui#4914 so that
27
+ * `@object-ui/components`' filter builder can read the SAME table: `fields`
28
+ * depends on `components`, so the six ruled predicate faces could not all
29
+ * reach a table that lived here. The module's docblock carries the full
30
+ * reasoning, including why a second copy in `components` would have broken the
31
+ * ruling's "fires once" clause on day one.
32
+ *
33
+ * Re-exported from here, and from the `./index` barrel, so `@object-ui/fields`'
34
+ * published surface is exactly what it was plus the newly ruled gate. This file
35
+ * remains the retirement table's first consumer (the `field:text` tail below).
36
+ */
37
+ export { RETIRED_FIELD_TYPES, isRetiredFieldType, reportRetiredFieldType, resetRetiredFieldTypeReports, } from '@object-ui/core';
22
38
  /**
23
39
  * Map field type to form component type
24
40
  *