/**
* Input Float numeric Plain React Hooks function
*
* props:
* value
* numeralDecimalScale
* onChange
* id
* className
* style
*/
function InputFloat(props) {
const e = React.createElement;
const refInput = React.useRef(null);
const numeralDecimalScale = React.useRef(props.numeralDecimalScale ? props.numeralDecimalScale : 2);
const SaveValue = React.useRef(format(props.value));
const Start = React.useRef(0);
const End = React.useRef(0);
const [value, setValue] = React.useState({ value: format(props.value) });
// Prop value changed
React.useEffect(() => {
if (props == undefined || props.value === undefined) { return; }
// console.log("change props", props, props.value);
if (float(props.value) == float(SaveValue.current)) {
// console.log("not change props.value", props, props.value);
return;
}
const v = format(props.value);
setV(v, 0, 0);
setValue({ value: v });
}, [props.value]);
// Set cursor position after change state value
React.useEffect(() => {
refInput.current.setSelectionRange(Start.current, End.current);
}, [value]);
function setV(v, start, end) {
// console.log("setV", v, start, end);
SaveValue.current = v;
Start.current = start;
End.current = end;
}
/**
* Using:
* agh.sprintf.js
* https://github.com/akinomyoga/agh.sprintf.js
*/
function format(v) {
// console.log("format from", v);
v += "";
const sign = v.indexOf("-") >= 0;
const f = float(v);
v = sprintf("%'0.0" + numeralDecimalScale.current + "f", f);
if (sign && f == 0) {
v = "-" + v;
}
return v;
}
// convert to float
function float(v) {
v += "";
const dot = dotPosition(v);
let f = parseFloat(v.substring(0, dot + numeralDecimalScale.current + 1).replaceAll(",", ""));
if (isNaN(f)) { f = 0; }
return f;
}
function dotPosition(v) {
v += "";
let dot = v.indexOf(".");
if (dot == -1) { dot = v.length; }
return dot;
}
/**
* position より前の位置にある , の数を数える
* Count the number of "," before the position of v
*/
function countComma(v, position) {
const targetStr = ",";
return (v.substring(0, position).match(new RegExp(targetStr, "g")) || []).length;
}
function replace(src, position, s) {
return format(src.substring(0, position) + s + src.substring(position + s.length));
}
function insert(src, position, s) {
return format(src.substring(0, position) + s + src.substring(position));
}
// src の position 位置から n 文字削除
// Delete n characters from position in src
function remove(src, position, n) {
return src.substring(0, position) + src.substring(position + n);
}
function handleKeyDown(e) {
// console.log("handleKeyDown", e);
// console.log("Key pressed: ", e.key);
// console.log("Key pressed: ", e.which);
let v = e.target.value;
let c = e.key;
let start = refInput.current.selectionStart;
let end = refInput.current.selectionEnd;
if (c == "Process" || c == "Control") {
return false;
}
// console.log("c", c, "start", start, "end", end, "dom", v, "v", v);
// Selected
if (start != end) {
if ((c >= "0" || c <= "9") || c == "-") {
const co1 = countComma(v, start);
v = remove(v, start, end - start);
v = insert(v, start, c);
v = format(v);
const co2 = countComma(v, start - 1);
const co = co1 - co2 - 1;
setV(v, start - co, start - co);
return;
}
// No change
setV(v, start, end);
return;
}
const dot = dotPosition(v);
// console.log("c", c, "start", start, "end", end, "dot", dot, "dom", v, "v", v);
if (c == "Delete") {
const del = v.substring(start + 1, start);
// console.log("del", del);
if (del == ".") {
// don't delete float point dot
// No change
setV(v, start, end);
return;
}
if (start > dot) { // decimal part
v = replace(v, start, "0");
setV(v, start + 1, end + 1);
return;
}
if (del == ",") {
const co1 = countComma(v, start + 1);
v = remove(v, start + 1, 1);
v = format(v);
const co2 = countComma(v, start);
const co = 1 - (co1 - co2);
setV(v, start + co, end + co);
return;
}
const co1 = countComma(v, start);
v = remove(v, start, 1);
v = format(v);
const co2 = countComma(v, start - 1);
const co = (co1 - co2);
setV(v, start - co, end - co);
return;
}
if (c == "Backspace") {
if (dot + 1 == start) {
// don't delete float point dot
// No change
setV(v, start - 1, end - 1);
return;
}
if (start > dot) { // decimal part
v = replace(v, start - 1, "0");
setV(v, start - 1, end - 1);
return;
}
// real part
const del = v.substring(start - 1, start);
let cursorBack = 1;
if (del == ",") {
start--;
end--;
cursorBack = 2;
}
const co1 = countComma(v, start);
v = remove(v, start - 1, 1);
v = format(v);
const co2 = countComma(v, start - 1);
const co = cursorBack + (co1 - co2);
setV(v, start - co, end - co);
return;
}
// Input text ------------------
// console.log("c", c, "dot", dot, "start", start, "end", end, "dom", v, "v", v);
if (c == ".") {
// No change and Move cursor
setV(v, dot + 1, dot + 1);
refInput.current.setSelectionRange(dot + 1, dot + 1);
return;
}
if (c == "-") {
if (float(v) >= 0) {
v = format("-" + v);
setV(v, start + 1, end + 1);
} else {
// No change
setV(v, start, end);
}
return;
}
if (c == "+") {
if (float(v) < 0) {
v = format(v.replace("-", ""));
setV(v, start - 1, end - 1);
} else {
// No change
setV(v, start, end);
}
return;
}
if (c == "0") {
if (start > dot) { // decimal part
v = replace(v, start, c);
setV(v, start + 1, end + 1);
return;
}
v = insert(v, start, c);
setV(v, start + 1, end + 1);
return;
}
if (c >= "1" && c <= "9") {
if (start > dot) { // decimal part
v = replace(v, start, c);
setV(v, start + 1, end + 1);
return;
}
// if real part is 0
if (parseInt(float(SaveValue.current)) == 0 && start <= dot) { // |0.?? or 0|.??
v = replace(v, dot - 1, c);
setV(v, dot, dot);
return;
}
if (start <= dot) {
const c1 = countComma(v, start);
v = insert(v, start, c);
const c2 = countComma(v, start + 1);
if (c2 > c1) {
start++;
end++;
}
setV(v, start + 1, end + 1);
return;
}
}
// No change
setV(v, start, end);
} // End of long long handleKeyDown
// Select all text
const handleFocus = () => {
refInput.current.setSelectionRange(0, refInput.current.value.length);
};
const handleChange = (event) => {
// console.log("handleChange");
setValue({ value: SaveValue.current });
// Call props.onChange
event.target.rawValue = float(SaveValue.current);
event.target.value = format(SaveValue.current);
props.onChange(event);
};
// Render
return e("input", {
ref: refInput,
id: props.id,
className: props.className,
style: props.style ? props.style : { textAlign: "right", imeMode: "disabled" },
value: value.value,
onKeyDown: e => { handleKeyDown(e); },
onChange: e => { handleChange(e); },
onFocus: () => { handleFocus(); },
});
}
export default InputFloat;