[Bug]: Mobile: "Hide decimal places" and delete 1 digit results in deleting 3 digits for certain number formattings #2219

Closed
opened 2026-02-28 20:07:10 -06:00 by GiteaMirror · 0 comments
Owner

Originally created by @intagaming on GitHub (Jun 21, 2025).

Verified issue does not already exist?

  • I have searched and found no existing issue

What happened?

When turning on "Hide decimal places" and using the Formatting number of "1,000", in the "New Transaction" screen, if you type in "1,234,567" and hit deletes once, it results in "1,235" instead of "123,456". The same happens with the format "1.000".

I have debugged the problem as follow:

  • I inspected the process in the file packages\loot-core\src\shared\util.ts, function appendDecimals. When hitting delete, the result is "1,234,56"; the currencyToAmount(result) is 1234.56; the amountToCurrency(currencyToAmount(result)) is "1,235".
  • When converting "1,234,56" to a number amount, I expect it to convert to 123456 but got 1234.56 instead.
  • This is because in the getNumberFormat function in the same file. amountToCurrency calls that function and doesn't provide the format, resulting in the default format being "comma-dot", and ultimately setting locale to 'en-US'. This is passed to the Intl.NumberFormat constructor. The constructor in the end looks like this: let formatter = new Intl.NumberFormat("en-US", { minimumFractionDigits: 0, maximumFractionDigits: 0 })
  • Test this NumberFormat with formatter.format(1234.56) and the result is indeed "1,235".

So we need to fix currencyToAmount so that it outputs 123456 instead of 1234.56.

In currencyToAmount, given the input 1,234,56, the function decided that the integer part is "1234" and the fraction being "56". This is incorrect. Fixing this function is one way of fixing the problem, but we can also fix the input of the function: Instead of passing in 1,234,56, we could add a step where it would re-apply the thousand separators again so that we could get 123,456 as the input.

I'm thinking of a fix that reapplys the thousand separators in the onChangeText callback in packages\desktop-client\src\components\mobile\transactions\FocusableAmountInput.tsx, just before calling applyDecimals. I'm testing this solution, but I want to ask for inputs since I'm new to the Actual Budget project.

// packages\desktop-client\src\components\mobile\transactions\FocusableAmountInput.tsx
  const onChangeText = (text: string) => {
    text = reapplyThousandSeparators(text);
    text = appendDecimals(text, String(hideFraction) === 'true');
    setEditing(true);
    setText(text);
    props.onChangeValue?.(text);
  };
// packages\loot-core\src\shared\util.ts
export function reapplyThousandSeparators(amountText: string) {
  const { decimalSeparator, thousandsSeparator } = getNumberFormat();
  const separatorIndex = amountText.lastIndexOf(decimalSeparator);
  let integerPart, decimalPart;
  if (separatorIndex === -1) {
    integerPart = amountText;
    decimalPart = '';
  } else {
    integerPart = amountText.slice(0, separatorIndex);
    decimalPart = amountText.slice(amountText.lastIndexOf(decimalSeparator));
  }

  // Remove thousand separators from the integer part, add back the separators
  // in the US locale (which is commas) and replace commas with our thousands separator.
  integerPart = integerPart.replaceAll(thousandsSeparator, '');
  integerPart = Number(integerPart)
    .toLocaleString('en-US')
    .replaceAll(',', thousandsSeparator);

  return integerPart + decimalPart;
}

How can we reproduce the issue?

  • Turn on "Hide decimal places"
  • Use the "1,000" number format
  • Go to create a new transaction
  • Type "1234567". It will result in "1,234,567" being displayed.
  • Hit deletes once. Expected to see "123,456" but got "1,235".

Where are you hosting Actual?

Other

What browsers are you seeing the problem on?

Other

Operating System

Mobile Device

Originally created by @intagaming on GitHub (Jun 21, 2025). ### Verified issue does not already exist? - [x] I have searched and found no existing issue ### What happened? When turning on "Hide decimal places" and using the Formatting number of "1,000", in the "New Transaction" screen, if you type in "1,234,567" and hit deletes once, it results in "1,235" instead of "123,456". The same happens with the format "1.000". I have debugged the problem as follow: - I inspected the process in the file `packages\loot-core\src\shared\util.ts`, function `appendDecimals`. When hitting delete, the `result` is `"1,234,56"`; the `currencyToAmount(result)` is `1234.56`; the `amountToCurrency(currencyToAmount(result))` is `"1,235"`. - When converting `"1,234,56"` to a number amount, I expect it to convert to `123456` but got `1234.56` instead. - This is because in the `getNumberFormat` function in the same file. `amountToCurrency` calls that function and doesn't provide the format, resulting in the default format being "comma-dot", and ultimately setting `locale` to `'en-US'`. This is passed to the `Intl.NumberFormat` constructor. The constructor in the end looks like this: `let formatter = new Intl.NumberFormat("en-US", { minimumFractionDigits: 0, maximumFractionDigits: 0 })` - Test this NumberFormat with `formatter.format(1234.56)` and the result is indeed `"1,235"`. So we need to fix `currencyToAmount` so that it outputs `123456` instead of `1234.56`. In `currencyToAmount`, given the input `1,234,56`, the function decided that the `integer` part is `"1234"` and the `fraction` being `"56"`. This is incorrect. Fixing this function is one way of fixing the problem, but we can also fix the input of the function: Instead of passing in `1,234,56`, we could add a step where it would re-apply the thousand separators again so that we could get `123,456` as the input. I'm thinking of a fix that reapplys the thousand separators in the `onChangeText` callback in `packages\desktop-client\src\components\mobile\transactions\FocusableAmountInput.tsx`, just before calling `applyDecimals`. I'm testing this solution, but I want to ask for inputs since I'm new to the Actual Budget project. ```js // packages\desktop-client\src\components\mobile\transactions\FocusableAmountInput.tsx const onChangeText = (text: string) => { text = reapplyThousandSeparators(text); text = appendDecimals(text, String(hideFraction) === 'true'); setEditing(true); setText(text); props.onChangeValue?.(text); }; ``` ```js // packages\loot-core\src\shared\util.ts export function reapplyThousandSeparators(amountText: string) { const { decimalSeparator, thousandsSeparator } = getNumberFormat(); const separatorIndex = amountText.lastIndexOf(decimalSeparator); let integerPart, decimalPart; if (separatorIndex === -1) { integerPart = amountText; decimalPart = ''; } else { integerPart = amountText.slice(0, separatorIndex); decimalPart = amountText.slice(amountText.lastIndexOf(decimalSeparator)); } // Remove thousand separators from the integer part, add back the separators // in the US locale (which is commas) and replace commas with our thousands separator. integerPart = integerPart.replaceAll(thousandsSeparator, ''); integerPart = Number(integerPart) .toLocaleString('en-US') .replaceAll(',', thousandsSeparator); return integerPart + decimalPart; } ``` ### How can we reproduce the issue? - Turn on "Hide decimal places" - Use the "1,000" number format - Go to create a new transaction - Type "1234567". It will result in "1,234,567" being displayed. - Hit deletes once. Expected to see "123,456" but got "1,235". ### Where are you hosting Actual? Other ### What browsers are you seeing the problem on? Other ### Operating System Mobile Device
GiteaMirror added the user interfacegood first issueresponsivebug labels 2026-02-28 20:07:10 -06:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/actual#2219