@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,357 +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
- import { describe, it, expect, vi } from 'vitest';
10
- import React from 'react';
11
- import { render, screen, fireEvent } from '@testing-library/react';
12
- import '@testing-library/jest-dom';
13
- import { CurrencyField } from './widgets/CurrencyField';
14
- import { NumberField } from './widgets/NumberField';
15
- import { TextField } from './widgets/TextField';
16
- import { SelectField } from './widgets/SelectField';
17
- import { BooleanField } from './widgets/BooleanField';
18
- import { DateField } from './widgets/DateField';
19
- import { EmailField } from './widgets/EmailField';
20
- import { UrlField } from './widgets/UrlField';
21
- import { PhoneField } from './widgets/PhoneField';
22
- import { TextAreaField } from './widgets/TextAreaField';
23
-
24
- const mockOnChange = vi.fn();
25
-
26
- describe('Standard Field Widgets', () => {
27
- describe('CurrencyField', () => {
28
- const fieldMock = { type: 'currency', currency: 'USD', precision: 2 };
29
-
30
- it('should render value with currency symbol in input mode', () => {
31
- render(
32
- <CurrencyField
33
- value={123.45}
34
- onChange={mockOnChange}
35
- field={fieldMock as any}
36
- />
37
- );
38
- const input = screen.getByRole('spinbutton'); // input type="number"
39
- expect(input).toHaveValue(123.45);
40
- expect(screen.getByText('$')).toBeInTheDocument();
41
- });
42
-
43
- it('should format currency in readonly mode', () => {
44
- const { container } = render(
45
- <CurrencyField
46
- value={1234.56}
47
- onChange={mockOnChange}
48
- field={fieldMock as any}
49
- readonly={true}
50
- />
51
- );
52
- // Expected output format might depend on locale, matching generic USD format
53
- expect(container.textContent).toMatch(/\$1,234.56/);
54
- });
55
-
56
- it('should handle EUR currency', () => {
57
- render(
58
- <CurrencyField
59
- value={100}
60
- onChange={mockOnChange}
61
- field={{ ...fieldMock, currency: 'EUR' } as any}
62
- />
63
- );
64
- expect(screen.getByText('EUR')).toBeInTheDocument();
65
- });
66
- });
67
-
68
- describe('NumberField', () => {
69
- const fieldMock = { type: 'number', precision: 2 };
70
-
71
- it('should render numeric input', () => {
72
- render(
73
- <NumberField
74
- value={42}
75
- onChange={mockOnChange}
76
- field={fieldMock as any}
77
- />
78
- );
79
- const input = screen.getByRole('spinbutton');
80
- expect(input).toHaveValue(42);
81
- });
82
-
83
- it('should call onChange with number', () => {
84
- render(
85
- <NumberField
86
- value={1}
87
- onChange={mockOnChange}
88
- field={fieldMock as any}
89
- />
90
- );
91
- const input = screen.getByRole('spinbutton');
92
- fireEvent.change(input, { target: { value: '10' } });
93
- expect(mockOnChange).toHaveBeenCalledWith(10);
94
- });
95
-
96
- it('should display value in readonly mode', () => {
97
- const { container } = render(
98
- <NumberField
99
- value={99.9}
100
- onChange={mockOnChange}
101
- field={fieldMock as any}
102
- readonly={true}
103
- />
104
- );
105
- expect(container.textContent).toBe('99.9');
106
- });
107
-
108
- it('should surface metadata min/max on the input (objectui#2572)', () => {
109
- render(
110
- <NumberField
111
- value={5}
112
- onChange={mockOnChange}
113
- field={{ type: 'number', min: 0, max: 100 } as any}
114
- />
115
- );
116
- const input = screen.getByRole('spinbutton');
117
- expect(input).toHaveAttribute('min', '0');
118
- expect(input).toHaveAttribute('max', '100');
119
- });
120
-
121
- it('should step by 1 for scale: 0 (whole numbers), not "any"', () => {
122
- render(
123
- <NumberField
124
- value={5}
125
- onChange={mockOnChange}
126
- field={{ type: 'number', scale: 0 } as any}
127
- />
128
- );
129
- expect(screen.getByRole('spinbutton')).toHaveAttribute('step', '1');
130
- });
131
-
132
- it('should honor an explicit metadata step over the scale-derived one', () => {
133
- render(
134
- <NumberField
135
- value={5}
136
- onChange={mockOnChange}
137
- field={{ type: 'number', scale: 2, step: 5 } as any}
138
- />
139
- );
140
- expect(screen.getByRole('spinbutton')).toHaveAttribute('step', '5');
141
- });
142
- });
143
-
144
- describe('TextField', () => {
145
- const fieldMock = { type: 'text' };
146
-
147
- it('should render text input', () => {
148
- render(
149
- <TextField
150
- value="Hello"
151
- onChange={mockOnChange}
152
- field={fieldMock as any}
153
- />
154
- );
155
- const input = screen.getByRole('textbox');
156
- expect(input).toHaveValue('Hello');
157
- });
158
-
159
- it('should render as password when type is password', () => {
160
- render(
161
- <TextField
162
- value="secret"
163
- onChange={mockOnChange}
164
- field={{ type: 'password' } as any}
165
- />
166
- );
167
- // getByRole('textbox') doesn't work for password type usually
168
- const input = screen.getByDisplayValue('secret');
169
- expect(input).toHaveAttribute('type', 'password');
170
- });
171
-
172
- it('should display value in readonly mode', () => {
173
- const { container } = render(
174
- <TextField
175
- value="ReadOnly"
176
- onChange={mockOnChange}
177
- field={fieldMock as any}
178
- readonly={true}
179
- />
180
- );
181
- expect(container.textContent).toBe('ReadOnly');
182
- });
183
- });
184
-
185
- describe('SelectField', () => {
186
- const fieldMock = {
187
- type: 'select',
188
- options: [
189
- { label: 'Option A', value: 'a' },
190
- { label: 'Option B', value: 'b' }
191
- ]
192
- };
193
-
194
- it('should render select trigger (Combobox/Select)', () => {
195
- render(
196
- <SelectField
197
- value={''}
198
- onChange={mockOnChange}
199
- field={fieldMock as any}
200
- />
201
- );
202
- expect(screen.getByRole('combobox')).toBeInTheDocument();
203
- });
204
-
205
- it('should display label in readonly mode', () => {
206
- const { container } = render(
207
- <SelectField
208
- value="a"
209
- onChange={mockOnChange}
210
- field={fieldMock as any}
211
- readonly={true}
212
- />
213
- );
214
- expect(container.textContent).toBe('Option A');
215
- });
216
-
217
- // A `select` declared `multiple` is a multi-value picker (spec allows
218
- // `multiple` on select). SelectField delegates to the chip picker so the
219
- // form, inline editor, and ActionParamDialog all get multi-select from the
220
- // one `select` widget — no single-select fallback, no per-surface drift.
221
- it('renders the multi-value chip picker when field.multiple is set', () => {
222
- render(
223
- <SelectField
224
- value={['a']}
225
- onChange={mockOnChange}
226
- field={{ ...fieldMock, multiple: true } as any}
227
- />
228
- );
229
- // Chip picker uses toggle buttons (aria-pressed), not a Radix combobox.
230
- expect(screen.queryByRole('combobox')).not.toBeInTheDocument();
231
- const optionA = screen.getByRole('button', { name: 'Option A' });
232
- expect(optionA).toHaveAttribute('aria-pressed', 'true');
233
- expect(screen.getByRole('button', { name: 'Option B' })).toHaveAttribute('aria-pressed', 'false');
234
- });
235
-
236
- it('multi-select toggles values as an array', () => {
237
- render(
238
- <SelectField
239
- value={['a']}
240
- onChange={mockOnChange}
241
- field={{ ...fieldMock, multiple: true } as any}
242
- />
243
- );
244
- fireEvent.click(screen.getByRole('button', { name: 'Option B' }));
245
- expect(mockOnChange).toHaveBeenCalledWith(['a', 'b']);
246
- });
247
-
248
- it('multi-select renders selected values as badges in readonly mode', () => {
249
- const { container } = render(
250
- <SelectField
251
- value={['a', 'b']}
252
- onChange={mockOnChange}
253
- field={{ ...fieldMock, multiple: true } as any}
254
- readonly={true}
255
- />
256
- );
257
- expect(container.textContent).toContain('Option A');
258
- expect(container.textContent).toContain('Option B');
259
- });
260
- });
261
-
262
- describe('BooleanField', () => {
263
- const fieldMock = { type: 'boolean' };
264
-
265
- it('should render checkbox/switch', () => {
266
- render(
267
- <BooleanField
268
- value={true}
269
- onChange={mockOnChange}
270
- field={fieldMock as any}
271
- />
272
- );
273
- // Ensure we can find the switch role (Shadcn switch uses role="switch")
274
- expect(screen.getByRole('switch')).toBeChecked();
275
- });
276
-
277
- it('should toggle value', () => {
278
- render(
279
- <BooleanField
280
- value={false}
281
- onChange={mockOnChange}
282
- field={fieldMock as any}
283
- />
284
- );
285
- const toggle = screen.getByRole('switch');
286
- fireEvent.click(toggle);
287
- expect(mockOnChange).toHaveBeenCalledWith(true);
288
- });
289
-
290
- it('should display Yes/No in readonly mode', () => {
291
- const { container } = render(
292
- <BooleanField
293
- value={true}
294
- onChange={mockOnChange}
295
- field={fieldMock as any}
296
- readonly={true}
297
- />
298
- );
299
- expect(container.textContent).toBe('Yes');
300
- });
301
- });
302
-
303
- describe('EmailField', () => {
304
- it('should render email input', () => {
305
- render(
306
- <EmailField
307
- value="test@example.com"
308
- onChange={mockOnChange}
309
- field={{ type: 'email' } as any}
310
- />
311
- );
312
- const input = screen.getByDisplayValue('test@example.com');
313
- expect(input).toHaveAttribute('type', 'email');
314
- });
315
-
316
- it('should render as link in readonly mode', () => {
317
- render(
318
- <EmailField
319
- value="test@example.com"
320
- onChange={mockOnChange}
321
- field={{ type: 'email' } as any}
322
- readonly={true}
323
- />
324
- );
325
- const link = screen.getByRole('link');
326
- expect(link).toHaveAttribute('href', 'mailto:test@example.com');
327
- expect(link).toHaveTextContent('test@example.com');
328
- });
329
- });
330
-
331
- describe('UrlField', () => {
332
- it('should render url input', () => {
333
- render(
334
- <UrlField
335
- value="https://example.com"
336
- onChange={mockOnChange}
337
- field={{ type: 'url' } as any}
338
- />
339
- );
340
- const input = screen.getByDisplayValue('https://example.com');
341
- expect(input).toHaveAttribute('type', 'url');
342
- });
343
-
344
- it('should render clickable link in readonly mode', () => {
345
- render(
346
- <UrlField
347
- value="https://example.com"
348
- onChange={mockOnChange}
349
- field={{ type: 'url' } as any}
350
- readonly={true}
351
- />
352
- );
353
- const link = screen.getByRole('link');
354
- expect(link).toHaveAttribute('href', 'https://example.com');
355
- });
356
- });
357
- });
@@ -1,132 +0,0 @@
1
- import React, { useId } from 'react';
2
- import { Input, Label, EmptyValue } from '@object-ui/components';
3
- import { FieldWidgetComponentProps } from './types';
4
- import { toDomProps } from './toDomProps';
5
-
6
- /**
7
- * Address data structure
8
- */
9
- export interface AddressValue {
10
- street?: string;
11
- city?: string;
12
- state?: string;
13
- zipCode?: string;
14
- country?: string;
15
- }
16
-
17
- /**
18
- * Address field widget - provides a structured address input
19
- * Supports street, city, state, zip code, and country
20
- */
21
- export function AddressField({ value, onChange, field, readonly, error, ...props }: FieldWidgetComponentProps<AddressValue>) {
22
- const address = value || {};
23
- // DOM pass-through (objectui#3318): the whitelist spread goes onto the FIRST
24
- // sub-input (street) — one carrier for the form renderer's aria-describedby;
25
- // the composite's validation state goes onto EVERY focusable sub-input via
26
- // `aria-invalid={!!error}` (a form-level failure means the whole address is
27
- // missing/invalid, and whichever sub-input the user reaches must announce it).
28
- const domProps = toDomProps(props);
29
- // Sub-input ids (objectui#3343): `useId()` prefix + sub-field name — the
30
- // `groupId` paradigm of RadioField / CheckboxesField. Hardcoded literals
31
- // ("street", "city", …) collide as soon as a form renders two address
32
- // fields, and every label's htmlFor then resolves to the FIRST match.
33
- const groupId = useId();
34
- const subId = (name: keyof AddressValue) => `${groupId}-${name}`;
35
-
36
- const handleFieldChange = (fieldName: keyof AddressValue, fieldValue: string) => {
37
- onChange({
38
- ...address,
39
- [fieldName]: fieldValue,
40
- });
41
- };
42
-
43
- const formatAddress = (addr: AddressValue): string => {
44
- const parts = [
45
- addr.street,
46
- addr.city,
47
- [addr.state, addr.zipCode].filter(Boolean).join(' '),
48
- addr.country,
49
- ].filter(Boolean);
50
- return parts.join(', ');
51
- };
52
-
53
- if (readonly) {
54
- const formatted = formatAddress(address);
55
- return formatted ? <span className="text-sm">{formatted}</span> : <EmptyValue />;
56
- }
57
-
58
- return (
59
- <div className="space-y-3">
60
- <div>
61
- <Label htmlFor={subId('street')} className="text-xs">Street Address</Label>
62
- <Input
63
- {...domProps}
64
- id={subId('street')}
65
- type="text"
66
- value={address.street || ''}
67
- onChange={(e) => handleFieldChange('street', e.target.value)}
68
- placeholder="123 Main St"
69
- disabled={readonly || props.disabled}
70
- className={props.className}
71
- aria-invalid={!!error}
72
- />
73
- </div>
74
-
75
- <div className="grid grid-cols-2 gap-3">
76
- <div>
77
- <Label htmlFor={subId('city')} className="text-xs">City</Label>
78
- <Input
79
- id={subId('city')}
80
- type="text"
81
- value={address.city || ''}
82
- onChange={(e) => handleFieldChange('city', e.target.value)}
83
- placeholder="San Francisco"
84
- disabled={readonly || props.disabled}
85
- aria-invalid={!!error}
86
- />
87
- </div>
88
-
89
- <div>
90
- <Label htmlFor={subId('state')} className="text-xs">State / Province</Label>
91
- <Input
92
- id={subId('state')}
93
- type="text"
94
- value={address.state || ''}
95
- onChange={(e) => handleFieldChange('state', e.target.value)}
96
- placeholder="CA"
97
- disabled={readonly || props.disabled}
98
- aria-invalid={!!error}
99
- />
100
- </div>
101
- </div>
102
-
103
- <div className="grid grid-cols-2 gap-3">
104
- <div>
105
- <Label htmlFor={subId('zipCode')} className="text-xs">ZIP / Postal Code</Label>
106
- <Input
107
- id={subId('zipCode')}
108
- type="text"
109
- value={address.zipCode || ''}
110
- onChange={(e) => handleFieldChange('zipCode', e.target.value)}
111
- placeholder="94102"
112
- disabled={readonly || props.disabled}
113
- aria-invalid={!!error}
114
- />
115
- </div>
116
-
117
- <div>
118
- <Label htmlFor={subId('country')} className="text-xs">Country</Label>
119
- <Input
120
- id={subId('country')}
121
- type="text"
122
- value={address.country || ''}
123
- onChange={(e) => handleFieldChange('country', e.target.value)}
124
- placeholder="United States"
125
- disabled={readonly || props.disabled}
126
- aria-invalid={!!error}
127
- />
128
- </div>
129
- </div>
130
- </div>
131
- );
132
- }
@@ -1,92 +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
- * Unique sub-input ids for the composite `address` widget (objectui#3343).
11
- *
12
- * The widget used to hardcode literal ids ("street", "city", "state",
13
- * "zipCode", "country") on its sub-inputs. Two address fields in one form —
14
- * e.g. billing + shipping — then produced duplicate DOM ids, and every
15
- * `Label htmlFor` resolved to the FIRST match in the document: each sub-label
16
- * of the second field clicked/announced the first field's input.
17
- *
18
- * The fix follows the `groupId` paradigm of RadioField / CheckboxesField:
19
- * a `useId()` prefix + the sub-field name. These tests pin it by rendering
20
- * TWO instances in one form:
21
- *
22
- * 1. every `[id]` in the document is globally unique (red again if anyone
23
- * reverts to hardcoded literals — both instances would emit id="street");
24
- * 2. each sub-label of the SECOND instance resolves — via the exact
25
- * mechanism browsers use for label activation, `for` → getElementById —
26
- * to an input inside its OWN instance, and focusing it lands there.
27
- */
28
- import { describe, it, expect, vi } from 'vitest';
29
- import React from 'react';
30
- import { render, within } from '@testing-library/react';
31
- import '@testing-library/jest-dom';
32
- import { AddressField } from './AddressField';
33
-
34
- const field = { name: 'address', type: 'address' } as any;
35
-
36
- const SUB_LABELS = [
37
- 'Street Address',
38
- 'City',
39
- 'State / Province',
40
- 'ZIP / Postal Code',
41
- 'Country',
42
- ];
43
-
44
- function renderTwoInOneForm() {
45
- return render(
46
- <form>
47
- <div data-testid="address-a">
48
- <AddressField value={{}} onChange={vi.fn()} field={field} />
49
- </div>
50
- <div data-testid="address-b">
51
- <AddressField value={{}} onChange={vi.fn()} field={field} />
52
- </div>
53
- </form>,
54
- );
55
- }
56
-
57
- describe('AddressField — unique sub-input ids (objectui#3343)', () => {
58
- it('two address fields in one form produce globally unique DOM ids', () => {
59
- const { baseElement } = renderTwoInOneForm();
60
- const ids = Array.from(baseElement.querySelectorAll('[id]')).map((el) => el.id);
61
- // 5 sub-inputs per instance — both instances must actually render ids.
62
- expect(ids.length).toBeGreaterThanOrEqual(10);
63
- const duplicates = ids.filter((id, i) => ids.indexOf(id) !== i);
64
- expect(duplicates).toEqual([]);
65
- });
66
-
67
- it("each sub-label of the SECOND field focuses that field's own input", () => {
68
- const { getByTestId } = renderTwoInOneForm();
69
- const first = getByTestId('address-a');
70
- const second = getByTestId('address-b');
71
-
72
- for (const text of SUB_LABELS) {
73
- const label = within(second).getByText(text);
74
- const forId = label.getAttribute('for');
75
- expect(forId, `label "${text}" must carry htmlFor`).toBeTruthy();
76
-
77
- // Exactly what the browser does on label click / SR announcement:
78
- // resolve `for` against the document.
79
- const control = document.getElementById(forId!);
80
- expect(control, `htmlFor of "${text}" must resolve`).not.toBeNull();
81
- expect(control!.tagName).toBe('INPUT');
82
- expect(
83
- second.contains(control),
84
- `"${text}" must resolve into its OWN field, not the first one`,
85
- ).toBe(true);
86
- expect(first.contains(control)).toBe(false);
87
-
88
- (control as HTMLInputElement).focus();
89
- expect(control).toHaveFocus();
90
- }
91
- });
92
- });
@@ -1,18 +0,0 @@
1
- import React from 'react';
2
- import { EmptyValue } from '@object-ui/components';
3
- import { FieldWidgetComponentProps } from './types';
4
-
5
- /**
6
- * AutoNumberField - Read-only auto-generated field
7
- * Values are automatically generated by the system and cannot be edited
8
- */
9
- export function AutoNumberField({ value, field, ...props }: FieldWidgetComponentProps<string | number>) {
10
- if (value == null) {
11
- return <EmptyValue className={props.className} />;
12
- }
13
- return (
14
- <span className={`text-sm font-mono text-gray-700 ${props.className || ''}`}>
15
- {String(value)}
16
- </span>
17
- );
18
- }