@object-ui/fields 17.3.0 → 17.6.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 (265) hide show
  1. package/CHANGELOG.md +1655 -0
  2. package/README.md +1 -12
  3. package/dist/FieldEditWidget.d.ts +23 -3
  4. package/dist/currency.d.ts +46 -0
  5. package/dist/field-type-alias.d.ts +35 -1
  6. package/dist/index.cjs +4 -4
  7. package/dist/index.css +651 -0
  8. package/dist/index.d.ts +474 -85
  9. package/dist/index.js +5418 -5023
  10. package/dist/widgets/AddressField.d.ts +4 -12
  11. package/dist/widgets/AutoNumberField.d.ts +1 -1
  12. package/dist/widgets/AvatarField.d.ts +1 -1
  13. package/dist/widgets/BooleanField.d.ts +1 -1
  14. package/dist/widgets/CapabilityMultiSelectField.d.ts +1 -1
  15. package/dist/widgets/CheckboxesField.d.ts +1 -1
  16. package/dist/widgets/CodeField.d.ts +1 -1
  17. package/dist/widgets/ColorField.d.ts +1 -1
  18. package/dist/widgets/CurrencyField.d.ts +1 -1
  19. package/dist/widgets/DateField.d.ts +1 -1
  20. package/dist/widgets/DateTimeField.d.ts +1 -1
  21. package/dist/widgets/EmailField.d.ts +1 -1
  22. package/dist/widgets/FileField.d.ts +18 -2
  23. package/dist/widgets/FilterConditionField.d.ts +28 -1
  24. package/dist/widgets/FormulaField.d.ts +1 -1
  25. package/dist/widgets/FullscreenFieldEditor.d.ts +49 -152
  26. package/dist/widgets/GeolocationField.d.ts +1 -1
  27. package/dist/widgets/GridField.d.ts +68 -6
  28. package/dist/widgets/ImageField.d.ts +1 -1
  29. package/dist/widgets/LocationField.d.ts +1 -1
  30. package/dist/widgets/LookupField.d.ts +1 -1
  31. package/dist/widgets/MasterDetailField.d.ts +1 -1
  32. package/dist/widgets/MultiSelectField.d.ts +1 -1
  33. package/dist/widgets/NumberField.d.ts +1 -1
  34. package/dist/widgets/ObjectField.d.ts +1 -1
  35. package/dist/widgets/ObjectRefField.d.ts +1 -1
  36. package/dist/widgets/OptionsEmptyState.d.ts +21 -1
  37. package/dist/widgets/PasswordField.d.ts +1 -1
  38. package/dist/widgets/PercentField.d.ts +1 -1
  39. package/dist/widgets/PhoneField.d.ts +1 -1
  40. package/dist/widgets/QRCodeField.d.ts +1 -1
  41. package/dist/widgets/RadioField.d.ts +1 -1
  42. package/dist/widgets/RatingField.d.ts +1 -1
  43. package/dist/widgets/RecipientPickerField.d.ts +1 -1
  44. package/dist/widgets/RecordPickerDialog.d.ts +34 -10
  45. package/dist/widgets/RichTextField.d.ts +37 -1
  46. package/dist/widgets/SelectField.d.ts +26 -6
  47. package/dist/widgets/SignatureField.d.ts +32 -2
  48. package/dist/widgets/SliderField.d.ts +37 -2
  49. package/dist/widgets/SummaryField.d.ts +1 -1
  50. package/dist/widgets/TagsField.d.ts +1 -1
  51. package/dist/widgets/TextAreaField.d.ts +6 -2
  52. package/dist/widgets/TextField.d.ts +1 -1
  53. package/dist/widgets/TimeField.d.ts +1 -1
  54. package/dist/widgets/UrlField.d.ts +1 -1
  55. package/dist/widgets/UserField.d.ts +1 -1
  56. package/dist/widgets/VectorField.d.ts +1 -1
  57. package/dist/widgets/_cell-renderer-bridge.d.ts +1 -1
  58. package/dist/widgets/address-format.d.ts +88 -0
  59. package/dist/widgets/file-size-guard.d.ts +41 -0
  60. package/dist/widgets/lookupColumnDisplay.d.ts +93 -0
  61. package/dist/widgets/nativeDateValue.d.ts +37 -0
  62. package/dist/widgets/richTextDisplay.d.ts +80 -0
  63. package/dist/widgets/toDomProps.d.ts +30 -10
  64. package/dist/widgets/toHostGroupProps.d.ts +189 -0
  65. package/dist/widgets/types.d.ts +8 -3
  66. package/dist/widgets/useRecordQuery.d.ts +7 -1
  67. package/package.json +17 -14
  68. package/dist/FieldEditWidget.test.d.ts +0 -8
  69. package/dist/__tests__/DateCellRenderer.test.d.ts +0 -0
  70. package/dist/__tests__/NumberCellRenderer.test.d.ts +0 -0
  71. package/dist/__tests__/cell-truncation.test.d.ts +0 -0
  72. package/dist/__tests__/field-carrier-sdui.test.d.ts +0 -0
  73. package/dist/__tests__/lookupCellDisplayField.test.d.ts +0 -0
  74. package/dist/__tests__/lookupCellDisplayName.test.d.ts +0 -0
  75. package/dist/__tests__/lookupCellReferenceKey.test.d.ts +0 -0
  76. package/dist/__tests__/registry-collision.test.d.ts +0 -0
  77. package/dist/__tests__/resolveFieldCurrency.test.d.ts +0 -1
  78. package/dist/__tests__/spec-symbol-batch7.test.d.ts +0 -8
  79. package/dist/__tests__/validation-feedback.test.d.ts +0 -0
  80. package/dist/__tests__/widget-aria-invalid-e2e.test.d.ts +0 -0
  81. package/dist/__tests__/widget-aria-invalid-registry-e2e.test.d.ts +0 -0
  82. package/dist/__tests__/widget-dom-leak-e2e.test.d.ts +0 -0
  83. package/dist/__tests__/widget-props-contract.test.d.ts +0 -8
  84. package/dist/coerce-safe-value.test.d.ts +0 -1
  85. package/dist/complex-widgets.test.d.ts +0 -1
  86. package/dist/datetime-widgets.test.d.ts +0 -1
  87. package/dist/field-type-coverage.test.d.ts +0 -1
  88. package/dist/standard-widgets.test.d.ts +0 -0
  89. package/dist/widgets/AddressField.uniqueIds.test.d.ts +0 -0
  90. package/dist/widgets/CapabilityMultiSelectField.test.d.ts +0 -0
  91. package/dist/widgets/CharacterCount.d.ts +0 -56
  92. package/dist/widgets/CheckboxesField.cascade.test.d.ts +0 -0
  93. package/dist/widgets/GeolocationField.uniqueIds.test.d.ts +0 -0
  94. package/dist/widgets/GridField.test.d.ts +0 -1
  95. package/dist/widgets/ImageLightbox.test.d.ts +0 -1
  96. package/dist/widgets/LookupField.dependsOn.test.d.ts +0 -8
  97. package/dist/widgets/LookupField.gateHintLabel.test.d.ts +0 -8
  98. package/dist/widgets/LookupField.hydration.test.d.ts +0 -1
  99. package/dist/widgets/LookupField.idField.test.d.ts +0 -1
  100. package/dist/widgets/MultiSelectField.cascade.test.d.ts +0 -0
  101. package/dist/widgets/OptionsEmptyState.no-provider.test.d.ts +0 -0
  102. package/dist/widgets/OptionsEmptyState.test.d.ts +0 -0
  103. package/dist/widgets/PeoplePicker.test.d.ts +0 -8
  104. package/dist/widgets/PeoplePickerComponents.test.d.ts +0 -8
  105. package/dist/widgets/RadioField.cascade.test.d.ts +0 -0
  106. package/dist/widgets/RecipientPickerField.test.d.ts +0 -0
  107. package/dist/widgets/RecordPickerDialog.filterOptions.test.d.ts +0 -8
  108. package/dist/widgets/RecordPickerDialog.selectLabel.test.d.ts +0 -8
  109. package/dist/widgets/SelectField.cascade.test.d.ts +0 -0
  110. package/dist/widgets/TagsField.placeholder.no-provider.test.d.ts +0 -0
  111. package/dist/widgets/TagsField.placeholder.test.d.ts +0 -0
  112. package/dist/widgets/UserFieldPicker.test.d.ts +0 -8
  113. package/dist/widgets/__tests__/FilterConditionField.matchAll.test.d.ts +0 -8
  114. package/dist/widgets/__tests__/FilterConditionField.operators.test.d.ts +0 -8
  115. package/dist/widgets/__tests__/FullscreenFieldEditor.i18n.test.d.ts +0 -0
  116. package/dist/widgets/__tests__/FullscreenFieldEditor.no-provider.test.d.ts +0 -0
  117. package/dist/widgets/__tests__/RichTextField.fullscreenDisabled.test.d.ts +0 -0
  118. package/dist/widgets/__tests__/RichTextField.mobileFullscreen.test.d.ts +0 -0
  119. package/dist/widgets/__tests__/TextAreaField.characterCount.announcements.test.d.ts +0 -0
  120. package/dist/widgets/__tests__/TextAreaField.characterCount.i18n.test.d.ts +0 -0
  121. package/dist/widgets/__tests__/TextAreaField.characterCount.no-provider.test.d.ts +0 -0
  122. package/dist/widgets/__tests__/TextAreaField.fullscreenCharacterCount.i18n.test.d.ts +0 -0
  123. package/dist/widgets/__tests__/TextAreaField.fullscreenCharacterCount.test.d.ts +0 -0
  124. package/dist/widgets/__tests__/TextAreaField.fullscreenDisabled.test.d.ts +0 -0
  125. package/dist/widgets/__tests__/TextAreaField.mobileFullscreen.test.d.ts +0 -0
  126. package/dist/widgets/deriveLookupColumns.test.d.ts +0 -1
  127. package/dist/widgets/file-value.test.d.ts +0 -1
  128. package/dist/widgets/personDisplay.test.d.ts +0 -8
  129. package/dist/widgets/recentLookups.test.d.ts +0 -1
  130. package/dist/widgets/useRecordQuery.test.d.ts +0 -8
  131. package/dist/widgets/useUploadingSignal.test.d.ts +0 -1
  132. package/src/FieldEditWidget.test.ts +0 -133
  133. package/src/FieldEditWidget.tsx +0 -191
  134. package/src/__tests__/DateCellRenderer.test.tsx +0 -73
  135. package/src/__tests__/NumberCellRenderer.test.tsx +0 -60
  136. package/src/__tests__/cell-truncation.test.tsx +0 -96
  137. package/src/__tests__/field-carrier-sdui.test.tsx +0 -169
  138. package/src/__tests__/lookupCellDisplayField.test.tsx +0 -107
  139. package/src/__tests__/lookupCellDisplayName.test.tsx +0 -119
  140. package/src/__tests__/lookupCellReferenceKey.test.tsx +0 -57
  141. package/src/__tests__/registry-collision.test.ts +0 -91
  142. package/src/__tests__/resolveFieldCurrency.test.ts +0 -20
  143. package/src/__tests__/spec-symbol-batch7.test.ts +0 -126
  144. package/src/__tests__/validation-feedback.test.tsx +0 -361
  145. package/src/__tests__/widget-aria-invalid-e2e.test.tsx +0 -265
  146. package/src/__tests__/widget-aria-invalid-registry-e2e.test.tsx +0 -338
  147. package/src/__tests__/widget-dom-leak-e2e.test.tsx +0 -598
  148. package/src/__tests__/widget-props-contract.test.tsx +0 -142
  149. package/src/coerce-safe-value.test.ts +0 -34
  150. package/src/complex-widgets.test.tsx +0 -908
  151. package/src/currency.ts +0 -14
  152. package/src/datetime-widgets.test.tsx +0 -93
  153. package/src/field-type-alias.ts +0 -100
  154. package/src/field-type-coverage.test.ts +0 -67
  155. package/src/index.tsx +0 -2503
  156. package/src/standard-widgets.test.tsx +0 -357
  157. package/src/widgets/AddressField.tsx +0 -132
  158. package/src/widgets/AddressField.uniqueIds.test.tsx +0 -92
  159. package/src/widgets/AutoNumberField.tsx +0 -18
  160. package/src/widgets/AvatarField.tsx +0 -117
  161. package/src/widgets/BooleanField.tsx +0 -52
  162. package/src/widgets/CapabilityMultiSelectField.test.tsx +0 -168
  163. package/src/widgets/CapabilityMultiSelectField.tsx +0 -229
  164. package/src/widgets/CharacterCount.tsx +0 -263
  165. package/src/widgets/CheckboxesField.cascade.test.tsx +0 -143
  166. package/src/widgets/CheckboxesField.tsx +0 -136
  167. package/src/widgets/CodeField.tsx +0 -41
  168. package/src/widgets/ColorField.tsx +0 -53
  169. package/src/widgets/CurrencyField.tsx +0 -95
  170. package/src/widgets/DateField.tsx +0 -31
  171. package/src/widgets/DateTimeField.tsx +0 -37
  172. package/src/widgets/EmailField.tsx +0 -37
  173. package/src/widgets/FileField.tsx +0 -494
  174. package/src/widgets/FilterConditionField.tsx +0 -454
  175. package/src/widgets/FormulaField.tsx +0 -33
  176. package/src/widgets/FullscreenFieldEditor.tsx +0 -271
  177. package/src/widgets/GeolocationField.tsx +0 -167
  178. package/src/widgets/GeolocationField.uniqueIds.test.tsx +0 -88
  179. package/src/widgets/GridField.test.tsx +0 -406
  180. package/src/widgets/GridField.tsx +0 -966
  181. package/src/widgets/ImageCropperDialog.tsx +0 -245
  182. package/src/widgets/ImageField.tsx +0 -255
  183. package/src/widgets/ImageLightbox.test.tsx +0 -52
  184. package/src/widgets/ImageLightbox.tsx +0 -108
  185. package/src/widgets/LocationField.tsx +0 -57
  186. package/src/widgets/LookupField.dependsOn.test.tsx +0 -233
  187. package/src/widgets/LookupField.gateHintLabel.test.tsx +0 -135
  188. package/src/widgets/LookupField.hydration.test.tsx +0 -136
  189. package/src/widgets/LookupField.idField.test.tsx +0 -61
  190. package/src/widgets/LookupField.tsx +0 -1291
  191. package/src/widgets/MarkdownContent.tsx +0 -19
  192. package/src/widgets/MasterDetailField.tsx +0 -131
  193. package/src/widgets/MultiSelectField.cascade.test.tsx +0 -143
  194. package/src/widgets/MultiSelectField.tsx +0 -142
  195. package/src/widgets/NumberField.tsx +0 -49
  196. package/src/widgets/ObjectField.tsx +0 -94
  197. package/src/widgets/ObjectRefField.tsx +0 -121
  198. package/src/widgets/OptionsEmptyState.no-provider.test.tsx +0 -88
  199. package/src/widgets/OptionsEmptyState.test.tsx +0 -169
  200. package/src/widgets/OptionsEmptyState.tsx +0 -83
  201. package/src/widgets/PasswordField.tsx +0 -49
  202. package/src/widgets/PeoplePicker.test.tsx +0 -360
  203. package/src/widgets/PeoplePicker.tsx +0 -579
  204. package/src/widgets/PeoplePickerComponents.test.tsx +0 -104
  205. package/src/widgets/PercentField.tsx +0 -95
  206. package/src/widgets/PersonRow.tsx +0 -117
  207. package/src/widgets/PhoneField.tsx +0 -37
  208. package/src/widgets/QRCodeField.tsx +0 -103
  209. package/src/widgets/RadioField.cascade.test.tsx +0 -141
  210. package/src/widgets/RadioField.tsx +0 -111
  211. package/src/widgets/RatingField.tsx +0 -81
  212. package/src/widgets/RecipientPickerField.test.tsx +0 -157
  213. package/src/widgets/RecipientPickerField.tsx +0 -188
  214. package/src/widgets/RecordPickerDialog.filterOptions.test.tsx +0 -611
  215. package/src/widgets/RecordPickerDialog.selectLabel.test.tsx +0 -112
  216. package/src/widgets/RecordPickerDialog.tsx +0 -1316
  217. package/src/widgets/RichTextField.tsx +0 -176
  218. package/src/widgets/SelectField.cascade.test.tsx +0 -138
  219. package/src/widgets/SelectField.tsx +0 -162
  220. package/src/widgets/SelectionTray.tsx +0 -110
  221. package/src/widgets/SignatureField.tsx +0 -140
  222. package/src/widgets/SliderField.tsx +0 -39
  223. package/src/widgets/SummaryField.tsx +0 -31
  224. package/src/widgets/TagsField.placeholder.no-provider.test.tsx +0 -68
  225. package/src/widgets/TagsField.placeholder.test.tsx +0 -95
  226. package/src/widgets/TagsField.tsx +0 -88
  227. package/src/widgets/TextAreaField.tsx +0 -213
  228. package/src/widgets/TextField.tsx +0 -45
  229. package/src/widgets/TimeField.tsx +0 -31
  230. package/src/widgets/UrlField.tsx +0 -46
  231. package/src/widgets/UserField.tsx +0 -58
  232. package/src/widgets/UserFieldPicker.test.tsx +0 -78
  233. package/src/widgets/VectorField.tsx +0 -29
  234. package/src/widgets/__tests__/FilterConditionField.matchAll.test.ts +0 -66
  235. package/src/widgets/__tests__/FilterConditionField.operators.test.ts +0 -142
  236. package/src/widgets/__tests__/FullscreenFieldEditor.i18n.test.tsx +0 -248
  237. package/src/widgets/__tests__/FullscreenFieldEditor.no-provider.test.tsx +0 -116
  238. package/src/widgets/__tests__/RichTextField.fullscreenDisabled.test.tsx +0 -244
  239. package/src/widgets/__tests__/RichTextField.mobileFullscreen.test.tsx +0 -195
  240. package/src/widgets/__tests__/TextAreaField.characterCount.announcements.test.tsx +0 -417
  241. package/src/widgets/__tests__/TextAreaField.characterCount.i18n.test.tsx +0 -287
  242. package/src/widgets/__tests__/TextAreaField.characterCount.no-provider.test.tsx +0 -160
  243. package/src/widgets/__tests__/TextAreaField.fullscreenCharacterCount.i18n.test.tsx +0 -161
  244. package/src/widgets/__tests__/TextAreaField.fullscreenCharacterCount.test.tsx +0 -346
  245. package/src/widgets/__tests__/TextAreaField.fullscreenDisabled.test.tsx +0 -246
  246. package/src/widgets/__tests__/TextAreaField.mobileFullscreen.test.tsx +0 -175
  247. package/src/widgets/_cell-renderer-bridge.ts +0 -19
  248. package/src/widgets/deriveLookupColumns.test.ts +0 -112
  249. package/src/widgets/deriveLookupColumns.ts +0 -108
  250. package/src/widgets/file-value.test.ts +0 -230
  251. package/src/widgets/file-value.ts +0 -246
  252. package/src/widgets/openNativePicker.ts +0 -18
  253. package/src/widgets/personDisplay.test.ts +0 -113
  254. package/src/widgets/personDisplay.ts +0 -92
  255. package/src/widgets/recentLookups.test.ts +0 -45
  256. package/src/widgets/recentLookups.ts +0 -47
  257. package/src/widgets/toDomProps.ts +0 -175
  258. package/src/widgets/types.ts +0 -242
  259. package/src/widgets/useCascadingOptions.ts +0 -46
  260. package/src/widgets/useFieldTranslation.ts +0 -150
  261. package/src/widgets/useRecordQuery.test.ts +0 -174
  262. package/src/widgets/useRecordQuery.ts +0 -264
  263. package/src/widgets/useUploadingSignal.test.tsx +0 -62
  264. package/src/widgets/useUploadingSignal.ts +0 -20
  265. package/src/withFieldCarrier.tsx +0 -75
@@ -1,126 +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
- * `@object-ui/fields` ↔ `@objectstack/spec` symbol-collision guards
11
- * (objectui#3161, objectstack#4115 ledger batch 7).
12
- *
13
- * Two symbols, two different verdicts:
14
- *
15
- * - `isFileIdToken` was a character-for-character copy of the spec's function
16
- * under the spec's own name. It is a re-export now, and the gate below is
17
- * REFERENCE IDENTITY — not behaviour. A faithful copy passes every behaviour
18
- * test ever written for it (objectui#3003, re-proved on
19
- * `isAggregatedViewContainer` in objectui#3169), so `toBe` is the only
20
- * assertion that can tell a re-export from a fork.
21
- *
22
- * - `FieldWidgetProps` is now `FieldWidgetComponentProps`. The spec owns that
23
- * name for the DECLARED widget-plugin props contract; this package's is the
24
- * React interface its widgets implement. The pins record what still makes
25
- * re-exporting impossible (`field` is the far richer `FieldMetadata` here)
26
- * and, since objectui#3222, what no longer does: the validation slot is the
27
- * spec's `error` on both sides.
28
- *
29
- * The other direction — "the spec must not start owning the NEW names" — is not
30
- * asserted here because `scripts/check-spec-symbol-derivation.mjs` already
31
- * answers it on every CI run, for every name in the package rather than the
32
- * handful a test would remember to list.
33
- */
34
-
35
- import { describe, it, expect } from 'vitest';
36
- import { isFileIdToken as specIsFileIdToken } from '@objectstack/spec/data';
37
- import type { FieldWidgetProps as SpecFieldWidgetProps } from '@objectstack/spec/ui';
38
-
39
- import { isFileIdToken, fileIdOf } from '../widgets/file-value';
40
- import type { FieldWidgetComponentProps } from '../widgets/types';
41
-
42
- describe('isFileIdToken IS the spec function', () => {
43
- it('is the same function object, not an equivalent one', () => {
44
- // The whole point. Every value/behaviour assertion below this line would
45
- // also pass against a re-forked copy; this one would not.
46
- expect(isFileIdToken).toBe(specIsFileIdToken);
47
- });
48
-
49
- it('is the arbiter the rest of the module routes through', () => {
50
- // `fileIdOf` must keep agreeing with the shared predicate, so a future
51
- // "small local tweak" to the id shape cannot be reintroduced one caller at
52
- // a time.
53
- for (const value of ['abc123', 'AbC-_9', 'x'.repeat(64)]) {
54
- expect(specIsFileIdToken(value)).toBe(true);
55
- expect(fileIdOf(value)).toBe(value);
56
- }
57
- for (const value of ['https://example.com/f.png', '/api/v1/storage/files/1', 'a.b', 'x'.repeat(65)]) {
58
- expect(specIsFileIdToken(value)).toBe(false);
59
- expect(fileIdOf(value)).toBeUndefined();
60
- }
61
- });
62
- });
63
-
64
- /* -------------------------------------------------------------------------- */
65
- /* Compile-time pins. A violation is a `tsc` error, not a runtime failure. */
66
- /* This package's tsconfig.json includes its tests, so `type-check` compiles */
67
- /* them (scripts/check-type-check-coverage.mjs keeps that true). */
68
- /* -------------------------------------------------------------------------- */
69
-
70
- type Assert<T extends true> = T;
71
- type Equal<A, B> = (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2 ? true : false;
72
- type Extends<A, B> = [A] extends [B] ? true : false;
73
- type IsAny<T> = 0 extends 1 & T ? true : false;
74
- type HasKey<T, K extends string> = K extends keyof T ? true : false;
75
-
76
- describe('FieldWidgetComponentProps is the RENDERED layer of the spec contract', () => {
77
- it('is pinned at compile time', () => {
78
- // The import above is itself half the tripwire: the day the spec stops
79
- // exporting `FieldWidgetProps`, this file stops compiling and the rename's
80
- // reason is up for re-triage (the plain name could come back).
81
- type _SpecIsReal = Assert<Equal<IsAny<SpecFieldWidgetProps>, false>>;
82
-
83
- // The validation-message slot no longer diverges. objectui#3161 could only
84
- // RECORD that the declared contract called it `error` while every widget
85
- // here read `errorMessage`; objectui#3222 resolved it in the direction the
86
- // contract points — this package adopted `error`, and the form renderer
87
- // started producing it, so the name and the delivery landed together.
88
- // Both sides are pinned: the spec still owns the name, and this package
89
- // still spells it the same way.
90
- type _SpecNamesItError = Assert<HasKey<SpecFieldWidgetProps, 'error'>>;
91
- type _LocalNamesItErrorToo = Assert<HasKey<FieldWidgetComponentProps, 'error'>>;
92
- type _SameOptionalStringSlot = Assert<
93
- Equal<FieldWidgetComponentProps['error'], SpecFieldWidgetProps['error']>
94
- >;
95
- // The old name is gone, not kept as an alias. An alias would be exactly the
96
- // lenient second dialect AGENTS.md #0.1 forbids — and the reason a missed
97
- // call site would have gone quiet again.
98
- type _OldNameRetired = Assert<Equal<HasKey<FieldWidgetComponentProps, 'errorMessage'>, false>>;
99
-
100
- // …and the reason nobody noticed used to sit right here: three pins
101
- // asserting that `[key: string]: any` was still present, and that
102
- // `props.required` / `props.error` therefore read as `any`. objectui#3221
103
- // removed that index signature, so those pins have gone red on purpose and
104
- // are gone with it. What replaces them is the inverse claim — the type is
105
- // now CLOSED, so a key the spec declares and this one does not can finally
106
- // be reported as missing. That is what made objectui#3222's rename
107
- // decidable by the compiler instead of by a symbol guard.
108
- type _NoStringIndexSignature = Assert<Equal<Extends<string, keyof FieldWidgetComponentProps>, false>>;
109
- // `required` is the one key that stayed behind, and deliberately so
110
- // (objectui#3222): the required marker has a single author — the form
111
- // renderer's `<FormLabel>` — and lowering the flag into widget props gives
112
- // it a second, i.e. the double-display failure that also keeps the
113
- // validation TEXT out of the widget. The a11y state a widget could
114
- // legitimately carry is `aria-required`, and `AriaAttributes` already
115
- // supplies that key with no contract change at all.
116
- type _RequiredIsAbsent = Assert<Equal<HasKey<FieldWidgetComponentProps, 'required'>, false>>;
117
-
118
- // `data-*` stays open (it is open in HTML too), but as a template-literal
119
- // key — the distinction that keeps `keyof` finite above. A pin, because
120
- // widening it back to `[key: string]` would silently restore the defect
121
- // while every assertion here still read as "closed".
122
- type _DataAttributesStayOpen = Assert<Extends<'data-testid', keyof FieldWidgetComponentProps>>;
123
-
124
- expect(true).toBe(true);
125
- });
126
- });
@@ -1,361 +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
- * P3.2 Field Widget Polish - Validation Feedback
11
- *
12
- * Tests that field widgets consistently handle validation feedback:
13
- * `error` prop, aria-invalid attribute, and disabled states.
14
- *
15
- * The slot is `error` — the name `@objectstack/spec/ui`'s
16
- * `FieldWidgetPropsSchema` gives it — since objectui#3222. These are the
17
- * CONSUMER half only: they prove a widget handed a message marks its control
18
- * invalid. That was true before #3222 too, under the name `errorMessage`, and
19
- * it did not help anyone, because no host ever passed the prop. The PRODUCER
20
- * half is `form-error-delivery.test.tsx` in `@object-ui/components` plus
21
- * `widget-aria-invalid-e2e.test.tsx` next to this file; a rename alone would
22
- * have swapped one dead key for another.
23
- */
24
-
25
- import { describe, it, expect, vi } from 'vitest';
26
- import React from 'react';
27
- import { render, screen } from '@testing-library/react';
28
- import '@testing-library/jest-dom';
29
-
30
- import { TextField } from '../widgets/TextField';
31
- import { NumberField } from '../widgets/NumberField';
32
- import { EmailField } from '../widgets/EmailField';
33
- import { UrlField } from '../widgets/UrlField';
34
- import { PhoneField } from '../widgets/PhoneField';
35
- import { TextAreaField } from '../widgets/TextAreaField';
36
- import { CurrencyField } from '../widgets/CurrencyField';
37
- import { BooleanField } from '../widgets/BooleanField';
38
- import { SelectField } from '../widgets/SelectField';
39
-
40
- const noop = vi.fn();
41
-
42
- describe('P3.2 Validation Feedback', () => {
43
- // ---------------------------------------------------------------
44
- // aria-invalid on `error`
45
- // ---------------------------------------------------------------
46
- describe('aria-invalid attribute', () => {
47
- it('EmailField sets aria-invalid when `error` provided', () => {
48
- render(
49
- <EmailField
50
- value=""
51
- onChange={noop}
52
- field={{ type: 'email' } as any}
53
- error="Invalid email"
54
- />
55
- );
56
- const input = screen.getByRole('textbox');
57
- expect(input).toHaveAttribute('aria-invalid', 'true');
58
- });
59
-
60
- it('EmailField does not set aria-invalid without `error`', () => {
61
- render(
62
- <EmailField
63
- value=""
64
- onChange={noop}
65
- field={{ type: 'email' } as any}
66
- />
67
- );
68
- const input = screen.getByRole('textbox');
69
- expect(input).not.toHaveAttribute('aria-invalid', 'true');
70
- });
71
-
72
- it('UrlField sets aria-invalid when `error` provided', () => {
73
- render(
74
- <UrlField
75
- value=""
76
- onChange={noop}
77
- field={{ type: 'url' } as any}
78
- error="Invalid URL"
79
- />
80
- );
81
- const input = screen.getByDisplayValue('');
82
- expect(input).toHaveAttribute('aria-invalid', 'true');
83
- });
84
-
85
- it('PhoneField sets aria-invalid when `error` provided', () => {
86
- render(
87
- <PhoneField
88
- value=""
89
- onChange={noop}
90
- field={{ type: 'phone' } as any}
91
- error="Invalid phone"
92
- />
93
- );
94
- const input = screen.getByRole('textbox');
95
- expect(input).toHaveAttribute('aria-invalid', 'true');
96
- });
97
-
98
- it('TextAreaField sets aria-invalid when `error` provided', () => {
99
- render(
100
- <TextAreaField
101
- value=""
102
- onChange={noop}
103
- field={{ type: 'textarea' } as any}
104
- error="Required"
105
- />
106
- );
107
- const textarea = screen.getByRole('textbox');
108
- expect(textarea).toHaveAttribute('aria-invalid', 'true');
109
- });
110
-
111
- it('CurrencyField sets aria-invalid when `error` provided', () => {
112
- render(
113
- <CurrencyField
114
- value={0}
115
- onChange={noop}
116
- field={{ type: 'currency' } as any}
117
- error="Must be positive"
118
- />
119
- );
120
- const input = screen.getByRole('spinbutton');
121
- expect(input).toHaveAttribute('aria-invalid', 'true');
122
- });
123
- });
124
-
125
- // ---------------------------------------------------------------
126
- // Disabled state consistency
127
- // ---------------------------------------------------------------
128
- describe('disabled state', () => {
129
- it('TextField supports disabled prop', () => {
130
- render(
131
- <TextField
132
- value="test"
133
- onChange={noop}
134
- field={{ type: 'text' } as any}
135
- disabled={true}
136
- />
137
- );
138
- expect(screen.getByRole('textbox')).toBeDisabled();
139
- });
140
-
141
- it('NumberField supports disabled prop', () => {
142
- render(
143
- <NumberField
144
- value={5}
145
- onChange={noop}
146
- field={{ type: 'number' } as any}
147
- disabled={true}
148
- />
149
- );
150
- expect(screen.getByRole('spinbutton')).toBeDisabled();
151
- });
152
-
153
- it('EmailField supports disabled prop', () => {
154
- render(
155
- <EmailField
156
- value=""
157
- onChange={noop}
158
- field={{ type: 'email' } as any}
159
- disabled={true}
160
- />
161
- );
162
- expect(screen.getByRole('textbox')).toBeDisabled();
163
- });
164
-
165
- it('PhoneField supports disabled prop', () => {
166
- render(
167
- <PhoneField
168
- value=""
169
- onChange={noop}
170
- field={{ type: 'phone' } as any}
171
- disabled={true}
172
- />
173
- );
174
- expect(screen.getByRole('textbox')).toBeDisabled();
175
- });
176
-
177
- it('TextAreaField supports disabled prop', () => {
178
- render(
179
- <TextAreaField
180
- value=""
181
- onChange={noop}
182
- field={{ type: 'textarea' } as any}
183
- disabled={true}
184
- />
185
- );
186
- expect(screen.getByRole('textbox')).toBeDisabled();
187
- });
188
- });
189
-
190
- // ---------------------------------------------------------------
191
- // Readonly mode consistency
192
- // ---------------------------------------------------------------
193
- describe('readonly mode output', () => {
194
- it('TextField shows dash for empty value in readonly', () => {
195
- const { container } = render(
196
- <TextField
197
- value=""
198
- onChange={noop}
199
- field={{ type: 'text' } as any}
200
- readonly={true}
201
- />
202
- );
203
- expect(container.textContent).toBe('—');
204
- });
205
-
206
- it('NumberField shows dash for null value in readonly', () => {
207
- const { container } = render(
208
- <NumberField
209
- value={null as any}
210
- onChange={noop}
211
- field={{ type: 'number' } as any}
212
- readonly={true}
213
- />
214
- );
215
- expect(container.textContent).toBe('—');
216
- });
217
-
218
- it('EmailField shows dash for empty value in readonly', () => {
219
- const { container } = render(
220
- <EmailField
221
- value=""
222
- onChange={noop}
223
- field={{ type: 'email' } as any}
224
- readonly={true}
225
- />
226
- );
227
- expect(container.textContent).toBe('—');
228
- });
229
-
230
- it('UrlField shows dash for empty value in readonly', () => {
231
- const { container } = render(
232
- <UrlField
233
- value=""
234
- onChange={noop}
235
- field={{ type: 'url' } as any}
236
- readonly={true}
237
- />
238
- );
239
- expect(container.textContent).toBe('—');
240
- });
241
-
242
- it('PhoneField shows dash for empty value in readonly', () => {
243
- const { container } = render(
244
- <PhoneField
245
- value=""
246
- onChange={noop}
247
- field={{ type: 'phone' } as any}
248
- readonly={true}
249
- />
250
- );
251
- expect(container.textContent).toBe('—');
252
- });
253
-
254
- it('TextAreaField shows dash for empty value in readonly', () => {
255
- const { container } = render(
256
- <TextAreaField
257
- value=""
258
- onChange={noop}
259
- field={{ type: 'textarea' } as any}
260
- readonly={true}
261
- />
262
- );
263
- expect(container.textContent).toBe('—');
264
- });
265
-
266
- it('BooleanField shows Yes/No in readonly', () => {
267
- const { container: yesC } = render(
268
- <BooleanField
269
- value={true}
270
- onChange={noop}
271
- field={{ type: 'boolean' } as any}
272
- readonly={true}
273
- />
274
- );
275
- expect(yesC.textContent).toBe('Yes');
276
-
277
- const { container: noC } = render(
278
- <BooleanField
279
- value={false}
280
- onChange={noop}
281
- field={{ type: 'boolean' } as any}
282
- readonly={true}
283
- />
284
- );
285
- expect(noC.textContent).toBe('No');
286
- });
287
-
288
- it('SelectField shows dash for empty value in readonly', () => {
289
- const { container } = render(
290
- <SelectField
291
- value=""
292
- onChange={noop}
293
- field={{ type: 'select', options: [] } as any}
294
- readonly={true}
295
- />
296
- );
297
- expect(container.textContent).toBe('—');
298
- });
299
-
300
- it('CurrencyField shows dash for null value in readonly', () => {
301
- const { container } = render(
302
- <CurrencyField
303
- value={null as any}
304
- onChange={noop}
305
- field={{ type: 'currency' } as any}
306
- readonly={true}
307
- />
308
- );
309
- expect(container.textContent).toBe('—');
310
- });
311
- });
312
-
313
- // ---------------------------------------------------------------
314
- // Placeholder consistency
315
- // ---------------------------------------------------------------
316
- describe('placeholder support', () => {
317
- it('TextField uses field placeholder', () => {
318
- render(
319
- <TextField
320
- value=""
321
- onChange={noop}
322
- field={{ type: 'text', placeholder: 'Enter name...' } as any}
323
- />
324
- );
325
- expect(screen.getByPlaceholderText('Enter name...')).toBeInTheDocument();
326
- });
327
-
328
- it('EmailField uses default placeholder', () => {
329
- render(
330
- <EmailField
331
- value=""
332
- onChange={noop}
333
- field={{ type: 'email' } as any}
334
- />
335
- );
336
- expect(screen.getByPlaceholderText('email@example.com')).toBeInTheDocument();
337
- });
338
-
339
- it('UrlField uses default placeholder', () => {
340
- render(
341
- <UrlField
342
- value=""
343
- onChange={noop}
344
- field={{ type: 'url' } as any}
345
- />
346
- );
347
- expect(screen.getByPlaceholderText('https://example.com')).toBeInTheDocument();
348
- });
349
-
350
- it('PhoneField uses default placeholder', () => {
351
- render(
352
- <PhoneField
353
- value=""
354
- onChange={noop}
355
- field={{ type: 'phone' } as any}
356
- />
357
- );
358
- expect(screen.getByPlaceholderText('(555) 123-4567')).toBeInTheDocument();
359
- });
360
- });
361
- });