@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,193 +0,0 @@
1
- import React, { useId } from 'react';
2
- import { Input, Button, Label, EmptyValue } from '@object-ui/components';
3
- import { MapPin, Crosshair } from 'lucide-react';
4
- import { FieldWidgetComponentProps } from './types';
5
- import { toDomProps } from './toDomProps';
6
- import { toHostGroupProps } from './toHostGroupProps';
7
-
8
- /**
9
- * Geolocation data structure
10
- */
11
- export interface GeolocationValue {
12
- latitude?: number;
13
- longitude?: number;
14
- accuracy?: number;
15
- }
16
-
17
- /**
18
- * Geolocation field widget - provides a location picker with coordinates
19
- * Supports manual entry and browser geolocation API
20
- */
21
- export function GeolocationField({ value, onChange, field, readonly, error, ...props }: FieldWidgetComponentProps<GeolocationValue>) {
22
- const [isLoading, setIsLoading] = React.useState(false);
23
- const location = value || {};
24
- // DOM pass-through (objectui#3318): the whitelist spread goes onto the FIRST
25
- // sub-input (latitude); the composite's validation state goes onto BOTH
26
- // focusable sub-inputs via `aria-invalid={!!error}`.
27
- //
28
- // `id` and `aria-labelledby` are held back and land on the group CONTAINER
29
- // instead (objectui#3961) — they address the whole field, not the latitude box.
30
- // The host `id` never reached the DOM before: it was overwritten one line later
31
- // by `id={subId('latitude')}` (objectui#3343), leaving the form's group label
32
- // pointing at nothing. And `aria-labelledby` on the latitude input would
33
- // OVERRIDE its own "Latitude" label with the field name. `aria-describedby` and
34
- // the rest stay on the first sub-input, where focus can reach them.
35
- const {
36
- id: _hostId,
37
- 'aria-labelledby': _hostLabelledBy,
38
- ...domProps
39
- } = toDomProps(props);
40
- // The pair held back above, in the one spelling every group-labelled widget
41
- // uses for it — and computed HERE, before the readonly early return below,
42
- // because that return used to drop both keys on the floor: a published label
43
- // id with no consumer in the document (objectui#3990). See `toHostGroupProps`.
44
- const hostGroupProps = toHostGroupProps(props);
45
- // Sub-input ids (objectui#3343): `useId()` prefix + sub-field name — the
46
- // `groupId` paradigm of RadioField / CheckboxesField. Hardcoded literals
47
- // ("latitude" / "longitude") collide as soon as a form renders two
48
- // geolocation fields, and every label's htmlFor then resolves to the
49
- // FIRST match.
50
- const groupId = useId();
51
- const subId = (name: keyof GeolocationValue) => `${groupId}-${name}`;
52
-
53
- const handleFieldChange = (fieldName: keyof GeolocationValue, fieldValue: string) => {
54
- onChange({
55
- ...location,
56
- [fieldName]: fieldValue ? Number(fieldValue) : undefined,
57
- });
58
- };
59
-
60
- const getCurrentLocation = () => {
61
- if (!navigator.geolocation) {
62
- console.error('Geolocation is not supported by this browser');
63
- return;
64
- }
65
-
66
- setIsLoading(true);
67
- navigator.geolocation.getCurrentPosition(
68
- (position) => {
69
- onChange({
70
- latitude: position.coords.latitude,
71
- longitude: position.coords.longitude,
72
- accuracy: position.coords.accuracy,
73
- });
74
- setIsLoading(false);
75
- },
76
- (error) => {
77
- console.error('Error getting location:', error.message);
78
- setIsLoading(false);
79
- },
80
- {
81
- enableHighAccuracy: true,
82
- timeout: 5000,
83
- maximumAge: 0,
84
- }
85
- );
86
- };
87
-
88
- const formatLocation = (loc: GeolocationValue): string => {
89
- if (!loc.latitude || !loc.longitude) return '';
90
- return `${loc.latitude.toFixed(6)}, ${loc.longitude.toFixed(6)}`;
91
- };
92
-
93
- const openInMaps = () => {
94
- if (!location.latitude || !location.longitude) return;
95
- const url = `https://www.google.com/maps?q=${location.latitude},${location.longitude}`;
96
- window.open(url, '_blank');
97
- };
98
-
99
- if (readonly) {
100
- // Readonly the composite collapses to the formatted coordinates (plus the
101
- // "View on map" link), so THAT row is the surface the host label names
102
- // (objectui#3990). The `EmptyValue` placeholder sits inside it, so it needs
103
- // nothing of its own.
104
- const formatted = formatLocation(location);
105
- return (
106
- <div {...hostGroupProps} className="flex items-center gap-2">
107
- <MapPin className="w-4 h-4 text-muted-foreground" />
108
- {formatted ? <span className="text-sm">{formatted}</span> : <EmptyValue />}
109
- {location.latitude && location.longitude && (
110
- <Button
111
- type="button"
112
- variant="link"
113
- size="sm"
114
- onClick={openInMaps}
115
- className="p-0 h-auto"
116
- >
117
- View on map
118
- </Button>
119
- )}
120
- </div>
121
- );
122
- }
123
-
124
- return (
125
- // `role="group"` only when a host actually named this container
126
- // (objectui#3961); standalone rendering stays exactly as it was. That
127
- // condition now lives in `toHostGroupProps` so this container and the
128
- // readonly row above cannot answer differently (objectui#3990).
129
- <div className="space-y-3" {...hostGroupProps}>
130
- <div className="flex items-center gap-2">
131
- <Button
132
- type="button"
133
- variant="outline"
134
- size="sm"
135
- onClick={getCurrentLocation}
136
- disabled={readonly || isLoading}
137
- >
138
- <Crosshair className="w-4 h-4 mr-2" />
139
- {isLoading ? 'Getting location...' : 'Use Current Location'}
140
- </Button>
141
- {location.latitude && location.longitude && (
142
- <Button
143
- type="button"
144
- variant="link"
145
- size="sm"
146
- onClick={openInMaps}
147
- >
148
- <MapPin className="w-4 h-4 mr-2" />
149
- View on map
150
- </Button>
151
- )}
152
- </div>
153
-
154
- <div className="grid grid-cols-2 gap-3">
155
- <div>
156
- <Label htmlFor={subId('latitude')} className="text-xs">Latitude</Label>
157
- <Input
158
- {...domProps}
159
- id={subId('latitude')}
160
- type="number"
161
- value={location.latitude ?? ''}
162
- onChange={(e) => handleFieldChange('latitude', e.target.value)}
163
- placeholder="37.7749"
164
- disabled={readonly || props.disabled}
165
- step="any"
166
- className={props.className}
167
- aria-invalid={!!error}
168
- />
169
- </div>
170
-
171
- <div>
172
- <Label htmlFor={subId('longitude')} className="text-xs">Longitude</Label>
173
- <Input
174
- id={subId('longitude')}
175
- type="number"
176
- value={location.longitude ?? ''}
177
- onChange={(e) => handleFieldChange('longitude', e.target.value)}
178
- placeholder="-122.4194"
179
- disabled={readonly || props.disabled}
180
- step="any"
181
- aria-invalid={!!error}
182
- />
183
- </div>
184
- </div>
185
-
186
- {location.accuracy && (
187
- <p className="text-xs text-muted-foreground">
188
- Accuracy: ±{location.accuracy.toFixed(0)}m
189
- </p>
190
- )}
191
- </div>
192
- );
193
- }
@@ -1,88 +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 `geolocation` widget
11
- * (objectui#3343).
12
- *
13
- * The widget used to hardcode literal ids ("latitude" / "longitude") on its
14
- * sub-inputs. Two geolocation fields in one form — e.g. origin + destination
15
- * — then produced duplicate DOM ids, and every `Label htmlFor` resolved to
16
- * the FIRST match in the document: each sub-label of the second field
17
- * clicked/announced the first field's input.
18
- *
19
- * The fix follows the `groupId` paradigm of RadioField / CheckboxesField:
20
- * a `useId()` prefix + the sub-field name. These tests pin it by rendering
21
- * TWO instances in one form:
22
- *
23
- * 1. every `[id]` in the document is globally unique (red again if anyone
24
- * reverts to hardcoded literals — both instances would emit
25
- * id="latitude");
26
- * 2. each sub-label of the SECOND instance resolves — via the exact
27
- * mechanism browsers use for label activation, `for` → getElementById —
28
- * to an input inside its OWN instance, and focusing it lands there.
29
- */
30
- import { describe, it, expect, vi } from 'vitest';
31
- import React from 'react';
32
- import { render, within } from '@testing-library/react';
33
- import '@testing-library/jest-dom';
34
- import { GeolocationField } from './GeolocationField';
35
-
36
- const field = { name: 'location', type: 'geolocation' } as any;
37
-
38
- const SUB_LABELS = ['Latitude', 'Longitude'];
39
-
40
- function renderTwoInOneForm() {
41
- return render(
42
- <form>
43
- <div data-testid="geo-a">
44
- <GeolocationField value={{}} onChange={vi.fn()} field={field} />
45
- </div>
46
- <div data-testid="geo-b">
47
- <GeolocationField value={{}} onChange={vi.fn()} field={field} />
48
- </div>
49
- </form>,
50
- );
51
- }
52
-
53
- describe('GeolocationField — unique sub-input ids (objectui#3343)', () => {
54
- it('two geolocation fields in one form produce globally unique DOM ids', () => {
55
- const { baseElement } = renderTwoInOneForm();
56
- const ids = Array.from(baseElement.querySelectorAll('[id]')).map((el) => el.id);
57
- // 2 sub-inputs per instance — both instances must actually render ids.
58
- expect(ids.length).toBeGreaterThanOrEqual(4);
59
- const duplicates = ids.filter((id, i) => ids.indexOf(id) !== i);
60
- expect(duplicates).toEqual([]);
61
- });
62
-
63
- it("each sub-label of the SECOND field focuses that field's own input", () => {
64
- const { getByTestId } = renderTwoInOneForm();
65
- const first = getByTestId('geo-a');
66
- const second = getByTestId('geo-b');
67
-
68
- for (const text of SUB_LABELS) {
69
- const label = within(second).getByText(text);
70
- const forId = label.getAttribute('for');
71
- expect(forId, `label "${text}" must carry htmlFor`).toBeTruthy();
72
-
73
- // Exactly what the browser does on label click / SR announcement:
74
- // resolve `for` against the document.
75
- const control = document.getElementById(forId!);
76
- expect(control, `htmlFor of "${text}" must resolve`).not.toBeNull();
77
- expect(control!.tagName).toBe('INPUT');
78
- expect(
79
- second.contains(control),
80
- `"${text}" must resolve into its OWN field, not the first one`,
81
- ).toBe(true);
82
- expect(first.contains(control)).toBe(false);
83
-
84
- (control as HTMLInputElement).focus();
85
- expect(control).toHaveFocus();
86
- }
87
- });
88
- });