Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Le format suit [Keep a Changelog](https://keepachangelog.com/fr/1.1.0/) et le pr
- **`ui-datepicker` : taper l'heure juste après l'année (`showTime`) pouvait corrompre l'année affichée** (FSHSP-118). Le segment année, volontairement sans borne (`1-12`/`1-31` s'appliquent au jour/mois, pas à elle), ne déclenchait jamais l'insertion de son propre séparateur (l'espace avant l'heure) une fois ses 4 chiffres tapés — un suivi de position réservé aux segments bornés. L'heure tapée ensuite s'accolait donc directement à l'année (ex. `08/07/2026` + `10` tapé → `08/07/202610`, relu comme une année à 6 chiffres). Le suivi de position du moteur de masque partagé (`mask-engine.ts`, utilisé aussi par `ui-input-mask`) couvre maintenant tout segment, borné ou non ; la prévisualisation en direct attend en plus que l'heure soit complète avant de la commiter.
- **`ui-datepicker` : la saisie tapée en `range`/`multiple` pouvait couper une date en plein milieu si son `dateFormat` custom contenait le caractère du séparateur** (FSHSP-118). Le découpage retenait la première occurrence littérale de `" - "` (`range`) ou `", "` (`multiple`) dans le texte tapé, sans vérifier qu'elle délimitait bien deux dates plutôt que d'appartenir à l'une d'elles (ex. un `dateFormat` ISO contenant un tiret). Chaque occurrence candidate doit désormais faire parser valablement le texte qui la précède comme une date avant d'être retenue comme frontière.
- **`ui-datepicker` : le séparateur affiché d'une plage (`range`) était le même que celui attendu en saisie**, rendant les deux indiscernables à l'écran. L'affichage utilise maintenant un tiret cadratin dédié (« 08/07/2026 – 18/07/2026 »), découplé du séparateur de saisie/parsing (`" - "`, inchangé).
- **`ui-datepicker` : ouvrir le panneau via l'icône calendrier en `range`/`multiple` ne redonnait plus le focus à la grille** (FSHSP-118). Sortir la lecture-seule du déclencheur de `triggerReadonly` (pour permettre la saisie tapée sur ces modes) avait supprimé avec elle le renvoi systématique du focus vers la grille à l'ouverture, propre à ces deux modes. Restauré explicitement, indépendamment de `triggerReadonly`.
- **`ui-datepicker` : ouvrir le panneau en `range`/`multiple` — icône ou champ — renvoyait toujours le focus vers la grille, y compris pour taper** (FSHSP-118). Un premier correctif (renvoi vers la grille dès que `selectionMode() !== 'single'`) visait à restaurer le comportement de l'icône, mais s'appliquait aussi au clic sur le **champ** lui-même — qui ouvre le panneau via le même `open()` — volant le focus au clavier au moment même de cliquer pour taper, dès que `range` a reçu son propre masque en direct. `open()` distingue maintenant explicitement les deux : un clic sur l'icône renvoie vers la grille (« je veux la grille »), un clic sur le champ garde le focus dedans pour continuer à taper — dans tous les modes, pas seulement `single`.

## [0.6.1] - 2026-08-22

Expand Down
32 changes: 18 additions & 14 deletions projects/ui-kit/forms/ui-datepicker/src/lib/ui-datepicker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -843,7 +843,11 @@ export class UiDatepicker extends BaseFormField<DatepickerValue> {

// --- Panel open/close ------------------------------------------------

open(): void {
/**
* @param viaIcon Whether this open was specifically triggered by the calendar icon (vs.
* clicking the field itself, or a keyboard shortcut) — see the rove-into-grid comment below.
*/
open(viaIcon = false): void {
if (this.inline() || this.isDisabled() || this.readonly() || this.panelOpen()) return;
const base = this.firstSelectedFrom(this.internalValue() ?? null) ?? startOfDay(new Date());
this.viewDate.set(firstOfMonth(base));
Expand All @@ -854,14 +858,13 @@ export class UiDatepicker extends BaseFormField<DatepickerValue> {
this.overlayOrigin.set(this.resolveOverlayOrigin());
this.panelOpen.set(true);
this.opened.emit();
// Keep focus in the input when it's typeable in single mode (so typing can continue
// uninterrupted); otherwise rove into the (active) grid. `range`/`multiple` always rove,
// regardless of `triggerReadonly()`: typing there is a plain-text complement (no live mask,
// see `typingSlots`), never the primary interaction — the grid is, exactly as before
// `allowInput` covered these modes, and opening via the calendar icon signals "I want the
// grid" (code review finding: this used to be implicit in `triggerReadonly` hardcoding
// non-single modes read-only; restored explicitly now that it no longer does).
if (this.showCalendar() && (this.triggerReadonly() || this.selectionMode() !== 'single')) {
// Keep focus in the input whenever it's typeable; only rove into the grid when it isn't
// (`triggerReadonly`) or when opened via the icon specifically (`viaIcon` — "I want the
// grid"). Used to key off `selectionMode() !== 'single'` instead, which broke once `range`
// got its own live mask: the field's own click ALSO calls `open()` (see the template), so
// roving on every non-`single` mode stole focus back out of the field the moment you clicked
// it to type (FSHSP-118 follow-up).
if (this.showCalendar() && (this.triggerReadonly() || viaIcon)) {
if (this.currentView() === 'date') this.queueDayFocus();
else if (this.currentView() === 'month') this.queueMonthFocus();
else this.queueYearFocus();
Expand All @@ -877,16 +880,17 @@ export class UiDatepicker extends BaseFormField<DatepickerValue> {
}

/** @ignore */
protected toggle(): void {
this.panelOpen() ? this.close() : this.open();
protected toggle(viaIcon = false): void {
this.panelOpen() ? this.close() : this.open(viaIcon);
}

/** @ignore Right action: clear when clearable + set, otherwise toggle the panel.
* `stopPropagation` keeps the click from bubbling to the field (which opens it). */
/** @ignore Right action: clear when clearable + set, otherwise toggle the panel (via the icon —
* see `open`'s `viaIcon`). `stopPropagation` keeps the click from bubbling to the field (which
* would otherwise also call `open()`, without `viaIcon`, right behind this one). */
protected onIconClick(event: MouseEvent): void {
event.stopPropagation();
if (this.showClearButton()) this.clear();
else this.toggle();
else this.toggle(true);
}

/** @ignore */
Expand Down
Loading