From 63303e51f8f2b4a224f3a8553ce8c1e8045f698f Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Mon, 16 Oct 2023 10:58:11 +0200 Subject: [PATCH] fix(dossier): improuve decimal field formatting --- .../decimal_number_component.html.haml | 2 +- .../decimal_number_input_controller.ts | 35 -------------- .../controllers/format_controller.ts | 48 ++++++++++++++----- spec/system/users/brouillon_spec.rb | 21 +++++++- 4 files changed, 57 insertions(+), 49 deletions(-) delete mode 100644 app/javascript/controllers/decimal_number_input_controller.ts diff --git a/app/components/editable_champ/decimal_number_component/decimal_number_component.html.haml b/app/components/editable_champ/decimal_number_component/decimal_number_component.html.haml index 1eca76fae..f4c7fac9d 100644 --- a/app/components/editable_champ/decimal_number_component/decimal_number_component.html.haml +++ b/app/components/editable_champ/decimal_number_component/decimal_number_component.html.haml @@ -1 +1 @@ -= @form.text_field(:value, input_opts(id: @champ.input_id, aria: { describedby: @champ.describedby_id }, required: @champ.required?, pattern: "-?[0-9]+([\.,][0-9]{1,3})?", inputmode: :decimal, data: { controller: 'format decimal-number-input', format: :decimal })) += @form.text_field(:value, input_opts(id: @champ.input_id, aria: { describedby: @champ.describedby_id }, required: @champ.required?, pattern: "-?[0-9]+([\.,][0-9]{1,3})?", inputmode: :decimal, data: { controller: 'format', format: :decimal })) diff --git a/app/javascript/controllers/decimal_number_input_controller.ts b/app/javascript/controllers/decimal_number_input_controller.ts deleted file mode 100644 index 74746e52c..000000000 --- a/app/javascript/controllers/decimal_number_input_controller.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { ApplicationController } from './application_controller'; - -export class DecimalNumberInputController extends ApplicationController { - connect() { - const value = this.inputElement.value; - - if (value) { - this.formatValue(value); - } - } - - formatValue(value: string) { - const number = parseFloat(value); - - if (isNaN(number)) { - return; - } - - this.inputElement.value = number.toLocaleString(); - this.emitInputEvent(); // trigger format controller - } - - private get inputElement(): HTMLInputElement { - return this.element as HTMLInputElement; - } - - private emitInputEvent() { - const event = new InputEvent('input', { - bubbles: true, - cancelable: true - }); - - this.inputElement.dispatchEvent(event); - } -} diff --git a/app/javascript/controllers/format_controller.ts b/app/javascript/controllers/format_controller.ts index f5ce0227d..8ae7ec1f8 100644 --- a/app/javascript/controllers/format_controller.ts +++ b/app/javascript/controllers/format_controller.ts @@ -7,25 +7,29 @@ export class FormatController extends ApplicationController { case 'list': this.on('change', (event) => { const target = event.target as HTMLInputElement; - target.value = this.formatList(target.value); + const value = this.formatList(target.value); + replaceValue(target, value); }); break; case 'iban': this.on('input', (event) => { const target = event.target as HTMLInputElement; - target.value = this.formatIBAN(target.value); + const value = this.formatIBAN(target.value); + replaceValue(target, value); }); break; case 'integer': this.on('input', (event) => { const target = event.target as HTMLInputElement; - target.value = this.formatInteger(target.value); + const value = this.formatInteger(target.value); + replaceValue(target, value); }); break; case 'decimal': this.on('input', (event) => { const target = event.target as HTMLInputElement; - target.value = this.formatDecimal(target.value); + const value = this.formatDecimal(target.value); + replaceValue(target, value); }); break; } @@ -48,12 +52,34 @@ export class FormatController extends ApplicationController { } private formatDecimal(value: string) { - // Le séparateur de décimales est toujours après le séparateur de milliers (un point ou une virgule). - // S'il n'y a qu'un seul séparateur, on considère que c'est celui des décimales. - // S'il n'y en a pas, ça n'a pas d'effet. - const decimalSeparator = - value.lastIndexOf(',') > value.lastIndexOf('.') ? ',' : '.'; - - return value.replace(new RegExp(`[^-?\\d${decimalSeparator}]`, 'g'), ''); + const decimalSeparator = getDecimalSeparator(value); + const number = + decimalSeparator == ',' + ? value.replace(/\./g, '').replace(/,/g, '.') + : value.replace(/,/g, ''); + return number.replace(new RegExp(`[^-?\\d.]`, 'g'), ''); } } + +function replaceValue(target: HTMLInputElement, value: string) { + const delta = target.value.length - value.length; + const start = target.selectionStart; + const end = target.selectionStart; + const dir = target.selectionDirection; + target.value = value; + target.selectionStart = start ? start - delta : 0; + target.selectionEnd = end ? end - delta : 0; + target.selectionDirection = dir; +} + +function getDecimalSeparator(value: string) { + if (value.indexOf('.') != -1 && value.indexOf(',') != -1) { + if (value.lastIndexOf('.') < value.lastIndexOf(',')) { + return ','; + } + return '.'; + } else if (value.indexOf(',') != -1) { + return ','; + } + return (1.1).toLocaleString().indexOf('.') != -1 ? '.' : ','; +} diff --git a/spec/system/users/brouillon_spec.rb b/spec/system/users/brouillon_spec.rb index d5ca28594..29c88d749 100644 --- a/spec/system/users/brouillon_spec.rb +++ b/spec/system/users/brouillon_spec.rb @@ -195,15 +195,20 @@ describe 'The user' do champ_value_for('nombre décimal') == '123456.78' } - fill_in('nombre décimal', with: '1,234.56') + champ_past_value_for('nombre décimal', '1,234.56') wait_until { champ_value_for('nombre décimal') == '1234.56' } - fill_in('nombre décimal', with: '-1,234.56') + champ_past_value_for('nombre décimal', '-1,234.56') wait_until { champ_value_for('nombre décimal') == '-1234.56' } + + champ_past_value_for('nombre décimal', '1.234,56') + wait_until { + champ_value_for('nombre décimal') == '1234.56' + } end scenario 'extends dossier experation date more than one time, ', js: true, retry: 3 do @@ -584,6 +589,18 @@ describe 'The user' do champ_for(libelle).value end + def champ_id_for(libelle) + champ_for(libelle).input_id + end + + def champ_past_value_for(libelle, value) + execute_script("{ + let target = document.querySelector('##{champ_id_for(libelle)}'); + target.value = \"#{value}\"; + target.dispatchEvent(new CustomEvent('input', { bubbles: true })); + }") + end + def champ_for(libelle) champs = user_dossier.reload.champs_public champs.find { |c| c.libelle == libelle }