@accelint/design-toolkit 8.1.2 → 8.1.4

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.
@@ -1,2 +1,2 @@
1
- import {getLogger}from'@accelint/logger';import {createCoordinate,coordinateSystems}from'@accelint/geo';import {COORDINATE_SYSTEMS}from'./types.js';const u=getLogger({enabled:process.env.NODE_ENV!=="production",level:"debug",prefix:"[CoordinateField]",pretty:true});const h=1e-6,l={INVALID:"Invalid coordinate",CONVERSION_FAILED:"Conversion failed",NOT_AVAILABLE_AT_POLES:"Not available at poles"};function S(t){if(t.length<2)return null;const r=Number.parseFloat(t[0]),n=Number.parseFloat(t[1]);if(Number.isNaN(r)||Number.isNaN(n))return null;const e=r>=0?"N":"S",a=n>=0?"E":"W";return `${Math.abs(r)} ${e} / ${Math.abs(n)} ${a}`}function C(t){return t.length<6?null:`${t[0]}\xB0 ${t[1]}' ${t[2]}, ${t[3]}\xB0 ${t[4]}' ${t[5]}`}function p(t){return t.length<8?null:`${t[0]}\xB0 ${t[1]}' ${t[2]}" ${t[3]}, ${t[4]}\xB0 ${t[5]}' ${t[6]}" ${t[7]}`}function N(t){return t.length<5?null:`${t[0]}${t[1]} ${t[2]} ${t[3]} ${t[4]}`}function b(t){return t.length<4?null:`${t[0]}${t[1]} ${t[2]} ${t[3]}`}function f(t,r){if(t.some(n=>n===""||n===void 0))return null;try{switch(r){case "dd":return S(t);case "ddm":return C(t);case "dms":return p(t);case "mgrs":return N(t);case "utm":return b(t);default:return null}}catch{return null}}function y(t){const r=t.match(/([-]?\d+\.?\d*)°?\s*([NS])?\s*[,/\s]+\s*([-]?\d+\.?\d*)°?\s*([EW])?/i);if(!r)return null;let n=r[1],e=r[3];return n&&e?(r[2]?.toUpperCase()==="S"&&!n.startsWith("-")&&(n=`-${n}`),r[4]?.toUpperCase()==="W"&&!e.startsWith("-")&&(e=`-${e}`),[n,e]):null}function D(t){const r=t.match(/(\d+)°?\s+([\d.]+)'?\s+([NS])\s*[,/]\s*(\d+)°?\s+([\d.]+)'?\s+([EW])/i);return r?[r[1],r[2],r[3],r[4],r[5],r[6]]:null}function $(t){const r=t.match(/(\d+)°?\s+(\d+)'?\s+([\d.]+)"?\s+([NS])\s*[,/]\s*(\d+)°?\s+(\d+)'?\s+([\d.]+)"?\s+([EW])/i);return r?[r[1],r[2],r[3],r[4],r[5],r[6],r[7],r[8]]:null}function v(t){const r=t.match(/(\d+)([A-Z])\s+([A-Z]{2})\s+(\d+)\s+(\d+)/i);return r?[r[1],r[2],r[3],r[4],r[5]]:null}function A(t){const r=t.match(/(\d+)\s*([NS])\s+(\d+)\s+(\d+)/i);return r?[r[1],r[2],r[3],r[4]]:null}function V(t,r){if(!t)return null;try{switch(r){case "dd":return y(t);case "ddm":return D(t);case "dms":return $(t);case "mgrs":return v(t);case "utm":return A(t);default:return null}}catch{return null}}function k(t,r){if(!t||typeof t.lat!="number"||typeof t.lon!="number")return null;try{const n=createCoordinate(coordinateSystems.dd,"LATLON"),e=Number(t.lat.toFixed(5)),a=Number(t.lon.toFixed(5)),s=`${e} / ${a}`,i=n(s);if(!i.valid)return null;let o;switch(r){case "dd":o=i.dd();break;case "ddm":o=i.ddm();break;case "dms":o=i.dms();break;case "mgrs":o=i.mgrs();break;case "utm":o=i.utm();break;default:return null}return V(o,r)}catch(n){return u.withContext({value:String(t),format:String(r)}).withError(n).error("Failed to convert DD to display"),null}}function x(t,r){const n=f(t,r);if(!n)return null;try{const a=createCoordinate(coordinateSystems[r],"LATLON")(n);if(!a.valid)return null;const{LAT:s,LON:i}=a.raw;return {lat:s,lon:i}}catch(e){return u.withContext({segments:JSON.stringify(t),format:String(r)}).withError(e).error("Failed to convert display to DD"),null}}function _(t,r){if(t.some(e=>e===""||e===void 0))return [];const n=f(t,r);if(!n)return ["Invalid coordinate value"];try{return createCoordinate(coordinateSystems[r],"LATLON")(n).valid?[]:["Invalid coordinate value"]}catch{return ["Invalid coordinate value"]}}function P(t){return t.every(r=>r!==""&&r!==void 0)}function U(t){return t.some(r=>r!==""&&r!==void 0)}function L(){return {dd:{value:l.INVALID,isValid:false},ddm:{value:l.INVALID,isValid:false},dms:{value:l.INVALID,isValid:false},mgrs:{value:l.INVALID,isValid:false},utm:{value:l.INVALID,isValid:false}}}function E(t){return t!==null&&typeof t.lat=="number"&&typeof t.lon=="number"}function I(t){const r=t instanceof Error?t.message:String(t);return r.includes("outside UTM limits")||r.includes("invalid UTM zone")}function M(t,r,n){try{let e;switch(r){case "dd":e=t.dd();break;case "ddm":e=t.ddm();break;case "dms":e=t.dms();break;case "mgrs":e=t.mgrs();break;case "utm":e=t.utm();break;default:return {value:l.INVALID,isValid:!1}}return {value:e,isValid:!0}}catch(e){return (r==="mgrs"||r==="utm")&&I(e)?{value:l.NOT_AVAILABLE_AT_POLES,isValid:false}:(u.withContext({value:JSON.stringify(n)}).withError(e).error(`Failed to convert to ${r}`),{value:l.CONVERSION_FAILED,isValid:false})}}function W(t){const r=L();if(!E(t))return r;const n=t;try{const a=createCoordinate(coordinateSystems.dd,"LATLON")(`${n.lat} / ${n.lon}`);if(!a.valid)return r;const s={};for(const i of COORDINATE_SYSTEMS)s[i]=M(a,i,n);return s}catch(e){return u.withContext({value:JSON.stringify(n)}).withError(e).error("Failed to get all coordinate formats"),r}}function G(t){if(!t||t.trim()==="")return false;const r=/[,/]|°|′|″|['"]|\s{2,}/.test(t),e=(t.match(/\d+/g)||[]).length>=2,a=/^\d{1,2}[A-Z]\s+[A-Z]{2}\s+\d+\s+\d+$/i.test(t.trim()),s=/^\d{1,2}[NS]\s+\d+\s+\d+$/i.test(t.trim());return r||e||a||s}function Z(t){if(!t||t.trim()==="")return [];const r=[];for(const n of COORDINATE_SYSTEMS)try{const a=createCoordinate(coordinateSystems[n],"LATLON")(t.trim());if(a.valid){const{LAT:s,LON:i}=a.raw;let o;switch(n){case "dd":o=a.dd();break;case "ddm":o=a.ddm();break;case "dms":o=a.dms();break;case "mgrs":o=a.mgrs();break;case "utm":o=a.utm();break;default:o="";}r.push({format:n,value:{lat:s,lon:i},displayString:o});}}catch(e){u.withContext({pastedText:t.trim(),format:String(n)}).withError(e).warn(`Failed to parse as ${n}`);}return r}function O(t,r,n=h){return Math.abs(t.lat-r.lat)<n&&Math.abs(t.lon-r.lon)<n}function B(t){const r=[];for(const n of t)r.some(a=>O(a.value,n.value))||r.push(n);return r}export{h as COORDINATE_EPSILON,l as COORDINATE_ERROR_MESSAGES,P as areAllSegmentsFilled,O as areCoordinatesEqual,k as convertDDToDisplaySegments,x as convertDisplaySegmentsToDD,B as deduplicateMatchesByLocation,f as formatSegmentsToCoordinateString,W as getAllCoordinateFormats,U as hasAnySegmentValue,G as isCompleteCoordinate,Z as parseCoordinatePaste,V as parseCoordinateStringToSegments,_ as validateCoordinateSegments};//# sourceMappingURL=coordinate-utils.js.map
1
+ import {getLogger}from'@accelint/logger';import {createCoordinate,coordinateSystems}from'@accelint/geo';import {COORDINATE_SYSTEMS}from'./types.js';const u=getLogger({enabled:process.env.NODE_ENV!=="production",level:"debug",prefix:"[CoordinateField]",pretty:true});const C=1e-6,l={INVALID:"Invalid coordinate",CONVERSION_FAILED:"Conversion failed",NOT_AVAILABLE_AT_POLES:"Not available at poles"};function N(t){if(t.length<2)return null;const r=Number.parseFloat(t[0]),n=Number.parseFloat(t[1]);if(Number.isNaN(r)||Number.isNaN(n))return null;const e=r>=0?"N":"S",a=n>=0?"E":"W";return `${Math.abs(r)} ${e} / ${Math.abs(n)} ${a}`}function $(t){return t.length<6?null:`${t[0]}\xB0 ${t[1]}' ${t[2]}, ${t[3]}\xB0 ${t[4]}' ${t[5]}`}function b(t){return t.length<8?null:`${t[0]}\xB0 ${t[1]}' ${t[2]}" ${t[3]}, ${t[4]}\xB0 ${t[5]}' ${t[6]}" ${t[7]}`}function y(t){return t.length<5?null:`${t[0]}${t[1]} ${t[2]} ${t[3]} ${t[4]}`}function D(t){return t.length<4?null:`${t[0]}${t[1]} ${t[2]} ${t[3]}`}function g(t,r){if(t.some(n=>n===""||n===void 0))return null;try{switch(r){case "dd":return N(t);case "ddm":return $(t);case "dms":return b(t);case "mgrs":return y(t);case "utm":return D(t);default:return null}}catch{return null}}function m(t,r){const n=Number.parseFloat(t);return Number.isNaN(n)?t:Number.parseFloat(n.toFixed(r)).toString()}function v(t){const r=t.match(/([-]?\d+\.?\d*)°?\s*([NS])?\s*[,/\s]+\s*([-]?\d+\.?\d*)°?\s*([EW])?/i);if(!r)return null;let n=r[1],e=r[3];return n&&e?(r[2]?.toUpperCase()==="S"&&!n.startsWith("-")&&(n=`-${n}`),r[4]?.toUpperCase()==="W"&&!e.startsWith("-")&&(e=`-${e}`),[n,e]):null}function S(t){const r=t.match(/(\d+)°?\s+([\d.]+)'?\s+([NS])\s*[,/]\s*(\d+)°?\s+([\d.]+)'?\s+([EW])/i);return r?[r[1],m(r[2],4),r[3],r[4],m(r[5],4),r[6]]:null}function h(t){const r=t.match(/(\d+)°?\s+(\d+)'?\s+([\d.]+)"?\s+([NS])\s*[,/]\s*(\d+)°?\s+(\d+)'?\s+([\d.]+)"?\s+([EW])/i);return r?[r[1],r[2],m(r[3],2),r[4],r[5],r[6],m(r[7],2),r[8]]:null}function A(t){const r=t.match(/(\d+)([A-Z])\s+([A-Z]{2})\s+(\d+)\s+(\d+)/i);return r?[r[1],r[2],r[3],r[4],r[5]]:null}function V(t){const r=t.match(/(\d+)\s*([NS])\s+(\d+)\s+(\d+)/i);return r?[r[1],r[2],r[3],r[4]]:null}function L(t,r){if(!t)return null;try{switch(r){case "dd":return v(t);case "ddm":return S(t);case "dms":return h(t);case "mgrs":return A(t);case "utm":return V(t);default:return null}}catch{return null}}function k(t,r){if(!t||typeof t.lat!="number"||typeof t.lon!="number")return null;try{const n=createCoordinate(coordinateSystems.dd,"LATLON"),e=Number(t.lat.toFixed(10)),a=Number(t.lon.toFixed(10)),o=`${e} / ${a}`,s=n(o);if(!s.valid)return null;let i;switch(r){case "dd":i=s.dd();break;case "ddm":i=s.ddm();break;case "dms":i=s.dms();break;case "mgrs":i=s.mgrs();break;case "utm":i=s.utm();break;default:return null}return L(i,r)}catch(n){return u.withContext({value:String(t),format:String(r)}).withError(n).error("Failed to convert DD to display"),null}}function _(t,r){const n=g(t,r);if(!n)return null;try{const a=createCoordinate(coordinateSystems[r],"LATLON")(n);if(!a.valid)return null;const{LAT:o,LON:s}=a.raw;return {lat:o,lon:s}}catch(e){return u.withContext({segments:JSON.stringify(t),format:String(r)}).withError(e).error("Failed to convert display to DD"),null}}function P(t,r){if(t.some(e=>e===""||e===void 0))return [];const n=g(t,r);if(!n)return ["Invalid coordinate value"];try{return createCoordinate(coordinateSystems[r],"LATLON")(n).valid?[]:["Invalid coordinate value"]}catch{return ["Invalid coordinate value"]}}function U(t){return t.every(r=>r!==""&&r!==void 0)}function W(t){return t.some(r=>r!==""&&r!==void 0)}function E(){return {dd:{value:l.INVALID,isValid:false},ddm:{value:l.INVALID,isValid:false},dms:{value:l.INVALID,isValid:false},mgrs:{value:l.INVALID,isValid:false},utm:{value:l.INVALID,isValid:false}}}function I(t){return t!==null&&typeof t.lat=="number"&&typeof t.lon=="number"}function M(t){const r=t instanceof Error?t.message:String(t);return r.includes("outside UTM limits")||r.includes("invalid UTM zone")}function O(t,r,n){try{let e;switch(r){case "dd":e=t.dd();break;case "ddm":{const a=t.ddm(),o=S(a);e=o?`${o[0]} ${o[1]} ${o[2]} / ${o[3]} ${o[4]} ${o[5]}`:a;break}case "dms":{const a=t.dms(),o=h(a);e=o?`${o[0]} ${o[1]} ${o[2]} ${o[3]} / ${o[4]} ${o[5]} ${o[6]} ${o[7]}`:a;break}case "mgrs":e=t.mgrs();break;case "utm":e=t.utm();break;default:return {value:l.INVALID,isValid:!1}}return {value:e,isValid:!0}}catch(e){return (r==="mgrs"||r==="utm")&&M(e)?{value:l.NOT_AVAILABLE_AT_POLES,isValid:false}:(u.withContext({value:JSON.stringify(n)}).withError(e).error(`Failed to convert to ${r}`),{value:l.CONVERSION_FAILED,isValid:false})}}function G(t){const r=E();if(!I(t))return r;const n=t;try{const a=createCoordinate(coordinateSystems.dd,"LATLON")(`${n.lat.toFixed(10)} / ${n.lon.toFixed(10)}`);if(!a.valid)return r;const o={};for(const s of COORDINATE_SYSTEMS)o[s]=O(a,s,n);return o}catch(e){return u.withContext({value:JSON.stringify(n)}).withError(e).error("Failed to get all coordinate formats"),r}}function Z(t){if(!t||t.trim()==="")return false;const r=/[,/]|°|′|″|['"]|\s{2,}/.test(t),e=(t.match(/\d+/g)||[]).length>=2,a=/^\d{1,2}[A-Z]\s+[A-Z]{2}\s+\d+\s+\d+$/i.test(t.trim()),o=/^\d{1,2}[NS]\s+\d+\s+\d+$/i.test(t.trim());return r||e||a||o}function B(t){if(!t||t.trim()==="")return [];const r=[];for(const n of COORDINATE_SYSTEMS)try{const a=createCoordinate(coordinateSystems[n],"LATLON")(t.trim());if(a.valid){const{LAT:o,LON:s}=a.raw;let i;switch(n){case "dd":i=a.dd();break;case "ddm":i=a.ddm();break;case "dms":i=a.dms();break;case "mgrs":i=a.mgrs();break;case "utm":i=a.utm();break;default:i="";}r.push({format:n,value:{lat:o,lon:s},displayString:i});}}catch(e){u.withContext({pastedText:t.trim(),format:String(n)}).withError(e).warn(`Failed to parse as ${n}`);}return r}function F(t,r,n=C){return Math.abs(t.lat-r.lat)<n&&Math.abs(t.lon-r.lon)<n}function J(t){const r=[];for(const n of t)r.some(a=>F(a.value,n.value))||r.push(n);return r}export{C as COORDINATE_EPSILON,l as COORDINATE_ERROR_MESSAGES,U as areAllSegmentsFilled,F as areCoordinatesEqual,k as convertDDToDisplaySegments,_ as convertDisplaySegmentsToDD,J as deduplicateMatchesByLocation,g as formatSegmentsToCoordinateString,G as getAllCoordinateFormats,W as hasAnySegmentValue,Z as isCompleteCoordinate,B as parseCoordinatePaste,L as parseCoordinateStringToSegments,P as validateCoordinateSegments};//# sourceMappingURL=coordinate-utils.js.map
2
2
  //# sourceMappingURL=coordinate-utils.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/coordinate-field/coordinate-utils.ts"],"names":["logger","getLogger","segments","lonNum","lonDir","latNum","latDir","format","seg","match","lat","lon","coordString","value","coordinateSystems","c","d","inputCoordString","coord","create","LAT","LON","error","formattedValue","isValidCoordinateValue","validValue","COORDINATE_SYSTEMS","convertToFormat","text","hasMultipleNumbers","isMGRS","isUTM","pastedText","displayString","matches","coord2","epsilon","COORDINATE_EPSILON","coord1","areCoordinatesEqual","existing","uniqueMatches"],"mappings":"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","file":"coordinate-utils.js","sourcesContent":["/*\n * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport { getLogger } from '@accelint/logger';\n\nconst logger = getLogger({\n enabled: process.env.NODE_ENV !== 'production',\n level: 'debug',\n prefix: '[CoordinateField]',\n pretty: true,\n});\n\n/**\n * Coordinate Conversion Utilities\n *\n * This module provides utilities for converting between:\n * 1. Segment values (user input) → Coordinate strings (for @accelint/geo parsing)\n * 2. Coordinate strings (from @accelint/geo output) → Segment values (for display)\n * 3. Decimal Degrees (internal format) ↔ Display format segment values\n *\n * All conversions use the local @accelint/geo package for accurate coordinate parsing\n * and conversion between coordinate systems.\n *\n * ## Architecture: Bridge Layer Between UI and Geo Package\n *\n * This module serves as a bridge layer to handle the impedance mismatch between:\n * - **UI Requirements**: Individual segment fields (e.g., degrees, minutes, seconds, direction)\n * - **Geo Package API**: Complete coordinate strings (e.g., \"40° 42' 46\" N / 74° 0' 22\" W\")\n *\n * ### Why This Bridge Layer Exists\n *\n * The @accelint/geo package provides excellent coordinate parsing, validation, and conversion,\n * but its API is designed around complete coordinate strings:\n *\n * ```typescript\n * // What geo provides:\n * const coord = createCoordinate(coordinateSystems.ddm, 'LATLON')('40 42.768 N / 74 0.36 W');\n * coord.dd() // Returns: \"40.7128 N / 74.006 W\" (formatted string)\n * coord.ddm() // Returns: \"40 42.768 N / 74 0.36 W\" (formatted string)\n * coord.raw // Returns: { LAT: 40.7128, LON: -74.006 } (only DD numbers)\n * ```\n *\n * The coordinate-field component needs segment-level data for individual input fields:\n * - DDM: ['40', '42.768', 'N', '74', '0.36', 'W'] ← Not provided by geo\n * - DMS: ['40', '42', '46.08', 'N', '74', '0', '21.6', 'W'] ← Not provided by geo\n *\n * ### Current Limitations and Duplication\n *\n * Because the geo package only exposes:\n * 1. **Formatters** that return complete strings (coord.ddm(), coord.dms(), etc.)\n * 2. **Raw values** in Decimal Degrees only (coord.raw)\n *\n * This module must:\n * 1. Build coordinate strings from segments → Pass to geo for parsing\n * 2. Parse geo's formatted output strings → Extract segments using regex\n *\n * This creates a circular conversion flow for DD → Display Segments:\n * ```\n * DD value → String → Geo parse → Geo format → String → Regex parse → Segments\n * ```\n *\n * **Note on Duplication**: The regex parsing in this module duplicates work that the\n * geo package parsers already do internally. However, since geo doesn't expose the\n * parsed segment components (only formatted strings and raw DD values), we must\n * re-parse its output to extract the individual segments for the UI.\n *\n * ### What Would Eliminate This Duplication\n *\n * If the geo package exported component-level converters like:\n * ```typescript\n * // Hypothetical API that would eliminate the bridge layer:\n * export function ddToDdmComponents(dd: number): {\n * degrees: number;\n * minutes: number;\n * direction: 'N' | 'S' | 'E' | 'W';\n * }\n * ```\n *\n * Then this bridge layer could be significantly simplified. The math for these conversions\n * exists in the geo package's formatters (e.g., formatDegreesDecimalMinutes), but it's\n * wrapped in string formatting logic rather than exposed as standalone utilities.\n *\n * ### Conversion Efficiency\n *\n * - **Efficient Path** (Display Segments → DD): Segments → String → Geo parse → DD\n * - Uses geo package for all parsing and validation ✓\n *\n * - **Inefficient Path** (DD → Display Segments): DD → String → Geo parse → Geo format → Regex parse → Segments\n * - Circular conversion with redundant string parsing/formatting ✗\n * - Necessary given current geo API limitations\n */\n\nimport { coordinateSystems, createCoordinate } from '@accelint/geo';\nimport {\n COORDINATE_SYSTEMS,\n type CoordinateSystem,\n type CoordinateValue,\n type ParsedCoordinateMatch,\n} from './types';\n\n/** Epsilon for coordinate equality comparison (≈11cm precision at equator) */\nexport const COORDINATE_EPSILON = 0.000001;\n\n/**\n * Error message constants for coordinate format conversion\n * @internal\n */\nexport const COORDINATE_ERROR_MESSAGES = {\n INVALID: 'Invalid coordinate',\n CONVERSION_FAILED: 'Conversion failed',\n NOT_AVAILABLE_AT_POLES: 'Not available at poles',\n} as const;\n\n/**\n * Result of coordinate format conversion\n */\nexport interface CoordinateFormatResult {\n /** The formatted coordinate string or error message */\n value: string;\n /** Whether the coordinate format is valid and can be used/copied */\n isValid: boolean;\n}\n\n/**\n * Format DD (Decimal Degrees) segments to coordinate string\n *\n * Bridge function that builds a coordinate string from UI segment values\n * for passing to the geo package parser. Part of the Segments → String → Geo Parse flow.\n *\n * @internal\n */\nfunction formatDDSegments(segments: string[]): string | null {\n if (segments.length < 2) {\n return null;\n }\n const latNum = Number.parseFloat(segments[0] as string);\n const lonNum = Number.parseFloat(segments[1] as string);\n\n if (Number.isNaN(latNum) || Number.isNaN(lonNum)) {\n return null;\n }\n\n const latDir = latNum >= 0 ? 'N' : 'S';\n const lonDir = lonNum >= 0 ? 'E' : 'W';\n\n return `${Math.abs(latNum)} ${latDir} / ${Math.abs(lonNum)} ${lonDir}`;\n}\n\n/**\n * Format DDM (Degrees Decimal Minutes) segments to coordinate string\n *\n * Bridge function that builds a coordinate string from UI segment values\n * for passing to the geo package parser. Part of the Segments → String → Geo Parse flow.\n *\n * @internal\n */\nfunction formatDDMSegments(segments: string[]): string | null {\n if (segments.length < 6) {\n return null;\n }\n return `${segments[0]}° ${segments[1]}' ${segments[2]}, ${segments[3]}° ${segments[4]}' ${segments[5]}`;\n}\n\n/**\n * Format DMS (Degrees Minutes Seconds) segments to coordinate string\n *\n * Bridge function that builds a coordinate string from UI segment values\n * for passing to the geo package parser. Part of the Segments → String → Geo Parse flow.\n *\n * @internal\n */\nfunction formatDMSSegments(segments: string[]): string | null {\n if (segments.length < 8) {\n return null;\n }\n return `${segments[0]}° ${segments[1]}' ${segments[2]}\" ${segments[3]}, ${segments[4]}° ${segments[5]}' ${segments[6]}\" ${segments[7]}`;\n}\n\n/**\n * Format MGRS (Military Grid Reference System) segments to coordinate string\n *\n * Bridge function that builds a coordinate string from UI segment values\n * for passing to the geo package parser. Part of the Segments → String → Geo Parse flow.\n *\n * @internal\n */\nfunction formatMGRSSegments(segments: string[]): string | null {\n if (segments.length < 5) {\n return null;\n }\n return `${segments[0]}${segments[1]} ${segments[2]} ${segments[3]} ${segments[4]}`;\n}\n\n/**\n * Format UTM (Universal Transverse Mercator) segments to coordinate string\n *\n * Bridge function that builds a coordinate string from UI segment values\n * for passing to the geo package parser. Part of the Segments → String → Geo Parse flow.\n *\n * @internal\n */\nfunction formatUTMSegments(segments: string[]): string | null {\n if (segments.length < 4) {\n return null;\n }\n return `${segments[0]}${segments[1]} ${segments[2]} ${segments[3]}`;\n}\n\n/**\n * Format segment values into a coordinate string suitable for @accelint/geo parsing\n *\n * Converts an array of segment values into a string format that the geo package\n * parsers can understand. Each format has different requirements:\n *\n * DD: [lat, lon] → \"lat, lon\"\n * DDM: [latDeg, latMin, latDir, lonDeg, lonMin, lonDir] → \"latDeg° latMin' latDir, lonDeg° lonMin' lonDir\"\n * DMS: [latDeg, latMin, latSec, latDir, lonDeg, lonMin, lonSec, lonDir] → \"latDeg° latMin' latSec\\\" latDir, lonDeg° lonMin' lonSec\\\" lonDir\"\n * MGRS: [zone, band, grid, easting, northing] → \"zone+band grid easting northing\"\n * UTM: [zone, hemisphere, easting, northing] → \"zone+hemisphere easting northing\"\n *\n * @param segments - Array of segment values from user input\n * @param format - The coordinate system format\n * @returns Formatted coordinate string, or null if segments are invalid\n */\nexport function formatSegmentsToCoordinateString(\n segments: string[],\n format: CoordinateSystem,\n): string | null {\n if (segments.some((seg) => seg === '' || seg === undefined)) {\n return null;\n }\n\n try {\n switch (format) {\n case 'dd':\n return formatDDSegments(segments);\n case 'ddm':\n return formatDDMSegments(segments);\n case 'dms':\n return formatDMSSegments(segments);\n case 'mgrs':\n return formatMGRSSegments(segments);\n case 'utm':\n return formatUTMSegments(segments);\n default:\n return null;\n }\n } catch (_error) {\n return null;\n }\n}\n\n/**\n * Parse DD coordinate string to segments\n *\n * Extracts segment values from a formatted DD coordinate string. This duplicates\n * parsing logic from @accelint/geo's parseDecimalDegrees, but is necessary because\n * geo doesn't expose the parsed components - only formatted strings and raw DD numbers.\n *\n * Part of the Geo Format → String → Regex Parse → Segments flow (circular conversion).\n *\n * @internal\n */\nfunction parseDDCoordinateString(coordString: string): string[] | null {\n // DD formats from @accelint/geo (no degree symbols):\n // \"40.7128 N / -74.006 W\" or \"0 N / 180 W\"\n // Also handle user input with symbols:\n // \"89.765432° N / 123.456789° W\" or \"89.765432, -123.456789\"\n\n // Match DD format with optional degree symbols and optional direction letters\n const match = coordString.match(\n /([-]?\\d+\\.?\\d*)°?\\s*([NS])?\\s*[,/\\s]+\\s*([-]?\\d+\\.?\\d*)°?\\s*([EW])?/i,\n );\n if (!match) {\n return null;\n }\n\n let lat = match[1];\n let lon = match[3];\n\n if (!(lat && lon)) {\n return null;\n }\n\n if (match[2]?.toUpperCase() === 'S' && !lat.startsWith('-')) {\n lat = `-${lat}`;\n }\n if (match[4]?.toUpperCase() === 'W' && !lon.startsWith('-')) {\n lon = `-${lon}`;\n }\n\n return [lat, lon];\n}\n\n/**\n * Parse DDM coordinate string to segments\n *\n * Extracts segment values from a formatted DDM coordinate string. This duplicates\n * parsing logic from @accelint/geo's parseDegreesDecimalMinutes, but is necessary because\n * geo doesn't expose the parsed components - only formatted strings and raw DD numbers.\n *\n * Part of the Geo Format → String → Regex Parse → Segments flow (circular conversion).\n *\n * @internal\n */\nfunction parseDDMCoordinateString(coordString: string): string[] | null {\n // DDM formats from @accelint/geo (no symbols):\n // \"40 42.768 N / 74 0.36 W\"\n // Also handle user input with symbols:\n // \"89° 45.9259' N / 123° 27.4073' W\"\n\n // Match DDM format with optional degree and minute symbols\n const match = coordString.match(\n /(\\d+)°?\\s+([\\d.]+)'?\\s+([NS])\\s*[,/]\\s*(\\d+)°?\\s+([\\d.]+)'?\\s+([EW])/i,\n );\n if (!match) {\n return null;\n }\n return [\n match[1] as string,\n match[2] as string,\n match[3] as string,\n match[4] as string,\n match[5] as string,\n match[6] as string,\n ];\n}\n\n/**\n * Parse DMS coordinate string to segments\n *\n * Extracts segment values from a formatted DMS coordinate string. This duplicates\n * parsing logic from @accelint/geo's parseDegreesMinutesSeconds, but is necessary because\n * geo doesn't expose the parsed components - only formatted strings and raw DD numbers.\n *\n * Part of the Geo Format → String → Regex Parse → Segments flow (circular conversion).\n *\n * @internal\n */\nfunction parseDMSCoordinateString(coordString: string): string[] | null {\n // DMS formats from @accelint/geo (no symbols):\n // \"40 42 46.08 N / 74 0 21.60 W\"\n // Also handle user input with symbols:\n // \"89° 45' 55.56\" N / 123° 27' 24.44\" W\"\n\n // Match DMS format with optional degree, minute, and second symbols\n const match = coordString.match(\n /(\\d+)°?\\s+(\\d+)'?\\s+([\\d.]+)\"?\\s+([NS])\\s*[,/]\\s*(\\d+)°?\\s+(\\d+)'?\\s+([\\d.]+)\"?\\s+([EW])/i,\n );\n if (!match) {\n return null;\n }\n return [\n match[1] as string,\n match[2] as string,\n match[3] as string,\n match[4] as string,\n match[5] as string,\n match[6] as string,\n match[7] as string,\n match[8] as string,\n ];\n}\n\n/**\n * Parse MGRS coordinate string to segments\n *\n * Extracts segment values from a formatted MGRS coordinate string. This duplicates\n * parsing logic from @accelint/geo's parseMGRS, but is necessary because\n * geo doesn't expose the parsed components - only formatted strings and raw DD numbers.\n *\n * Part of the Geo Format → String → Regex Parse → Segments flow (circular conversion).\n *\n * @internal\n */\nfunction parseMGRSCoordinateString(coordString: string): string[] | null {\n // MGRS: \"18T WM 12345 67890\"\n const match = coordString.match(/(\\d+)([A-Z])\\s+([A-Z]{2})\\s+(\\d+)\\s+(\\d+)/i);\n if (!match) {\n return null;\n }\n return [\n match[1] as string,\n match[2] as string,\n match[3] as string,\n match[4] as string,\n match[5] as string,\n ];\n}\n\n/**\n * Parse UTM coordinate string to segments\n *\n * Extracts segment values from a formatted UTM coordinate string. This duplicates\n * parsing logic from @accelint/geo's parseUTM, but is necessary because\n * geo doesn't expose the parsed components - only formatted strings and raw DD numbers.\n *\n * Part of the Geo Format → String → Regex Parse → Segments flow (circular conversion).\n *\n * @internal\n */\nfunction parseUTMCoordinateString(coordString: string): string[] | null {\n // UTM: \"18N 585628 4511644\" or \"18 N 585628 4511644\" (with optional space)\n const match = coordString.match(/(\\d+)\\s*([NS])\\s+(\\d+)\\s+(\\d+)/i);\n if (!match) {\n return null;\n }\n return [\n match[1] as string,\n match[2] as string,\n match[3] as string,\n match[4] as string,\n ];\n}\n\n/**\n * Parse a coordinate string into segment values\n *\n * Converts a formatted coordinate string (from @accelint/geo output or user input)\n * back into individual segment values for display.\n *\n * This is the inverse of formatSegmentsToCoordinateString.\n *\n * **Note on Duplication**: This function and its helpers (parseDDCoordinateString,\n * parseDDMCoordinateString, etc.) duplicate parsing logic that already exists in\n * the @accelint/geo package parsers:\n *\n * - Geo parsers: parseDecimalDegrees, parseDegreesDecimalMinutes, etc.\n * - These functions: parseDDCoordinateString, parseDDMCoordinateString, etc.\n *\n * Both use regex patterns to extract coordinate components from strings. The duplication\n * exists because:\n *\n * 1. **Geo parsers** extract components, validate them, convert to DD, then format back to strings\n * 2. **This function** extracts components from those formatted strings for the UI\n *\n * We're essentially undoing the formatting that geo just did. This is the second half\n * of the circular conversion described in convertDDToDisplaySegments.\n *\n * **Why we can't use geo parsers directly**: The geo parsers return coord objects with\n * only `coord.raw` (DD numbers) and formatting methods. They don't expose the parsed\n * segment components we need for the UI (e.g., the degrees, minutes, and direction values).\n *\n * **Parsing Order**:\n * - **convertDisplaySegmentsToDD**: Segments → String → **Geo parse** → DD ✓ (efficient)\n * - **convertDDToDisplaySegments**: DD → String → Geo parse → Geo format → **This parse** → Segments ✗ (circular)\n *\n * @param coordString - Formatted coordinate string\n * @param format - The coordinate system format\n * @returns Array of segment values, or null if parsing fails\n */\nexport function parseCoordinateStringToSegments(\n coordString: string,\n format: CoordinateSystem,\n): string[] | null {\n if (!coordString) {\n return null;\n }\n\n try {\n switch (format) {\n case 'dd':\n return parseDDCoordinateString(coordString);\n case 'ddm':\n return parseDDMCoordinateString(coordString);\n case 'dms':\n return parseDMSCoordinateString(coordString);\n case 'mgrs':\n return parseMGRSCoordinateString(coordString);\n case 'utm':\n return parseUTMCoordinateString(coordString);\n default:\n return null;\n }\n } catch (_error) {\n return null;\n }\n}\n\n/**\n * Convert DD (internal format) to display format segment values\n *\n * Takes a CoordinateValue in Decimal Degrees format and converts it to the\n * segment values needed for the specified display format.\n *\n * Uses @accelint/geo to ensure accurate conversion between coordinate systems.\n *\n * **Note on Circular Conversion**: This function demonstrates the circular conversion\n * pattern discussed in the module documentation. The flow is:\n *\n * 1. Start with DD value: `{ lat: 40.7128, lon: -74.0060 }`\n * 2. Convert to coordinate string: `\"40.7128 / -74.006\"`\n * 3. Parse with geo package (creates coord object with internal parsed state)\n * 4. Format to target system using geo: `coord.ddm()` → `\"40 42.768 N / 74 0.36 W\"`\n * 5. Parse the formatted string AGAIN with regex to extract segments: `['40', '42.768', 'N', ...]`\n *\n * This is inefficient because:\n * - The geo package already has the component values (degrees, minutes, direction) internally\n * - We format them into a string, then immediately parse the string back apart\n * - The regex parsing duplicates work the geo parsers already did\n *\n * However, this approach is necessary because:\n * - The geo package only exposes `coord.raw` (DD numbers) and formatted strings\n * - It doesn't expose the intermediate component values we need for the UI segments\n * - We need individual segment values for separate input fields\n *\n * **Future Improvement**: If geo package exported component extractors like:\n * ```typescript\n * coord.components.ddm // { latDeg: 40, latMin: 42.768, latDir: 'N', ... }\n * ```\n * Then we could eliminate the format→parse cycle entirely.\n *\n * @param value - Coordinate value in DD format `{ lat: number, lon: number }`\n * @param format - Target display format\n * @returns Array of segment values for display, or null if conversion fails\n *\n * @example\n * const segments = convertDDToDisplaySegments({ lat: 40.7128, lon: -74.0060 }, 'ddm');\n * // Returns: ['40', '42.7680', 'N', '74', '0.3600', 'W']\n */\nexport function convertDDToDisplaySegments(\n value: CoordinateValue,\n format: CoordinateSystem,\n): string[] | null {\n if (\n !value ||\n typeof value.lat !== 'number' ||\n typeof value.lon !== 'number'\n ) {\n return null;\n }\n\n try {\n const create = createCoordinate(coordinateSystems.dd, 'LATLON');\n\n // Round to 5 decimal places to prevent precision issues with geo package\n // Use signed numbers (not cardinal directions) for reliable conversions to all formats\n const lat = Number(value.lat.toFixed(5));\n const lon = Number(value.lon.toFixed(5));\n const inputCoordString = `${lat} / ${lon}`;\n\n const coord = create(inputCoordString);\n\n if (!coord.valid) {\n return null;\n }\n\n // Format the coordinate using geo package formatters\n // These return complete coordinate strings (e.g., \"40 42.768 N / 74 0.36 W\")\n let coordString: string;\n switch (format) {\n case 'dd':\n coordString = coord.dd();\n break;\n case 'ddm':\n coordString = coord.ddm();\n break;\n case 'dms':\n coordString = coord.dms();\n break;\n case 'mgrs':\n coordString = coord.mgrs();\n break;\n case 'utm':\n coordString = coord.utm();\n break;\n default:\n return null;\n }\n\n // Parse the formatted string to extract individual segment values\n // This is the circular part: geo formatted it, now we parse it back apart\n // Necessary because geo doesn't expose the components directly\n const segments = parseCoordinateStringToSegments(coordString, format);\n return segments;\n } catch (error) {\n logger\n .withContext({\n value: String(value),\n format: String(format),\n })\n .withError(error)\n .error('Failed to convert DD to display');\n return null;\n }\n}\n\n/**\n * Convert display format segment values to DD (internal format)\n *\n * Takes segment values from user input and converts them to a CoordinateValue\n * in Decimal Degrees format using @accelint/geo for validation and conversion.\n *\n * **Note on Efficiency**: This function demonstrates the EFFICIENT conversion direction.\n * The flow is:\n *\n * 1. Start with UI segments: `['40', '42.768', 'N', '74', '0.36', 'W']`\n * 2. Build coordinate string: `\"40° 42.768' N, 74° 0.36' W\"`\n * 3. Parse with geo package (validates and converts internally)\n * 4. Extract DD from coord.raw: `{ lat: 40.7128, lon: -74.0060 }`\n *\n * This is efficient because:\n * - We let geo do what it's designed for: parsing and validating coordinate strings\n * - We extract the DD values directly from `coord.raw` (no string parsing needed)\n * - Single direction: Segments → String → Geo Parse → DD (no circular conversion)\n *\n * Contrast with `convertDDToDisplaySegments` which has the circular pattern:\n * DD → String → Geo Parse → Geo Format → String → Regex Parse → Segments\n *\n * @param segments - Array of segment values from user input\n * @param format - The coordinate system format of the segments\n * @returns CoordinateValue in DD format, or null if invalid\n *\n * @example\n * const coord = convertDisplaySegmentsToDD(['40', '42.7680', 'N', '74', '0.3600', 'W'], 'ddm');\n * // Returns: { lat: 40.7128, lon: -74.0060 }\n */\nexport function convertDisplaySegmentsToDD(\n segments: string[],\n format: CoordinateSystem,\n): CoordinateValue | null {\n // Build coordinate string from segments for geo parsing\n const coordString = formatSegmentsToCoordinateString(segments, format);\n if (!coordString) {\n return null;\n }\n\n try {\n // Parse and validate with geo package\n const create = createCoordinate(coordinateSystems[format], 'LATLON');\n const coord = create(coordString);\n\n if (!coord.valid) {\n // Return null for invalid coordinates (errors will be handled separately)\n return null;\n }\n\n // Extract DD values directly from coord.raw (no string parsing needed)\n const { LAT, LON } = coord.raw;\n\n return {\n lat: LAT,\n lon: LON,\n };\n } catch (error) {\n logger\n .withContext({\n segments: JSON.stringify(segments),\n format: String(format),\n })\n .withError(error)\n .error('Failed to convert display to DD');\n return null;\n }\n}\n\n/**\n * Validate coordinate segments and return errors\n *\n * Uses @accelint/geo to validate the segments and returns any validation errors.\n * Only validates when all required segments are filled.\n *\n * @param segments - Array of segment values from user input\n * @param format - The coordinate system format\n * @returns Array of error messages, empty if valid or incomplete\n *\n * @example\n * const errors = validateCoordinateSegments(['91', '0', 'N', '0', '0', 'E'], 'ddm');\n * // Returns: ['Invalid coordinate value']\n */\nexport function validateCoordinateSegments(\n segments: string[],\n format: CoordinateSystem,\n): string[] {\n if (segments.some((seg) => seg === '' || seg === undefined)) {\n return [];\n }\n\n const coordString = formatSegmentsToCoordinateString(segments, format);\n if (!coordString) {\n return ['Invalid coordinate value'];\n }\n\n try {\n const create = createCoordinate(coordinateSystems[format], 'LATLON');\n const coord = create(coordString);\n\n if (!coord.valid) {\n return ['Invalid coordinate value'];\n }\n\n return [];\n } catch (_error) {\n return ['Invalid coordinate value'];\n }\n}\n\n/**\n * Check if all segments are filled\n *\n * Helper to determine if the user has completed entering all segment values.\n * Used to determine when to trigger validation.\n *\n * @param segments - Array of segment values\n * @returns True if all segments have values, false otherwise\n */\nexport function areAllSegmentsFilled(segments: string[]): boolean {\n return segments.every((seg) => seg !== '' && seg !== undefined);\n}\n\n/**\n * Check if any segments have values\n *\n * Helper to determine if the user has started entering coordinate values.\n *\n * @param segments - Array of segment values\n * @returns True if any segment has a value, false if all empty\n */\nexport function hasAnySegmentValue(segments: string[]): boolean {\n return segments.some((seg) => seg !== '' && seg !== undefined);\n}\n\n/**\n * Create an invalid result object for all coordinate systems\n * @internal\n */\nfunction createInvalidResult(): Record<\n CoordinateSystem,\n CoordinateFormatResult\n> {\n return {\n dd: { value: COORDINATE_ERROR_MESSAGES.INVALID, isValid: false },\n ddm: { value: COORDINATE_ERROR_MESSAGES.INVALID, isValid: false },\n dms: { value: COORDINATE_ERROR_MESSAGES.INVALID, isValid: false },\n mgrs: { value: COORDINATE_ERROR_MESSAGES.INVALID, isValid: false },\n utm: { value: COORDINATE_ERROR_MESSAGES.INVALID, isValid: false },\n };\n}\n\n/**\n * Validate coordinate value\n * @internal\n */\nfunction isValidCoordinateValue(value: CoordinateValue | null): boolean {\n return (\n value !== null &&\n typeof value.lat === 'number' &&\n typeof value.lon === 'number'\n );\n}\n\n/**\n * Check if error is due to geographic limitation (poles)\n * @internal\n */\nfunction isGeographicLimitationError(error: unknown): boolean {\n const errorMessage = error instanceof Error ? error.message : String(error);\n return (\n errorMessage.includes('outside UTM limits') ||\n errorMessage.includes('invalid UTM zone')\n );\n}\n\n/**\n * Convert coordinate to a specific format with error handling\n * @internal\n */\nfunction convertToFormat(\n coord: {\n dd: () => string;\n ddm: () => string;\n dms: () => string;\n mgrs: () => string;\n utm: () => string;\n },\n format: CoordinateSystem,\n value: CoordinateValue,\n): CoordinateFormatResult {\n try {\n let formattedValue: string;\n switch (format) {\n case 'dd':\n formattedValue = coord.dd();\n break;\n case 'ddm':\n formattedValue = coord.ddm();\n break;\n case 'dms':\n formattedValue = coord.dms();\n break;\n case 'mgrs':\n formattedValue = coord.mgrs();\n break;\n case 'utm':\n formattedValue = coord.utm();\n break;\n default:\n return { value: COORDINATE_ERROR_MESSAGES.INVALID, isValid: false };\n }\n return { value: formattedValue, isValid: true };\n } catch (error) {\n // Handle geographic limitations for MGRS/UTM\n if (\n (format === 'mgrs' || format === 'utm') &&\n isGeographicLimitationError(error)\n ) {\n return {\n value: COORDINATE_ERROR_MESSAGES.NOT_AVAILABLE_AT_POLES,\n isValid: false,\n };\n }\n\n // Log other errors in development\n logger\n .withContext({\n value: JSON.stringify(value),\n })\n .withError(error)\n .error(`Failed to convert to ${format}`);\n return {\n value: COORDINATE_ERROR_MESSAGES.CONVERSION_FAILED,\n isValid: false,\n };\n }\n}\n\n/**\n * Get all coordinate formats for a given DD value\n *\n * Converts a Decimal Degrees coordinate to all 5 supported coordinate systems\n * for display in the format conversion popover.\n *\n * Each format is tried independently - if one fails (e.g., UTM/MGRS at poles),\n * the others can still succeed.\n *\n * @param value - Coordinate value in DD format `{ lat: number, lon: number }`\n * @returns Object containing formatted strings and validity status for all coordinate systems\n *\n * @example\n * const formats = getAllCoordinateFormats({ lat: 40.7128, lon: -74.0060 });\n * // Returns: {\n * // dd: { value: \"40.7128 N / 74.006 W\", isValid: true },\n * // ddm: { value: \"40 42.768 N / 74 0.36 W\", isValid: true },\n * // dms: { value: \"40 42 46.08 N / 74 0 21.6 W\", isValid: true },\n * // mgrs: { value: \"18T WL 80654 06346\", isValid: true },\n * // utm: { value: \"18N 585628 4511644\", isValid: true }\n * // }\n *\n * @example\n * const formats = getAllCoordinateFormats({ lat: 90, lon: 0 });\n * // Returns: {\n * // dd: { value: \"90 N / 0 E\", isValid: true },\n * // ddm: { value: \"90 0 N / 0 0 E\", isValid: true },\n * // dms: { value: \"90 0 0 N / 0 0 0 E\", isValid: true },\n * // mgrs: { value: \"Not available at poles\", isValid: false },\n * // utm: { value: \"Not available at poles\", isValid: false }\n * // }\n */\nexport function getAllCoordinateFormats(\n value: CoordinateValue | null,\n): Record<CoordinateSystem, CoordinateFormatResult> {\n const invalidResult = createInvalidResult();\n\n if (!isValidCoordinateValue(value)) {\n return invalidResult;\n }\n\n const validValue = value as CoordinateValue;\n\n try {\n const create = createCoordinate(coordinateSystems.dd, 'LATLON');\n const coord = create(`${validValue.lat} / ${validValue.lon}`);\n\n if (!coord.valid) {\n return invalidResult;\n }\n\n const result = {} as Record<CoordinateSystem, CoordinateFormatResult>;\n\n for (const format of COORDINATE_SYSTEMS) {\n result[format] = convertToFormat(coord, format, validValue);\n }\n\n return result;\n } catch (error) {\n logger\n .withContext({\n value: JSON.stringify(validValue),\n })\n .withError(error)\n .error('Failed to get all coordinate formats');\n return invalidResult;\n }\n}\n\n/**\n * Check if pasted text looks like a complete coordinate string\n *\n * Uses heuristics to detect if the pasted text contains a full coordinate\n * rather than just a single segment value. This prevents intercepting\n * single-segment pastes.\n *\n * Indicators of a complete coordinate:\n * - Contains separators: comma, slash, or multiple consecutive spaces\n * - Contains coordinate symbols: °, ′, ″, ', \"\n * - Multiple numbers separated by whitespace\n *\n * @param text - The pasted text to check\n * @returns True if it looks like a complete coordinate string\n *\n * @example\n * isCompleteCoordinate(\"40.7128, -74.0060\") // true - contains comma\n * isCompleteCoordinate(\"40° 42' 46\\\" N / 74° 0' 22\\\" W\") // true - contains symbols\n * isCompleteCoordinate(\"18T WM 12345 67890\") // true - multiple parts\n * isCompleteCoordinate(\"42\") // false - single number\n * isCompleteCoordinate(\"N\") // false - single letter\n */\nexport function isCompleteCoordinate(text: string): boolean {\n if (!text || text.trim() === '') {\n return false;\n }\n\n const hasSeparators = /[,/]|°|′|″|['\"]|\\s{2,}/.test(text);\n const numbers = text.match(/\\d+/g) || [];\n const hasMultipleNumbers = numbers.length >= 2;\n\n // Explicitly detect MGRS format: <zone><band> <grid> <easting> <northing>\n // Example: \"18T WL 80654 06346\"\n const isMGRS = /^\\d{1,2}[A-Z]\\s+[A-Z]{2}\\s+\\d+\\s+\\d+$/i.test(text.trim());\n\n // Explicitly detect UTM format: <zone><hemisphere> <easting> <northing>\n // Example: \"18N 585628 4511644\"\n const isUTM = /^\\d{1,2}[NS]\\s+\\d+\\s+\\d+$/i.test(text.trim());\n\n return hasSeparators || hasMultipleNumbers || isMGRS || isUTM;\n}\n\n/**\n * Attempt to parse pasted text as all coordinate formats\n *\n * Tries to parse the pasted text using each of the 5 coordinate system parsers.\n * Returns all formats that successfully parse the text.\n *\n * This enables automatic detection of coordinate format and disambiguation\n * when multiple formats match the same input string.\n *\n * @param pastedText - The raw text from clipboard\n * @returns Array of successfully parsed coordinate matches (may be empty)\n *\n * @example\n * const matches = parseCoordinatePaste(\"40.7128, -74.0060\");\n * // Returns: [{ format: 'dd', value: { lat: 40.7128, lon: -74.0060 }, displayString: \"...\" }]\n *\n * @example\n * const matches = parseCoordinatePaste(\"18T WM 12345 67890\");\n * // Returns: [{ format: 'mgrs', value: { lat: ..., lon: ... }, displayString: \"...\" }]\n *\n * @example\n * const matches = parseCoordinatePaste(\"invalid text\");\n * // Returns: []\n */\nexport function parseCoordinatePaste(\n pastedText: string,\n): ParsedCoordinateMatch[] {\n if (!pastedText || pastedText.trim() === '') {\n return [];\n }\n\n const matches: ParsedCoordinateMatch[] = [];\n\n for (const format of COORDINATE_SYSTEMS) {\n try {\n const create = createCoordinate(coordinateSystems[format], 'LATLON');\n const coord = create(pastedText.trim());\n\n if (coord.valid) {\n const { LAT, LON } = coord.raw;\n\n let displayString: string;\n switch (format) {\n case 'dd':\n displayString = coord.dd();\n break;\n case 'ddm':\n displayString = coord.ddm();\n break;\n case 'dms':\n displayString = coord.dms();\n break;\n case 'mgrs':\n displayString = coord.mgrs();\n break;\n case 'utm':\n displayString = coord.utm();\n break;\n default:\n displayString = '';\n }\n\n matches.push({\n format,\n value: { lat: LAT, lon: LON },\n displayString,\n });\n }\n } catch (error) {\n // Log parsing errors in development for debugging\n logger\n .withContext({\n pastedText: pastedText.trim(),\n format: String(format),\n })\n .withError(error)\n .warn(`Failed to parse as ${format}`);\n // Continue trying other parsers\n }\n }\n\n return matches;\n}\n\n/**\n * Check if two coordinates are equal within epsilon tolerance\n */\nexport function areCoordinatesEqual(\n coord1: { lat: number; lon: number },\n coord2: { lat: number; lon: number },\n epsilon = COORDINATE_EPSILON,\n): boolean {\n return (\n Math.abs(coord1.lat - coord2.lat) < epsilon &&\n Math.abs(coord1.lon - coord2.lon) < epsilon\n );\n}\n\n/**\n * Deduplicate coordinate matches by location, keeping first match for each unique location\n */\nexport function deduplicateMatchesByLocation(\n matches: ParsedCoordinateMatch[],\n): ParsedCoordinateMatch[] {\n const uniqueMatches: ParsedCoordinateMatch[] = [];\n\n for (const match of matches) {\n const isDuplicate = uniqueMatches.some((existing) =>\n areCoordinatesEqual(existing.value, match.value),\n );\n\n if (!isDuplicate) {\n uniqueMatches.push(match);\n }\n }\n\n return uniqueMatches;\n}\n"]}
1
+ {"version":3,"sources":["../../../src/components/coordinate-field/coordinate-utils.ts"],"names":["logger","getLogger","segments","lonNum","latNum","lonDir","latDir","format","seg","decimals","value","match","lat","lon","formatDecimalPrecision","coordString","coordinateSystems","c","d","inputCoordString","coord","create","LAT","LON","formattedValue","s","parseDDMCoordinateString","raw","parseDMSCoordinateString","isGeographicLimitationError","isValidCoordinateValue","validValue","COORDINATE_SYSTEMS","convertToFormat","text","hasMultipleNumbers","isMGRS","isUTM","pastedText","displayString","matches","coord2","epsilon","COORDINATE_EPSILON","coord1","areCoordinatesEqual","existing","uniqueMatches"],"mappings":"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","file":"coordinate-utils.js","sourcesContent":["/*\n * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport { getLogger } from '@accelint/logger';\n\nconst logger = getLogger({\n enabled: process.env.NODE_ENV !== 'production',\n level: 'debug',\n prefix: '[CoordinateField]',\n pretty: true,\n});\n\n/**\n * Coordinate Conversion Utilities\n *\n * This module provides utilities for converting between:\n * 1. Segment values (user input) → Coordinate strings (for @accelint/geo parsing)\n * 2. Coordinate strings (from @accelint/geo output) → Segment values (for display)\n * 3. Decimal Degrees (internal format) ↔ Display format segment values\n *\n * All conversions use the local @accelint/geo package for accurate coordinate parsing\n * and conversion between coordinate systems.\n *\n * ## Architecture: Bridge Layer Between UI and Geo Package\n *\n * This module serves as a bridge layer to handle the impedance mismatch between:\n * - **UI Requirements**: Individual segment fields (e.g., degrees, minutes, seconds, direction)\n * - **Geo Package API**: Complete coordinate strings (e.g., \"40° 42' 46\" N / 74° 0' 22\" W\")\n *\n * ### Why This Bridge Layer Exists\n *\n * The @accelint/geo package provides excellent coordinate parsing, validation, and conversion,\n * but its API is designed around complete coordinate strings:\n *\n * ```typescript\n * // What geo provides:\n * const coord = createCoordinate(coordinateSystems.ddm, 'LATLON')('40 42.768 N / 74 0.36 W');\n * coord.dd() // Returns: \"40.7128 N / 74.006 W\" (formatted string)\n * coord.ddm() // Returns: \"40 42.768 N / 74 0.36 W\" (formatted string)\n * coord.raw // Returns: { LAT: 40.7128, LON: -74.006 } (only DD numbers)\n * ```\n *\n * The coordinate-field component needs segment-level data for individual input fields:\n * - DDM: ['40', '42.768', 'N', '74', '0.36', 'W'] ← Not provided by geo\n * - DMS: ['40', '42', '46.08', 'N', '74', '0', '21.6', 'W'] ← Not provided by geo\n *\n * ### Current Limitations and Duplication\n *\n * Because the geo package only exposes:\n * 1. **Formatters** that return complete strings (coord.ddm(), coord.dms(), etc.)\n * 2. **Raw values** in Decimal Degrees only (coord.raw)\n *\n * This module must:\n * 1. Build coordinate strings from segments → Pass to geo for parsing\n * 2. Parse geo's formatted output strings → Extract segments using regex\n *\n * This creates a circular conversion flow for DD → Display Segments:\n * ```\n * DD value → String → Geo parse → Geo format → String → Regex parse → Segments\n * ```\n *\n * **Note on Duplication**: The regex parsing in this module duplicates work that the\n * geo package parsers already do internally. However, since geo doesn't expose the\n * parsed segment components (only formatted strings and raw DD values), we must\n * re-parse its output to extract the individual segments for the UI.\n *\n * ### What Would Eliminate This Duplication\n *\n * If the geo package exported component-level converters like:\n * ```typescript\n * // Hypothetical API that would eliminate the bridge layer:\n * export function ddToDdmComponents(dd: number): {\n * degrees: number;\n * minutes: number;\n * direction: 'N' | 'S' | 'E' | 'W';\n * }\n * ```\n *\n * Then this bridge layer could be significantly simplified. The math for these conversions\n * exists in the geo package's formatters (e.g., formatDegreesDecimalMinutes), but it's\n * wrapped in string formatting logic rather than exposed as standalone utilities.\n *\n * ### Conversion Efficiency\n *\n * - **Efficient Path** (Display Segments → DD): Segments → String → Geo parse → DD\n * - Uses geo package for all parsing and validation ✓\n *\n * - **Inefficient Path** (DD → Display Segments): DD → String → Geo parse → Geo format → Regex parse → Segments\n * - Circular conversion with redundant string parsing/formatting ✗\n * - Necessary given current geo API limitations\n */\n\nimport { coordinateSystems, createCoordinate } from '@accelint/geo';\nimport {\n COORDINATE_SYSTEMS,\n type CoordinateSystem,\n type CoordinateValue,\n type ParsedCoordinateMatch,\n} from './types';\n\n/** Epsilon for coordinate equality comparison (≈11cm precision at equator) */\nexport const COORDINATE_EPSILON = 0.000001;\n\n/**\n * Error message constants for coordinate format conversion\n * @internal\n */\nexport const COORDINATE_ERROR_MESSAGES = {\n INVALID: 'Invalid coordinate',\n CONVERSION_FAILED: 'Conversion failed',\n NOT_AVAILABLE_AT_POLES: 'Not available at poles',\n} as const;\n\n/**\n * Result of coordinate format conversion\n */\nexport interface CoordinateFormatResult {\n /** The formatted coordinate string or error message */\n value: string;\n /** Whether the coordinate format is valid and can be used/copied */\n isValid: boolean;\n}\n\n/**\n * Format DD (Decimal Degrees) segments to coordinate string\n *\n * Bridge function that builds a coordinate string from UI segment values\n * for passing to the geo package parser. Part of the Segments → String → Geo Parse flow.\n *\n * @internal\n */\nfunction formatDDSegments(segments: string[]): string | null {\n if (segments.length < 2) {\n return null;\n }\n const latNum = Number.parseFloat(segments[0] as string);\n const lonNum = Number.parseFloat(segments[1] as string);\n\n if (Number.isNaN(latNum) || Number.isNaN(lonNum)) {\n return null;\n }\n\n const latDir = latNum >= 0 ? 'N' : 'S';\n const lonDir = lonNum >= 0 ? 'E' : 'W';\n\n return `${Math.abs(latNum)} ${latDir} / ${Math.abs(lonNum)} ${lonDir}`;\n}\n\n/**\n * Format DDM (Degrees Decimal Minutes) segments to coordinate string\n *\n * Bridge function that builds a coordinate string from UI segment values\n * for passing to the geo package parser. Part of the Segments → String → Geo Parse flow.\n *\n * @internal\n */\nfunction formatDDMSegments(segments: string[]): string | null {\n if (segments.length < 6) {\n return null;\n }\n return `${segments[0]}° ${segments[1]}' ${segments[2]}, ${segments[3]}° ${segments[4]}' ${segments[5]}`;\n}\n\n/**\n * Format DMS (Degrees Minutes Seconds) segments to coordinate string\n *\n * Bridge function that builds a coordinate string from UI segment values\n * for passing to the geo package parser. Part of the Segments → String → Geo Parse flow.\n *\n * @internal\n */\nfunction formatDMSSegments(segments: string[]): string | null {\n if (segments.length < 8) {\n return null;\n }\n return `${segments[0]}° ${segments[1]}' ${segments[2]}\" ${segments[3]}, ${segments[4]}° ${segments[5]}' ${segments[6]}\" ${segments[7]}`;\n}\n\n/**\n * Format MGRS (Military Grid Reference System) segments to coordinate string\n *\n * Bridge function that builds a coordinate string from UI segment values\n * for passing to the geo package parser. Part of the Segments → String → Geo Parse flow.\n *\n * @internal\n */\nfunction formatMGRSSegments(segments: string[]): string | null {\n if (segments.length < 5) {\n return null;\n }\n return `${segments[0]}${segments[1]} ${segments[2]} ${segments[3]} ${segments[4]}`;\n}\n\n/**\n * Format UTM (Universal Transverse Mercator) segments to coordinate string\n *\n * Bridge function that builds a coordinate string from UI segment values\n * for passing to the geo package parser. Part of the Segments → String → Geo Parse flow.\n *\n * @internal\n */\nfunction formatUTMSegments(segments: string[]): string | null {\n if (segments.length < 4) {\n return null;\n }\n return `${segments[0]}${segments[1]} ${segments[2]} ${segments[3]}`;\n}\n\n/**\n * Format segment values into a coordinate string suitable for @accelint/geo parsing\n *\n * Converts an array of segment values into a string format that the geo package\n * parsers can understand. Each format has different requirements:\n *\n * DD: [lat, lon] → \"lat, lon\"\n * DDM: [latDeg, latMin, latDir, lonDeg, lonMin, lonDir] → \"latDeg° latMin' latDir, lonDeg° lonMin' lonDir\"\n * DMS: [latDeg, latMin, latSec, latDir, lonDeg, lonMin, lonSec, lonDir] → \"latDeg° latMin' latSec\\\" latDir, lonDeg° lonMin' lonSec\\\" lonDir\"\n * MGRS: [zone, band, grid, easting, northing] → \"zone+band grid easting northing\"\n * UTM: [zone, hemisphere, easting, northing] → \"zone+hemisphere easting northing\"\n *\n * @param segments - Array of segment values from user input\n * @param format - The coordinate system format\n * @returns Formatted coordinate string, or null if segments are invalid\n */\nexport function formatSegmentsToCoordinateString(\n segments: string[],\n format: CoordinateSystem,\n): string | null {\n if (segments.some((seg) => seg === '' || seg === undefined)) {\n return null;\n }\n\n try {\n switch (format) {\n case 'dd':\n return formatDDSegments(segments);\n case 'ddm':\n return formatDDMSegments(segments);\n case 'dms':\n return formatDMSSegments(segments);\n case 'mgrs':\n return formatMGRSSegments(segments);\n case 'utm':\n return formatUTMSegments(segments);\n default:\n return null;\n }\n } catch (_error) {\n return null;\n }\n}\n\n/**\n * Format a decimal string to specified precision for display\n *\n * This is used to ensure consistent decimal precision when displaying\n * coordinate segments, regardless of the precision returned by the geo package.\n *\n * @param value - The decimal string to format\n * @param decimals - Number of decimal places to display\n * @returns Formatted string with specified precision\n * @internal\n */\nfunction formatDecimalPrecision(value: string, decimals: number): string {\n const num = Number.parseFloat(value);\n if (Number.isNaN(num)) {\n return value;\n }\n // Round to specified decimals, then remove trailing zeros\n return Number.parseFloat(num.toFixed(decimals)).toString();\n}\n\n/**\n * Parse DD coordinate string to segments\n *\n * Extracts segment values from a formatted DD coordinate string. This duplicates\n * parsing logic from @accelint/geo's parseDecimalDegrees, but is necessary because\n * geo doesn't expose the parsed components - only formatted strings and raw DD numbers.\n *\n * Part of the Geo Format → String → Regex Parse → Segments flow (circular conversion).\n *\n * @internal\n */\nfunction parseDDCoordinateString(coordString: string): string[] | null {\n // DD formats from @accelint/geo (no degree symbols):\n // \"40.7128 N / -74.006 W\" or \"0 N / 180 W\"\n // Also handle user input with symbols:\n // \"89.765432° N / 123.456789° W\" or \"89.765432, -123.456789\"\n\n // Match DD format with optional degree symbols and optional direction letters\n const match = coordString.match(\n /([-]?\\d+\\.?\\d*)°?\\s*([NS])?\\s*[,/\\s]+\\s*([-]?\\d+\\.?\\d*)°?\\s*([EW])?/i,\n );\n if (!match) {\n return null;\n }\n\n let lat = match[1];\n let lon = match[3];\n\n if (!(lat && lon)) {\n return null;\n }\n\n if (match[2]?.toUpperCase() === 'S' && !lat.startsWith('-')) {\n lat = `-${lat}`;\n }\n if (match[4]?.toUpperCase() === 'W' && !lon.startsWith('-')) {\n lon = `-${lon}`;\n }\n\n return [lat, lon];\n}\n\n/**\n * Parse DDM coordinate string to segments\n *\n * Extracts segment values from a formatted DDM coordinate string. This duplicates\n * parsing logic from @accelint/geo's parseDegreesDecimalMinutes, but is necessary because\n * geo doesn't expose the parsed components - only formatted strings and raw DD numbers.\n *\n * Part of the Geo Format → String → Regex Parse → Segments flow (circular conversion).\n *\n * @internal\n */\nfunction parseDDMCoordinateString(coordString: string): string[] | null {\n // DDM formats from @accelint/geo (no symbols):\n // \"40 42.768 N / 74 0.36 W\"\n // Also handle user input with symbols:\n // \"89° 45.9259' N / 123° 27.4073' W\"\n\n // Match DDM format with optional degree and minute symbols\n const match = coordString.match(\n /(\\d+)°?\\s+([\\d.]+)'?\\s+([NS])\\s*[,/]\\s*(\\d+)°?\\s+([\\d.]+)'?\\s+([EW])/i,\n );\n if (!match) {\n return null;\n }\n // Round minutes to 4 decimal places for display (CJCSI 3900.01E compliance)\n return [\n match[1] as string,\n formatDecimalPrecision(match[2] as string, 4),\n match[3] as string,\n match[4] as string,\n formatDecimalPrecision(match[5] as string, 4),\n match[6] as string,\n ];\n}\n\n/**\n * Parse DMS coordinate string to segments\n *\n * Extracts segment values from a formatted DMS coordinate string. This duplicates\n * parsing logic from @accelint/geo's parseDegreesMinutesSeconds, but is necessary because\n * geo doesn't expose the parsed components - only formatted strings and raw DD numbers.\n *\n * Part of the Geo Format → String → Regex Parse → Segments flow (circular conversion).\n *\n * @internal\n */\nfunction parseDMSCoordinateString(coordString: string): string[] | null {\n // DMS formats from @accelint/geo (no symbols):\n // \"40 42 46.08 N / 74 0 21.60 W\"\n // Also handle user input with symbols:\n // \"89° 45' 55.56\" N / 123° 27' 24.44\" W\"\n\n // Match DMS format with optional degree, minute, and second symbols\n const match = coordString.match(\n /(\\d+)°?\\s+(\\d+)'?\\s+([\\d.]+)\"?\\s+([NS])\\s*[,/]\\s*(\\d+)°?\\s+(\\d+)'?\\s+([\\d.]+)\"?\\s+([EW])/i,\n );\n if (!match) {\n return null;\n }\n // Round seconds to 2 decimal places for display\n return [\n match[1] as string,\n match[2] as string,\n formatDecimalPrecision(match[3] as string, 2),\n match[4] as string,\n match[5] as string,\n match[6] as string,\n formatDecimalPrecision(match[7] as string, 2),\n match[8] as string,\n ];\n}\n\n/**\n * Parse MGRS coordinate string to segments\n *\n * Extracts segment values from a formatted MGRS coordinate string. This duplicates\n * parsing logic from @accelint/geo's parseMGRS, but is necessary because\n * geo doesn't expose the parsed components - only formatted strings and raw DD numbers.\n *\n * Part of the Geo Format → String → Regex Parse → Segments flow (circular conversion).\n *\n * @internal\n */\nfunction parseMGRSCoordinateString(coordString: string): string[] | null {\n // MGRS: \"18T WM 12345 67890\"\n const match = coordString.match(/(\\d+)([A-Z])\\s+([A-Z]{2})\\s+(\\d+)\\s+(\\d+)/i);\n if (!match) {\n return null;\n }\n return [\n match[1] as string,\n match[2] as string,\n match[3] as string,\n match[4] as string,\n match[5] as string,\n ];\n}\n\n/**\n * Parse UTM coordinate string to segments\n *\n * Extracts segment values from a formatted UTM coordinate string. This duplicates\n * parsing logic from @accelint/geo's parseUTM, but is necessary because\n * geo doesn't expose the parsed components - only formatted strings and raw DD numbers.\n *\n * Part of the Geo Format → String → Regex Parse → Segments flow (circular conversion).\n *\n * @internal\n */\nfunction parseUTMCoordinateString(coordString: string): string[] | null {\n // UTM: \"18N 585628 4511644\" or \"18 N 585628 4511644\" (with optional space)\n const match = coordString.match(/(\\d+)\\s*([NS])\\s+(\\d+)\\s+(\\d+)/i);\n if (!match) {\n return null;\n }\n return [\n match[1] as string,\n match[2] as string,\n match[3] as string,\n match[4] as string,\n ];\n}\n\n/**\n * Parse a coordinate string into segment values\n *\n * Converts a formatted coordinate string (from @accelint/geo output or user input)\n * back into individual segment values for display.\n *\n * This is the inverse of formatSegmentsToCoordinateString.\n *\n * **Note on Duplication**: This function and its helpers (parseDDCoordinateString,\n * parseDDMCoordinateString, etc.) duplicate parsing logic that already exists in\n * the @accelint/geo package parsers:\n *\n * - Geo parsers: parseDecimalDegrees, parseDegreesDecimalMinutes, etc.\n * - These functions: parseDDCoordinateString, parseDDMCoordinateString, etc.\n *\n * Both use regex patterns to extract coordinate components from strings. The duplication\n * exists because:\n *\n * 1. **Geo parsers** extract components, validate them, convert to DD, then format back to strings\n * 2. **This function** extracts components from those formatted strings for the UI\n *\n * We're essentially undoing the formatting that geo just did. This is the second half\n * of the circular conversion described in convertDDToDisplaySegments.\n *\n * **Why we can't use geo parsers directly**: The geo parsers return coord objects with\n * only `coord.raw` (DD numbers) and formatting methods. They don't expose the parsed\n * segment components we need for the UI (e.g., the degrees, minutes, and direction values).\n *\n * **Parsing Order**:\n * - **convertDisplaySegmentsToDD**: Segments → String → **Geo parse** → DD ✓ (efficient)\n * - **convertDDToDisplaySegments**: DD → String → Geo parse → Geo format → **This parse** → Segments ✗ (circular)\n *\n * @param coordString - Formatted coordinate string\n * @param format - The coordinate system format\n * @returns Array of segment values, or null if parsing fails\n */\nexport function parseCoordinateStringToSegments(\n coordString: string,\n format: CoordinateSystem,\n): string[] | null {\n if (!coordString) {\n return null;\n }\n\n try {\n switch (format) {\n case 'dd':\n return parseDDCoordinateString(coordString);\n case 'ddm':\n return parseDDMCoordinateString(coordString);\n case 'dms':\n return parseDMSCoordinateString(coordString);\n case 'mgrs':\n return parseMGRSCoordinateString(coordString);\n case 'utm':\n return parseUTMCoordinateString(coordString);\n default:\n return null;\n }\n } catch (_error) {\n return null;\n }\n}\n\n/**\n * Convert DD (internal format) to display format segment values\n *\n * Takes a CoordinateValue in Decimal Degrees format and converts it to the\n * segment values needed for the specified display format.\n *\n * Uses @accelint/geo to ensure accurate conversion between coordinate systems.\n *\n * **Note on Circular Conversion**: This function demonstrates the circular conversion\n * pattern discussed in the module documentation. The flow is:\n *\n * 1. Start with DD value: `{ lat: 40.7128, lon: -74.0060 }`\n * 2. Convert to coordinate string: `\"40.7128 / -74.006\"`\n * 3. Parse with geo package (creates coord object with internal parsed state)\n * 4. Format to target system using geo: `coord.ddm()` → `\"40 42.768 N / 74 0.36 W\"`\n * 5. Parse the formatted string AGAIN with regex to extract segments: `['40', '42.768', 'N', ...]`\n *\n * This is inefficient because:\n * - The geo package already has the component values (degrees, minutes, direction) internally\n * - We format them into a string, then immediately parse the string back apart\n * - The regex parsing duplicates work the geo parsers already did\n *\n * However, this approach is necessary because:\n * - The geo package only exposes `coord.raw` (DD numbers) and formatted strings\n * - It doesn't expose the intermediate component values we need for the UI segments\n * - We need individual segment values for separate input fields\n *\n * **Future Improvement**: If geo package exported component extractors like:\n * ```typescript\n * coord.components.ddm // { latDeg: 40, latMin: 42.768, latDir: 'N', ... }\n * ```\n * Then we could eliminate the format→parse cycle entirely.\n *\n * @param value - Coordinate value in DD format `{ lat: number, lon: number }`\n * @param format - Target display format\n * @returns Array of segment values for display, or null if conversion fails\n *\n * @example\n * const segments = convertDDToDisplaySegments({ lat: 40.7128, lon: -74.0060 }, 'ddm');\n * // Returns: ['40', '42.7680', 'N', '74', '0.3600', 'W']\n */\nexport function convertDDToDisplaySegments(\n value: CoordinateValue,\n format: CoordinateSystem,\n): string[] | null {\n if (\n !value ||\n typeof value.lat !== 'number' ||\n typeof value.lon !== 'number'\n ) {\n return null;\n }\n\n try {\n const create = createCoordinate(coordinateSystems.dd, 'LATLON');\n\n // Round to 10 decimal places to match geo package internal precision\n // Use signed numbers (not cardinal directions) for reliable conversions to all formats\n const lat = Number(value.lat.toFixed(10));\n const lon = Number(value.lon.toFixed(10));\n const inputCoordString = `${lat} / ${lon}`;\n\n const coord = create(inputCoordString);\n\n if (!coord.valid) {\n return null;\n }\n\n // Format the coordinate using geo package formatters\n // These return complete coordinate strings (e.g., \"40 42.768 N / 74 0.36 W\")\n let coordString: string;\n switch (format) {\n case 'dd':\n coordString = coord.dd();\n break;\n case 'ddm':\n coordString = coord.ddm();\n break;\n case 'dms':\n coordString = coord.dms();\n break;\n case 'mgrs':\n coordString = coord.mgrs();\n break;\n case 'utm':\n coordString = coord.utm();\n break;\n default:\n return null;\n }\n\n // Parse the formatted string to extract individual segment values\n // This is the circular part: geo formatted it, now we parse it back apart\n // Necessary because geo doesn't expose the components directly\n const segments = parseCoordinateStringToSegments(coordString, format);\n return segments;\n } catch (error) {\n logger\n .withContext({\n value: String(value),\n format: String(format),\n })\n .withError(error)\n .error('Failed to convert DD to display');\n return null;\n }\n}\n\n/**\n * Convert display format segment values to DD (internal format)\n *\n * Takes segment values from user input and converts them to a CoordinateValue\n * in Decimal Degrees format using @accelint/geo for validation and conversion.\n *\n * **Note on Efficiency**: This function demonstrates the EFFICIENT conversion direction.\n * The flow is:\n *\n * 1. Start with UI segments: `['40', '42.768', 'N', '74', '0.36', 'W']`\n * 2. Build coordinate string: `\"40° 42.768' N, 74° 0.36' W\"`\n * 3. Parse with geo package (validates and converts internally)\n * 4. Extract DD from coord.raw: `{ lat: 40.7128, lon: -74.0060 }`\n *\n * This is efficient because:\n * - We let geo do what it's designed for: parsing and validating coordinate strings\n * - We extract the DD values directly from `coord.raw` (no string parsing needed)\n * - Single direction: Segments → String → Geo Parse → DD (no circular conversion)\n *\n * Contrast with `convertDDToDisplaySegments` which has the circular pattern:\n * DD → String → Geo Parse → Geo Format → String → Regex Parse → Segments\n *\n * @param segments - Array of segment values from user input\n * @param format - The coordinate system format of the segments\n * @returns CoordinateValue in DD format, or null if invalid\n *\n * @example\n * const coord = convertDisplaySegmentsToDD(['40', '42.7680', 'N', '74', '0.3600', 'W'], 'ddm');\n * // Returns: { lat: 40.7128, lon: -74.0060 }\n */\nexport function convertDisplaySegmentsToDD(\n segments: string[],\n format: CoordinateSystem,\n): CoordinateValue | null {\n // Build coordinate string from segments for geo parsing\n const coordString = formatSegmentsToCoordinateString(segments, format);\n if (!coordString) {\n return null;\n }\n\n try {\n // Parse and validate with geo package\n const create = createCoordinate(coordinateSystems[format], 'LATLON');\n const coord = create(coordString);\n\n if (!coord.valid) {\n // Return null for invalid coordinates (errors will be handled separately)\n return null;\n }\n\n // Extract DD values directly from coord.raw and round to 6 decimals\n // This ensures consistency with convertDDToDisplaySegments and getAllCoordinateFormats\n const { LAT, LON } = coord.raw;\n\n return {\n lat: LAT,\n lon: LON,\n };\n } catch (error) {\n logger\n .withContext({\n segments: JSON.stringify(segments),\n format: String(format),\n })\n .withError(error)\n .error('Failed to convert display to DD');\n return null;\n }\n}\n\n/**\n * Validate coordinate segments and return errors\n *\n * Uses @accelint/geo to validate the segments and returns any validation errors.\n * Only validates when all required segments are filled.\n *\n * @param segments - Array of segment values from user input\n * @param format - The coordinate system format\n * @returns Array of error messages, empty if valid or incomplete\n *\n * @example\n * const errors = validateCoordinateSegments(['91', '0', 'N', '0', '0', 'E'], 'ddm');\n * // Returns: ['Invalid coordinate value']\n */\nexport function validateCoordinateSegments(\n segments: string[],\n format: CoordinateSystem,\n): string[] {\n if (segments.some((seg) => seg === '' || seg === undefined)) {\n return [];\n }\n\n const coordString = formatSegmentsToCoordinateString(segments, format);\n if (!coordString) {\n return ['Invalid coordinate value'];\n }\n\n try {\n const create = createCoordinate(coordinateSystems[format], 'LATLON');\n const coord = create(coordString);\n\n if (!coord.valid) {\n return ['Invalid coordinate value'];\n }\n\n return [];\n } catch (_error) {\n return ['Invalid coordinate value'];\n }\n}\n\n/**\n * Check if all segments are filled\n *\n * Helper to determine if the user has completed entering all segment values.\n * Used to determine when to trigger validation.\n *\n * @param segments - Array of segment values\n * @returns True if all segments have values, false otherwise\n */\nexport function areAllSegmentsFilled(segments: string[]): boolean {\n return segments.every((seg) => seg !== '' && seg !== undefined);\n}\n\n/**\n * Check if any segments have values\n *\n * Helper to determine if the user has started entering coordinate values.\n *\n * @param segments - Array of segment values\n * @returns True if any segment has a value, false if all empty\n */\nexport function hasAnySegmentValue(segments: string[]): boolean {\n return segments.some((seg) => seg !== '' && seg !== undefined);\n}\n\n/**\n * Create an invalid result object for all coordinate systems\n * @internal\n */\nfunction createInvalidResult(): Record<\n CoordinateSystem,\n CoordinateFormatResult\n> {\n return {\n dd: { value: COORDINATE_ERROR_MESSAGES.INVALID, isValid: false },\n ddm: { value: COORDINATE_ERROR_MESSAGES.INVALID, isValid: false },\n dms: { value: COORDINATE_ERROR_MESSAGES.INVALID, isValid: false },\n mgrs: { value: COORDINATE_ERROR_MESSAGES.INVALID, isValid: false },\n utm: { value: COORDINATE_ERROR_MESSAGES.INVALID, isValid: false },\n };\n}\n\n/**\n * Validate coordinate value\n * @internal\n */\nfunction isValidCoordinateValue(value: CoordinateValue | null): boolean {\n return (\n value !== null &&\n typeof value.lat === 'number' &&\n typeof value.lon === 'number'\n );\n}\n\n/**\n * Check if error is due to geographic limitation (poles)\n * @internal\n */\nfunction isGeographicLimitationError(error: unknown): boolean {\n const errorMessage = error instanceof Error ? error.message : String(error);\n return (\n errorMessage.includes('outside UTM limits') ||\n errorMessage.includes('invalid UTM zone')\n );\n}\n\n/**\n * Convert coordinate to a specific format with error handling\n * @internal\n */\nfunction convertToFormat(\n coord: {\n dd: () => string;\n ddm: () => string;\n dms: () => string;\n mgrs: () => string;\n utm: () => string;\n },\n format: CoordinateSystem,\n value: CoordinateValue,\n): CoordinateFormatResult {\n try {\n let formattedValue: string;\n switch (format) {\n case 'dd':\n formattedValue = coord.dd();\n break;\n case 'ddm': {\n const raw = coord.ddm();\n const s = parseDDMCoordinateString(raw);\n // parseDDMCoordinateString already applies 4 decimal precision to minutes\n formattedValue = s\n ? `${s[0]} ${s[1]} ${s[2]} / ${s[3]} ${s[4]} ${s[5]}`\n : raw;\n break;\n }\n case 'dms': {\n const raw = coord.dms();\n const s = parseDMSCoordinateString(raw);\n // parseDMSCoordinateString already applies 2 decimal precision to seconds\n formattedValue = s\n ? `${s[0]} ${s[1]} ${s[2]} ${s[3]} / ${s[4]} ${s[5]} ${s[6]} ${s[7]}`\n : raw;\n break;\n }\n case 'mgrs':\n formattedValue = coord.mgrs();\n break;\n case 'utm':\n formattedValue = coord.utm();\n break;\n default:\n return { value: COORDINATE_ERROR_MESSAGES.INVALID, isValid: false };\n }\n return { value: formattedValue, isValid: true };\n } catch (error) {\n // Handle geographic limitations for MGRS/UTM\n if (\n (format === 'mgrs' || format === 'utm') &&\n isGeographicLimitationError(error)\n ) {\n return {\n value: COORDINATE_ERROR_MESSAGES.NOT_AVAILABLE_AT_POLES,\n isValid: false,\n };\n }\n\n // Log other errors in development\n logger\n .withContext({\n value: JSON.stringify(value),\n })\n .withError(error)\n .error(`Failed to convert to ${format}`);\n return {\n value: COORDINATE_ERROR_MESSAGES.CONVERSION_FAILED,\n isValid: false,\n };\n }\n}\n\n/**\n * Get all coordinate formats for a given DD value\n *\n * Converts a Decimal Degrees coordinate to all 5 supported coordinate systems\n * for display in the format conversion popover.\n *\n * Each format is tried independently - if one fails (e.g., UTM/MGRS at poles),\n * the others can still succeed.\n *\n * @param value - Coordinate value in DD format `{ lat: number, lon: number }`\n * @returns Object containing formatted strings and validity status for all coordinate systems\n *\n * @example\n * const formats = getAllCoordinateFormats({ lat: 40.7128, lon: -74.0060 });\n * // Returns: {\n * // dd: { value: \"40.7128 N / 74.006 W\", isValid: true },\n * // ddm: { value: \"40 42.768 N / 74 0.36 W\", isValid: true },\n * // dms: { value: \"40 42 46.08 N / 74 0 21.6 W\", isValid: true },\n * // mgrs: { value: \"18T WL 80654 06346\", isValid: true },\n * // utm: { value: \"18N 585628 4511644\", isValid: true }\n * // }\n *\n * @example\n * const formats = getAllCoordinateFormats({ lat: 90, lon: 0 });\n * // Returns: {\n * // dd: { value: \"90 N / 0 E\", isValid: true },\n * // ddm: { value: \"90 0 N / 0 0 E\", isValid: true },\n * // dms: { value: \"90 0 0 N / 0 0 0 E\", isValid: true },\n * // mgrs: { value: \"Not available at poles\", isValid: false },\n * // utm: { value: \"Not available at poles\", isValid: false }\n * // }\n */\nexport function getAllCoordinateFormats(\n value: CoordinateValue | null,\n): Record<CoordinateSystem, CoordinateFormatResult> {\n const invalidResult = createInvalidResult();\n\n if (!isValidCoordinateValue(value)) {\n return invalidResult;\n }\n\n const validValue = value as CoordinateValue;\n\n try {\n const create = createCoordinate(coordinateSystems.dd, 'LATLON');\n const coord = create(\n `${validValue.lat.toFixed(10)} / ${validValue.lon.toFixed(10)}`,\n );\n\n if (!coord.valid) {\n return invalidResult;\n }\n\n const result = {} as Record<CoordinateSystem, CoordinateFormatResult>;\n\n for (const format of COORDINATE_SYSTEMS) {\n result[format] = convertToFormat(coord, format, validValue);\n }\n\n return result;\n } catch (error) {\n logger\n .withContext({\n value: JSON.stringify(validValue),\n })\n .withError(error)\n .error('Failed to get all coordinate formats');\n return invalidResult;\n }\n}\n\n/**\n * Check if pasted text looks like a complete coordinate string\n *\n * Uses heuristics to detect if the pasted text contains a full coordinate\n * rather than just a single segment value. This prevents intercepting\n * single-segment pastes.\n *\n * Indicators of a complete coordinate:\n * - Contains separators: comma, slash, or multiple consecutive spaces\n * - Contains coordinate symbols: °, ′, ″, ', \"\n * - Multiple numbers separated by whitespace\n *\n * @param text - The pasted text to check\n * @returns True if it looks like a complete coordinate string\n *\n * @example\n * isCompleteCoordinate(\"40.7128, -74.0060\") // true - contains comma\n * isCompleteCoordinate(\"40° 42' 46\\\" N / 74° 0' 22\\\" W\") // true - contains symbols\n * isCompleteCoordinate(\"18T WM 12345 67890\") // true - multiple parts\n * isCompleteCoordinate(\"42\") // false - single number\n * isCompleteCoordinate(\"N\") // false - single letter\n */\nexport function isCompleteCoordinate(text: string): boolean {\n if (!text || text.trim() === '') {\n return false;\n }\n\n const hasSeparators = /[,/]|°|′|″|['\"]|\\s{2,}/.test(text);\n const numbers = text.match(/\\d+/g) || [];\n const hasMultipleNumbers = numbers.length >= 2;\n\n // Explicitly detect MGRS format: <zone><band> <grid> <easting> <northing>\n // Example: \"18T WL 80654 06346\"\n const isMGRS = /^\\d{1,2}[A-Z]\\s+[A-Z]{2}\\s+\\d+\\s+\\d+$/i.test(text.trim());\n\n // Explicitly detect UTM format: <zone><hemisphere> <easting> <northing>\n // Example: \"18N 585628 4511644\"\n const isUTM = /^\\d{1,2}[NS]\\s+\\d+\\s+\\d+$/i.test(text.trim());\n\n return hasSeparators || hasMultipleNumbers || isMGRS || isUTM;\n}\n\n/**\n * Attempt to parse pasted text as all coordinate formats\n *\n * Tries to parse the pasted text using each of the 5 coordinate system parsers.\n * Returns all formats that successfully parse the text.\n *\n * This enables automatic detection of coordinate format and disambiguation\n * when multiple formats match the same input string.\n *\n * @param pastedText - The raw text from clipboard\n * @returns Array of successfully parsed coordinate matches (may be empty)\n *\n * @example\n * const matches = parseCoordinatePaste(\"40.7128, -74.0060\");\n * // Returns: [{ format: 'dd', value: { lat: 40.7128, lon: -74.0060 }, displayString: \"...\" }]\n *\n * @example\n * const matches = parseCoordinatePaste(\"18T WM 12345 67890\");\n * // Returns: [{ format: 'mgrs', value: { lat: ..., lon: ... }, displayString: \"...\" }]\n *\n * @example\n * const matches = parseCoordinatePaste(\"invalid text\");\n * // Returns: []\n */\nexport function parseCoordinatePaste(\n pastedText: string,\n): ParsedCoordinateMatch[] {\n if (!pastedText || pastedText.trim() === '') {\n return [];\n }\n\n const matches: ParsedCoordinateMatch[] = [];\n\n for (const format of COORDINATE_SYSTEMS) {\n try {\n const create = createCoordinate(coordinateSystems[format], 'LATLON');\n const coord = create(pastedText.trim());\n\n if (coord.valid) {\n const { LAT, LON } = coord.raw;\n\n let displayString: string;\n switch (format) {\n case 'dd':\n displayString = coord.dd();\n break;\n case 'ddm':\n displayString = coord.ddm();\n break;\n case 'dms':\n displayString = coord.dms();\n break;\n case 'mgrs':\n displayString = coord.mgrs();\n break;\n case 'utm':\n displayString = coord.utm();\n break;\n default:\n displayString = '';\n }\n\n matches.push({\n format,\n value: { lat: LAT, lon: LON },\n displayString,\n });\n }\n } catch (error) {\n // Log parsing errors in development for debugging\n logger\n .withContext({\n pastedText: pastedText.trim(),\n format: String(format),\n })\n .withError(error)\n .warn(`Failed to parse as ${format}`);\n // Continue trying other parsers\n }\n }\n\n return matches;\n}\n\n/**\n * Check if two coordinates are equal within epsilon tolerance\n */\nexport function areCoordinatesEqual(\n coord1: { lat: number; lon: number },\n coord2: { lat: number; lon: number },\n epsilon = COORDINATE_EPSILON,\n): boolean {\n return (\n Math.abs(coord1.lat - coord2.lat) < epsilon &&\n Math.abs(coord1.lon - coord2.lon) < epsilon\n );\n}\n\n/**\n * Deduplicate coordinate matches by location, keeping first match for each unique location\n */\nexport function deduplicateMatchesByLocation(\n matches: ParsedCoordinateMatch[],\n): ParsedCoordinateMatch[] {\n const uniqueMatches: ParsedCoordinateMatch[] = [];\n\n for (const match of matches) {\n const isDuplicate = uniqueMatches.some((existing) =>\n areCoordinatesEqual(existing.value, match.value),\n );\n\n if (!isDuplicate) {\n uniqueMatches.push(match);\n }\n }\n\n return uniqueMatches;\n}\n"]}
@@ -1,4 +1,4 @@
1
1
  'use client';
2
2
 
3
- import {jsx,jsxs}from'react/jsx-runtime';import'client-only';import {clsx}from'@accelint/design-foundation/lib/utils';import q from'@accelint/icons/check';import W from'@accelint/icons/copy-to-clipboard';import H from'@accelint/icons/global-share';import {filterDOMProps}from'@react-aria/utils';import {useState,useCallback,useMemo}from'react';import {useContextProps,Provider,GroupContext,LabelContext,TextContext,FieldErrorContext,Text,FieldError,composeRenderProps}from'react-aria-components';import {useCoordinateField}from'../../hooks/coordinate-field/index.js';import {Button}from'../button/index.js';import {Dialog}from'../dialog/index.js';import {DialogContent}from'../dialog/content.js';import {DialogFooter}from'../dialog/footer.js';import {DialogTitle}from'../dialog/title.js';import {DialogTrigger}from'../dialog/trigger.js';import {Icon}from'../icon/index.js';import {Label}from'../label/index.js';import {Popover}from'../popover/index.js';import {PopoverContent}from'../popover/content.js';import {PopoverTitle}from'../popover/title.js';import {PopoverTrigger}from'../popover/trigger.js';import {Radio}from'../radio/index.js';import {RadioGroup}from'../radio/group.js';import {CoordinateFieldContext,CoordinateFieldStateContext}from'./context.js';import {getAllCoordinateFormats}from'./coordinate-utils.js';import {CoordinateSegment}from'./segment.js';import {getSegmentLabel}from'./segment-configs.js';import t from'./styles.module.css';import {COORDINATE_SYSTEMS,COORDINATE_FORMAT_LABELS,COORDINATE_FORMAT_NAMES}from'./types.js';import {calculateMaxControlWidth}from'./width-utils.js';function to({ref:S,...p}){[p,S]=useContextProps(p,S,CoordinateFieldContext);const{classNames:s,description:N,label:f,format:C="dd",size:v="medium",showFormatButton:h=true,isDisabled:c=false,isInvalid:Te=false,isRequired:T=false,...b}=p,P=v==="small",M=filterDOMProps(b,{global:true}),E=P&&f?f:b["aria-label"],{state:a,focus:n,paste:l,copy:D,registerTimeout:I,fieldProps:y,labelProps:L,descriptionProps:V,errorProps:w,validation:B,effectiveErrorMessage:k,isInvalid:F}=useCoordinateField(p,E,b["aria-describedby"],b["aria-details"]),$={segmentValues:a.segmentValues,format:C,currentValue:a.currentValue,validationErrors:a.validationErrors,isDisabled:c,isInvalid:F,isRequired:T,size:v,registerTimeout:I},[x,z]=useState(null),_=useCallback(o=>{o&&z(getAllCoordinateFormats(a.currentValue));},[a.currentValue]),G=useMemo(()=>calculateMaxControlWidth(a.editableSegmentConfigs,a.segmentConfigs,h),[a.editableSegmentConfigs,a.segmentConfigs,h]);return jsx(Provider,{values:[[CoordinateFieldContext,p],[CoordinateFieldStateContext,$],[GroupContext,y],[LabelContext,L],[TextContext,{slots:{description:V,errorMessage:w}}],[FieldErrorContext,B]],children:jsxs("div",{...M,...y,ref:S,className:clsx("group/coordinate-field",t.field,s?.field),"data-size":v,"data-disabled":c||null,"data-invalid":F||null,children:[!P&&f&&jsx(Label,{className:s?.label,isDisabled:c,isRequired:T,children:f}),jsxs("div",{className:clsx(t.control,s?.control),style:{width:G},children:[jsx("div",{className:clsx(t.input,s?.input),onPasteCapture:l.handleInputPaste,"data-input-container":true,children:a.segmentConfigs.map((o,m)=>{if(o.type==="literal")return jsx("span",{className:"fg-primary-muted",children:o.value},`${C}-literal-${m}-${o.value}`);const r=a.segmentConfigs.slice(0,m).filter(u=>u.type!=="literal").length;return jsx(CoordinateSegment,{value:a.segmentValues[r]||"",onChange:u=>a.handleSegmentChange(r,u),onFocus:()=>n.setFocusedSegmentIndex(r),onBlur:()=>{n.setFocusedSegmentIndex(-1),a.flushPendingValidation();},onKeyDown:u=>n.handleSegmentKeyDown(r,u),onAutoAdvance:()=>n.focusNextSegment(r),onAutoRetreat:()=>n.focusPreviousSegment(r),placeholder:o.placeholder,maxLength:o.maxLength,pad:o.pad,className:clsx(t.segment,s?.segment),isDisabled:c,allowedChars:o.allowedChars,segmentRef:n.segmentRefs[r],segmentIndex:r,totalSegments:a.editableSegmentConfigs.length,ariaLabel:getSegmentLabel(C,r)},`${C}-segment-${r}`)})}),h&&jsxs(PopoverTrigger,{onOpenChange:_,children:[jsx(Button,{variant:"icon",size:v,color:"mono-bold",className:s?.formatButton,"aria-label":"View coordinate in all formats",isDisabled:!D.isFormatButtonEnabled,children:jsx(Icon,{children:jsx(H,{})})}),jsxs(Popover,{classNames:{popover:t.popover},children:[jsx(PopoverTitle,{className:t.popoverTitle,children:"Copy Coordinates"}),jsx(PopoverContent,{children:x&&COORDINATE_SYSTEMS.map(o=>{const m=x[o],r=D.copiedFormat===o;return jsxs("div",{className:t.formatRow,children:[jsxs("div",{className:t.formatLabels,children:[jsx("span",{className:t.formatLabel,children:COORDINATE_FORMAT_LABELS[o]}),jsx("span",{className:t.formatValue,title:m.value,children:m.value})]}),jsx(Button,{variant:"icon",color:"mono-bold","aria-label":`Copy ${COORDINATE_FORMAT_LABELS[o]} format`,onClick:()=>D.handleCopyFormat(o),isDisabled:!m.isValid,children:jsx(Icon,{children:r?jsx(q,{}):jsx(W,{})})})]},o)})})]})]})]}),N&&!P&&(!F||c)&&jsx(Text,{className:clsx(t.description,s?.description),slot:"description",children:N}),jsx(FieldError,{className:composeRenderProps(s?.error,o=>clsx(t.error,o)),children:k}),jsxs(DialogTrigger,{isOpen:l.showDisambiguationModal,onOpenChange:l.setShowDisambiguationModal,children:[jsx(Button,{className:"hidden",children:"Hidden Trigger"}),jsxs(Dialog,{size:"small",children:[jsx(DialogTitle,{className:t.modalTitle,children:"Select Coordinate Format"}),jsxs(DialogContent,{children:[jsx("p",{className:t.modalDescription,children:"The pasted value matches multiple coordinate formats. Please select the correct interpretation:"}),jsx(RadioGroup,{classNames:{group:t.formatOptions},value:l.selectedDisambiguationFormat,onChange:o=>l.setSelectedDisambiguationFormat(o),children:l.disambiguationMatches.map(o=>jsx(Radio,{value:o.format,children:jsxs("div",{className:t.modalOptionContent,children:[jsx("span",{className:t.formatOptionLabel,children:COORDINATE_FORMAT_NAMES[o.format]}),jsx("span",{className:t.formatOptionValue,children:o.displayString})]})},o.format))})]}),jsxs(DialogFooter,{className:t.modalActions,children:[jsx(Button,{variant:"flat",onPress:()=>l.setShowDisambiguationModal(false),children:"Cancel"}),jsx(Button,{onPress:l.handleDisambiguationSelect,children:"Apply Selected"})]})]})]})]})})}export{to as CoordinateField};//# sourceMappingURL=index.js.map
3
+ import {jsx,jsxs}from'react/jsx-runtime';import {clsx}from'@accelint/design-foundation/lib/utils';import U from'@accelint/icons/check';import Y from'@accelint/icons/copy-to-clipboard';import J from'@accelint/icons/global-share';import {filterDOMProps}from'@react-aria/utils';import'client-only';import {useState,useCallback,useMemo}from'react';import {useContextProps,Provider,GroupContext,LabelContext,TextContext,FieldErrorContext,Text,FieldError,composeRenderProps}from'react-aria-components';import {useCoordinateField}from'../../hooks/coordinate-field/index.js';import {Button}from'../button/index.js';import {Dialog}from'../dialog/index.js';import {DialogContent}from'../dialog/content.js';import {DialogFooter}from'../dialog/footer.js';import {DialogTitle}from'../dialog/title.js';import {DialogTrigger}from'../dialog/trigger.js';import {Icon}from'../icon/index.js';import {Label}from'../label/index.js';import {Popover}from'../popover/index.js';import {PopoverContent}from'../popover/content.js';import {PopoverTitle}from'../popover/title.js';import {PopoverTrigger}from'../popover/trigger.js';import {Radio}from'../radio/index.js';import {RadioGroup}from'../radio/group.js';import {CoordinateFieldContext,CoordinateFieldStateContext}from'./context.js';import {getAllCoordinateFormats}from'./coordinate-utils.js';import {CoordinateSegment}from'./segment.js';import {GROUP_SEPARATOR,getSegmentLabel}from'./segment-configs.js';import t from'./styles.module.css';import {COORDINATE_SYSTEMS,COORDINATE_FORMAT_LABELS,COORDINATE_FORMAT_NAMES}from'./types.js';import {calculateMinControlWidth}from'./width-utils.js';function so({ref:P,...p}){[p,P]=useContextProps(p,P,CoordinateFieldContext);const{classNames:l,description:T,label:v,format:u="dd",size:b="medium",variant:c="inline",showFormatButton:N=true,isDisabled:g=false,isInvalid:xe=false,isRequired:y=false,...S}=p,D=b==="small",L=filterDOMProps(S,{global:true}),V=D&&v?v:S["aria-label"],{state:a,focus:d,paste:n,copy:F,registerTimeout:w,fieldProps:O,labelProps:B,descriptionProps:k,errorProps:$,validation:G,effectiveErrorMessage:_,isInvalid:R}=useCoordinateField(p,V,S["aria-describedby"],S["aria-details"]),z={segmentValues:a.segmentValues,format:u,currentValue:a.currentValue,validationErrors:a.validationErrors,isDisabled:g,isInvalid:R,isRequired:y,size:b,variant:c,registerTimeout:w},[x,q]=useState(null),W=useCallback(o=>{o&&q(getAllCoordinateFormats(a.currentValue));},[a.currentValue]),H=useMemo(()=>calculateMinControlWidth(a.editableSegmentConfigs,a.segmentConfigs,N),[a.editableSegmentConfigs,a.segmentConfigs,N]);return jsx(Provider,{values:[[CoordinateFieldContext,p],[CoordinateFieldStateContext,z],[GroupContext,O],[LabelContext,B],[TextContext,{slots:{description:k,errorMessage:$}}],[FieldErrorContext,G]],children:jsxs("div",{...L,...O,ref:P,className:clsx("group/coordinate-field",t.field,l?.field),"data-size":b,"data-disabled":g||null,"data-invalid":R||null,children:[!D&&v&&jsx(Label,{className:l?.label,isDisabled:g,isRequired:y,children:v}),jsxs("div",{style:{"--min-width":c==="stacked"?"unset":H},className:clsx(t.control,l?.control),children:[jsx("div",{className:clsx(t.input,t[c],l?.input),onPasteCapture:n.handleInputPaste,"data-input-container":true,children:a.segmentConfigs.reduce((o,r,h)=>{const A=o.length-1;if(r.value===GROUP_SEPARATOR&&c==="stacked")return o.push([]),o;if(r.type==="literal")return o[A]?.push(jsx("span",{className:t.literal,children:r.value},`${u}-literal-${h}-${r.value}`)),o;const s=a.segmentConfigs.slice(0,h).filter(f=>f.type!=="literal").length;return o[A]?.push(jsx(CoordinateSegment,{value:a.segmentValues[s]||"",onChange:f=>a.handleSegmentChange(s,f),onFocus:()=>d.setFocusedSegmentIndex(s),onBlur:()=>{d.setFocusedSegmentIndex(-1),a.flushPendingValidation();},onKeyDown:f=>d.handleSegmentKeyDown(s,f),onAutoAdvance:()=>d.focusNextSegment(s),onAutoRetreat:()=>d.focusPreviousSegment(s),placeholder:r.placeholder,maxLength:r.maxLength,pad:r.pad,className:clsx(t.segment,l?.segment),isDisabled:g,allowedChars:r.allowedChars,segmentRef:d.segmentRefs[s],segmentIndex:s,totalSegments:a.editableSegmentConfigs.length,ariaLabel:getSegmentLabel(u,s)},`${u}-segment-${s}`)),o},[[]]).map((o,r)=>jsx("div",{className:clsx(t.segmentGroup,t[c]),children:o},`${u}-group-${r}`))}),N&&jsxs(PopoverTrigger,{onOpenChange:W,children:[jsx(Button,{variant:"icon",size:b,color:"mono-bold",className:l?.formatButton,"aria-label":"View coordinate in all formats",isDisabled:!F.isFormatButtonEnabled,children:jsx(Icon,{children:jsx(J,{})})}),jsxs(Popover,{classNames:{popover:t.popover},children:[jsx(PopoverTitle,{className:t.popoverTitle,children:"Copy Coordinates"}),jsx(PopoverContent,{children:x&&COORDINATE_SYSTEMS.map(o=>{const r=x[o],h=F.copiedFormat===o;return jsxs("div",{className:t.formatRow,children:[jsxs("div",{className:t.formatLabels,children:[jsx("span",{className:t.formatLabel,children:COORDINATE_FORMAT_LABELS[o]}),jsx("span",{className:t.formatValue,title:r.value,children:r.value})]}),jsx(Button,{variant:"icon",color:"mono-bold","aria-label":`Copy ${COORDINATE_FORMAT_LABELS[o]} format`,onClick:()=>F.handleCopyFormat(o),isDisabled:!r.isValid,children:jsx(Icon,{children:h?jsx(U,{}):jsx(Y,{})})})]},o)})})]})]})]}),T&&!D&&(!R||g)&&jsx(Text,{className:clsx(t.description,l?.description),slot:"description",children:T}),jsx(FieldError,{className:composeRenderProps(l?.error,o=>clsx(t.error,o)),children:_}),jsxs(DialogTrigger,{isOpen:n.showDisambiguationModal,onOpenChange:n.setShowDisambiguationModal,children:[jsx(Button,{className:"hidden",children:"Hidden Trigger"}),jsxs(Dialog,{size:"small",children:[jsx(DialogTitle,{className:t.modalTitle,children:"Select Coordinate Format"}),jsxs(DialogContent,{children:[jsx("p",{className:t.modalDescription,children:"The pasted value matches multiple coordinate formats. Please select the correct interpretation:"}),jsx(RadioGroup,{classNames:{group:t.formatOptions},value:n.selectedDisambiguationFormat,onChange:o=>n.setSelectedDisambiguationFormat(o),children:n.disambiguationMatches.map(o=>jsx(Radio,{value:o.format,children:jsxs("div",{className:t.modalOptionContent,children:[jsx("span",{className:t.formatOptionLabel,children:COORDINATE_FORMAT_NAMES[o.format]}),jsx("span",{className:t.formatOptionValue,children:o.displayString})]})},o.format))})]}),jsxs(DialogFooter,{className:t.modalActions,children:[jsx(Button,{variant:"flat",onPress:()=>n.setShowDisambiguationModal(false),children:"Cancel"}),jsx(Button,{onPress:n.handleDisambiguationSelect,children:"Apply Selected"})]})]})]})]})})}export{so as CoordinateField};//# sourceMappingURL=index.js.map
4
4
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/coordinate-field/index.tsx"],"names":["isDisabled","re","isInvalidProp","O","rest","filterDOMProps","ariaLabelForSmallSize","isSmall","labelProp","state","paste","fieldProps","labelProps","Y","errorProps","effectiveErrorMessage","useCoordinateField","format","ie","isInvalid","registerTimeout","handlePopoverOpenChange","setAllCoordinateFormats","getAllCoordinateFormats","calculateMaxControlWidth","Se","jsx","Provider","CoordinateFieldContext","props","Q","CoordinateFieldStateContext","Ne","GroupContext","jsxs","e","te","styles","ae","classNames","size","i","Label","isRequired","maxControlWidth","pe","clsx","d","configIndex","c","newValue","editableIndex","focus","he","config","getSegmentLabel","PopoverTrigger","Button","Pe","fe","copy","R","allCoordinateFormats","COORDINATE_SYSTEMS","ce","formatKey","formatResult","ue","Fe","COORDINATE_FORMAT_LABELS","A","g","Icon","isCopied","CopyToClipboard","descriptionProp","FieldError","composeRenderProps","className","X","j","Z","de","DialogContent","value","ve","match","COORDINATE_FORMAT_NAMES"],"mappings":"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","file":"index.js","sourcesContent":["/*\n * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\n'use client';\n\nimport 'client-only';\nimport { clsx } from '@accelint/design-foundation/lib/utils';\nimport Check from '@accelint/icons/check';\nimport CopyToClipboard from '@accelint/icons/copy-to-clipboard';\nimport GlobalShare from '@accelint/icons/global-share';\nimport { filterDOMProps } from '@react-aria/utils';\nimport { useCallback, useMemo, useState } from 'react';\nimport {\n Text as AriaText,\n composeRenderProps,\n FieldError,\n FieldErrorContext,\n GroupContext,\n LabelContext,\n Provider,\n TextContext,\n useContextProps,\n} from 'react-aria-components';\nimport { useCoordinateField } from '../../hooks/coordinate-field';\nimport { Button } from '../button';\nimport { Dialog } from '../dialog';\nimport { DialogContent } from '../dialog/content';\nimport { DialogFooter } from '../dialog/footer';\nimport { DialogTitle } from '../dialog/title';\nimport { DialogTrigger } from '../dialog/trigger';\nimport { Icon } from '../icon';\nimport { Label } from '../label';\nimport { Popover } from '../popover';\nimport { PopoverContent } from '../popover/content';\nimport { PopoverTitle } from '../popover/title';\nimport { PopoverTrigger } from '../popover/trigger';\nimport { Radio } from '../radio';\nimport { RadioGroup } from '../radio/group';\nimport { CoordinateFieldContext, CoordinateFieldStateContext } from './context';\nimport {\n type CoordinateFormatResult,\n getAllCoordinateFormats,\n} from './coordinate-utils';\nimport { CoordinateSegment } from './segment';\nimport { getSegmentLabel } from './segment-configs';\nimport styles from './styles.module.css';\nimport {\n COORDINATE_FORMAT_LABELS,\n COORDINATE_FORMAT_NAMES,\n COORDINATE_SYSTEMS,\n type CoordinateSystem,\n} from './types';\nimport { calculateMaxControlWidth } from './width-utils';\nimport type { CoordinateFieldProps } from './types';\n\n/**\n * CoordinateField - A comprehensive coordinate input component with multiple format support\n *\n * Provides accessible coordinate input functionality with support for multiple coordinate\n * systems (DD, DDM, DMS, MGRS, UTM). All values are normalized to Decimal Degrees internally\n * for consistency.\n *\n * @example\n * // Basic coordinate field\n * <CoordinateField label=\"Location\" />\n *\n * @example\n * // Coordinate field with validation\n * <CoordinateField\n * label=\"Target Coordinates\"\n * isRequired\n * isInvalid={hasError}\n * errorMessage=\"Please enter a valid coordinate\"\n * />\n *\n * @example\n * // Coordinate field with specific format\n * <CoordinateField\n * label=\"Position\"\n * format=\"dms\"\n * description=\"Enter coordinates in Degrees Minutes Seconds format\"\n * />\n *\n * @example\n * // Compact coordinate field\n * <CoordinateField\n * label=\"Coordinates\"\n * size=\"small\"\n * format=\"dd\"\n * />\n *\n * @example\n * // Controlled coordinate field\n * <CoordinateField\n * label=\"Selected Location\"\n * value={coordinates}\n * onChange={setCoordinates}\n * />\n *\n * @example\n * // Coordinate field with error handling\n * <CoordinateField\n * label=\"Target Coordinates\"\n * onError={(message, context) => {\n * // message will be \"Invalid coordinate value\" for validation errors\n * // or \"Invalid coordinate format\" for paste errors\n * setErrorMessage(message);\n * console.error(message, context);\n * }}\n * isInvalid={!!errorMessage}\n * errorMessage={errorMessage}\n * />\n */\nexport function CoordinateField({ ref, ...props }: CoordinateFieldProps) {\n [props, ref] = useContextProps(props, ref, CoordinateFieldContext);\n\n const {\n classNames,\n description: descriptionProp,\n label: labelProp,\n format = 'dd',\n size = 'medium',\n showFormatButton = true,\n isDisabled = false,\n isInvalid: isInvalidProp = false,\n isRequired = false,\n ...rest\n } = props;\n\n const isSmall = size === 'small';\n\n const DOMProps = filterDOMProps(rest, { global: true });\n\n // When size is small and label is hidden, use aria-label instead of aria-labelledby\n // to ensure screen readers have an accessible name\n const ariaLabelForSmallSize =\n isSmall && labelProp ? labelProp : rest['aria-label'];\n\n const {\n state,\n focus,\n paste,\n copy,\n registerTimeout,\n fieldProps,\n labelProps,\n descriptionProps,\n errorProps,\n validation,\n effectiveErrorMessage,\n isInvalid,\n } = useCoordinateField(\n props,\n ariaLabelForSmallSize,\n rest['aria-describedby'],\n rest['aria-details'],\n );\n\n const componentState = {\n segmentValues: state.segmentValues,\n format,\n currentValue: state.currentValue,\n validationErrors: state.validationErrors,\n isDisabled,\n isInvalid,\n isRequired,\n size,\n registerTimeout,\n };\n\n // Store all coordinate formats, calculated only when popover opens\n const [allCoordinateFormats, setAllCoordinateFormats] = useState<Record<\n CoordinateSystem,\n CoordinateFormatResult\n > | null>(null);\n\n const handlePopoverOpenChange = useCallback(\n (isOpen: boolean) => {\n if (isOpen) {\n setAllCoordinateFormats(getAllCoordinateFormats(state.currentValue));\n }\n },\n [state.currentValue],\n );\n\n // Calculate the maximum width needed for the control container\n // This keeps the outlined container at a fixed width while segments animate\n const maxControlWidth = useMemo(\n () =>\n calculateMaxControlWidth(\n state.editableSegmentConfigs,\n state.segmentConfigs,\n showFormatButton,\n ),\n [state.editableSegmentConfigs, state.segmentConfigs, showFormatButton],\n );\n\n return (\n <Provider\n values={[\n [CoordinateFieldContext, props],\n [CoordinateFieldStateContext, componentState],\n [GroupContext, fieldProps],\n [LabelContext, labelProps],\n [\n TextContext,\n {\n slots: {\n description: descriptionProps,\n errorMessage: errorProps,\n },\n },\n ],\n [FieldErrorContext, validation],\n ]}\n >\n <div\n {...DOMProps}\n {...fieldProps}\n ref={ref}\n className={clsx(\n 'group/coordinate-field',\n styles.field,\n classNames?.field,\n )}\n data-size={size}\n data-disabled={isDisabled || null}\n data-invalid={isInvalid || null}\n >\n {!isSmall && labelProp && (\n <Label\n className={classNames?.label}\n isDisabled={isDisabled}\n isRequired={isRequired}\n >\n {labelProp}\n </Label>\n )}\n\n <div\n className={clsx(styles.control, classNames?.control)}\n style={{ width: maxControlWidth }}\n >\n <div\n className={clsx(styles.input, classNames?.input)}\n onPasteCapture={paste.handleInputPaste}\n data-input-container\n >\n {state.segmentConfigs.map((config, configIndex) => {\n if (config.type === 'literal') {\n return (\n <span\n key={`${format}-literal-${configIndex}-${config.value}`}\n className='fg-primary-muted'\n >\n {config.value}\n </span>\n );\n }\n\n const editableIndex = state.segmentConfigs\n .slice(0, configIndex)\n .filter((c) => c.type !== 'literal').length;\n\n return (\n <CoordinateSegment\n key={`${format}-segment-${editableIndex}`}\n value={state.segmentValues[editableIndex] || ''}\n onChange={(newValue) =>\n state.handleSegmentChange(editableIndex, newValue)\n }\n onFocus={() => focus.setFocusedSegmentIndex(editableIndex)}\n onBlur={() => {\n focus.setFocusedSegmentIndex(-1);\n state.flushPendingValidation();\n }}\n onKeyDown={(e) =>\n focus.handleSegmentKeyDown(editableIndex, e)\n }\n onAutoAdvance={() => focus.focusNextSegment(editableIndex)}\n onAutoRetreat={() =>\n focus.focusPreviousSegment(editableIndex)\n }\n placeholder={config.placeholder}\n maxLength={config.maxLength}\n pad={config.pad}\n className={clsx(styles.segment, classNames?.segment)}\n isDisabled={isDisabled}\n allowedChars={config.allowedChars}\n segmentRef={focus.segmentRefs[editableIndex]}\n segmentIndex={editableIndex}\n totalSegments={state.editableSegmentConfigs.length}\n ariaLabel={getSegmentLabel(format, editableIndex)}\n />\n );\n })}\n </div>\n\n {showFormatButton && (\n <PopoverTrigger onOpenChange={handlePopoverOpenChange}>\n <Button\n variant='icon'\n size={size}\n color='mono-bold'\n className={classNames?.formatButton}\n aria-label='View coordinate in all formats'\n isDisabled={!copy.isFormatButtonEnabled}\n >\n <Icon>\n <GlobalShare />\n </Icon>\n </Button>\n <Popover classNames={{ popover: styles.popover }}>\n <PopoverTitle className={styles.popoverTitle}>\n Copy Coordinates\n </PopoverTitle>\n <PopoverContent>\n {allCoordinateFormats &&\n COORDINATE_SYSTEMS.map((formatKey) => {\n const formatResult = allCoordinateFormats[formatKey];\n const isCopied = copy.copiedFormat === formatKey;\n\n return (\n <div key={formatKey} className={styles.formatRow}>\n <div className={styles.formatLabels}>\n <span className={styles.formatLabel}>\n {COORDINATE_FORMAT_LABELS[formatKey]}\n </span>\n <span\n className={styles.formatValue}\n title={formatResult.value}\n >\n {formatResult.value}\n </span>\n </div>\n <Button\n variant='icon'\n color='mono-bold'\n aria-label={`Copy ${COORDINATE_FORMAT_LABELS[formatKey]} format`}\n onClick={() => copy.handleCopyFormat(formatKey)}\n isDisabled={!formatResult.isValid}\n >\n <Icon>\n {isCopied ? <Check /> : <CopyToClipboard />}\n </Icon>\n </Button>\n </div>\n );\n })}\n </PopoverContent>\n </Popover>\n </PopoverTrigger>\n )}\n </div>\n\n {/* Description is hidden when field is invalid (unless disabled) to make room for error message */}\n {descriptionProp && !isSmall && (!isInvalid || isDisabled) && (\n <AriaText\n className={clsx(styles.description, classNames?.description)}\n slot='description'\n >\n {descriptionProp}\n </AriaText>\n )}\n\n <FieldError\n className={composeRenderProps(classNames?.error, (className) =>\n clsx(styles.error, className),\n )}\n >\n {effectiveErrorMessage}\n </FieldError>\n\n <DialogTrigger\n isOpen={paste.showDisambiguationModal}\n onOpenChange={paste.setShowDisambiguationModal}\n >\n <Button className='hidden'>Hidden Trigger</Button>\n <Dialog size='small'>\n <DialogTitle className={styles.modalTitle}>\n Select Coordinate Format\n </DialogTitle>\n <DialogContent>\n <p className={styles.modalDescription}>\n The pasted value matches multiple coordinate formats. Please\n select the correct interpretation:\n </p>\n\n <RadioGroup\n classNames={{ group: styles.formatOptions }}\n value={paste.selectedDisambiguationFormat}\n onChange={(value) =>\n paste.setSelectedDisambiguationFormat(\n value as CoordinateSystem,\n )\n }\n >\n {paste.disambiguationMatches.map((match) => (\n <Radio key={match.format} value={match.format}>\n <div className={styles.modalOptionContent}>\n <span className={styles.formatOptionLabel}>\n {COORDINATE_FORMAT_NAMES[match.format]}\n </span>\n <span className={styles.formatOptionValue}>\n {match.displayString}\n </span>\n </div>\n </Radio>\n ))}\n </RadioGroup>\n </DialogContent>\n <DialogFooter className={styles.modalActions}>\n <Button\n variant='flat'\n onPress={() => paste.setShowDisambiguationModal(false)}\n >\n Cancel\n </Button>\n <Button onPress={paste.handleDisambiguationSelect}>\n Apply Selected\n </Button>\n </DialogFooter>\n </Dialog>\n </DialogTrigger>\n </div>\n </Provider>\n );\n}\n"]}
1
+ {"version":3,"sources":["../../../src/components/coordinate-field/index.tsx"],"names":["showFormatButton","le","isDisabled","props","isSmall","DOMProps","rest","labelProp","state","paste","fieldProps","labelProps","Q","errorProps","effectiveErrorMessage","useCoordinateField","format","ne","isInvalid","isRequired","variant","allCoordinateFormats","setAllCoordinateFormats","useCallback","minControlWidth","calculateMinControlWidth","j","X","jsx","Provider","CoordinateFieldContext","Z","CoordinateFieldStateContext","Oe","GroupContext","e","ie","I","Pe","ae","re","styles","se","classNames","size","i","Label","clsx","m","configIndex","acc","config","Fe","editableIndex","c","currentGroupIndex","CoordinateSegment","newValue","focus","De","getSegmentLabel","jsxs","PopoverTrigger","handlePopoverOpenChange","Button","be","copy","E","COORDINATE_SYSTEMS","fe","formatKey","formatResult","ve","Ce","ye","COORDINATE_FORMAT_LABELS","M","C","Icon","isCopied","CopyToClipboard","descriptionProp","FieldError","composeRenderProps","className","K","DialogTrigger","oe","ee","ce","DialogContent","ue","de","value","he","match","COORDINATE_FORMAT_NAMES","Te"],"mappings":"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","file":"index.js","sourcesContent":["/*\n * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\n'use client';\n\nimport { clsx } from '@accelint/design-foundation/lib/utils';\nimport Check from '@accelint/icons/check';\nimport CopyToClipboard from '@accelint/icons/copy-to-clipboard';\nimport GlobalShare from '@accelint/icons/global-share';\nimport { filterDOMProps } from '@react-aria/utils';\nimport 'client-only';\nimport {\n type CSSProperties,\n type ReactNode,\n useCallback,\n useMemo,\n useState,\n} from 'react';\nimport {\n Text as AriaText,\n composeRenderProps,\n FieldError,\n FieldErrorContext,\n GroupContext,\n LabelContext,\n Provider,\n TextContext,\n useContextProps,\n} from 'react-aria-components';\nimport { useCoordinateField } from '../../hooks/coordinate-field';\nimport { Button } from '../button';\nimport { Dialog } from '../dialog';\nimport { DialogContent } from '../dialog/content';\nimport { DialogFooter } from '../dialog/footer';\nimport { DialogTitle } from '../dialog/title';\nimport { DialogTrigger } from '../dialog/trigger';\nimport { Icon } from '../icon';\nimport { Label } from '../label';\nimport { Popover } from '../popover';\nimport { PopoverContent } from '../popover/content';\nimport { PopoverTitle } from '../popover/title';\nimport { PopoverTrigger } from '../popover/trigger';\nimport { Radio } from '../radio';\nimport { RadioGroup } from '../radio/group';\nimport { CoordinateFieldContext, CoordinateFieldStateContext } from './context';\nimport {\n type CoordinateFormatResult,\n getAllCoordinateFormats,\n} from './coordinate-utils';\nimport { CoordinateSegment } from './segment';\nimport { GROUP_SEPARATOR, getSegmentLabel } from './segment-configs';\nimport styles from './styles.module.css';\nimport {\n COORDINATE_FORMAT_LABELS,\n COORDINATE_FORMAT_NAMES,\n COORDINATE_SYSTEMS,\n type CoordinateSystem,\n} from './types';\nimport { calculateMinControlWidth } from './width-utils';\nimport type { CoordinateFieldProps } from './types';\n\n/**\n * CoordinateField - A comprehensive coordinate input component with multiple format support\n *\n * Provides accessible coordinate input functionality with support for multiple coordinate\n * systems (DD, DDM, DMS, MGRS, UTM). All values are normalized to Decimal Degrees internally\n * for consistency.\n *\n * @example\n * // Basic coordinate field\n * <CoordinateField label=\"Location\" />\n *\n * @example\n * // Coordinate field with validation\n * <CoordinateField\n * label=\"Target Coordinates\"\n * isRequired\n * isInvalid={hasError}\n * errorMessage=\"Please enter a valid coordinate\"\n * />\n *\n * @example\n * // Coordinate field with specific format\n * <CoordinateField\n * label=\"Position\"\n * format=\"dms\"\n * description=\"Enter coordinates in Degrees Minutes Seconds format\"\n * />\n *\n * @example\n * // Compact coordinate field\n * <CoordinateField\n * label=\"Coordinates\"\n * size=\"small\"\n * format=\"dd\"\n * />\n *\n * @example\n * // Controlled coordinate field\n * <CoordinateField\n * label=\"Selected Location\"\n * value={coordinates}\n * onChange={setCoordinates}\n * />\n *\n * @example\n * // Coordinate field with error handling\n * <CoordinateField\n * label=\"Target Coordinates\"\n * onError={(message, context) => {\n * // message will be \"Invalid coordinate value\" for validation errors\n * // or \"Invalid coordinate format\" for paste errors\n * setErrorMessage(message);\n * console.error(message, context);\n * }}\n * isInvalid={!!errorMessage}\n * errorMessage={errorMessage}\n * />\n */\nexport function CoordinateField({ ref, ...props }: CoordinateFieldProps) {\n [props, ref] = useContextProps(props, ref, CoordinateFieldContext);\n\n const {\n classNames,\n description: descriptionProp,\n label: labelProp,\n format = 'dd',\n size = 'medium',\n variant = 'inline',\n showFormatButton = true,\n isDisabled = false,\n isInvalid: isInvalidProp = false,\n isRequired = false,\n ...rest\n } = props;\n\n const isSmall = size === 'small';\n\n const DOMProps = filterDOMProps(rest, { global: true });\n\n // When size is small and label is hidden, use aria-label instead of aria-labelledby\n // to ensure screen readers have an accessible name\n const ariaLabelForSmallSize =\n isSmall && labelProp ? labelProp : rest['aria-label'];\n\n const {\n state,\n focus,\n paste,\n copy,\n registerTimeout,\n fieldProps,\n labelProps,\n descriptionProps,\n errorProps,\n validation,\n effectiveErrorMessage,\n isInvalid,\n } = useCoordinateField(\n props,\n ariaLabelForSmallSize,\n rest['aria-describedby'],\n rest['aria-details'],\n );\n\n const componentState = {\n segmentValues: state.segmentValues,\n format,\n currentValue: state.currentValue,\n validationErrors: state.validationErrors,\n isDisabled,\n isInvalid,\n isRequired,\n size,\n variant,\n registerTimeout,\n };\n\n // Store all coordinate formats, calculated only when popover opens\n const [allCoordinateFormats, setAllCoordinateFormats] = useState<Record<\n CoordinateSystem,\n CoordinateFormatResult\n > | null>(null);\n\n const handlePopoverOpenChange = useCallback(\n (isOpen: boolean) => {\n if (isOpen) {\n setAllCoordinateFormats(getAllCoordinateFormats(state.currentValue));\n }\n },\n [state.currentValue],\n );\n\n // Calculate the minimum width needed for the control container\n // This keeps the outlined container at a fixed width while segments animate\n const minControlWidth = useMemo(\n () =>\n calculateMinControlWidth(\n state.editableSegmentConfigs,\n state.segmentConfigs,\n showFormatButton,\n ),\n [state.editableSegmentConfigs, state.segmentConfigs, showFormatButton],\n );\n\n return (\n <Provider\n values={[\n [CoordinateFieldContext, props],\n [CoordinateFieldStateContext, componentState],\n [GroupContext, fieldProps],\n [LabelContext, labelProps],\n [\n TextContext,\n {\n slots: {\n description: descriptionProps,\n errorMessage: errorProps,\n },\n },\n ],\n [FieldErrorContext, validation],\n ]}\n >\n <div\n {...DOMProps}\n {...fieldProps}\n ref={ref}\n className={clsx(\n 'group/coordinate-field',\n styles.field,\n classNames?.field,\n )}\n data-size={size}\n data-disabled={isDisabled || null}\n data-invalid={isInvalid || null}\n >\n {!isSmall && labelProp && (\n <Label\n className={classNames?.label}\n isDisabled={isDisabled}\n isRequired={isRequired}\n >\n {labelProp}\n </Label>\n )}\n\n <div\n style={\n {\n '--min-width': variant === 'stacked' ? 'unset' : minControlWidth,\n } as CSSProperties\n }\n className={clsx(styles.control, classNames?.control)}\n >\n <div\n className={clsx(styles.input, styles[variant], classNames?.input)}\n onPasteCapture={paste.handleInputPaste}\n data-input-container\n >\n {state.segmentConfigs\n .reduce<ReactNode[][]>(\n (acc, config, configIndex) => {\n const currentGroupIndex = acc.length - 1;\n\n if (\n config.value === GROUP_SEPARATOR &&\n variant === 'stacked'\n ) {\n acc.push([]);\n\n return acc;\n }\n\n if (config.type === 'literal') {\n acc[currentGroupIndex]?.push(\n <span\n key={`${format}-literal-${configIndex}-${config.value}`}\n className={styles.literal}\n >\n {config.value}\n </span>,\n );\n\n return acc;\n }\n\n const editableIndex = state.segmentConfigs\n .slice(0, configIndex)\n .filter((c) => c.type !== 'literal').length;\n\n acc[currentGroupIndex]?.push(\n <CoordinateSegment\n key={`${format}-segment-${editableIndex}`}\n value={state.segmentValues[editableIndex] || ''}\n onChange={(newValue) =>\n state.handleSegmentChange(editableIndex, newValue)\n }\n onFocus={() =>\n focus.setFocusedSegmentIndex(editableIndex)\n }\n onBlur={() => {\n focus.setFocusedSegmentIndex(-1);\n state.flushPendingValidation();\n }}\n onKeyDown={(e) =>\n focus.handleSegmentKeyDown(editableIndex, e)\n }\n onAutoAdvance={() =>\n focus.focusNextSegment(editableIndex)\n }\n onAutoRetreat={() =>\n focus.focusPreviousSegment(editableIndex)\n }\n placeholder={config.placeholder}\n maxLength={config.maxLength}\n pad={config.pad}\n className={clsx(styles.segment, classNames?.segment)}\n isDisabled={isDisabled}\n allowedChars={config.allowedChars}\n segmentRef={focus.segmentRefs[editableIndex]}\n segmentIndex={editableIndex}\n totalSegments={state.editableSegmentConfigs.length}\n ariaLabel={getSegmentLabel(format, editableIndex)}\n />,\n );\n\n return acc;\n },\n [[]],\n )\n .map((group, groupIndex) => (\n <div\n className={clsx(styles.segmentGroup, styles[variant])}\n key={`${format}-group-${\n // biome-ignore lint/suspicious/noArrayIndexKey: intentional\n groupIndex\n }`}\n >\n {group}\n </div>\n ))}\n </div>\n\n {showFormatButton && (\n <PopoverTrigger onOpenChange={handlePopoverOpenChange}>\n <Button\n variant='icon'\n size={size}\n color='mono-bold'\n className={classNames?.formatButton}\n aria-label='View coordinate in all formats'\n isDisabled={!copy.isFormatButtonEnabled}\n >\n <Icon>\n <GlobalShare />\n </Icon>\n </Button>\n <Popover classNames={{ popover: styles.popover }}>\n <PopoverTitle className={styles.popoverTitle}>\n Copy Coordinates\n </PopoverTitle>\n <PopoverContent>\n {allCoordinateFormats &&\n COORDINATE_SYSTEMS.map((formatKey) => {\n const formatResult = allCoordinateFormats[formatKey];\n const isCopied = copy.copiedFormat === formatKey;\n\n return (\n <div key={formatKey} className={styles.formatRow}>\n <div className={styles.formatLabels}>\n <span className={styles.formatLabel}>\n {COORDINATE_FORMAT_LABELS[formatKey]}\n </span>\n <span\n className={styles.formatValue}\n title={formatResult.value}\n >\n {formatResult.value}\n </span>\n </div>\n <Button\n variant='icon'\n color='mono-bold'\n aria-label={`Copy ${COORDINATE_FORMAT_LABELS[formatKey]} format`}\n onClick={() => copy.handleCopyFormat(formatKey)}\n isDisabled={!formatResult.isValid}\n >\n <Icon>\n {isCopied ? <Check /> : <CopyToClipboard />}\n </Icon>\n </Button>\n </div>\n );\n })}\n </PopoverContent>\n </Popover>\n </PopoverTrigger>\n )}\n </div>\n\n {/* Description is hidden when field is invalid (unless disabled) to make room for error message */}\n {descriptionProp && !isSmall && (!isInvalid || isDisabled) && (\n <AriaText\n className={clsx(styles.description, classNames?.description)}\n slot='description'\n >\n {descriptionProp}\n </AriaText>\n )}\n\n <FieldError\n className={composeRenderProps(classNames?.error, (className) =>\n clsx(styles.error, className),\n )}\n >\n {effectiveErrorMessage}\n </FieldError>\n\n <DialogTrigger\n isOpen={paste.showDisambiguationModal}\n onOpenChange={paste.setShowDisambiguationModal}\n >\n <Button className='hidden'>Hidden Trigger</Button>\n <Dialog size='small'>\n <DialogTitle className={styles.modalTitle}>\n Select Coordinate Format\n </DialogTitle>\n <DialogContent>\n <p className={styles.modalDescription}>\n The pasted value matches multiple coordinate formats. Please\n select the correct interpretation:\n </p>\n\n <RadioGroup\n classNames={{ group: styles.formatOptions }}\n value={paste.selectedDisambiguationFormat}\n onChange={(value) =>\n paste.setSelectedDisambiguationFormat(\n value as CoordinateSystem,\n )\n }\n >\n {paste.disambiguationMatches.map((match) => (\n <Radio key={match.format} value={match.format}>\n <div className={styles.modalOptionContent}>\n <span className={styles.formatOptionLabel}>\n {COORDINATE_FORMAT_NAMES[match.format]}\n </span>\n <span className={styles.formatOptionValue}>\n {match.displayString}\n </span>\n </div>\n </Radio>\n ))}\n </RadioGroup>\n </DialogContent>\n <DialogFooter className={styles.modalActions}>\n <Button\n variant='flat'\n onPress={() => paste.setShowDisambiguationModal(false)}\n >\n Cancel\n </Button>\n <Button onPress={paste.handleDisambiguationSelect}>\n Apply Selected\n </Button>\n </DialogFooter>\n </Dialog>\n </DialogTrigger>\n </div>\n </Provider>\n );\n}\n"]}
@@ -24,6 +24,8 @@ import 'react-aria-components';
24
24
  * - UTM parser: /packages/geo/src/coordinates/utm/parser.ts
25
25
  */
26
26
 
27
+ /** The separator used for the logical field groups. */
28
+ declare const GROUP_SEPARATOR = ", ";
27
29
  /**
28
30
  * DD (Decimal Degrees) Segment Configuration
29
31
  *
@@ -160,4 +162,4 @@ declare const EXPECTED_SEGMENT_COUNTS: Record<CoordinateSystem, number>;
160
162
  */
161
163
  declare function getSegmentLabel(format: CoordinateSystem, editableIndex: number): string;
162
164
 
163
- export { EXPECTED_SEGMENT_COUNTS, ddSegmentConfigs, ddmSegmentConfigs, dmsSegmentConfigs, getEditableSegmentCount, getFormatDescription, getSegmentConfigs, getSegmentLabel, mgrsSegmentConfigs, utmSegmentConfigs };
165
+ export { EXPECTED_SEGMENT_COUNTS, GROUP_SEPARATOR, ddSegmentConfigs, ddmSegmentConfigs, dmsSegmentConfigs, getEditableSegmentCount, getFormatDescription, getSegmentConfigs, getSegmentLabel, mgrsSegmentConfigs, utmSegmentConfigs };
@@ -1,2 +1,2 @@
1
- const a=[{type:"numeric",placeholder:"00.000000",allowedChars:"[0-9\\-\\.]",maxLength:10,pad:.25},{type:"literal",value:", "},{type:"numeric",placeholder:"-000.000000",allowedChars:"[0-9\\-\\.]",maxLength:11,pad:.5}],n=[{type:"numeric",placeholder:"00",allowedChars:"[0-9]",maxLength:2,pad:.25},{type:"literal",value:"\xB0 "},{type:"numeric",placeholder:"00.0000",allowedChars:"[0-9\\.]",maxLength:7,pad:.25},{type:"literal",value:"' "},{type:"directional",placeholder:"N",allowedChars:"[NS]",maxLength:1,pad:0},{type:"literal",value:", "},{type:"numeric",placeholder:"000",allowedChars:"[0-9]",maxLength:3,pad:.25},{type:"literal",value:"\xB0 "},{type:"numeric",placeholder:"00.0000",allowedChars:"[0-9\\.]",maxLength:7,pad:.5},{type:"literal",value:"' "},{type:"directional",placeholder:"W",allowedChars:"[EW]",maxLength:1,pad:0}],l=[{type:"numeric",placeholder:"00",allowedChars:"[0-9]",maxLength:2,pad:.25},{type:"literal",value:"\xB0 "},{type:"numeric",placeholder:"00",allowedChars:"[0-9]",maxLength:2,pad:.25},{type:"literal",value:"' "},{type:"numeric",placeholder:"00.00",allowedChars:"[0-9\\.]",maxLength:5,pad:.25},{type:"literal",value:'" '},{type:"directional",placeholder:"N",allowedChars:"[NS]",maxLength:1,pad:0},{type:"literal",value:", "},{type:"numeric",placeholder:"000",allowedChars:"[0-9]",maxLength:3,pad:.25},{type:"literal",value:"\xB0 "},{type:"numeric",placeholder:"00",allowedChars:"[0-9]",maxLength:2,pad:.25},{type:"literal",value:"' "},{type:"numeric",placeholder:"00.00",allowedChars:"[0-9\\.]",maxLength:5,pad:.25},{type:"literal",value:'" '},{type:"directional",placeholder:"W",allowedChars:"[EW]",maxLength:1,pad:0}],o=[{type:"numeric",placeholder:"00",allowedChars:"[0-9]",maxLength:2,pad:.25},{type:"alphanumeric",placeholder:"T",allowedChars:"[C-HJ-NP-X]",maxLength:1,pad:0},{type:"literal",value:" "},{type:"alphanumeric",placeholder:"WM",allowedChars:"[A-HJ-NP-Z]",maxLength:2,pad:0},{type:"literal",value:" "},{type:"numeric",placeholder:"00000",allowedChars:"[0-9]",maxLength:5,pad:.25},{type:"literal",value:" "},{type:"numeric",placeholder:"00000",allowedChars:"[0-9]",maxLength:5,pad:.5}],d=[{type:"numeric",placeholder:"00",allowedChars:"[0-9]",maxLength:2,pad:.25},{type:"directional",placeholder:"N",allowedChars:"[NS]",maxLength:1,pad:0},{type:"literal",value:" "},{type:"numeric",placeholder:"000000",allowedChars:"[0-9]",maxLength:7,pad:.25},{type:"literal",value:" "},{type:"numeric",placeholder:"0000000",allowedChars:"[0-9]",maxLength:7,pad:.5}];function s(t){switch(t){case "dd":return a;case "ddm":return n;case "dms":return l;case "mgrs":return o;case "utm":return d;default:return a}}function c(t){switch(t){case "dd":return "Example: 40.7128, -74.0060 (New York City)";case "ddm":return "Example: 40\xB0 42.768' N, 74\xB0 0.360' W (New York City)";case "dms":return `Example: 40\xB0 42' 46.08" N, 74\xB0 0' 21.60" W (New York City)`;case "mgrs":return "Example: 18T WL 80654 06346 (New York City)";case "utm":return "Example: 18N 585628 4511644 (New York City)";default:return ""}}function p(t){return s(t).filter(r=>r.type!=="literal").length}const m={dd:2,ddm:6,dms:8,mgrs:5,utm:4};function g(t,e){switch(t){case "dd":return e===0?"Latitude":"Longitude";case "ddm":switch(e){case 0:return "Latitude degrees";case 1:return "Latitude minutes";case 2:return "Latitude direction";case 3:return "Longitude degrees";case 4:return "Longitude minutes";case 5:return "Longitude direction";default:return `Coordinate segment ${e+1}`}case "dms":switch(e){case 0:return "Latitude degrees";case 1:return "Latitude minutes";case 2:return "Latitude seconds";case 3:return "Latitude direction";case 4:return "Longitude degrees";case 5:return "Longitude minutes";case 6:return "Longitude seconds";case 7:return "Longitude direction";default:return `Coordinate segment ${e+1}`}case "mgrs":switch(e){case 0:return "MGRS zone";case 1:return "MGRS band";case 2:return "MGRS grid square";case 3:return "MGRS easting";case 4:return "MGRS northing";default:return `Coordinate segment ${e+1}`}case "utm":switch(e){case 0:return "UTM zone";case 1:return "UTM hemisphere";case 2:return "UTM easting";case 3:return "UTM northing";default:return `Coordinate segment ${e+1}`}default:return `Coordinate segment ${e+1}`}}export{m as EXPECTED_SEGMENT_COUNTS,a as ddSegmentConfigs,n as ddmSegmentConfigs,l as dmsSegmentConfigs,p as getEditableSegmentCount,c as getFormatDescription,s as getSegmentConfigs,g as getSegmentLabel,o as mgrsSegmentConfigs,d as utmSegmentConfigs};//# sourceMappingURL=segment-configs.js.map
1
+ const c=", ",a=[{type:"numeric",placeholder:"00.000000",allowedChars:"[0-9\\-\\.]",maxLength:10,pad:.25},{type:"literal",value:", "},{type:"numeric",placeholder:"-000.000000",allowedChars:"[0-9\\-\\.]",maxLength:11,pad:.5}],n=[{type:"numeric",placeholder:"00",allowedChars:"[0-9]",maxLength:2,pad:.25},{type:"literal",value:"\xB0"},{type:"numeric",placeholder:"00.0000",allowedChars:"[0-9\\.]",maxLength:7,pad:.25},{type:"literal",value:"'"},{type:"directional",placeholder:"N",allowedChars:"[NS]",maxLength:1,pad:0},{type:"literal",value:", "},{type:"numeric",placeholder:"000",allowedChars:"[0-9]",maxLength:3,pad:.25},{type:"literal",value:"\xB0"},{type:"numeric",placeholder:"00.0000",allowedChars:"[0-9\\.]",maxLength:7,pad:.5},{type:"literal",value:"'"},{type:"directional",placeholder:"W",allowedChars:"[EW]",maxLength:1,pad:0}],l=[{type:"numeric",placeholder:"00",allowedChars:"[0-9]",maxLength:2,pad:.25},{type:"literal",value:"\xB0"},{type:"numeric",placeholder:"00",allowedChars:"[0-9]",maxLength:2,pad:.25},{type:"literal",value:"'"},{type:"numeric",placeholder:"00.00",allowedChars:"[0-9\\.]",maxLength:5,pad:.25},{type:"literal",value:'"'},{type:"directional",placeholder:"N",allowedChars:"[NS]",maxLength:1,pad:0},{type:"literal",value:", "},{type:"numeric",placeholder:"000",allowedChars:"[0-9]",maxLength:3,pad:.25},{type:"literal",value:"\xB0"},{type:"numeric",placeholder:"00",allowedChars:"[0-9]",maxLength:2,pad:.25},{type:"literal",value:"'"},{type:"numeric",placeholder:"00.00",allowedChars:"[0-9\\.]",maxLength:5,pad:.25},{type:"literal",value:'"'},{type:"directional",placeholder:"W",allowedChars:"[EW]",maxLength:1,pad:0}],o=[{type:"numeric",placeholder:"00",allowedChars:"[0-9]",maxLength:2,pad:.25},{type:"alphanumeric",placeholder:"T",allowedChars:"[C-HJ-NP-X]",maxLength:1,pad:0},{type:"literal",value:" "},{type:"alphanumeric",placeholder:"WM",allowedChars:"[A-HJ-NP-Z]",maxLength:2,pad:0},{type:"literal",value:" "},{type:"numeric",placeholder:"00000",allowedChars:"[0-9]",maxLength:5,pad:.25},{type:"literal",value:" "},{type:"numeric",placeholder:"00000",allowedChars:"[0-9]",maxLength:5,pad:.5}],d=[{type:"numeric",placeholder:"00",allowedChars:"[0-9]",maxLength:2,pad:.25},{type:"directional",placeholder:"N",allowedChars:"[NS]",maxLength:1,pad:0},{type:"literal",value:" "},{type:"numeric",placeholder:"000000",allowedChars:"[0-9]",maxLength:7,pad:.25},{type:"literal",value:" "},{type:"numeric",placeholder:"0000000",allowedChars:"[0-9]",maxLength:7,pad:.5}];function s(t){switch(t){case "dd":return a;case "ddm":return n;case "dms":return l;case "mgrs":return o;case "utm":return d;default:return a}}function p(t){switch(t){case "dd":return "Example: 40.7128, -74.0060 (New York City)";case "ddm":return "Example: 40\xB0 42.768' N, 74\xB0 0.360' W (New York City)";case "dms":return `Example: 40\xB0 42' 46.08" N, 74\xB0 0' 21.60" W (New York City)`;case "mgrs":return "Example: 18T WL 80654 06346 (New York City)";case "utm":return "Example: 18N 585628 4511644 (New York City)";default:return ""}}function m(t){return s(t).filter(r=>r.type!=="literal").length}const g={dd:2,ddm:6,dms:8,mgrs:5,utm:4};function h(t,e){switch(t){case "dd":return e===0?"Latitude":"Longitude";case "ddm":switch(e){case 0:return "Latitude degrees";case 1:return "Latitude minutes";case 2:return "Latitude direction";case 3:return "Longitude degrees";case 4:return "Longitude minutes";case 5:return "Longitude direction";default:return `Coordinate segment ${e+1}`}case "dms":switch(e){case 0:return "Latitude degrees";case 1:return "Latitude minutes";case 2:return "Latitude seconds";case 3:return "Latitude direction";case 4:return "Longitude degrees";case 5:return "Longitude minutes";case 6:return "Longitude seconds";case 7:return "Longitude direction";default:return `Coordinate segment ${e+1}`}case "mgrs":switch(e){case 0:return "MGRS zone";case 1:return "MGRS band";case 2:return "MGRS grid square";case 3:return "MGRS easting";case 4:return "MGRS northing";default:return `Coordinate segment ${e+1}`}case "utm":switch(e){case 0:return "UTM zone";case 1:return "UTM hemisphere";case 2:return "UTM easting";case 3:return "UTM northing";default:return `Coordinate segment ${e+1}`}default:return `Coordinate segment ${e+1}`}}export{g as EXPECTED_SEGMENT_COUNTS,c as GROUP_SEPARATOR,a as ddSegmentConfigs,n as ddmSegmentConfigs,l as dmsSegmentConfigs,m as getEditableSegmentCount,p as getFormatDescription,s as getSegmentConfigs,h as getSegmentLabel,o as mgrsSegmentConfigs,d as utmSegmentConfigs};//# sourceMappingURL=segment-configs.js.map
2
2
  //# sourceMappingURL=segment-configs.js.map