@opengeoweb/form-fields 14.0.1 → 14.2.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/index.esm.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
2
|
import { FormControl, FormHelperText, InputLabel, Select, RadioGroup, TextField, InputAdornment } from '@mui/material';
|
|
3
|
-
import { useFormContext, useController, useForm, FormProvider } from 'react-hook-form';
|
|
3
|
+
import { useFormContext, useController, useForm, FormProvider, Controller } from 'react-hook-form';
|
|
4
4
|
import { isEqual } from 'lodash';
|
|
5
5
|
import { dateUtils } from '@opengeoweb/shared';
|
|
6
6
|
import 'i18next';
|
|
7
7
|
import { useTranslation } from 'react-i18next';
|
|
8
|
-
import React from 'react';
|
|
8
|
+
import React, { useState } from 'react';
|
|
9
9
|
import { DateTimePicker } from '@mui/x-date-pickers';
|
|
10
10
|
import { CalendarToday } from '@opengeoweb/theme';
|
|
11
11
|
import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns';
|
|
@@ -482,8 +482,7 @@ var ReactHookFormSelect = function ReactHookFormSelect(_ref) {
|
|
|
482
482
|
// default props
|
|
483
483
|
_onChange(changeEvent, child);
|
|
484
484
|
},
|
|
485
|
-
|
|
486
|
-
value: value || '',
|
|
485
|
+
value: value != null ? value : '',
|
|
487
486
|
variant: "filled"
|
|
488
487
|
}, otherProps, {
|
|
489
488
|
children: children
|
|
@@ -678,12 +677,10 @@ var ReactHookFormNumberField = function ReactHookFormNumberField(_ref) {
|
|
|
678
677
|
var before = inputElement.value.slice(0, inputElement.selectionStart);
|
|
679
678
|
var after = inputElement.value.slice(inputElement.selectionEnd);
|
|
680
679
|
var newValue = before + clipboardText + after;
|
|
681
|
-
|
|
682
|
-
if (inputMode === 'numeric' && !/^\-?[0-9]+$/.exec(newValue)) {
|
|
680
|
+
if (inputMode === 'numeric' && !/^-?[0-9]+$/.exec(newValue)) {
|
|
683
681
|
event.preventDefault();
|
|
684
682
|
}
|
|
685
|
-
|
|
686
|
-
if (inputMode === 'decimal' && !/^\-?[0-9]*(\.[0-9]+)?$/.exec(newValue)) {
|
|
683
|
+
if (inputMode === 'decimal' && !/^-?[0-9]*(.[0-9]+)?$/.exec(newValue)) {
|
|
687
684
|
event.preventDefault();
|
|
688
685
|
}
|
|
689
686
|
};
|
|
@@ -702,9 +699,50 @@ var ReactHookFormNumberField = function ReactHookFormNumberField(_ref) {
|
|
|
702
699
|
event.preventDefault();
|
|
703
700
|
}
|
|
704
701
|
};
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
702
|
+
// Transform number value to string for display, following React Hook Form transform pattern
|
|
703
|
+
var formatValueForDisplay = React.useCallback(function (numValue) {
|
|
704
|
+
if (numValue === null || numValue === undefined || isNaN(numValue)) {
|
|
705
|
+
return '';
|
|
706
|
+
}
|
|
707
|
+
return String(numValue);
|
|
708
|
+
}, []);
|
|
709
|
+
// Local state to preserve string precision during editing (e.g., trailing zeros), only sync from form value when not actively editing
|
|
710
|
+
var _React$useState = React.useState(null),
|
|
711
|
+
localValue = _React$useState[0],
|
|
712
|
+
setLocalValue = _React$useState[1];
|
|
713
|
+
var isFocusedRef = React.useRef(false);
|
|
714
|
+
var previousValueRef = React.useRef(value);
|
|
715
|
+
var lastSetValueRef = React.useRef(null);
|
|
716
|
+
// Sync local value from form value when value changes externally, but only when not actively editing and the change wasn't from our own update
|
|
717
|
+
React.useEffect(function () {
|
|
718
|
+
if (!isFocusedRef.current && value !== previousValueRef.current) {
|
|
719
|
+
var wasOurUpdate = lastSetValueRef.current !== null && lastSetValueRef.current === value;
|
|
720
|
+
if (!wasOurUpdate) {
|
|
721
|
+
setLocalValue(null); // Clear local value to use form value
|
|
722
|
+
} else if (localValue !== null) {
|
|
723
|
+
// It was our update, but check if localValue still represents this number accurately
|
|
724
|
+
var numFromLocal = convertNumericInputValue(localValue, inputMode);
|
|
725
|
+
if (numFromLocal !== value) {
|
|
726
|
+
// Local value no longer matches form value, clear it
|
|
727
|
+
setLocalValue(null);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
previousValueRef.current = value;
|
|
731
|
+
}
|
|
732
|
+
}, [value, localValue, inputMode]);
|
|
733
|
+
// Compute display value: prefer local value if it has more precision than number representation
|
|
734
|
+
var displayValue = React.useMemo(function () {
|
|
735
|
+
// Always use local value if it exists and represents the same number as form value
|
|
736
|
+
if (localValue !== null) {
|
|
737
|
+
var numFromLocal = convertNumericInputValue(localValue, inputMode);
|
|
738
|
+
var valuesMatch = numFromLocal === value || numFromLocal === null && value === null || numFromLocal !== null && typeof numFromLocal === 'number' && typeof value === 'number' && isNaN(numFromLocal) && isNaN(value);
|
|
739
|
+
if (valuesMatch) {
|
|
740
|
+
return localValue;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
// When no local value or it doesn't match, use formatted form value
|
|
744
|
+
return formatValueForDisplay(value);
|
|
745
|
+
}, [value, localValue, inputMode, formatValueForDisplay]);
|
|
708
746
|
return jsx(ReactHookFormFormControl, {
|
|
709
747
|
className: className,
|
|
710
748
|
disabled: disabled,
|
|
@@ -719,12 +757,30 @@ var ReactHookFormNumberField = function ReactHookFormNumberField(_ref) {
|
|
|
719
757
|
inputRef: ref,
|
|
720
758
|
label: label,
|
|
721
759
|
name: name,
|
|
760
|
+
onBlur: function onBlur(evt) {
|
|
761
|
+
isFocusedRef.current = false;
|
|
762
|
+
// Keep local value on blur to preserve precision (e.g., trailing zeros), it will be cleared only if form value changes externally
|
|
763
|
+
previousValueRef.current = value;
|
|
764
|
+
otherProps.onBlur == null || otherProps.onBlur(evt);
|
|
765
|
+
},
|
|
722
766
|
onChange: function onChange(evt) {
|
|
723
|
-
var
|
|
724
|
-
|
|
725
|
-
|
|
767
|
+
var inputValue = evt.target.value;
|
|
768
|
+
// Update local value to preserve string precision during editing
|
|
769
|
+
setLocalValue(inputValue);
|
|
770
|
+
// Convert to number for form value
|
|
771
|
+
var numericValue = convertNumericInputValue(inputValue, inputMode);
|
|
772
|
+
lastSetValueRef.current = numericValue;
|
|
773
|
+
onChangeField(numericValue);
|
|
726
774
|
_onChange(null);
|
|
727
775
|
},
|
|
776
|
+
onFocus: function onFocus(evt) {
|
|
777
|
+
isFocusedRef.current = true;
|
|
778
|
+
// Initialize local value with current display value when focusing
|
|
779
|
+
if (localValue === null) {
|
|
780
|
+
setLocalValue(formatValueForDisplay(value));
|
|
781
|
+
}
|
|
782
|
+
otherProps.onFocus == null || otherProps.onFocus(evt);
|
|
783
|
+
},
|
|
728
784
|
onKeyDown: onKeyDown,
|
|
729
785
|
size: size,
|
|
730
786
|
type: "text",
|
|
@@ -940,6 +996,131 @@ var ReactHookFormHiddenInput = function ReactHookFormHiddenInput(_ref) {
|
|
|
940
996
|
}, props)) : null;
|
|
941
997
|
};
|
|
942
998
|
|
|
999
|
+
function decimalToDegreesMinutes(value) {
|
|
1000
|
+
var sign = value < 0 ? '-' : '';
|
|
1001
|
+
var abs = Math.abs(value);
|
|
1002
|
+
var degrees = Math.floor(abs);
|
|
1003
|
+
var minutes = Math.round((abs - degrees) * 60);
|
|
1004
|
+
// normalize rounding overflow (e.g. 59.9 min → +1 degree)
|
|
1005
|
+
if (minutes === 60) {
|
|
1006
|
+
abs += 1;
|
|
1007
|
+
return decimalToDegreesMinutes(sign === '-' ? -abs : abs);
|
|
1008
|
+
}
|
|
1009
|
+
return "" + sign + degrees + "\xB0" + minutes.toString().padStart(2, '0') + "\u2032";
|
|
1010
|
+
}
|
|
1011
|
+
function degreesMinutesToDecimal(input) {
|
|
1012
|
+
var raw = input.toString().trim();
|
|
1013
|
+
var negative = raw.startsWith('-');
|
|
1014
|
+
var digits = raw.replace(/[^0-9]/g, '');
|
|
1015
|
+
var sign = negative ? -1 : 1;
|
|
1016
|
+
if (digits.length <= 2) {
|
|
1017
|
+
var _deg = parseInt(digits || '0', 10);
|
|
1018
|
+
return sign * _deg;
|
|
1019
|
+
}
|
|
1020
|
+
// split into degrees + minutes
|
|
1021
|
+
var deg = parseInt(digits.slice(0, digits.length - 2), 10);
|
|
1022
|
+
var min = parseInt(digits.slice(-2), 10);
|
|
1023
|
+
// normalize minutes overflow (e.g. 65 → 1°05′)
|
|
1024
|
+
var extraDeg = Math.floor(min / 60);
|
|
1025
|
+
min %= 60;
|
|
1026
|
+
return sign * (deg + extraDeg + min / 60);
|
|
1027
|
+
}
|
|
1028
|
+
var DegreesMinutesField = function DegreesMinutesField(_ref) {
|
|
1029
|
+
var name = _ref.name,
|
|
1030
|
+
label = _ref.label,
|
|
1031
|
+
_ref$disabled = _ref.disabled,
|
|
1032
|
+
disabled = _ref$disabled === void 0 ? false : _ref$disabled,
|
|
1033
|
+
_ref$isReadOnly = _ref.isReadOnly,
|
|
1034
|
+
isReadOnly = _ref$isReadOnly === void 0 ? false : _ref$isReadOnly,
|
|
1035
|
+
_ref$size = _ref.size,
|
|
1036
|
+
size = _ref$size === void 0 ? 'small' : _ref$size,
|
|
1037
|
+
_ref$isLongitude = _ref.isLongitude,
|
|
1038
|
+
isLongitude = _ref$isLongitude === void 0 ? false : _ref$isLongitude,
|
|
1039
|
+
t = _ref.t;
|
|
1040
|
+
var _useFormContext = useFormContext(),
|
|
1041
|
+
control = _useFormContext.control;
|
|
1042
|
+
var _useState = useState(null),
|
|
1043
|
+
localInput = _useState[0],
|
|
1044
|
+
setLocalInput = _useState[1];
|
|
1045
|
+
var _useState2 = useState(false),
|
|
1046
|
+
isEditing = _useState2[0],
|
|
1047
|
+
setIsEditing = _useState2[1];
|
|
1048
|
+
return jsx(Controller, {
|
|
1049
|
+
control: control,
|
|
1050
|
+
name: name,
|
|
1051
|
+
render: function render(_ref2) {
|
|
1052
|
+
var _fieldState$error;
|
|
1053
|
+
var field = _ref2.field,
|
|
1054
|
+
fieldState = _ref2.fieldState;
|
|
1055
|
+
var value = field.value,
|
|
1056
|
+
onChange = field.onChange;
|
|
1057
|
+
var handleChange = function handleChange(e) {
|
|
1058
|
+
setLocalInput(e.target.value);
|
|
1059
|
+
setIsEditing(true);
|
|
1060
|
+
};
|
|
1061
|
+
var handleBlur = function handleBlur() {
|
|
1062
|
+
if (localInput != null && localInput !== '-' && localInput !== '') {
|
|
1063
|
+
var input = localInput.replace(/[^\d-]/g, '');
|
|
1064
|
+
var negative = input.startsWith('-');
|
|
1065
|
+
var digits = input.replace(/[^0-9]/g, '');
|
|
1066
|
+
// Latitude: 4 digits (DDMM), Longitude: 5 (DDDMM)
|
|
1067
|
+
var maxLen = isLongitude ? 5 : 4;
|
|
1068
|
+
if (digits.length > maxLen) {
|
|
1069
|
+
digits = digits.slice(0, maxLen);
|
|
1070
|
+
}
|
|
1071
|
+
var normalized = negative ? "-" + digits : digits;
|
|
1072
|
+
try {
|
|
1073
|
+
var decimal = degreesMinutesToDecimal(normalized);
|
|
1074
|
+
onChange(decimal);
|
|
1075
|
+
} catch (_unused) {
|
|
1076
|
+
// Invalid, do not update form value
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
setIsEditing(false);
|
|
1080
|
+
setLocalInput(null);
|
|
1081
|
+
};
|
|
1082
|
+
var displayValue;
|
|
1083
|
+
if (isEditing) {
|
|
1084
|
+
displayValue = localInput != null ? localInput : '';
|
|
1085
|
+
} else if (value != null) {
|
|
1086
|
+
displayValue = decimalToDegreesMinutes(value);
|
|
1087
|
+
} else {
|
|
1088
|
+
displayValue = '';
|
|
1089
|
+
}
|
|
1090
|
+
return jsx(TextField, {
|
|
1091
|
+
disabled: disabled,
|
|
1092
|
+
error: !!fieldState.error,
|
|
1093
|
+
helperText: (_fieldState$error = fieldState.error) == null ? void 0 : _fieldState$error.message,
|
|
1094
|
+
label: label,
|
|
1095
|
+
onBlur: handleBlur,
|
|
1096
|
+
onChange: handleChange,
|
|
1097
|
+
placeholder: isLongitude ? 'DDD°MM′' : 'DD°MM′',
|
|
1098
|
+
size: size,
|
|
1099
|
+
slotProps: {
|
|
1100
|
+
input: {
|
|
1101
|
+
readOnly: isReadOnly
|
|
1102
|
+
}
|
|
1103
|
+
},
|
|
1104
|
+
value: displayValue
|
|
1105
|
+
});
|
|
1106
|
+
},
|
|
1107
|
+
rules: {
|
|
1108
|
+
validate: function validate(value) {
|
|
1109
|
+
if (value == null || isNaN(value)) {
|
|
1110
|
+
return true;
|
|
1111
|
+
} // let empty pass
|
|
1112
|
+
if (!isLongitude && (value < -90 || value > 90)) {
|
|
1113
|
+
return t('form-fields-error-latitude-minutes');
|
|
1114
|
+
}
|
|
1115
|
+
if (isLongitude && (value < -180 || value > 180)) {
|
|
1116
|
+
return t('form-fields-error-longitude-minutes');
|
|
1117
|
+
}
|
|
1118
|
+
return true;
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
});
|
|
1122
|
+
};
|
|
1123
|
+
|
|
943
1124
|
// hidden input helpers, should not be part of send formdata
|
|
944
1125
|
var HIDDEN_INPUT_HELPER_IS_DRAFT = 'IS_DRAFT';
|
|
945
1126
|
var useDraftFormHelpers = function useDraftFormHelpers(isDefaultDraft) {
|
|
@@ -954,8 +1135,7 @@ var useDraftFormHelpers = function useDraftFormHelpers(isDefaultDraft) {
|
|
|
954
1135
|
setValue(HIDDEN_INPUT_HELPER_IS_DRAFT, isDefaultDraft, {
|
|
955
1136
|
shouldDirty: false
|
|
956
1137
|
});
|
|
957
|
-
|
|
958
|
-
}, []);
|
|
1138
|
+
}, [isDefaultDraft, setValue]);
|
|
959
1139
|
var isDraft = function isDraft() {
|
|
960
1140
|
return getValues(HIDDEN_INPUT_HELPER_IS_DRAFT) === true;
|
|
961
1141
|
};
|
|
@@ -983,4 +1163,4 @@ var useDraftFormHelpers = function useDraftFormHelpers(isDefaultDraft) {
|
|
|
983
1163
|
};
|
|
984
1164
|
};
|
|
985
1165
|
|
|
986
|
-
export { FORM_FIELDS_NAMESPACE, HIDDEN_INPUT_HELPER_IS_DRAFT, ReactHookKeyboardDateTimePicker as ReactHookFormDateTime, ReactHookFormFormControl, ReactHookFormHiddenInput, ReactHookFormNumberField, ReactHookFormProviderWrapper as ReactHookFormProvider, ReactHookFormRadioGroup, ReactHookFormSelect, ReactHookFormTextField, countIntersectionPoints, defaultFormOptions, errorMessages, formFieldsTranslations, getDateValue, getDeepProperty, getFormattedValueForDatePicker, hasIntersectionWithFIR, hasMulitpleIntersections, hasValidGeometry, isEmpty, isGeometryDirty, isInteger, isLatitude, isLongitude, isMaximumOneDrawing, isNonOrBothCoordinates, isValidGeoJsonCoordinates, isValidMax, isValidMin, isXHoursAfter, isXHoursBefore, useDraftFormHelpers };
|
|
1166
|
+
export { DegreesMinutesField, FORM_FIELDS_NAMESPACE, HIDDEN_INPUT_HELPER_IS_DRAFT, ReactHookKeyboardDateTimePicker as ReactHookFormDateTime, ReactHookFormFormControl, ReactHookFormHiddenInput, ReactHookFormNumberField, ReactHookFormProviderWrapper as ReactHookFormProvider, ReactHookFormRadioGroup, ReactHookFormSelect, ReactHookFormTextField, countIntersectionPoints, defaultFormOptions, errorMessages, formFieldsTranslations, getDateValue, getDeepProperty, getFormattedValueForDatePicker, hasIntersectionWithFIR, hasMulitpleIntersections, hasValidGeometry, isEmpty, isGeometryDirty, isInteger, isLatitude, isLongitude, isMaximumOneDrawing, isNonOrBothCoordinates, isValidGeoJsonCoordinates, isValidMax, isValidMin, isXHoursAfter, isXHoursBefore, useDraftFormHelpers };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeoweb/form-fields",
|
|
3
|
-
"version": "14.
|
|
3
|
+
"version": "14.2.1",
|
|
4
4
|
"description": "GeoWeb form-fields library for the opengeoweb project",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -8,11 +8,11 @@
|
|
|
8
8
|
"url": "git@gitlab.com:opengeoweb/opengeoweb.git"
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"@opengeoweb/theme": "14.
|
|
11
|
+
"@opengeoweb/theme": "14.2.1",
|
|
12
12
|
"react-hook-form": "^7.50.1",
|
|
13
13
|
"@mui/x-date-pickers": "^8.11.2",
|
|
14
14
|
"lodash": "^4.17.21",
|
|
15
|
-
"@opengeoweb/shared": "14.
|
|
15
|
+
"@opengeoweb/shared": "14.2.1",
|
|
16
16
|
"react-i18next": "^15.1.1",
|
|
17
17
|
"@mui/material": "^7.0.1",
|
|
18
18
|
"i18next": "^25.0.1",
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { TFunction } from 'i18next';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
export declare function decimalToDegreesMinutes(value: number): string;
|
|
4
|
+
export declare function degreesMinutesToDecimal(input: string | number): number;
|
|
5
|
+
interface Props {
|
|
6
|
+
name: string;
|
|
7
|
+
label: string;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
isReadOnly?: boolean;
|
|
10
|
+
size?: 'small' | 'medium';
|
|
11
|
+
isLongitude?: boolean;
|
|
12
|
+
t: TFunction;
|
|
13
|
+
}
|
|
14
|
+
export declare const DegreesMinutesField: React.FC<Props>;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -7,6 +7,7 @@ export { default as ReactHookFormDateTime } from './ReactHookFormDateTime';
|
|
|
7
7
|
export * from './ReactHookFormDateTime';
|
|
8
8
|
export { default as ReactHookFormProvider } from './ReactHookFormProvider';
|
|
9
9
|
export { default as ReactHookFormHiddenInput } from './ReactHookFormHiddenInput';
|
|
10
|
+
export { DegreesMinutesField } from './DegreesMinutesField';
|
|
10
11
|
export { defaultFormOptions } from './ReactHookFormProvider';
|
|
11
12
|
export { errorMessages, isMaximumOneDrawing, hasValidGeometry, isGeometryDirty, isValidGeoJsonCoordinates, hasIntersectionWithFIR, countIntersectionPoints, isValidMax, isValidMin, isInteger, hasMulitpleIntersections, isLatitude, isLongitude, isNonOrBothCoordinates, isEmpty, isXHoursAfter, isXHoursBefore, } from './utils';
|
|
12
13
|
export { getDeepProperty } from './formUtils';
|