@opengeoweb/form-fields 19.4.0 → 19.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.esm.js CHANGED
@@ -21,8 +21,10 @@ var en = {
21
21
  "form-fields-error-min-level": "The minimum level is 1",
22
22
  "form-fields-error-latitude": "Invalid latitude, expected number with maximum two decimal degrees between -90.00 and 90.00",
23
23
  "form-fields-error-longitude": "Invalid longitude, expected number with maximum two decimal degrees between -180.00 and 180.00",
24
- "form-fields-error-latitude-minutes": "Latitude must be between -90° and 90°",
25
- "form-fields-error-longitude-minutes": "Longitude must be between -180° and 180°",
24
+ "form-fields-error-latitude-minutes": "Latitude degrees must be between -90° and 90°, minutes between 00 and 59",
25
+ "form-fields-error-longitude-minutes": "Longitude degrees must be between -180° and 180°, minutes between 00 and 59",
26
+ "form-fields-error-invalid-minutes": "Minutes must be between 00 and 59",
27
+ "form-fields-error-invalid-coordinate-format": "Invalid format: latitude must be DDMM, longitude must be DDDMM",
26
28
  "form-fields-error-valid-max": "The maximum value is exceeded",
27
29
  "form-fields-error-level-lower": "The lower level has to be below the upper level",
28
30
  "form-fields-error-non-or-both-cooordinates": "Please enter both coordinates",
@@ -39,6 +41,10 @@ var fi = {
39
41
  "form-fields-error-min-level": "Pienin sallittu taso on 1",
40
42
  "form-fields-error-latitude": "Virheellinen leveysaste, odotettu arvo on kahden desimaalin tarkkuudella -90.00 ja 90.00 asteen välissä",
41
43
  "form-fields-error-longitude": "Virheellinen pituusaste, odotettu arvo on kahden desimaalin tarkkuudella -180 ja 180 asteen välissä",
44
+ "form-fields-error-latitude-minutes": "Leveysasteiden on oltava välillä -90° ja 90°, minuuttien välillä 00 ja 59",
45
+ "form-fields-error-longitude-minutes": "Pituusasteiden on oltava välillä -180° ja 180°, minuuttien välillä 00 ja 59",
46
+ "form-fields-error-invalid-minutes": "Minuuttien on oltava välillä 00 ja 59",
47
+ "form-fields-error-invalid-coordinate-format": "Virheellinen muoto: leveysaste DDMM, pituusaste DDDMM",
42
48
  "form-fields-error-valid-max": "Maksimiarvo on ylitetty",
43
49
  "form-fields-error-level-lower": "Alemman tason arvon on oltava ylemmän tason alla",
44
50
  "form-fields-error-non-or-both-cooordinates": "Lisää molemmat kordinaatit",
@@ -55,8 +61,10 @@ var no = {
55
61
  "form-fields-error-min-level": "The minimum level is 1",
56
62
  "form-fields-error-latitude": "Invalid latitude, expected number with maximum two decimal degrees between -90.00 and 90.00",
57
63
  "form-fields-error-longitude": "Invalid longitude, expected number with maximum two decimal degrees between -180.00 and 180.00",
58
- "form-fields-error-latitude-minutes": "Latitude must be between -90° and 90°",
59
- "form-fields-error-longitude-minutes": "Longitude must be between -180° and 180°",
64
+ "form-fields-error-latitude-minutes": "Latitude degrees must be between -90° and 90°, minutes between 00 and 59",
65
+ "form-fields-error-longitude-minutes": "Longitude degrees must be between -180° and 180°, minutes between 00 and 59",
66
+ "form-fields-error-invalid-minutes": "Minutes must be between 00 and 59",
67
+ "form-fields-error-invalid-coordinate-format": "Invalid format: latitude must be DDMM, longitude must be DDDMM",
60
68
  "form-fields-error-valid-max": "The maximum value is exceeded",
61
69
  "form-fields-error-level-lower": "The lower level has to be below the upper level",
62
70
  "form-fields-error-non-or-both-cooordinates": "Please enter both coordinates",
@@ -73,6 +81,10 @@ var nl = {
73
81
  "form-fields-error-min-level": "Het minimum niveau is 1",
74
82
  "form-fields-error-latitude": "Ongeldige breedtegraad, er wordt een getal verwacht van maximaal 2 decimalen in graden tussen -90.00 en 90.00",
75
83
  "form-fields-error-longitude": "Ongeldige lengtegraad, er wordt een getal verwacht van maximaal 2 decimalen in graden tussen -180.00 en 180.00",
84
+ "form-fields-error-latitude-minutes": "Breedtegraad moet tussen -90° en 90° zijn, minuten tussen 00 en 59",
85
+ "form-fields-error-longitude-minutes": "Lengtegraad moet tussen -180° en 180° zijn, minuten tussen 00 en 59",
86
+ "form-fields-error-invalid-minutes": "Minuten moeten tussen 00 en 59 zijn",
87
+ "form-fields-error-invalid-coordinate-format": "Ongeldig formaat: breedtegraad moet DDMM zijn, lengtegraad moet DDDMM zijn",
76
88
  "form-fields-error-valid-max": "Het maximum niveau is bereikt",
77
89
  "form-fields-error-level-lower": "Het lage niveau moet onder het hoge niveau zijn",
78
90
  "form-fields-error-non-or-both-cooordinates": "Voer beide coördinaten in",
@@ -1019,22 +1031,70 @@ function decimalToDegreesMinutes(value) {
1019
1031
  }
1020
1032
  return "" + sign + degrees + "\xB0" + minutes.toString().padStart(2, '0') + "\u2032";
1021
1033
  }
1022
- function degreesMinutesToDecimal(input) {
1034
+ /**
1035
+ * Parse a cardinal direction prefix (N/S/E/W) from input string.
1036
+ * Returns { negative: boolean, remaining: string }
1037
+ */
1038
+ function parseCardinalDirection(input, isLongitude) {
1039
+ var upperInput = input.toUpperCase().trim();
1040
+ var firstChar = upperInput.charAt(0);
1041
+ var remaining = upperInput.slice(1);
1042
+ // Check for valid cardinal direction based on coordinate type
1043
+ var validDirections = isLongitude ? ['W', 'E'] : ['S', 'N'];
1044
+ if (!validDirections.includes(firstChar)) {
1045
+ return {
1046
+ negative: false,
1047
+ remaining: input
1048
+ };
1049
+ }
1050
+ switch (firstChar) {
1051
+ case 'W':
1052
+ case 'S':
1053
+ return {
1054
+ negative: true,
1055
+ remaining: remaining
1056
+ };
1057
+ case 'E':
1058
+ case 'N':
1059
+ return {
1060
+ negative: false,
1061
+ remaining: remaining
1062
+ };
1063
+ default:
1064
+ return {
1065
+ negative: false,
1066
+ remaining: input
1067
+ };
1068
+ }
1069
+ }
1070
+ function degreesMinutesToDecimal(input, isLongitude) {
1071
+ if (isLongitude === void 0) {
1072
+ isLongitude = false;
1073
+ }
1023
1074
  var raw = input.toString().trim();
1024
- var negative = raw.startsWith('-');
1025
- var digits = raw.replace(/[^0-9]/g, '');
1026
- var sign = negative ? -1 : 1;
1027
- if (digits.length <= 2) {
1075
+ // Parse cardinal direction prefix (W/E/S/N)
1076
+ var _parseCardinalDirecti = parseCardinalDirection(raw, isLongitude),
1077
+ cardinalNegative = _parseCardinalDirecti.negative,
1078
+ remaining = _parseCardinalDirecti.remaining;
1079
+ // Check for minus sign in remaining input
1080
+ var hasMinusSign = remaining.startsWith('-');
1081
+ var digits = remaining.replace(/[^0-9]/g, '');
1082
+ // Determine sign: either from cardinal direction or minus sign
1083
+ var sign = cardinalNegative || hasMinusSign ? -1 : 1;
1084
+ // For latitude: <= 2 digits is degrees only (e.g., "90" = 90°)
1085
+ // For longitude: <= 3 digits is degrees only (e.g., "180" = 180°)
1086
+ var degreesOnlyMaxLen = isLongitude ? 3 : 2;
1087
+ if (digits.length <= degreesOnlyMaxLen) {
1028
1088
  var _deg = parseInt(digits || '0', 10);
1029
1089
  return sign * _deg;
1030
1090
  }
1031
1091
  // split into degrees + minutes
1032
1092
  var deg = parseInt(digits.slice(0, digits.length - 2), 10);
1033
1093
  var min = parseInt(digits.slice(-2), 10);
1034
- // normalize minutes overflow (e.g. 65 → 1°05′)
1035
- var extraDeg = Math.floor(min / 60);
1036
- min %= 60;
1037
- return sign * (deg + extraDeg + min / 60);
1094
+ // NOTE: We no longer normalize minutes overflow here.
1095
+ // Invalid minutes (>59) will be rejected by validation.
1096
+ // This preserves the raw value for validation purposes.
1097
+ return sign * (deg + min / 60);
1038
1098
  }
1039
1099
  var DegreesMinutesField = function DegreesMinutesField(_ref) {
1040
1100
  var name = _ref.name,
@@ -1046,16 +1106,24 @@ var DegreesMinutesField = function DegreesMinutesField(_ref) {
1046
1106
  _ref$size = _ref.size,
1047
1107
  size = _ref$size === void 0 ? 'small' : _ref$size,
1048
1108
  _ref$isLongitude = _ref.isLongitude,
1049
- isLongitude = _ref$isLongitude === void 0 ? false : _ref$isLongitude,
1050
- t = _ref.t;
1109
+ isLongitude = _ref$isLongitude === void 0 ? false : _ref$isLongitude;
1051
1110
  var _useFormContext = useFormContext(),
1052
1111
  control = _useFormContext.control;
1112
+ var _useFormFieldsTransla = useFormFieldsTranslation(),
1113
+ t = _useFormFieldsTransla.t;
1053
1114
  var _useState = useState(null),
1054
1115
  localInput = _useState[0],
1055
1116
  setLocalInput = _useState[1];
1056
1117
  var _useState2 = useState(false),
1057
1118
  isEditing = _useState2[0],
1058
1119
  setIsEditing = _useState2[1];
1120
+ // Track raw input when there's a validation error so we show what user typed
1121
+ var _useState3 = useState(null),
1122
+ rawInputOnError = _useState3[0],
1123
+ setRawInputOnError = _useState3[1];
1124
+ // Error key constants to distinguish error types
1125
+ var INVALID_MINUTES_ERROR = 'invalidMinutes';
1126
+ var INVALID_FORMAT_ERROR = 'invalidFormat';
1059
1127
  return jsx(Controller, {
1060
1128
  control: control,
1061
1129
  name: name,
@@ -1066,34 +1134,104 @@ var DegreesMinutesField = function DegreesMinutesField(_ref) {
1066
1134
  var value = field.value,
1067
1135
  onChange = field.onChange;
1068
1136
  var handleChange = function handleChange(e) {
1069
- setLocalInput(e.target.value);
1137
+ var rawValue = e.target.value;
1138
+ // Filter to only allow valid characters:
1139
+ // - Digits (0-9)
1140
+ // - Minus sign (-) only at the beginning
1141
+ // - Cardinal directions (W/E/N/S for longitude, N/S for latitude) only at the beginning
1142
+ // - Common formatting chars (°, ′, ', *) which will be stripped on blur
1143
+ var validCardinals = isLongitude ? /^[WwEe]/ : /^[NnSs]/;
1144
+ var filtered = '';
1145
+ for (var i = 0; i < rawValue.length; i += 1) {
1146
+ var _char = rawValue[i];
1147
+ var upperChar = _char.toUpperCase();
1148
+ if (/[0-9]/.test(_char)) {
1149
+ // Always allow digits
1150
+ filtered += _char;
1151
+ } else if (_char === '-' && filtered === '') {
1152
+ // Allow minus sign only at the beginning
1153
+ filtered += _char;
1154
+ } else if (i === 0 && validCardinals.test(upperChar) && filtered === '') {
1155
+ // Allow valid cardinal direction only at the very beginning
1156
+ filtered += upperChar;
1157
+ } else if (/[°′'*]/.test(_char)) {
1158
+ // Allow common formatting characters (will be stripped on blur)
1159
+ filtered += _char;
1160
+ }
1161
+ // All other characters are silently ignored
1162
+ }
1163
+ setLocalInput(filtered);
1070
1164
  setIsEditing(true);
1165
+ // Clear error state when user starts typing
1166
+ setRawInputOnError(null);
1071
1167
  };
1072
1168
  var handleBlur = function handleBlur() {
1073
- if (localInput != null && localInput !== '-' && localInput !== '') {
1074
- var input = localInput.replace(/[^\d-]/g, '');
1075
- var negative = input.startsWith('-');
1076
- var digits = input.replace(/[^0-9]/g, '');
1077
- // Latitude: 4 digits (DDMM), Longitude: 5 (DDDMM)
1078
- var maxLen = isLongitude ? 5 : 4;
1079
- if (digits.length > maxLen) {
1080
- digits = digits.slice(0, maxLen);
1081
- }
1082
- var normalized = negative ? "-" + digits : digits;
1083
- try {
1084
- var decimal = degreesMinutesToDecimal(normalized);
1085
- onChange(decimal);
1086
- } catch (_unused) {
1087
- // Invalid, do not update form value
1169
+ // Handle clearing the field
1170
+ if (localInput === '' || localInput === null) {
1171
+ onChange(null);
1172
+ setIsEditing(false);
1173
+ setLocalInput(null);
1174
+ setRawInputOnError(null);
1175
+ return;
1176
+ }
1177
+ // Handle just a minus sign
1178
+ if (localInput === '-') {
1179
+ setIsEditing(false);
1180
+ setLocalInput(null);
1181
+ return;
1182
+ }
1183
+ // Parse cardinal direction prefix (W/E/S/N)
1184
+ var _parseCardinalDirecti2 = parseCardinalDirection(localInput, isLongitude),
1185
+ isNegativeFromCardinal = _parseCardinalDirecti2.negative,
1186
+ remainingInput = _parseCardinalDirecti2.remaining;
1187
+ var input = remainingInput.replace(/[^\d-]/g, '');
1188
+ var hasMinusSign = input.startsWith('-');
1189
+ var digits = input.replace(/[^0-9]/g, '');
1190
+ // Latitude: max 4 digits (DDMM), Longitude: max 5 (DDDMM)
1191
+ var maxLen = isLongitude ? 5 : 4;
1192
+ if (digits.length > maxLen) {
1193
+ // Too many digits - show error but keep user's input visible
1194
+ setRawInputOnError(localInput);
1195
+ onChange(INVALID_FORMAT_ERROR);
1196
+ setIsEditing(false);
1197
+ setLocalInput(null);
1198
+ return;
1199
+ }
1200
+ // Check if minutes are valid (0-59) based on coordinate type
1201
+ // For latitude: > 2 digits means DDMM format
1202
+ // For longitude: > 3 digits means DDDMM format
1203
+ var degreesOnlyMaxLen = isLongitude ? 3 : 2;
1204
+ if (digits.length > degreesOnlyMaxLen) {
1205
+ var minutes = parseInt(digits.slice(-2), 10);
1206
+ if (minutes > 59) {
1207
+ // Invalid minutes - show error but keep user's input visible
1208
+ setRawInputOnError(localInput);
1209
+ onChange(INVALID_MINUTES_ERROR);
1210
+ setIsEditing(false);
1211
+ setLocalInput(null);
1212
+ return;
1088
1213
  }
1089
1214
  }
1215
+ // Determine if result should be negative
1216
+ var isNegative = isNegativeFromCardinal || hasMinusSign;
1217
+ var normalized = isNegative ? "-" + digits : digits;
1218
+ try {
1219
+ var decimal = degreesMinutesToDecimal(normalized, isLongitude);
1220
+ onChange(decimal);
1221
+ setRawInputOnError(null);
1222
+ } catch (_unused) {
1223
+ // Invalid, do not update form value
1224
+ }
1090
1225
  setIsEditing(false);
1091
1226
  setLocalInput(null);
1092
1227
  };
1093
1228
  var displayValue;
1094
1229
  if (isEditing) {
1095
1230
  displayValue = localInput != null ? localInput : '';
1096
- } else if (value != null) {
1231
+ } else if (fieldState.error && rawInputOnError !== null) {
1232
+ // Show the invalid input the user typed when there's an error
1233
+ displayValue = rawInputOnError;
1234
+ } else if (value != null && typeof value === 'number') {
1097
1235
  displayValue = decimalToDegreesMinutes(value);
1098
1236
  } else {
1099
1237
  displayValue = '';
@@ -1117,9 +1255,20 @@ var DegreesMinutesField = function DegreesMinutesField(_ref) {
1117
1255
  },
1118
1256
  rules: {
1119
1257
  validate: function validate(value) {
1120
- if (value == null || isNaN(value)) {
1258
+ if (value == null) {
1121
1259
  return true;
1122
1260
  } // let empty pass
1261
+ // Check for special error markers
1262
+ if (value === INVALID_MINUTES_ERROR) {
1263
+ return t('form-fields-error-invalid-minutes');
1264
+ }
1265
+ if (value === INVALID_FORMAT_ERROR) {
1266
+ return t('form-fields-error-invalid-coordinate-format');
1267
+ }
1268
+ // Must be a number for range checks
1269
+ if (typeof value !== 'number' || isNaN(value)) {
1270
+ return true;
1271
+ }
1123
1272
  if (!isLongitude && (value < -90 || value > 90)) {
1124
1273
  return t('form-fields-error-latitude-minutes');
1125
1274
  }
@@ -1,7 +1,13 @@
1
- import type { TFunction } from 'i18next';
2
1
  import React from 'react';
3
2
  export declare function decimalToDegreesMinutes(value: number): string;
4
- export declare function degreesMinutesToDecimal(input: string | number): number;
3
+ /**
4
+ * Check if the raw input has valid minutes (0-59).
5
+ * For latitude: digits > 2 means DDMM format
6
+ * For longitude: digits > 3 means DDDMM format
7
+ * Returns true if valid, false otherwise.
8
+ */
9
+ export declare function hasValidMinutes(input: string | number, isLongitude?: boolean): boolean;
10
+ export declare function degreesMinutesToDecimal(input: string | number, isLongitude?: boolean): number;
5
11
  interface Props {
6
12
  name: string;
7
13
  label: string;
@@ -9,7 +15,6 @@ interface Props {
9
15
  isReadOnly?: boolean;
10
16
  size?: 'small' | 'medium';
11
17
  isLongitude?: boolean;
12
- t: TFunction;
13
18
  }
14
19
  export declare const DegreesMinutesField: React.FC<Props>;
15
20
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeoweb/form-fields",
3
- "version": "19.4.0",
3
+ "version": "19.5.1",
4
4
  "description": "GeoWeb form-fields library for the opengeoweb project",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {