feat(date) : anneau focus clavier et ouverture clavier sur CalendarField

Couvre toute la famille date (Date, DateRange, DateTime, DateWeek) :
- Anneau de focus clavier (clavier-only) ; ouvert, l'anneau entoure
  champ + calendrier d'un seul tenant (combo)
- Entrée / Espace ouvrent/ferment le calendrier (mode non éditable),
  Échap ferme ; mode éditable inchangé
- Anneau sur la croix d'effacement

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-09 17:22:26 +02:00
parent 16514d7f96
commit ef1d7a5d94
@@ -23,10 +23,10 @@
placeholder="_"
type="text"
@click="onFieldClick"
@focus="onFocus"
@focus="onFocus(); onKbdFocus()"
@input="onInput"
@blur="onBlur"
@keydown.enter.prevent="onEnter"
@blur="onBlur(); onKbdBlur()"
@keydown="onKeydown"
>
<label
@@ -42,7 +42,7 @@
v-if="showClear"
type="button"
data-test="clear"
class="text-m-muted hover:text-m-primary"
class="m-focus-ring rounded-malio text-m-muted hover:text-m-primary"
aria-label="Effacer la date"
@click.stop="emit('clear')"
>
@@ -66,6 +66,7 @@
data-test="popover"
role="dialog"
class="absolute left-0 right-0 top-full z-20 box-border w-full rounded-b-md bg-white p-[10px] shadow-[0_4px_4px_0_rgba(0,0,0,0.25)]"
:class="keyboardFocused ? 'm-combo-ring-bottom' : ''"
>
<CalendarHeader
:view-mode="viewMode"
@@ -114,9 +115,12 @@ import CalendarHeader from './CalendarHeader.vue'
import MonthPicker from './MonthPicker.vue'
import {useCalendarPopover} from '../composables/useCalendarPopover'
import {useCalendarView} from '../composables/useCalendarView'
import {useKbdFocusRing} from '../../shared/useKbdFocusRing'
defineOptions({name: 'MalioCalendarField', inheritAttrs: false})
const {keyboardFocused, onFocus: onKbdFocus, onBlur: onKbdBlur} = useKbdFocusRing()
const props = withDefaults(
defineProps<{
displayValue: string
@@ -236,6 +240,33 @@ const onEnter = () => {
closePopover()
}
const onKeydown = (e: KeyboardEvent) => {
if (props.disabled || props.readonly) return
if (e.key === 'Escape') {
if (isOpen.value) {
e.preventDefault()
closePopover()
}
return
}
if (props.editable) {
// En mode éditable, Entrée valide la saisie (Espace = caractère normal)
if (e.key === 'Enter') {
e.preventDefault()
onEnter()
}
return
}
// Mode non éditable : Entrée / Espace ouvre ou ferme le calendrier
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault()
onFieldClick()
}
}
watch(() => props.syncTo, (value) => {
if (isOpen.value) syncToIso(value)
})
@@ -262,6 +293,7 @@ const mergedInputClass = computed(() =>
? 'border-m-success'
: isReadonly.value ? '' : 'focus:border-m-primary',
(!isReadonly.value && isOpen.value) ? 'border-m-primary !py-[9px] !rounded-b-none' : '',
keyboardFocused.value ? (isOpen.value ? 'm-combo-ring-top' : 'm-focus-ring-kbd') : '',
props.inputClass,
),
)