CoordinateInput
Known, unknown, invalid, and disabled
const [position, setPosition] = useState<CoordinateValue>({
latitude: 34.0522,
longitude: -118.2437,
});
const [invalid, setInvalid] = useState(false);
<CoordinateInput
label="Observation position"
value={position}
onValueChange={setPosition}
onValidationChange={setInvalid}
latitudeName="latitude"
longitudeName="longitude"
/>;
<Button
disabled={invalid || position.latitude === null || position.longitude === null}
onClick={() => requestGoTo(position)}
>
Go to position
</Button>;
The flat value contains latitude and longitude, each a number or null. A missing
axis makes the position unknown. Latitude accepts −90 through 90 and longitude accepts
−180 through 180, inclusive. Zero is a known coordinate. Input is decimal degrees;
direction letters, DMS, grid references, geocoding, wrapping, and datum conversion are
outside this control's contract.
Invalid text remains visible with an explicit Position invalid state. Out-of-range
coordinates are never silently clipped or wrapped. Valid axis edits preserve the other
axis; invalid drafts do not emit new coordinates. onValidationChange(invalid) reports
aggregate validity initially and when it changes, including typed drafts, supplied
numeric values, and the caller's error. Use it to block a Go-to or submit action from
using the previous valid coordinates while invalid text is visible. Missing axes remain
unknown rather than invalid, so require both axes separately when the action needs a
complete position. Native form constraints remain available. defaultValue supports
uncontrolled use; without it both axes start unknown.
Latitude and longitude sit side by side when the group itself has room for two fields of about 12rem, and stack one above the other when it does not, by the group's own width and never the window's: in a narrow panel on a wide screen they stack, so neither coordinate is cut. What is entered stays as it is across the change.
Props
| PROP | TYPE | DEFAULT | REQUIRED |
|---|---|---|---|
label | string | "Coordinates" | no |
valueControlled { latitude: number | null, longitude: number | null }. Latitude is -90…90; longitude is -180…180, inclusive. Null axes are unknown. | node | — | no |
defaultValueInitial uncontrolled coordinates. Defaults to both axes null. | node | — | no |
onValueChange(position: CoordinateValue) => void. Only valid axis edits or a cleared null axis are emitted; the other axis is preserved. Does not normalize or wrap out-of-range input. | function | — | no |
onValidationChange(invalid: boolean) => void. Reports aggregate invalid state initially and on change, including local typed drafts, supplied values and error. Missing/null axes are unknown rather than invalid; callers separately require both axes before Go-to or submission. | function | — | no |
disabled | boolean | false | no |
description | string | — | no |
error | string | — | no |
latitudeNameNative form field name for latitude. | string | — | no |
longitudeNameNative form field name for longitude. | string | — | no |
Accessibility
- A fieldset names the position; separately labeled latitude and longitude spinbuttons describe their degree units.
- Tab moves between axes. Arrow keys step, and Home/End select each axis's bounds.
- The associated status distinguishes known decimal degrees, unknown, and invalid positions in text.
- Both controls retain visible focus and minimum target geometry;
disableddisables both axes and shows them as unavailable, their values still readable. - A caller's
erroris announced as an alert. The legend keeps the case it is written in; the legend, description, status and message wrap.
Manifest entry
<CoordinateInput label="Observation position" value={position} onValueChange={setPosition} />View agent manifest
{
"description": "Decimal-degree latitude and longitude fields with bounded validation and explicit unknown or invalid position states.",
"props": {
"label": {
"type": "string",
"default": "Coordinates"
},
"value": {
"type": "node",
"description": "Controlled { latitude: number | null, longitude: number | null }. Latitude is -90…90; longitude is -180…180, inclusive. Null axes are unknown."
},
"defaultValue": {
"type": "node",
"description": "Initial uncontrolled coordinates. Defaults to both axes null."
},
"onValueChange": {
"type": "function",
"description": "(position: CoordinateValue) => void. Only valid axis edits or a cleared null axis are emitted; the other axis is preserved. Does not normalize or wrap out-of-range input."
},
"onValidationChange": {
"type": "function",
"description": "(invalid: boolean) => void. Reports aggregate invalid state initially and on change, including local typed drafts, supplied values and error. Missing/null axes are unknown rather than invalid; callers separately require both axes before Go-to or submission."
},
"disabled": {
"type": "boolean",
"default": false
},
"description": {
"type": "string"
},
"error": {
"type": "string"
},
"latitudeName": {
"type": "string",
"description": "Native form field name for latitude."
},
"longitudeName": {
"type": "string",
"description": "Native form field name for longitude."
}
},
"composable_with": [
"Card",
"NumberInput"
],
"never_inside": [
"Button"
],
"example": "<CoordinateInput label=\"Observation position\" value={position} onValueChange={setPosition} />"
}