41010060ff
## Sélecteur d'année dans le calendrier (3ᵉ niveau de navigation) Ajoute un 3ᵉ niveau de navigation à la famille de composants date, et corrige le bornage min/max du sélecteur de mois. ### Comportement - Clic sur le champ → calendrier (vue **jours**) - Clic sur l'en-tête → **sélecteur de mois** - **Re-clic sur l'en-tête → sélecteur d'année** (grille de 12 ans, chevrons paginant par pas de 12 ans, fenêtre centrée sur l'année courante − 5) - Clic sur une année → retour au sélecteur de mois ; clic sur un mois → retour à la grille de jours - Les props `min`/`max` **grisent les mois ET les années** hors plage (corrige l'asymétrie : le `MonthPicker` affichait jusqu'ici tous les mois) En-tête contextuel : « Mai 2026 » (jours) / « 2026 » (mois) / « 2020 – 2031 » (années). ### Périmètre - Shell partagé `internal/CalendarField.vue` → bénéficie aux 4 composants publics `Date`, `DateRange`, `DateTime`, `DateWeek` - **Aucune API publique modifiée** - Nouveau composant `internal/YearPicker.vue` (calqué sur `MonthPicker`) - Helpers purs `isMonthInRange` / `isYearInRange` (comparaison par préfixe ISO, bornes inclusives) - State machine `viewMode` à 3 niveaux (`useCalendarPopover` / `useCalendarView`) ### Tests - Suite date **246/246 verte**, ESLint propre - Unitaires : helpers, `YearPicker`, `MonthPicker` (grisage), composables (pagination ±12, recentrage, `selectYear`) - e2e `Date.test.ts` : flux complet jours→mois→années→mois→jours + grisage min/max ### Process Développé en brainstorming → spec → plan → exécution TDD (un commit par étape). Spec et plan inclus sous `docs/superpowers/`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Reviewed-on: #83 Co-authored-by: tristan <tristan@yuno.malio.fr> Co-committed-by: tristan <tristan@yuno.malio.fr>
71 lines
1.8 KiB
TypeScript
71 lines
1.8 KiB
TypeScript
import {ref, watch, type Ref} from 'vue'
|
||
import {isValidIso} from './dateFormat'
|
||
|
||
export function useCalendarView(viewMode: Ref<'days' | 'months' | 'years'>) {
|
||
const today = new Date()
|
||
const currentMonth = ref(today.getMonth())
|
||
const currentYear = ref(today.getFullYear())
|
||
// Fenêtre de 12 ans calée pour que l'année courante tombe en 2e ligne / 2e
|
||
// colonne d'une grille 3 colonnes (index 4) → début = année courante − 4.
|
||
const yearPageStart = ref(today.getFullYear() - 4)
|
||
|
||
watch(viewMode, (mode) => {
|
||
if (mode === 'years') yearPageStart.value = currentYear.value - 4
|
||
})
|
||
|
||
const goToPrev = () => {
|
||
if (viewMode.value === 'years') {
|
||
yearPageStart.value -= 12
|
||
return
|
||
}
|
||
if (viewMode.value === 'months') {
|
||
currentYear.value -= 1
|
||
return
|
||
}
|
||
if (currentMonth.value === 0) {
|
||
currentMonth.value = 11
|
||
currentYear.value -= 1
|
||
} else {
|
||
currentMonth.value -= 1
|
||
}
|
||
}
|
||
|
||
const goToNext = () => {
|
||
if (viewMode.value === 'years') {
|
||
yearPageStart.value += 12
|
||
return
|
||
}
|
||
if (viewMode.value === 'months') {
|
||
currentYear.value += 1
|
||
return
|
||
}
|
||
if (currentMonth.value === 11) {
|
||
currentMonth.value = 0
|
||
currentYear.value += 1
|
||
} else {
|
||
currentMonth.value += 1
|
||
}
|
||
}
|
||
|
||
const selectMonth = (m: number) => {
|
||
currentMonth.value = m
|
||
}
|
||
|
||
const selectYear = (y: number) => {
|
||
currentYear.value = y
|
||
}
|
||
|
||
const syncToIso = (iso: string | null) => {
|
||
if (iso && isValidIso(iso)) {
|
||
currentMonth.value = Number(iso.slice(5, 7)) - 1
|
||
currentYear.value = Number(iso.slice(0, 4))
|
||
} else {
|
||
const now = new Date()
|
||
currentMonth.value = now.getMonth()
|
||
currentYear.value = now.getFullYear()
|
||
}
|
||
}
|
||
|
||
return {currentMonth, currentYear, yearPageStart, goToPrev, goToNext, selectMonth, selectYear, syncToIso}
|
||
}
|