@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
@@ -1,338 +0,0 @@
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
- /**
10
- * GATE: every registered field widget must announce a validation failure to
11
- * assistive tech — `aria-invalid="true"` on some element inside its own form
12
- * row (objectui#3306, the paradigm of objectui#3291's leak sweep).
13
- *
14
- * ## Why a registry-wide sweep
15
- *
16
- * This failure class recurs per widget: objectui#3222 fixed seven widgets that
17
- * computed `aria-invalid` from a prop nobody produced; objectui#3290 fixed the
18
- * `aria-required` state channel; objectui#3306 fixed `select`, whose Radix
19
- * `Select.Root` — not being a DOM element — silently swallowed every `aria-*`
20
- * the form renderer delivered. Each was invisible until someone happened to
21
- * audit that one widget, because the per-widget tests only cover the widgets
22
- * someone already fixed. This sweep covers the REGISTRY, so the next widget
23
- * with a swallowed or missing `aria-invalid` fails here by name instead of
24
- * shipping.
25
- *
26
- * ## The ratchet
27
- *
28
- * Widgets listed in {@link NOT_YET_DELIVERED} are known not to deliver the
29
- * attribute today. They are asserted in the OPPOSITE direction — the sweep
30
- * proves they still do NOT deliver — so fixing one turns its entry red and
31
- * forces its removal from the ledger. The ledger can only shrink; a widget can
32
- * never silently regress INTO it, and a fix can never silently go unrecorded.
33
- * The remaining entries are tracked as follow-up work (see the ledger's doc
34
- * comment); this file is the source of truth for what is left.
35
- *
36
- * ## Discipline inherited from the #3291 sweep
37
- *
38
- * - **An error variant that produces no error tests nothing** — every case
39
- * asserts the "is required" message rendered before it reads any aria. The
40
- * `required` check is a PRESENCE check (`null` is missing, `false`/`0` are
41
- * values — cloud#972), so `null` drives a real failure for every type.
42
- * - **Popovers are not opened** (happy-dom lacks the pointer-capture APIs
43
- * Radix needs); the inline control every widget renders is the surface under
44
- * test, which is exactly where `aria-invalid` must sit.
45
- * - Widgets are registered from STATIC imports wrapped in `withFieldCarrier`
46
- * (the real registration seam, objectui#3233), never `registerAllFields()`,
47
- * whose `React.lazy` loaders are this repo's known flake generator
48
- * (AGENTS.md 测试纪律 / objectui#3010).
49
- */
50
-
51
- import type { ComponentType } from 'react';
52
- import { describe, it, expect, beforeAll, beforeEach, afterEach, vi } from 'vitest';
53
- import { render, screen, fireEvent, waitFor, cleanup } from '@testing-library/react';
54
- import '@testing-library/jest-dom';
55
- import { ComponentRegistry } from '@object-ui/core';
56
- // Module scope: pulls in the form renderer's registration side effect.
57
- import '@object-ui/components';
58
-
59
- import { withFieldCarrier } from '../withFieldCarrier';
60
- import { FORM_FIELD_TYPES } from '../index';
61
-
62
- import { TextField } from '../widgets/TextField';
63
- import { TextAreaField } from '../widgets/TextAreaField';
64
- import { NumberField } from '../widgets/NumberField';
65
- import { BooleanField } from '../widgets/BooleanField';
66
- import { SelectField } from '../widgets/SelectField';
67
- import { DateField } from '../widgets/DateField';
68
- import { DateTimeField } from '../widgets/DateTimeField';
69
- import { TimeField } from '../widgets/TimeField';
70
- import { EmailField } from '../widgets/EmailField';
71
- import { PhoneField } from '../widgets/PhoneField';
72
- import { UrlField } from '../widgets/UrlField';
73
- import { MultiSelectField } from '../widgets/MultiSelectField';
74
- import { RadioField } from '../widgets/RadioField';
75
- import { CheckboxesField } from '../widgets/CheckboxesField';
76
- import { TagsField } from '../widgets/TagsField';
77
- import { CurrencyField } from '../widgets/CurrencyField';
78
- import { PercentField } from '../widgets/PercentField';
79
- import { PasswordField } from '../widgets/PasswordField';
80
- import { RichTextField } from '../widgets/RichTextField';
81
- import { LookupField } from '../widgets/LookupField';
82
- import { FileField } from '../widgets/FileField';
83
- import { ImageField } from '../widgets/ImageField';
84
- import { LocationField } from '../widgets/LocationField';
85
- import { FormulaField } from '../widgets/FormulaField';
86
- import { SummaryField } from '../widgets/SummaryField';
87
- import { AutoNumberField } from '../widgets/AutoNumberField';
88
- import { UserField } from '../widgets/UserField';
89
- import { ObjectField } from '../widgets/ObjectField';
90
- import { VectorField } from '../widgets/VectorField';
91
- import { GridField } from '../widgets/GridField';
92
- import { ColorField } from '../widgets/ColorField';
93
- import { SliderField } from '../widgets/SliderField';
94
- import { RatingField } from '../widgets/RatingField';
95
- import { CodeField } from '../widgets/CodeField';
96
- import { AvatarField } from '../widgets/AvatarField';
97
- import { AddressField } from '../widgets/AddressField';
98
- import { GeolocationField } from '../widgets/GeolocationField';
99
- import { SignatureField } from '../widgets/SignatureField';
100
- import { QRCodeField } from '../widgets/QRCodeField';
101
- import { ObjectRefField } from '../widgets/ObjectRefField';
102
- import { FilterConditionField } from '../widgets/FilterConditionField';
103
- import { RecipientPickerField } from '../widgets/RecipientPickerField';
104
-
105
- /** Same widget map as the #3291 sweep, so the parity check covers the registry. */
106
- const WIDGETS: Record<string, ComponentType<any>> = {
107
- text: TextField,
108
- textarea: TextAreaField,
109
- number: NumberField,
110
- boolean: BooleanField,
111
- select: SelectField,
112
- date: DateField,
113
- datetime: DateTimeField,
114
- time: TimeField,
115
- email: EmailField,
116
- phone: PhoneField,
117
- url: UrlField,
118
- multiselect: MultiSelectField,
119
- radio: RadioField,
120
- checkboxes: CheckboxesField,
121
- tags: TagsField,
122
- currency: CurrencyField,
123
- percent: PercentField,
124
- password: PasswordField,
125
- markdown: RichTextField,
126
- html: RichTextField,
127
- richtext: RichTextField,
128
- lookup: LookupField,
129
- master_detail: LookupField,
130
- file: FileField,
131
- image: ImageField,
132
- location: LocationField,
133
- formula: FormulaField,
134
- summary: SummaryField,
135
- auto_number: AutoNumberField,
136
- user: UserField,
137
- owner: UserField,
138
- object: ObjectField,
139
- vector: VectorField,
140
- grid: GridField,
141
- color: ColorField,
142
- slider: SliderField,
143
- rating: RatingField,
144
- code: CodeField,
145
- avatar: AvatarField,
146
- address: AddressField,
147
- geolocation: GeolocationField,
148
- signature: SignatureField,
149
- qrcode: QRCodeField,
150
- 'object-ref': ObjectRefField,
151
- 'filter-condition': FilterConditionField,
152
- 'recipient-picker': RecipientPickerField,
153
- };
154
-
155
- /**
156
- * THE RATCHET LEDGER — widget types MEASURED (by this very sweep, run with an
157
- * empty ledger) not to put `aria-invalid` on any element of their row after a
158
- * real validation failure. Every entry is a known accessibility gap tracked in
159
- * objectui#3318, not an accepted state: the field shows its red message while
160
- * assistive tech is told nothing.
161
- *
162
- * The objectui#3318 delivery batch cleared 20 of the original 29 entries with
163
- * the objectui#3222/#3306 pattern. What remains cannot take that pattern
164
- * as-is; each entry names why, so the follow-up starts from the blocker and
165
- * not from a re-audit:
166
- *
167
- * - `formula` / `summary` / `auto_number` / `vector` — read-only computed /
168
- * display widgets: they render static text and NO focusable control, so
169
- * there is no element assistive tech would read the state from (marking the
170
- * text span would be exactly the non-focusable-wrapper move this ledger's
171
- * rules forbid). A `required` failure on a non-editable field is a metadata
172
- * authoring problem more than a widget one.
173
- * - `grid` — composite line-item editor with its own per-CELL `aria-invalid`
174
- * for line validation; driving it from a FORM-level failure needs a design
175
- * (which cell? the add-row button?) rather than a spread.
176
- * - `slider` — the focusable thumb (`role="slider"`, the ARIA-designated
177
- * carrier) is rendered inside the synced shadcn `ui/slider.tsx` (a #7
178
- * no-touch file), which forwards arbitrary props only to its non-focusable
179
- * Root span; delivering needs a components-level change.
180
- * - `signature` — the drawing `<canvas>` is not focusable at all (no keyboard
181
- * input path exists); the only focusable element is the auxiliary Clear
182
- * button. Needs a real a11y design, not an attribute.
183
- * - `filter-condition` / `recipient-picker` — dependency-gated: with no
184
- * sibling `object_name` / `recipient_type` chosen (the state a fresh form
185
- * and THIS SWEEP render) they show a hint paragraph with no focusable
186
- * control. Their editable states DO deliver `aria-invalid` since #3318's
187
- * delivery batch.
188
- *
189
- * Do NOT add to this list to make a new widget pass; fix the widget (the
190
- * objectui#3222/#3306 pattern: spread `toDomProps(props)` onto the real
191
- * focusable control — never a non-DOM Radix Root — then `aria-invalid=
192
- * {!!error}` after the spread). An entry is removed by fixing the widget: the
193
- * sweep asserts each entry still fails to deliver, so a fixed widget turns
194
- * its own ledger row red until it is removed here. The ledger only shrinks.
195
- */
196
- const NOT_YET_DELIVERED: ReadonlySet<string> = new Set([
197
- 'formula',
198
- 'summary',
199
- 'auto_number',
200
- 'vector',
201
- 'grid',
202
- 'slider',
203
- 'signature',
204
- 'filter-condition',
205
- 'recipient-picker',
206
- ]);
207
-
208
- /** Option widgets render an "unfillable" placeholder unless offered a list. */
209
- const OPTION_TYPES = new Set(['select', 'multiselect', 'radio', 'checkboxes', 'tags']);
210
- const OPTIONS = [
211
- { label: 'Alpha', value: 'alpha' },
212
- { label: 'Beta', value: 'beta' },
213
- ];
214
-
215
- /** `null` is MISSING for the presence-check `required` (cloud#972). */
216
- const MISSING_VALUE = null;
217
-
218
- function fieldConfig(type: string) {
219
- return {
220
- name: 'f',
221
- label: 'F',
222
- type: `field:${type}`,
223
- required: true,
224
- ...(OPTION_TYPES.has(type) ? { options: OPTIONS } : {}),
225
- };
226
- }
227
-
228
- function renderForm(field: Record<string, unknown>) {
229
- const Form = ComponentRegistry.get('form')!;
230
- return render(
231
- <Form
232
- schema={{
233
- type: 'form',
234
- mode: 'create',
235
- showSubmit: true,
236
- showCancel: false,
237
- submitLabel: 'Create',
238
- defaultValues: { f: MISSING_VALUE },
239
- fields: [field],
240
- onSubmit: () => {},
241
- }}
242
- />,
243
- );
244
- }
245
-
246
- async function formRow(): Promise<Element> {
247
- return waitFor(() => {
248
- const row = document.querySelector('[data-field="f"]');
249
- if (!row) throw new Error('field row never rendered');
250
- return row;
251
- });
252
- }
253
-
254
- /**
255
- * Drive a REAL validation failure and prove it rendered before reading aria:
256
- * submit, then wait for the "is required" message inside the row.
257
- */
258
- async function failValidation(): Promise<Element> {
259
- fireEvent.click(screen.getByRole('button', { name: /create/i }));
260
- await waitFor(() => {
261
- expect(document.querySelector('[data-field="f"]')?.textContent ?? '').toContain(
262
- 'is required',
263
- );
264
- });
265
- return formRow();
266
- }
267
-
268
- beforeAll(() => {
269
- for (const [type, Widget] of Object.entries(WIDGETS)) {
270
- ComponentRegistry.register(type, withFieldCarrier(Widget) as any, {
271
- namespace: 'field',
272
- skipFallback: true,
273
- });
274
- }
275
- }, 60000);
276
-
277
- beforeEach(() => {
278
- if (!(Element.prototype as any).scrollIntoView) {
279
- (Element.prototype as any).scrollIntoView = () => {};
280
- }
281
- });
282
-
283
- afterEach(() => {
284
- cleanup();
285
- vi.restoreAllMocks();
286
- });
287
-
288
- const ALL_TYPES = Object.keys(WIDGETS);
289
- const DELIVERING = ALL_TYPES.filter((type) => !NOT_YET_DELIVERED.has(type));
290
- const LEDGERED = ALL_TYPES.filter((type) => NOT_YET_DELIVERED.has(type));
291
-
292
- describe('every registered field widget announces a failed validation (objectui#3306)', () => {
293
- it('covers every field type the form can render', () => {
294
- expect(Object.keys(WIDGETS).sort()).toEqual([...FORM_FIELD_TYPES].sort());
295
- });
296
-
297
- it('the ledger only names types that exist', () => {
298
- // A renamed/removed widget must not leave a stale ledger entry that would
299
- // silently assert against nothing.
300
- expect([...NOT_YET_DELIVERED].filter((type) => !(type in WIDGETS))).toEqual([]);
301
- });
302
-
303
- it.each(DELIVERING)(
304
- 'field:%s — carries aria-invalid inside its row after a real failure',
305
- async (type) => {
306
- renderForm(fieldConfig(type));
307
- const row = await formRow();
308
-
309
- // No premature alarm: a widget must not claim invalid before validation
310
- // ran. (`aria-invalid="false"` is fine and expected — asserting on the
311
- // "true" value keeps this half meaningful for widgets that stay silent
312
- // until they fail.)
313
- expect(row.querySelector('[aria-invalid="true"]')).toBeNull();
314
-
315
- const after = await failValidation();
316
- expect(
317
- after.querySelector('[aria-invalid="true"]'),
318
- `field:${type} rendered its "is required" message but no element in its row carries aria-invalid="true" — assistive tech is never told the field failed`,
319
- ).not.toBeNull();
320
- },
321
- );
322
-
323
- it.each(LEDGERED)(
324
- 'field:%s — KNOWN GAP: still does not deliver aria-invalid (fix it, then remove it from NOT_YET_DELIVERED)',
325
- async (type) => {
326
- renderForm(fieldConfig(type));
327
- await formRow();
328
-
329
- const after = await failValidation();
330
- // The ratchet: the day this widget starts delivering, this assertion
331
- // fails and the entry MUST be removed from the ledger above.
332
- expect(
333
- after.querySelector('[aria-invalid="true"]'),
334
- `field:${type} now delivers aria-invalid — remove it from NOT_YET_DELIVERED so the sweep guards it forward`,
335
- ).toBeNull();
336
- },
337
- );
338
- });