From 74ee815d822eed8505b34e61881c72b7af7ebdfe Mon Sep 17 00:00:00 2001 From: tristan Date: Tue, 9 Jun 2026 16:36:57 +0200 Subject: [PATCH] =?UTF-8?q?feat(upload)=20:=20anneau=20focus=20clavier,=20?= =?UTF-8?q?activation=20Entr=C3=A9e/Espace=20et=20prop=20clearable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Anneau de focus clavier sur le champ (via useKbdFocusRing) - Ouverture du sélecteur de fichier au clavier (Entrée / Espace) - Nouveau prop `clearable` (défaut false) : croix `mdi:close` focusable (Entrée/Espace, anneau clavier) qui vide le champ et émet l'event `clear` - Playground : carte de démonstration "Clearable" Co-Authored-By: Claude Opus 4.8 (1M context) --- .../pages/composant/input/inputUpload.vue | 16 +++++ app/components/malio/input/InputUpload.vue | 67 +++++++++++++++---- 2 files changed, 69 insertions(+), 14 deletions(-) diff --git a/.playground/pages/composant/input/inputUpload.vue b/.playground/pages/composant/input/inputUpload.vue index abb474a..5762dba 100644 --- a/.playground/pages/composant/input/inputUpload.vue +++ b/.playground/pages/composant/input/inputUpload.vue @@ -14,6 +14,17 @@

Valeur : {{ uploadValue || '(aucun)' }}

+
+

Clearable (croix pour vider)

+ +

Valeur : {{ clearableUpload || '(aucun)' }}

+
+

Avec accept (PDF)

{ + clearableUpload.value = '' +} const dynamicError = computed(() => { if (!dynamicUpload.value) return '' diff --git a/app/components/malio/input/InputUpload.vue b/app/components/malio/input/InputUpload.vue index 2923ae9..0c7861d 100644 --- a/app/components/malio/input/InputUpload.vue +++ b/app/components/malio/input/InputUpload.vue @@ -26,8 +26,10 @@ placeholder="_" type="text" @click="openFilePicker" - @focus="isFocused = true" - @blur="isFocused = false" + @keydown.enter.prevent="openFilePicker" + @keydown.space.prevent="openFilePicker" + @focus="isFocused = true; onKbdFocus()" + @blur="isFocused = false; onKbdBlur()" > - +
+ + +

(), { @@ -111,6 +133,7 @@ const props = withDefaults( displayIcon: true, accept: '', required: false, + clearable: false, reserveMessageSpace: true, }, ) @@ -143,6 +166,7 @@ const mergedGroupClass = computed(() => const mergedInputClass = computed(() => twMerge( 'floating-input peer min-h-[40px] w-full border bg-white pl-3 pr-3 py-1 outline-none placeholder:text-transparent text-lg rounded-md cursor-pointer', + keyboardFocused.value ? 'm-focus-ring-kbd' : '', isReadonly.value ? '' : 'grow-height', isReadonly.value ? 'border-black' @@ -153,7 +177,9 @@ const mergedInputClass = computed(() => : hasSuccess.value ? 'border-m-success focus:border-m-success [&:not(:placeholder-shown)]:border-m-success' : isReadonly.value ? '' : 'focus:border-m-primary', - props.displayIcon ? '!pr-10' : '', + showClear.value + ? (props.displayIcon ? '!pr-16' : '!pr-10') + : (props.displayIcon ? '!pr-10' : ''), isReadonly.value ? '' : 'focus:pl-[11px]', isReadonly.value ? 'cursor-default' : '', disabled.value ? 'cursor-not-allowed' : '', @@ -191,8 +217,21 @@ const describedBy = computed(() => { const emit = defineEmits<{ (event: 'update:modelValue', value: string): void (event: 'file-selected', file: File): void + (event: 'clear'): void }>() +const showClear = computed(() => + props.clearable && isFilled.value && !props.disabled && !isReadonly.value, +) + +const onClear = () => { + if (props.disabled || isReadonly.value) return + if (!isControlled.value) localValue.value = '' + if (fileInputRef.value) fileInputRef.value.value = '' + emit('update:modelValue', '') + emit('clear') +} + const openFilePicker = () => { if (props.disabled || props.readonly) return fileInputRef.value?.click()