• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

geosolutions-it / MapStore2 / 12767533386

14 Jan 2025 12:22PM UTC coverage: 77.12% (+0.4%) from 76.716%
12767533386

Pull #10609

github

web-flow
Merge 372554eeb into 779416da2
Pull Request #10609: Normalize layers/groups management in TOC #10247

30300 of 47064 branches covered (64.38%)

6 of 6 new or added lines in 1 file covered. (100.0%)

522 existing lines in 35 files now uncovered.

37639 of 48806 relevant lines covered (77.12%)

34.91 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

75.34
/web/client/components/I18N/IntlNumberFormControl.jsx
1
/*
2
 * Copyright 2020, GeoSolutions Sas.
3
 * All rights reserved.
4
 *
5
 * This source code is licensed under the BSD-style license found in the
6
 * LICENSE file in the root directory of this source tree.
7
 */
8
import PropTypes from 'prop-types';
9

10
import React from 'react';
11
import './css/formControlIntl.css';
12
import NumericInput from '../../libs/numeric-input/NumericInput';
13
/**
14
 * Localized Numeric Input. It provides an numeric input value that uses
15
 * separators (eg. ",",".") as they are used in the current selected language from context.
16
 * @prop {string|number} value a numeric value
17
 * @prop {string|number] defaultValue a value to use as default
18
 * @prop {function} onChange handler of change event. the argument of this handler is the current value inserted.
19
 * @prop {number} step the step when you click on arrows (up, down) of the input
20
 * @prop {function} onBlur event handler on blur event
21
 */
22
class IntlNumberFormControl extends React.Component {
23

24
    static propTypes = {
1✔
25
        type: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
26
        value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
27
        defaultValue: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
28
        onChange: PropTypes.func,
29
        step: PropTypes.number,
30
        locale: PropTypes.string,
31
        disabled: PropTypes.bool,
32
        onBlur: PropTypes.func,
33
        onKeyDown: PropTypes.func,
34
        onKeyUp: PropTypes.func
35
    }
36
    static contextTypes = {
1✔
37
        intl: PropTypes.object
38
    };
39
    constructor(props) {
40
        super(props);
327✔
41
        this.state = {
327✔
42
            inputRef: null
43
        };
44
        this.value = '';
327✔
45
        this.currentInputCursor = null;
327✔
46
    }
47

48
    componentDidUpdate(prevProps) {
49
        const currentValue = this.format(this.props.value || "");
274✔
50
        const prevValue = this.format(prevProps.value || "");
274✔
51
        const currentCursorPos = this.currentInputCursor;
274✔
52
        // update cursor position in case adding/deleting numbers at the middle of the current input value
53
        if (prevValue !== currentValue) {
274✔
54
            this.value = currentValue;      // set value
40✔
55
            const locale = this.context && this.context.intl && this.context.intl.locale || "en-US";
40!
56
            const formatParts = new Intl.NumberFormat(locale).formatToParts(10000.1);
40✔
57
            const groupSeparator = formatParts?.find(part => part.type === 'group').value;
80✔
58
            let isFormattedCurrentVal = currentValue && groupSeparator && (currentValue.includes(groupSeparator));
40✔
59
            let isFormattedPrevVal = prevValue && groupSeparator && (prevValue.includes(groupSeparator));
40✔
60
            if ((isFormattedCurrentVal || isFormattedPrevVal)) {
40!
61
                let currentValueLength = currentValue.length;           // length of current value
×
62
                let prevValueLength = prevValue.length;                 // length of prev value
×
63
                let groupSeparatorPrevValue   = prevValueLength  - prevValue.replaceAll(groupSeparator, "").length;
×
64
                let groupSeparatorCurrentValue  = currentValueLength - currentValue.replaceAll(groupSeparator, "").length;
×
65

66
                if (currentValueLength - prevValueLength === 2 && groupSeparatorPrevValue < groupSeparatorCurrentValue) {           // in adding numbers and a group separator will be appeared due to insertion
×
67
                    this.state.inputRef.setSelectionRange(currentCursorPos + 2, currentCursorPos + 2 );
×
68
                } else if (currentValueLength - prevValueLength === 1 && groupSeparatorPrevValue === groupSeparatorCurrentValue) {  // in adding numbers and the group separators is the same after insertion
×
69
                    this.state.inputRef.setSelectionRange(currentCursorPos + 1, currentCursorPos + 1);
×
70
                } else if (prevValueLength - currentValueLength === 2 && groupSeparatorPrevValue > groupSeparatorCurrentValue) {    // in deleting numbers and a group separator will be reduced due to deletion
×
71
                    this.state.inputRef.setSelectionRange(currentCursorPos - 2 || 0, currentCursorPos - 2 || 0);
×
72
                } else if (prevValueLength - currentValueLength === 1 && groupSeparatorPrevValue === groupSeparatorCurrentValue) {  // in deleting numbers and the group separators is the same after deletion
×
73
                    this.state.inputRef.setSelectionRange(currentCursorPos - 1 || 0, currentCursorPos - 1 || 0);
×
74
                }
75
            }
76
        }
77
    }
78
    onKeyUp = (ev) =>{
327✔
79
        this.props.onKeyUp && this.props.onKeyUp(ev);
1✔
80
        const currentCursorPos = this.currentInputCursor;
1✔
81
        let isDelete = ev.keyCode === 8 || ev.keyCode === 46;       // delete by delete key or backspace key
1✔
82
        // move the cursor of adding number at the end of input
83
        if (![37, 39, 17].includes(ev.keyCode) && !isDelete && currentCursorPos === ev.target.value.length - 1) {
1!
84
            ev.target.setSelectionRange(-1, -1);
×
85
        }
86
    }
87

88
    render() {
89
        const {onChange, onBlur, disabled, type, step, value, defaultValue, onKeyDown,
90
            ...formProps} = this.props;
601✔
91
        return (
601✔
92
            <NumericInput
93
                id={'intl-numeric'}
94
                step={step}
95
                {...formProps}
96
                {...value !== undefined ? {value: this.format(value) } : {defaultValue: this.format(defaultValue)}}
601✔
97
                format={this.format}
98
                onChange={(val) => {
99
                    val === null ? this.props.onChange("") : this.props.onChange(val.toString());
50✔
100
                }}
101
                onKeyUp={this.onKeyUp}
102
                onBlur={e=>{
103
                    let val = e.target.value;
8✔
104
                    if (onBlur) {
8!
105
                        e.target.value = this.parse(val);
×
106
                        onBlur(e);
×
107
                    }
108
                }}
109
                disabled={disabled || false}
1,150✔
110
                onFocus={(e) =>{
111
                    // save input ref into state to enable getting/updating selection range [input cursor position]
112
                    if (!this.state.inputRef) {
16✔
113
                        this.setState({ inputRef: e.target });
10✔
114
                        return;
10✔
115
                    }
116
                    return;
6✔
117
                }}
118
                onKeyDown={(e) => {
119
                    // store the current cursor before any update
120
                    this.currentInputCursor =  e.target.selectionStart;
11✔
121
                    onKeyDown && onKeyDown(e);
11✔
122
                }
123
                }
124
                parse={this.parse}
125
                onKeyPress={e => {
126
                    const allow = e.key.match(/^[a-zA-Z]*$/);
×
127
                    allow !== null && e.preventDefault();
×
128
                }}
129
                componentClass={"input"}
130
                className="form-control intl-numeric"
131
                locale={this.context && this.context.intl && this.context.intl.locale || "en-US"}
1,804✔
132
            />
133
        );
134
    }
135

136
    parse = value => {
327✔
137
        let formatValue = value;
568✔
138
        // eslint-disable-next-line use-isnan
139
        if (formatValue !== NaN && formatValue !== "NaN") {  // Allow locale string to parse
568!
140
            const locale = this.context && this.context.intl && this.context.intl.locale || "en-US";
568✔
141
            const format = new Intl.NumberFormat(locale);
568✔
142
            const parts = format.formatToParts(12345.6);
568✔
143
            const numerals = Array.from({ length: 10 }).map((_, i) => format.format(i));
5,680✔
144
            const index = new Map(numerals.map((d, i) => [d, i]));
5,680✔
145
            const group = new RegExp(
568✔
146
                `[${parts.find(d => d.type === "group").value}]`,
1,136✔
147
                "g"
148
            );
149
            const decimal = new RegExp(
568✔
150
                `[${parts.find(d => d.type === "decimal").value}]`
2,272✔
151
            );
152
            const numeral = new RegExp(`[${numerals.join("")}]`, "g");
568✔
153
            const rIndex = d => index.get(d);
1,105✔
154

155
            formatValue = (formatValue
568✔
156
                .trim()
157
                .replace(group, "")
158
                .replace(decimal, ".")
159
                .replace(numeral, rIndex));
160
            return formatValue ? +formatValue : NaN;
568✔
161
        }
UNCOV
162
        return "";
×
163
    };
164

165
    format = val => {
327✔
166
        if (!isNaN(val) && val !== "NaN") {
1,658✔
167
            const locale = this.context && this.context.intl && this.context.intl.locale || "en-US";
1,550✔
168
            const formatter = new Intl.NumberFormat(locale, {minimumFractionDigits: 0, maximumFractionDigits: 20});
1,550✔
169
            return formatter.format(val);
1,550✔
170
        }
171
        return "";
108✔
172
    };
173
}
174

175
export default IntlNumberFormControl;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc