Élargir le panneau en glissant son bord
Trois cent quarante pixels suffisaient à des champs de date ; ils ne suffisent pas à une liste imbriquée, où chaque niveau mange une indentation. Le panneau reçoit donc la même poignée que la colonne des libellés (décision 18) : même geste, même double-clic de remise à zéro, même mémorisation dans `localStorage`. Le code de glisser, jusque-là écrit pour la seule colonne, est paramétré par le sens et la largeur qu'il pilote plutôt que dupliqué. Une largeur unique pour les deux panneaux : ce qu'on élargit est le volet, pas l'un ou l'autre de ses contenus. La largeur voulue et la largeur appliquée sont deux variables distinctes, faute de quoi réduire un instant la fenêtre ferait perdre le réglage. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
175
js/app.js
175
js/app.js
@@ -474,49 +474,150 @@ function restaurerLargeurLibelles() {
|
||||
|
||||
restaurerLargeurLibelles();
|
||||
|
||||
{
|
||||
const poignee = $('poignee-colonne');
|
||||
let depart = null;
|
||||
// ---------------------------------------------------------------------------
|
||||
// Largeur du panneau
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
poignee.addEventListener('pointerdown', (evenement) => {
|
||||
if (evenement.button !== 0) return;
|
||||
depart = { x: evenement.clientX, largeur: largeurLibelles };
|
||||
poignee.setPointerCapture(evenement.pointerId);
|
||||
poignee.dataset.glisse = 'true';
|
||||
document.body.classList.add('glisse-en-cours');
|
||||
evenement.preventDefault();
|
||||
});
|
||||
/**
|
||||
* Le panneau porte désormais les notes, qui prennent toute sa hauteur restante
|
||||
* et se rédigent en markdown. Trois cent quarante pixels suffisaient à des
|
||||
* champs de date et à quatre lignes de texte ; ils ne suffisent plus à une liste
|
||||
* à puces imbriquée, où chaque niveau mange une indentation.
|
||||
*
|
||||
* D'où la même poignée qu'à la colonne des libellés (décision 18) : même geste,
|
||||
* même double-clic de remise à zéro, même mémorisation dans `localStorage`. Une
|
||||
* largeur unique pour les deux panneaux, parce que ce qu'on élargit est le
|
||||
* volet — l'espace où l'on écrit —, pas l'un ou l'autre de ses contenus.
|
||||
*/
|
||||
// `defaut` doit rester en accord avec `--largeur-panneau` dans style.css.
|
||||
const LARGEUR_PANNEAU = { min: 300, max: 900, defaut: 380 };
|
||||
const CLE_LARGEUR_PANNEAU = 'frise.largeurPanneau';
|
||||
|
||||
poignee.addEventListener('pointermove', (evenement) => {
|
||||
if (!depart) return;
|
||||
appliquerLargeurLibelles(depart.largeur + (evenement.clientX - depart.x));
|
||||
});
|
||||
/**
|
||||
* Deux largeurs plutôt qu'une : celle que l'utilisateur a demandée, et celle
|
||||
* qui tient réellement dans la fenêtre.
|
||||
*
|
||||
* Le panneau ne peut pas couvrir tout l'écran — il laisse la frise vivante
|
||||
* derrière lui (décision 17) —, et une largeur choisie sur un 27 pouces doit
|
||||
* donc être rabotée sur un portable. Mais seulement rabotée : sans cette
|
||||
* distinction, réduire un instant la fenêtre écraserait la largeur mémorisée
|
||||
* par sa valeur contrainte, et l'agrandir de nouveau ne la rendrait pas.
|
||||
*/
|
||||
let largeurPanneauVoulue = LARGEUR_PANNEAU.defaut;
|
||||
let largeurPanneau = LARGEUR_PANNEAU.defaut;
|
||||
|
||||
const terminer = (evenement) => {
|
||||
if (!depart) return;
|
||||
depart = null;
|
||||
poignee.releasePointerCapture?.(evenement.pointerId);
|
||||
delete poignee.dataset.glisse;
|
||||
document.body.classList.remove('glisse-en-cours');
|
||||
};
|
||||
function appliquerLargeurPanneau(largeur, { memoriser = true } = {}) {
|
||||
if (memoriser) {
|
||||
largeurPanneauVoulue = Math.min(
|
||||
LARGEUR_PANNEAU.max,
|
||||
Math.max(LARGEUR_PANNEAU.min, Math.round(largeur))
|
||||
);
|
||||
try {
|
||||
localStorage.setItem(CLE_LARGEUR_PANNEAU, String(largeurPanneauVoulue));
|
||||
} catch {
|
||||
// Mode privé, quota saturé : la largeur vaut pour la séance, sans plus.
|
||||
}
|
||||
}
|
||||
|
||||
poignee.addEventListener('pointerup', terminer);
|
||||
poignee.addEventListener('pointercancel', terminer);
|
||||
|
||||
poignee.addEventListener('dblclick', () => appliquerLargeurLibelles(LARGEUR_LIBELLES.defaut));
|
||||
|
||||
// Les flèches ajustent aussi la largeur quand la poignée a le focus.
|
||||
// `stopPropagation` est indispensable : sans lui, le gestionnaire posé plus
|
||||
// bas sur `document` décalerait en même temps la phase sélectionnée.
|
||||
poignee.addEventListener('keydown', (evenement) => {
|
||||
const pas = evenement.key === 'ArrowLeft' ? -10 : evenement.key === 'ArrowRight' ? 10 : 0;
|
||||
if (pas === 0) return;
|
||||
evenement.preventDefault();
|
||||
evenement.stopPropagation();
|
||||
appliquerLargeurLibelles(largeurLibelles + pas);
|
||||
});
|
||||
const plafond = Math.max(LARGEUR_PANNEAU.min, window.innerWidth - 80);
|
||||
largeurPanneau = Math.min(largeurPanneauVoulue, plafond);
|
||||
document.documentElement.style.setProperty('--largeur-panneau', `${largeurPanneau}px`);
|
||||
return largeurPanneau;
|
||||
}
|
||||
|
||||
function restaurerLargeurPanneau() {
|
||||
let memorisee = null;
|
||||
try {
|
||||
memorisee = localStorage.getItem(CLE_LARGEUR_PANNEAU);
|
||||
} catch {
|
||||
// Lecture refusée : on garde la valeur de la feuille de style.
|
||||
}
|
||||
const largeur = Number(memorisee);
|
||||
if (Number.isFinite(largeur) && largeur > 0) appliquerLargeurPanneau(largeur);
|
||||
}
|
||||
|
||||
restaurerLargeurPanneau();
|
||||
|
||||
/**
|
||||
* Installe une poignée de redimensionnement.
|
||||
*
|
||||
* Deux réglages emploient le même geste, à deux détails près : le sens dans
|
||||
* lequel tirer élargit, et l'endroit où l'on range le résultat. Le reste — la
|
||||
* capture du pointeur, la classe qui fige le curseur pendant le glisser, le
|
||||
* double-clic qui remet à la valeur d'usine, les flèches au clavier — est
|
||||
* strictement le même, et n'a pas à être écrit deux fois.
|
||||
*
|
||||
* @param {object} options
|
||||
* - sens : +1 si tirer vers la droite élargit (la colonne, ancrée à gauche),
|
||||
* −1 sinon (le panneau, ancré à droite).
|
||||
*/
|
||||
function installerPoignee({ poignees, sens, largeur, appliquer, defaut }) {
|
||||
for (const poignee of poignees) {
|
||||
let depart = null;
|
||||
|
||||
poignee.addEventListener('pointerdown', (evenement) => {
|
||||
if (evenement.button !== 0) return;
|
||||
depart = { x: evenement.clientX, largeur: largeur() };
|
||||
poignee.setPointerCapture(evenement.pointerId);
|
||||
poignee.dataset.glisse = 'true';
|
||||
document.body.classList.add('glisse-en-cours');
|
||||
evenement.preventDefault();
|
||||
});
|
||||
|
||||
poignee.addEventListener('pointermove', (evenement) => {
|
||||
if (!depart) return;
|
||||
appliquer(depart.largeur + sens * (evenement.clientX - depart.x));
|
||||
});
|
||||
|
||||
const terminer = (evenement) => {
|
||||
if (!depart) return;
|
||||
depart = null;
|
||||
poignee.releasePointerCapture?.(evenement.pointerId);
|
||||
delete poignee.dataset.glisse;
|
||||
document.body.classList.remove('glisse-en-cours');
|
||||
};
|
||||
|
||||
poignee.addEventListener('pointerup', terminer);
|
||||
poignee.addEventListener('pointercancel', terminer);
|
||||
|
||||
poignee.addEventListener('dblclick', () => appliquer(defaut));
|
||||
|
||||
// Les flèches ajustent aussi la largeur quand la poignée a le focus.
|
||||
// `stopPropagation` est indispensable : sans lui, le gestionnaire posé plus
|
||||
// bas sur `document` décalerait en même temps la phase sélectionnée.
|
||||
poignee.addEventListener('keydown', (evenement) => {
|
||||
const pas = evenement.key === 'ArrowLeft' ? -10 : evenement.key === 'ArrowRight' ? 10 : 0;
|
||||
if (pas === 0) return;
|
||||
evenement.preventDefault();
|
||||
evenement.stopPropagation();
|
||||
appliquer(largeur() + sens * pas);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
installerPoignee({
|
||||
poignees: [$('poignee-colonne')],
|
||||
sens: 1,
|
||||
largeur: () => largeurLibelles,
|
||||
appliquer: appliquerLargeurLibelles,
|
||||
defaut: LARGEUR_LIBELLES.defaut,
|
||||
});
|
||||
|
||||
installerPoignee({
|
||||
poignees: document.querySelectorAll('[data-poignee-panneau]'),
|
||||
sens: -1,
|
||||
largeur: () => largeurPanneau,
|
||||
appliquer: appliquerLargeurPanneau,
|
||||
defaut: LARGEUR_PANNEAU.defaut,
|
||||
});
|
||||
|
||||
// Rétrécir la fenêtre sous la largeur du panneau le laisserait déborder : on
|
||||
// rejoue le plafond sans toucher à la largeur mémorisée, qu'on retrouvera
|
||||
// intacte en rouvrant la fenêtre en grand.
|
||||
window.addEventListener('resize', () =>
|
||||
appliquerLargeurPanneau(largeurPanneauVoulue, { memoriser: false })
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Fenêtre temporelle et défilement sans fin
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user