fix(input) : InputAutocomplete garde la valeur collée après sélection (MUI-48) (#87)

## Contexte — MUI-48
« Le champ adresse disparaît après un copier-coller » (Malio UI, bug frontend).

## Repro exacte
1. Taper une adresse, **sélectionner une suggestion** dans la liste.
2. Sans re-cliquer dans le champ : `Ctrl+A` puis `Ctrl+V` pour remplacer.
3. → Le champ se **vide** (label qui redescend, dropdown « Tapez pour rechercher »), la valeur collée est perdue.

À la souris (re-clic dans le champ) le bug ne se produit pas.

## Cause racine
`onSelect` repassait `isFocused` à `false` alors que l'input **garde le focus DOM** (l'option est cliquée en `mousedown.prevent`). Au collage, `onInput` émet `update:modelValue(null)` ; le `watch` de synchronisation, protégé par le seul `isFocused`, remettait alors `inputValue` à `''`.

## Correctif
`onInput` resynchronise `isFocused = true` : recevoir un évènement `input` prouve que le champ est en cours d'édition. Le `watch` se protège correctement et ne stompe plus la valeur collée. Correctif d'une ligne, au point exact de la cause.

## Tests / vérifs
- Test de non-régression colocalisé (séquence sélection → `Ctrl+A`/`Ctrl+V`) — échoue sans le fix, passe avec.
- Suite complète : **1057/1057** tests OK, lint sans erreur.
- Page playground : nouvelle section **allowCreate + BAN** dédiée au test manuel.
- `CHANGELOG.md` : entrée `Fixed` MUI-48.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Reviewed-on: #87
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
This commit was merged in pull request #87.
This commit is contained in:
2026-06-25 08:42:42 +00:00
committed by Autin
parent 37434fbfc6
commit 82f9321506
4 changed files with 104 additions and 1 deletions
@@ -1,6 +1,6 @@
import {describe, expect, it, vi} from 'vitest'
import {mount} from '@vue/test-utils'
import type {DefineComponent} from 'vue'
import {defineComponent, nextTick, ref, type DefineComponent} from 'vue'
import {Icon as IconifyIcon} from '@iconify/vue'
import InputAutocomplete from './InputAutocomplete.vue'
@@ -569,4 +569,40 @@ describe('MalioInputAutocomplete', () => {
expect(msg.exists()).toBe(true)
expect(msg.classes()).not.toContain('min-h-[1rem]')
})
// MUI-48 : après avoir sélectionné une option dans la liste, le champ garde le focus DOM
// mais isFocused interne passe à false (clic option en mousedown.prevent). Un collage qui
// remplace tout (Ctrl+A puis Ctrl+V) déclenche update:modelValue(null) ; le watch ne doit
// PAS vider la valeur collée. Régression : le champ se vidait au lieu de prendre le texte collé.
it('MUI-48 : un collage après sélection dans la liste remplace la valeur (ne la vide pas)', async () => {
const Harness = defineComponent({
components: {InputAutocomplete},
setup() {
const val = ref<string | number | null>(null)
const opts = ref([{label: '10 Rue de la Paix', value: '10 Rue de la Paix'}])
return {val, opts}
},
template: '<InputAutocomplete v-model="val" :options="opts" allow-create />',
})
const wrapper = mount(Harness, {
attachTo: document.body,
global: {stubs: {IconifyIcon: {template: '<span data-test="icon" v-bind="$attrs" />'}}},
})
const input = wrapper.get('input')
// saisie puis sélection d'une suggestion (commit, focus DOM conservé)
await input.trigger('focus')
await input.setValue('10')
await wrapper.findAll('[data-test="option"]')[0].trigger('click')
await nextTick()
expect(input.element.value).toBe('10 Rue de la Paix')
// Ctrl+A puis Ctrl+V : input toujours focalisé DOM, aucun nouvel évènement focus
await input.setValue('25 Avenue Victor Hugo')
await nextTick()
expect(input.element.value).toBe('25 Avenue Victor Hugo')
wrapper.unmount()
})
})
@@ -393,6 +393,11 @@ const scheduleSearch = () => {
const onInput = (event: Event) => {
const target = event.target as HTMLInputElement
// Un évènement input prouve que le champ est en cours d'édition : on resynchronise
// isFocused, qu'une sélection précédente (onSelect) a pu passer à false tout en gardant
// le focus DOM (clic option en mousedown.prevent). Sans ça, le watch ci-dessous remettrait
// inputValue à '' au collage et la valeur collée serait perdue (MUI-48).
isFocused.value = true
inputValue.value = target.value
if (!isOpen.value) isOpen.value = true
activeIndex.value = -1