@@ -6673,7 +6673,9 @@ function pinTooltip() {
e . stopPropagation ( ) ;
e . stopPropagation ( ) ;
_softUnpinPopup ( popup ) ;
_softUnpinPopup ( popup ) ;
} ) ;
} ) ;
popup . appendChild ( closeBtn ) ;
topbar . appendChild ( unpinBtn ) ;
popup . appendChild ( topbar ) ;
// v4.3.3 : barre de drag en haut, pour déplacer la popup à la souris.
// v4.3.3 : barre de drag en haut, pour déplacer la popup à la souris.
// Ancrée en haut à 22px de haut ; le padding-top de la popup est augmenté
// Ancrée en haut à 22px de haut ; le padding-top de la popup est augmenté
@@ -6727,6 +6729,9 @@ function pinTooltip() {
popup . style . top = pos . docY + "px" ;
popup . style . top = pos . docY + "px" ;
popup . style . visibility = "visible" ;
popup . style . visibility = "visible" ;
// v2026.5.20 : clamper dans la safe area (topbar + dock)
_clampPopupInSafeArea ( popup ) ;
// Enregistrer dans la liste
// Enregistrer dans la liste
pinnedPopups . push ( { el : popup , iv : iv , rect : pos . rect } ) ;
pinnedPopups . push ( { el : popup , iv : iv , rect : pos . rect } ) ;
@@ -6753,6 +6758,44 @@ function _closePinnedPopup(el) {
el . remove ( ) ;
el . remove ( ) ;
}
}
/ * *
* v2026 . 5.19 : re - fetch les infos d ' une intervention et met à jour le contenu
* du popup épinglé correspondant . Utilise fetchAndUpdateIntervention qui fait
* xhr2 + fiche , puis régénère le HTML du tooltip avec buildTooltipHTML .
* /
async function _refreshPinnedPopupIv ( popup , iv ) {
if ( ! popup || ! iv ) return ;
try {
// Forcer le refetch : on invalide les flags qui disent "déjà fetché"
iv . xhr2Fetched = false ;
iv . xhr2Fetching = false ;
iv . ficheFetched = false ;
iv . ficheFetching = false ;
// Token de refresh actuel (pour que fetchAndUpdateIntervention ne soit
// pas abortée par les checks isRefreshAborted)
const token = ( typeof currentRefreshToken !== "undefined" ) ? currentRefreshToken : 0 ;
await fetchAndUpdateIntervention ( iv , token ) ;
// Régénérer le HTML du tooltip avec les nouvelles infos.
// On doit réinjecter juste le contenu, en gardant la topbar et la dragbar
// (qui ne sont PAS dans le tooltip source, elles sont propres au popup).
const topbar = popup . querySelector ( ".pinned-popup-topbar" ) ;
const dragbar = popup . querySelector ( ".pinned-popup-dragbar" ) ;
const newHtml = buildTooltipHTML ( iv ) ;
popup . innerHTML = newHtml ;
// Virer aussi la vieille icône 📌 si elle revient dans le rebuild
const oldPin = popup . querySelector ( '.tooltip-pinbtn[data-action="pin"]' ) ;
if ( oldPin ) oldPin . remove ( ) ;
// Remettre topbar et dragbar
if ( topbar ) popup . appendChild ( topbar ) ;
if ( dragbar ) popup . appendChild ( dragbar ) ;
} catch ( err ) {
console . warn ( "[refresh-popup]" , err ) ;
}
}
/ * *
/ * *
* Désépinglage "mou" : la popup n 'est plus considérée épinglée (elle n' est
* Désépinglage "mou" : la popup n 'est plus considérée épinglée (elle n' est
* plus dans pinnedPopups , donc le comptage pour Ctrl × 2 etc . ignore ) mais on
* plus dans pinnedPopups , donc le comptage pour Ctrl × 2 etc . ignore ) mais on
@@ -6779,6 +6822,35 @@ function _softUnpinPopup(el) {
if ( dragbar ) dragbar . remove ( ) ;
if ( dragbar ) dragbar . remove ( ) ;
const closeBtn = el . querySelector ( ".pinned-popup-close" ) ;
const closeBtn = el . querySelector ( ".pinned-popup-close" ) ;
if ( closeBtn ) closeBtn . remove ( ) ;
if ( closeBtn ) closeBtn . remove ( ) ;
// v2026.5.17 : retirer aussi la nouvelle topbar et le conteneur minimisé
const topbar = el . querySelector ( ".pinned-popup-topbar" ) ;
if ( topbar ) topbar . remove ( ) ;
el . classList . remove ( "pinned-popup-minimized" ) ;
el . classList . remove ( "pinned-popup-reduced" ) ;
// v2026.5.18 : retirer aussi la pastille du dock si elle existe
if ( el . _linkedPill ) {
try { el . _linkedPill . remove ( ) ; } catch ( e ) { }
el . _linkedPill = null ;
}
// Si le dock est vide, le cacher ; mettre à jour le bouton "Fermer tous"
const dock = document . getElementById ( "pinned-popups-dock" ) ;
if ( dock && dock . querySelectorAll ( ".pinned-popup-dock-pill" ) . length === 0 ) {
dock . classList . remove ( "visible" ) ;
const closeAllBtn = document . getElementById ( "pinned-popups-close-all" ) ;
if ( closeAllBtn ) closeAllBtn . remove ( ) ;
} else {
_ensureDockCloseAllBtn ( ) ;
}
// v2026.5.22 : si le tooltip hover est actuellement affiché pour la même
// intervention que celle qu'on désépingle, il faut regénérer son HTML pour
// que l'icône passe de 📍 (active rouge) à 📌 (non active) — sinon l'user
// voit l'ancienne icône et croit qu'il est toujours épinglé.
const tip = tooltipEl ( ) ;
if ( tip && tip . classList . contains ( "visible" ) && state . currentTooltipIv ) {
tip . innerHTML = buildTooltipHTML ( state . currentTooltipIv ) ;
}
// Helper qui joue l'animation de sortie puis supprime le DOM
// Helper qui joue l'animation de sortie puis supprime le DOM
const animateAndRemove = ( ) => {
const animateAndRemove = ( ) => {
@@ -6795,6 +6867,430 @@ function _softUnpinPopup(el) {
el . addEventListener ( "mouseleave" , animateAndRemove , { once : true } ) ;
el . addEventListener ( "mouseleave" , animateAndRemove , { once : true } ) ;
}
}
// ============================================================================
// v2026.5.17 : États d'un popup épinglé
// - Normal (complet, flottant)
// - Minimisé (compact, flottant, juste la ref + topbar)
// - Réduit (docké dans la taskbar en bas de l'écran)
// ============================================================================
/ * *
* Passe un popup épinglé en mode Minimisé : on ne montre plus que la ref ,
* dans un petit cadre flottant toujours drag - able .
*
* v2026 . 5.19 : au lieu de masquer tout le contenu via CSS et tenter de
* réafficher la ref ( fragile ) , on crée un élément dédié ` .pinned-popup-minref `
* qui contient juste la ref + la date . Cet élément est ajouté / retiré au besoin .
* /
function _minimizePinnedPopup ( popup ) {
if ( ! popup ) return ;
popup . classList . add ( "pinned-popup-minimized" ) ;
// Adapter les boutons topbar : [▭] devient [⬆] (agrandir)
const minBtn = popup . querySelector ( ".pinned-popup-minimize" ) ;
if ( minBtn ) {
minBtn . innerHTML = "⬆" ;
minBtn . title = "Agrandir" ;
// On retire les anciens listeners en clonant l'élément
const newBtn = minBtn . cloneNode ( true ) ;
minBtn . replaceWith ( newBtn ) ;
newBtn . addEventListener ( "click" , ( e ) => {
e . stopPropagation ( ) ;
_expandPinnedPopup ( popup ) ;
} ) ;
}
// Créer un élément dédié pour afficher la ref en mode minimisé
let minRef = popup . querySelector ( ".pinned-popup-minref" ) ;
if ( ! minRef ) {
minRef = document . createElement ( "div" ) ;
minRef . className = "pinned-popup-minref" ;
const refText = popup . dataset . ref || "(sans ref)" ;
minRef . textContent = refText ;
minRef . title = "Cliquer pour agrandir" ;
minRef . addEventListener ( "click" , ( e ) => {
e . stopPropagation ( ) ;
_expandPinnedPopup ( popup ) ;
} ) ;
popup . appendChild ( minRef ) ;
}
}
/ * *
* Repasse un popup minimisé en mode Normal ( complet ) .
* /
function _expandPinnedPopup ( popup ) {
if ( ! popup ) return ;
popup . classList . remove ( "pinned-popup-minimized" ) ;
// Restaurer bouton Minimiser
const minBtn = popup . querySelector ( ".pinned-popup-minimize" ) ;
if ( minBtn ) {
minBtn . innerHTML = "▭" ;
minBtn . title = "Minimiser (reste flottant mais compact)" ;
const newBtn = minBtn . cloneNode ( true ) ;
minBtn . replaceWith ( newBtn ) ;
newBtn . addEventListener ( "click" , ( e ) => {
e . stopPropagation ( ) ;
_minimizePinnedPopup ( popup ) ;
} ) ;
}
// Retirer l'élément ref dédié (s'il existe)
const minRef = popup . querySelector ( ".pinned-popup-minref" ) ;
if ( minRef ) minRef . remove ( ) ;
}
/ * *
* Passe un popup épinglé en mode Réduit : il disparaît de son emplacement
* flottant et vient s 'ajouter dans une taskbar en bas de l' écran sous forme
* de pastille cliquable .
* /
function _reducePinnedPopup ( popup ) {
if ( ! popup ) return ;
// Récupérer la référence pour le label de la pastille
// v2026.5.18 : préférer le dataset.ref mémorisé à la création plutôt que
// le textContent (qui peut contenir "—" si la ref n'était pas encore
// disponible à l'épinglage)
const refEl = popup . querySelector ( ".iv-ref-header" ) ;
const label = popup . dataset . ref
|| ( refEl ? ( refEl . textContent || "" ) . trim ( ) : "" )
|| "Popup" ;
const colorKey = popup . dataset . colorKey || "autre" ;
// S'assurer que la taskbar du bas existe
let dock = document . getElementById ( "pinned-popups-dock" ) ;
if ( ! dock ) {
dock = document . createElement ( "div" ) ;
dock . id = "pinned-popups-dock" ;
dock . className = "pinned-popups-dock" ;
document . body . appendChild ( dock ) ;
}
// Créer la pastille dock
// v2026.5.18 : le fond de la pastille prend la couleur de catégorie
// (via la classe color-XXX déjà utilisée ailleurs dans le CSS)
// v2026.5.19 : pastille à 2 lignes — ref (gras) + date origine (petit)
const pill = document . createElement ( "button" ) ;
pill . type = "button" ;
pill . className = "pinned-popup-dock-pill color-" + colorKey ;
pill . title = "Cliquer pour agrandir" ;
const pillRef = document . createElement ( "span" ) ;
pillRef . className = "pinned-popup-dock-pill-ref" ;
pillRef . textContent = label ;
pill . appendChild ( pillRef ) ;
// Date d'origine (ex: "21.04")
const originDate = popup . dataset . originDate || "" ;
if ( originDate ) {
const pillDate = document . createElement ( "span" ) ;
pillDate . className = "pinned-popup-dock-pill-date" ;
pillDate . textContent = _formatDateShort ( originDate ) ;
pill . appendChild ( pillDate ) ;
}
// Mémoriser la position/taille du popup avant de le masquer
const rect = popup . getBoundingClientRect ( ) ;
popup . dataset . prevLeft = popup . style . left || ( rect . left + "px" ) ;
popup . dataset . prevTop = popup . style . top || ( rect . top + "px" ) ;
popup . dataset . prevWidth = popup . style . width || "" ;
// Cacher le popup (on le garde en DOM pour conserver son état et restaurer
// instantanément)
popup . classList . add ( "pinned-popup-reduced" ) ;
// Associer pill ↔ popup
pill . _linkedPopup = popup ;
popup . _linkedPill = pill ;
pill . addEventListener ( "click" , ( e ) => {
e . stopPropagation ( ) ;
_restorePinnedPopupFromDock ( popup ) ;
} ) ;
// v2026.5.20 : mini-menu au survol (Agrandir / Fermer)
pill . addEventListener ( "mouseenter" , ( ) => {
_showPillHoverMenu ( pill , popup ) ;
} ) ;
pill . addEventListener ( "mouseleave" , ( e ) => {
// Le menu peut être sous la souris — on ne ferme pas si on entre dans le menu
_schedulePillMenuClose ( ) ;
} ) ;
dock . appendChild ( pill ) ;
dock . classList . add ( "visible" ) ;
// v2026.5.18 : s'assurer qu'il y a un bouton "Fermer tous" si 2+ popups
_ensureDockCloseAllBtn ( ) ;
// v2026.5.20 : le dock qui apparaît peut chevaucher des popups flottants —
// les reclamper pour qu'ils restent dans la safe area.
_reclampAllFloatingPopups ( ) ;
}
/ * *
* v2026 . 5.20 : affiche un mini - menu au - dessus d ' une pastille dock au survol .
* Contient 2 actions : Agrandir , Fermer .
* /
let _pillMenuCloseTimer = null ;
function _showPillHoverMenu ( pill , popup ) {
// Annuler une fermeture en cours
if ( _pillMenuCloseTimer ) {
clearTimeout ( _pillMenuCloseTimer ) ;
_pillMenuCloseTimer = null ;
}
// S'il existe déjà un menu pour un autre pill, le fermer
const existing = document . getElementById ( "pill-hover-menu" ) ;
if ( existing ) {
if ( existing . _linkedPill === pill ) return ; // déjà pour ce pill
existing . remove ( ) ;
}
const menu = document . createElement ( "div" ) ;
menu . id = "pill-hover-menu" ;
menu . className = "pill-hover-menu" ;
menu . _linkedPill = pill ;
menu . _linkedPopup = popup ;
const restoreBtn = document . createElement ( "button" ) ;
restoreBtn . type = "button" ;
restoreBtn . className = "pill-hover-menu-btn" ;
restoreBtn . innerHTML = '<span class="pill-menu-ico">⬆</span> Agrandir' ;
restoreBtn . addEventListener ( "click" , ( e ) => {
e . stopPropagation ( ) ;
_hidePillHoverMenu ( ) ;
_restorePinnedPopupFromDock ( popup ) ;
} ) ;
menu . appendChild ( restoreBtn ) ;
const closeBtn = document . createElement ( "button" ) ;
closeBtn . type = "button" ;
closeBtn . className = "pill-hover-menu-btn pill-hover-menu-close" ;
closeBtn . innerHTML = '<span class="pill-menu-ico">✕</span> Fermer' ;
closeBtn . addEventListener ( "click" , ( e ) => {
e . stopPropagation ( ) ;
_hidePillHoverMenu ( ) ;
// Retirer le popup de la liste et supprimer le DOM
const idx = pinnedPopups . findIndex ( p => p . el === popup ) ;
if ( idx >= 0 ) pinnedPopups . splice ( idx , 1 ) ;
try { popup . remove ( ) ; } catch ( err ) { }
try { pill . remove ( ) ; } catch ( err ) { }
const dock = document . getElementById ( "pinned-popups-dock" ) ;
if ( dock && dock . querySelectorAll ( ".pinned-popup-dock-pill" ) . length === 0 ) {
dock . classList . remove ( "visible" ) ;
const closeAllBtn = document . getElementById ( "pinned-popups-close-all" ) ;
if ( closeAllBtn ) closeAllBtn . remove ( ) ;
_reclampAllFloatingPopups ( ) ;
} else {
_ensureDockCloseAllBtn ( ) ;
}
} ) ;
menu . appendChild ( closeBtn ) ;
document . body . appendChild ( menu ) ;
// Positionner au-dessus de la pastille
const r = pill . getBoundingClientRect ( ) ;
const menuR = menu . getBoundingClientRect ( ) ;
let left = r . left + ( r . width / 2 ) - ( menuR . width / 2 ) ;
if ( left < 4 ) left = 4 ;
if ( left + menuR . width > window . innerWidth - 4 ) left = window . innerWidth - menuR . width - 4 ;
menu . style . left = left + "px" ;
menu . style . top = ( r . top - menuR . height - 8 ) + "px" ;
// Garder ouvert si la souris entre dans le menu
menu . addEventListener ( "mouseenter" , ( ) => {
if ( _pillMenuCloseTimer ) {
clearTimeout ( _pillMenuCloseTimer ) ;
_pillMenuCloseTimer = null ;
}
} ) ;
menu . addEventListener ( "mouseleave" , ( ) => {
_schedulePillMenuClose ( ) ;
} ) ;
}
function _schedulePillMenuClose ( ) {
if ( _pillMenuCloseTimer ) clearTimeout ( _pillMenuCloseTimer ) ;
_pillMenuCloseTimer = setTimeout ( ( ) => {
_hidePillHoverMenu ( ) ;
_pillMenuCloseTimer = null ;
} , 250 ) ;
}
function _hidePillHoverMenu ( ) {
const existing = document . getElementById ( "pill-hover-menu" ) ;
if ( existing ) existing . remove ( ) ;
}
/ * *
* v2026 . 5.20 : calcule la safe area pour les popups épinglés .
* Retourne { top , bottom , left , right } en coords viewport .
* - top : hauteur de la topbar ( les popups ne doivent pas passer dessous )
* - bottom : top du dock si visible , sinon hauteur viewport
* /
function _getPopupSafeArea ( ) {
let topLimit = 4 ;
const topbar = document . querySelector ( "header.topbar" ) ;
if ( topbar ) {
const r = topbar . getBoundingClientRect ( ) ;
if ( r . bottom > topLimit ) topLimit = r . bottom + 4 ;
}
let bottomLimit = window . innerHeight - 4 ;
const dock = document . getElementById ( "pinned-popups-dock" ) ;
if ( dock && dock . classList . contains ( "visible" ) ) {
const r = dock . getBoundingClientRect ( ) ;
if ( r . top < bottomLimit ) bottomLimit = r . top - 4 ;
}
return { top : topLimit , bottom : bottomLimit , left : 4 , right : window . innerWidth - 4 } ;
}
/ * *
* v2026 . 5.20 : contraint un popup flottant ( en coords document via style . left / top )
* dans la safe area . Appelé à l ' épinglage , pendant le drag , et quand le dock
* apparaît / disparaît .
* /
function _clampPopupInSafeArea ( popup ) {
if ( ! popup ) return ;
if ( popup . classList . contains ( "pinned-popup-reduced" ) ) return ; // pas clamp si docké
const safe = _getPopupSafeArea ( ) ;
const rect = popup . getBoundingClientRect ( ) ;
const w = rect . width || popup . offsetWidth || 280 ;
const h = rect . height || popup . offsetHeight || 200 ;
// Les coords viewport actuelles
const vLeft = rect . left ;
const vTop = rect . top ;
// Calcul des coords viewport cibles après clamp
let newVLeft = vLeft ;
let newVTop = vTop ;
if ( newVLeft < safe . left ) newVLeft = safe . left ;
if ( newVLeft + w > safe . right ) newVLeft = safe . right - w ;
if ( newVLeft < safe . left ) newVLeft = safe . left ; // si popup plus large que viewport
if ( newVTop < safe . top ) newVTop = safe . top ;
if ( newVTop + h > safe . bottom ) newVTop = safe . bottom - h ;
if ( newVTop < safe . top ) newVTop = safe . top ;
if ( newVLeft === vLeft && newVTop === vTop ) return ; // rien à faire
// Différence = appliquer au style.left / style.top (qui sont en document coords)
const dx = newVLeft - vLeft ;
const dy = newVTop - vTop ;
const curLeft = parseFloat ( popup . style . left ) || 0 ;
const curTop = parseFloat ( popup . style . top ) || 0 ;
popup . style . left = ( curLeft + dx ) + "px" ;
popup . style . top = ( curTop + dy ) + "px" ;
}
/ * *
* Réclampe tous les popups flottants ( utile après apparition / disparition du dock ) .
* /
function _reclampAllFloatingPopups ( ) {
document . querySelectorAll ( ".pinned-popup:not(.pinned-popup-reduced)" ) . forEach ( p => {
_clampPopupInSafeArea ( p ) ;
} ) ;
}
/ * *
* v2026 . 5.19 : réduit TOUS les popups épinglés actuellement ouverts ( en mode
* normal ou minimisé ) dans la taskbar du bas . Appelé au changement de date .
* /
function _reduceAllPinnedPopups ( ) {
const popups = document . querySelectorAll ( ".pinned-popup:not(.pinned-popup-reduced)" ) ;
popups . forEach ( popup => {
try { _reducePinnedPopup ( popup ) ; } catch ( e ) { }
} ) ;
}
/ * *
* v2026 . 5.19 : ISO date ( YYYY - MM - DD ) → format court "DD.MM" pour le dock .
* /
function _formatDateShort ( iso ) {
if ( ! iso ) return "" ;
const m = String ( iso ) . match ( /^(\d{4})-(\d{2})-(\d{2})$/ ) ;
if ( ! m ) return iso ;
return ` ${ m [ 3 ] } . ${ m [ 2 ] } ` ;
}
/ * *
* v2026 . 5.18 : ajoute ( ou met à jour ) le bouton "Fermer tous" dans le dock
* quand au moins 2 popups épinglés existent ( réduits OU affichés ) .
* Le bouton est placé à droite du dock .
* /
function _ensureDockCloseAllBtn ( ) {
const dock = document . getElementById ( "pinned-popups-dock" ) ;
if ( ! dock ) return ;
const allPinned = document . querySelectorAll ( ".pinned-popup" ) ;
let closeAllBtn = document . getElementById ( "pinned-popups-close-all" ) ;
if ( allPinned . length >= 2 ) {
if ( ! closeAllBtn ) {
closeAllBtn = document . createElement ( "button" ) ;
closeAllBtn . type = "button" ;
closeAllBtn . id = "pinned-popups-close-all" ;
closeAllBtn . className = "pinned-popups-close-all" ;
closeAllBtn . textContent = "✕ Fermer tous" ;
closeAllBtn . title = "Fermer tous les popups épinglés" ;
closeAllBtn . addEventListener ( "click" , ( e ) => {
e . stopPropagation ( ) ;
closeAllPinnedPopups ( ) ;
} ) ;
dock . appendChild ( closeAllBtn ) ;
} else {
// Remettre à la fin (après les pastilles éventuellement ajoutées)
dock . appendChild ( closeAllBtn ) ;
}
dock . classList . add ( "visible" ) ;
} else if ( closeAllBtn ) {
closeAllBtn . remove ( ) ;
}
}
/ * *
* Ramène un popup réduit en mode Normal : retire la pastille du dock et
* réaffiche le popup flottant à sa position d ' avant réduction .
* /
function _restorePinnedPopupFromDock ( popup ) {
if ( ! popup ) return ;
popup . classList . remove ( "pinned-popup-reduced" ) ;
// Si le popup était minimisé avant d'être réduit, on l'agrandit direct
// (la demande était : "Si la reduit et rappeller s'affiche en grand direct")
popup . classList . remove ( "pinned-popup-minimized" ) ;
const minBtn = popup . querySelector ( ".pinned-popup-minimize" ) ;
if ( minBtn ) {
minBtn . innerHTML = "▭" ;
minBtn . title = "Minimiser (reste flottant mais compact)" ;
const newBtn = minBtn . cloneNode ( true ) ;
minBtn . replaceWith ( newBtn ) ;
newBtn . addEventListener ( "click" , ( e ) => {
e . stopPropagation ( ) ;
_minimizePinnedPopup ( popup ) ;
} ) ;
}
// Supprimer la pastille associée
if ( popup . _linkedPill ) {
popup . _linkedPill . remove ( ) ;
popup . _linkedPill = null ;
}
// Si le dock est vide (sauf le bouton "Fermer tous"), le masquer
const dock = document . getElementById ( "pinned-popups-dock" ) ;
if ( dock ) {
const remainingPills = dock . querySelectorAll ( ".pinned-popup-dock-pill" ) . length ;
if ( remainingPills === 0 ) {
dock . classList . remove ( "visible" ) ;
const closeAllBtn = document . getElementById ( "pinned-popups-close-all" ) ;
if ( closeAllBtn ) closeAllBtn . remove ( ) ;
} else {
_ensureDockCloseAllBtn ( ) ;
}
}
}
/** Ferme toutes les popups épinglées (appelé par Échap ou changement de date). */
/** Ferme toutes les popups épinglées (appelé par Échap ou changement de date). */
/ * *
/ * *
* v5 . 0.1 : helper pour déclencher la suppression d ' une absence ou réservation .
* v5 . 0.1 : helper pour déclencher la suppression d ' une absence ou réservation .
@@ -6855,6 +7351,13 @@ function closeAllPinnedPopups() {
pinnedPopups . length = 0 ;
pinnedPopups . length = 0 ;
// Fermer aussi les popups en état soft-unpinned qui trainent encore
// Fermer aussi les popups en état soft-unpinned qui trainent encore
document . querySelectorAll ( ".pinned-popup.soft-unpinned" ) . forEach ( el => el . remove ( ) ) ;
document . querySelectorAll ( ".pinned-popup.soft-unpinned" ) . forEach ( el => el . remove ( ) ) ;
// v2026.5.18 : supprimer aussi les éléments du dock
document . querySelectorAll ( ".pinned-popup" ) . forEach ( el => el . remove ( ) ) ;
document . querySelectorAll ( ".pinned-popup-dock-pill" ) . forEach ( el => el . remove ( ) ) ;
const closeAllBtn = document . getElementById ( "pinned-popups-close-all" ) ;
if ( closeAllBtn ) closeAllBtn . remove ( ) ;
const dock = document . getElementById ( "pinned-popups-dock" ) ;
if ( dock ) dock . classList . remove ( "visible" ) ;
}
}
/ * *
/ * *
@@ -6875,12 +7378,12 @@ function _attachPopupDragHandler(popup, dragbar) {
let newLeft = startLeft + dx ;
let newLeft = startLeft + dx ;
let newTop = startTop + dy ;
let newTop = startTop + dy ;
// Clamper dans le document (pas sortir trop à gauche/haut)
// v2026.5.20 : clamper dans la safe area (topbar en haut, dock en bas,
if ( newLeft < 4 ) newLeft = 4 ;
// bordures viewport gauche/droite). On calcule en coords viewport puis
if ( newTop < 4 ) newTop = 4 ;
// on applique en coords document.
popup . style . left = newLeft + "px" ;
popup . style . left = newLeft + "px" ;
popup . style . top = newTop + "px" ;
popup . style . top = newTop + "px" ;
_clampPopupInSafeArea ( popup ) ;
} ;
} ;
const onMouseUp = ( ) => {
const onMouseUp = ( ) => {
@@ -6889,6 +7392,11 @@ function _attachPopupDragHandler(popup, dragbar) {
popup . classList . remove ( "dragging" ) ;
popup . classList . remove ( "dragging" ) ;
document . removeEventListener ( "mousemove" , onMouseMove ) ;
document . removeEventListener ( "mousemove" , onMouseMove ) ;
document . removeEventListener ( "mouseup" , onMouseUp ) ;
document . removeEventListener ( "mouseup" , onMouseUp ) ;
// v2026.5.19 : retirer la classe body et le flag global après un petit
// délai pour laisser le temps au mouseleave de la carte de se propager
// sans déclencher de tooltip parasite.
document . body . classList . remove ( "popup-dragging" ) ;
setTimeout ( ( ) => { state . _popupDragging = false ; } , 50 ) ;
// Mettre à jour le rect mémorisé pour la détection de chevauchement
// Mettre à jour le rect mémorisé pour la détection de chevauchement
const entry = pinnedPopups . find ( p => p . el === popup ) ;
const entry = pinnedPopups . find ( p => p . el === popup ) ;