@object-ui/fields 17.5.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.
- package/CHANGELOG.md +714 -0
- package/dist/FieldEditWidget.d.ts +16 -2
- package/dist/field-type-alias.d.ts +16 -0
- package/dist/index.cjs +4 -4
- package/dist/index.css +27 -9
- package/dist/index.d.ts +425 -72
- package/dist/index.js +2530 -2228
- package/dist/widgets/AddressField.d.ts +3 -3
- package/dist/widgets/AutoNumberField.d.ts +1 -1
- package/dist/widgets/AvatarField.d.ts +1 -1
- package/dist/widgets/BooleanField.d.ts +1 -1
- package/dist/widgets/CapabilityMultiSelectField.d.ts +1 -1
- package/dist/widgets/CheckboxesField.d.ts +1 -1
- package/dist/widgets/CodeField.d.ts +1 -1
- package/dist/widgets/ColorField.d.ts +1 -1
- package/dist/widgets/CurrencyField.d.ts +1 -1
- package/dist/widgets/DateField.d.ts +1 -1
- package/dist/widgets/DateTimeField.d.ts +1 -1
- package/dist/widgets/EmailField.d.ts +1 -1
- package/dist/widgets/FileField.d.ts +18 -2
- package/dist/widgets/FilterConditionField.d.ts +28 -1
- package/dist/widgets/FormulaField.d.ts +1 -1
- package/dist/widgets/FullscreenFieldEditor.d.ts +9 -2
- package/dist/widgets/GeolocationField.d.ts +1 -1
- package/dist/widgets/GridField.d.ts +56 -4
- package/dist/widgets/ImageField.d.ts +1 -1
- package/dist/widgets/LocationField.d.ts +1 -1
- package/dist/widgets/LookupField.d.ts +1 -1
- package/dist/widgets/MasterDetailField.d.ts +1 -1
- package/dist/widgets/MultiSelectField.d.ts +1 -1
- package/dist/widgets/NumberField.d.ts +1 -1
- package/dist/widgets/ObjectField.d.ts +1 -1
- package/dist/widgets/ObjectRefField.d.ts +1 -1
- package/dist/widgets/OptionsEmptyState.d.ts +13 -7
- package/dist/widgets/PasswordField.d.ts +1 -1
- package/dist/widgets/PercentField.d.ts +1 -1
- package/dist/widgets/PhoneField.d.ts +1 -1
- package/dist/widgets/QRCodeField.d.ts +1 -1
- package/dist/widgets/RadioField.d.ts +1 -1
- package/dist/widgets/RatingField.d.ts +1 -1
- package/dist/widgets/RecipientPickerField.d.ts +1 -1
- package/dist/widgets/RecordPickerDialog.d.ts +2 -4
- package/dist/widgets/RichTextField.d.ts +37 -1
- package/dist/widgets/SelectField.d.ts +1 -1
- package/dist/widgets/SignatureField.d.ts +32 -2
- package/dist/widgets/SliderField.d.ts +37 -2
- package/dist/widgets/SummaryField.d.ts +1 -1
- package/dist/widgets/TagsField.d.ts +1 -1
- package/dist/widgets/TextAreaField.d.ts +6 -2
- package/dist/widgets/TextField.d.ts +1 -1
- package/dist/widgets/TimeField.d.ts +1 -1
- package/dist/widgets/UrlField.d.ts +1 -1
- package/dist/widgets/UserField.d.ts +1 -1
- package/dist/widgets/VectorField.d.ts +1 -1
- package/dist/widgets/_cell-renderer-bridge.d.ts +1 -1
- package/dist/widgets/address-format.d.ts +27 -7
- package/dist/widgets/lookupColumnDisplay.d.ts +93 -0
- package/dist/widgets/richTextDisplay.d.ts +80 -0
- package/dist/widgets/toHostGroupProps.d.ts +119 -21
- package/dist/widgets/types.d.ts +8 -3
- package/package.json +9 -10
- package/dist/widgets/CharacterCount.d.ts +0 -56
- package/src/FieldEditWidget.test.ts +0 -133
- package/src/FieldEditWidget.tsx +0 -207
- package/src/__tests__/AddressCellRenderer.test.tsx +0 -166
- package/src/__tests__/CurrencyField.minorUnits.test.tsx +0 -191
- package/src/__tests__/CurrencyField.symbol.test.tsx +0 -198
- package/src/__tests__/DateCellRenderer.test.tsx +0 -82
- package/src/__tests__/NumberCellRenderer.grouping.test.tsx +0 -146
- package/src/__tests__/NumberCellRenderer.test.tsx +0 -60
- package/src/__tests__/PercentCellRenderer.locale.test.tsx +0 -152
- package/src/__tests__/PercentCellRenderer.test.tsx +0 -115
- package/src/__tests__/boolean-label-association-e2e.test.tsx +0 -298
- package/src/__tests__/capability-multiselect-retired.test.ts +0 -109
- package/src/__tests__/cell-truncation.test.tsx +0 -96
- package/src/__tests__/composite-group-label-e2e.test.tsx +0 -436
- package/src/__tests__/composite-group-label-readonly-e2e.test.tsx +0 -403
- package/src/__tests__/date-formatter-residue-4272.test.ts +0 -120
- package/src/__tests__/date-locale-channel.test.tsx +0 -305
- package/src/__tests__/field-carrier-sdui.test.tsx +0 -169
- package/src/__tests__/formatCurrency.centsDigit.test.tsx +0 -158
- package/src/__tests__/formatCurrency.minorUnits.test.tsx +0 -221
- package/src/__tests__/group-labelling-declaration.test.ts +0 -148
- package/src/__tests__/lookupCellDisplayField.test.tsx +0 -107
- package/src/__tests__/lookupCellDisplayName.test.tsx +0 -119
- package/src/__tests__/lookupCellRecordLink.test.tsx +0 -171
- package/src/__tests__/lookupCellReferenceKey.test.tsx +0 -57
- package/src/__tests__/percent-cell-vs-measure-4576.test.ts +0 -115
- package/src/__tests__/percent-formatter-locale-4553.test.ts +0 -147
- package/src/__tests__/percent-tie-halfup-4590.test.ts +0 -224
- package/src/__tests__/registry-collision.test.ts +0 -91
- package/src/__tests__/resolveFieldCurrency.test.ts +0 -20
- package/src/__tests__/select-label-association-e2e.test.tsx +0 -199
- package/src/__tests__/spec-symbol-batch7.test.ts +0 -126
- package/src/__tests__/validation-feedback.test.tsx +0 -361
- package/src/__tests__/widget-aria-invalid-e2e.test.tsx +0 -265
- package/src/__tests__/widget-aria-invalid-registry-e2e.test.tsx +0 -338
- package/src/__tests__/widget-dom-leak-e2e.test.tsx +0 -357
- package/src/__tests__/widget-props-contract.test.tsx +0 -142
- package/src/coerce-safe-value.test.ts +0 -34
- package/src/complex-widgets.test.tsx +0 -924
- package/src/currency.ts +0 -144
- package/src/datetime-widgets.test.tsx +0 -185
- package/src/field-type-alias.multiple.test.ts +0 -81
- package/src/field-type-alias.ts +0 -156
- package/src/field-type-coverage.test.ts +0 -67
- package/src/index.css +0 -44
- package/src/index.tsx +0 -2745
- package/src/standard-widgets.test.tsx +0 -364
- package/src/widgets/AddressField.postalCode.test.tsx +0 -257
- package/src/widgets/AddressField.tsx +0 -182
- package/src/widgets/AddressField.uniqueIds.test.tsx +0 -93
- package/src/widgets/AutoNumberField.tsx +0 -18
- package/src/widgets/AvatarField.tsx +0 -117
- package/src/widgets/BooleanField.tsx +0 -86
- package/src/widgets/CapabilityMultiSelectField.test.tsx +0 -168
- package/src/widgets/CapabilityMultiSelectField.tsx +0 -241
- package/src/widgets/CharacterCount.tsx +0 -263
- package/src/widgets/CheckboxesField.cascade.test.tsx +0 -143
- package/src/widgets/CheckboxesField.tsx +0 -169
- package/src/widgets/CodeField.tsx +0 -41
- package/src/widgets/ColorField.tsx +0 -53
- package/src/widgets/CurrencyField.tsx +0 -135
- package/src/widgets/DateField.tsx +0 -40
- package/src/widgets/DateTimeField.tsx +0 -46
- package/src/widgets/EmailField.tsx +0 -37
- package/src/widgets/FileField.tsx +0 -512
- package/src/widgets/FilterConditionField.tsx +0 -454
- package/src/widgets/FormulaField.tsx +0 -38
- package/src/widgets/FullscreenFieldEditor.tsx +0 -60
- package/src/widgets/GeolocationField.tsx +0 -193
- package/src/widgets/GeolocationField.uniqueIds.test.tsx +0 -88
- package/src/widgets/GridField.test.tsx +0 -632
- package/src/widgets/GridField.tsx +0 -1070
- package/src/widgets/ImageCropperDialog.tsx +0 -245
- package/src/widgets/ImageField.maxSize.test.tsx +0 -154
- package/src/widgets/ImageField.tsx +0 -305
- package/src/widgets/ImageLightbox.test.tsx +0 -52
- package/src/widgets/ImageLightbox.tsx +0 -108
- package/src/widgets/LocationField.tsx +0 -57
- package/src/widgets/LookupField.dependsOn.test.tsx +0 -233
- package/src/widgets/LookupField.gateHintLabel.test.tsx +0 -135
- package/src/widgets/LookupField.hydration.test.tsx +0 -136
- package/src/widgets/LookupField.idField.test.tsx +0 -61
- package/src/widgets/LookupField.tsx +0 -1295
- package/src/widgets/MarkdownContent.tsx +0 -19
- package/src/widgets/MasterDetailField.tsx +0 -131
- package/src/widgets/MultiSelectField.cascade.test.tsx +0 -143
- package/src/widgets/MultiSelectField.tsx +0 -196
- package/src/widgets/NumberField.tsx +0 -49
- package/src/widgets/ObjectField.tsx +0 -94
- package/src/widgets/ObjectRefField.tsx +0 -121
- package/src/widgets/OptionsEmptyState.no-provider.test.tsx +0 -88
- package/src/widgets/OptionsEmptyState.test.tsx +0 -169
- package/src/widgets/OptionsEmptyState.tsx +0 -99
- package/src/widgets/PasswordField.tsx +0 -49
- package/src/widgets/PeoplePicker.test.tsx +0 -360
- package/src/widgets/PeoplePicker.tsx +0 -579
- package/src/widgets/PeoplePickerComponents.test.tsx +0 -104
- package/src/widgets/PercentField.tsx +0 -95
- package/src/widgets/PersonRow.tsx +0 -117
- package/src/widgets/PhoneField.tsx +0 -37
- package/src/widgets/QRCodeField.tsx +0 -103
- package/src/widgets/RadioField.cascade.test.tsx +0 -141
- package/src/widgets/RadioField.tsx +0 -143
- package/src/widgets/RatingField.tsx +0 -94
- package/src/widgets/RecipientPickerField.test.tsx +0 -157
- package/src/widgets/RecipientPickerField.tsx +0 -188
- package/src/widgets/RecordPickerDialog.baseFilterShapes.test.tsx +0 -191
- package/src/widgets/RecordPickerDialog.dateLocale.test.tsx +0 -124
- package/src/widgets/RecordPickerDialog.filterOptions.test.tsx +0 -611
- package/src/widgets/RecordPickerDialog.selectLabel.test.tsx +0 -112
- package/src/widgets/RecordPickerDialog.tsx +0 -1365
- package/src/widgets/RichTextField.tsx +0 -176
- package/src/widgets/SelectField.cascade.test.tsx +0 -138
- package/src/widgets/SelectField.tsx +0 -193
- package/src/widgets/SelectionTray.tsx +0 -110
- package/src/widgets/SignatureField.tsx +0 -140
- package/src/widgets/SliderField.tsx +0 -39
- package/src/widgets/SummaryField.tsx +0 -31
- package/src/widgets/TagsField.placeholder.no-provider.test.tsx +0 -68
- package/src/widgets/TagsField.placeholder.test.tsx +0 -95
- package/src/widgets/TagsField.tsx +0 -88
- package/src/widgets/TextAreaField.tsx +0 -213
- package/src/widgets/TextField.tsx +0 -45
- package/src/widgets/TimeField.tsx +0 -31
- package/src/widgets/UrlField.tsx +0 -46
- package/src/widgets/UserField.tsx +0 -59
- package/src/widgets/UserFieldPicker.test.tsx +0 -78
- package/src/widgets/VectorField.tsx +0 -29
- package/src/widgets/__tests__/FilterConditionField.matchAll.test.ts +0 -66
- package/src/widgets/__tests__/FilterConditionField.operators.test.ts +0 -166
- package/src/widgets/__tests__/FullscreenFieldEditor.i18n.test.tsx +0 -248
- package/src/widgets/__tests__/FullscreenFieldEditor.no-provider.test.tsx +0 -116
- package/src/widgets/__tests__/RichTextField.fullscreenDisabled.test.tsx +0 -244
- package/src/widgets/__tests__/RichTextField.mobileFullscreen.test.tsx +0 -195
- package/src/widgets/__tests__/TextAreaField.characterCount.announcements.test.tsx +0 -417
- package/src/widgets/__tests__/TextAreaField.characterCount.i18n.test.tsx +0 -287
- package/src/widgets/__tests__/TextAreaField.characterCount.no-provider.test.tsx +0 -160
- package/src/widgets/__tests__/TextAreaField.fullscreenCharacterCount.i18n.test.tsx +0 -161
- package/src/widgets/__tests__/TextAreaField.fullscreenCharacterCount.test.tsx +0 -346
- package/src/widgets/__tests__/TextAreaField.fullscreenDisabled.test.tsx +0 -246
- package/src/widgets/__tests__/TextAreaField.mobileFullscreen.test.tsx +0 -175
- package/src/widgets/_cell-renderer-bridge.ts +0 -19
- package/src/widgets/address-format.ts +0 -120
- package/src/widgets/deriveLookupColumns.test.ts +0 -112
- package/src/widgets/deriveLookupColumns.ts +0 -108
- package/src/widgets/file-size-guard.ts +0 -55
- package/src/widgets/file-value.test.ts +0 -230
- package/src/widgets/file-value.ts +0 -246
- package/src/widgets/nativeDateValue.test.ts +0 -115
- package/src/widgets/nativeDateValue.ts +0 -112
- package/src/widgets/openNativePicker.ts +0 -18
- package/src/widgets/optionWidgets.gatedOptions.test.tsx +0 -267
- package/src/widgets/optionWidgets.unconfiguredOptions.test.tsx +0 -150
- package/src/widgets/personDisplay.test.ts +0 -113
- package/src/widgets/personDisplay.ts +0 -92
- package/src/widgets/recentLookups.test.ts +0 -45
- package/src/widgets/recentLookups.ts +0 -47
- package/src/widgets/toDomProps.ts +0 -204
- package/src/widgets/toHostGroupProps.ts +0 -102
- package/src/widgets/types.ts +0 -242
- package/src/widgets/useCascadingOptions.ts +0 -46
- package/src/widgets/useFieldTranslation.ts +0 -132
- package/src/widgets/useRecordQuery.test.ts +0 -174
- package/src/widgets/useRecordQuery.ts +0 -291
- package/src/widgets/useUploadingSignal.test.tsx +0 -62
- package/src/widgets/useUploadingSignal.ts +0 -20
- 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
|
-
});
|