// ---------- Déclencheurs : recherche des nœuds + attache des écouteurs ---------- // Extrait de templates/play.html (voir plan de modularisation). // Déclenche tous les nœuds "À l'affichage de l'écran" de CET écran — pas // besoin d'avoir cliqué sur quoi que ce soit : ça couvre le premier // affichage, un retour en arrière, un changement d'écran (ecran_suivant/ // ecran_precedent/aller_a/ouvrir_ligne appellent tous showScreen). C'est // ce qui permet à une condition ("SI outil débloqué = faux") + une action // "Modifier un élément → Visibilité" de refléter l'état de la partie sans // attendre un clic (voir 1.2 dans la doc de cadrage Forge Engine). function runScreenShowTriggers(screenId) { const flow = gameData.flows[String(screenId)]; if (!flow) return; flow.nodes .filter(function(n){ return n.node_type === 'trigger' && n.trigger_event === 'affichage'; }) .forEach(function(n){ runFlowFrom(n.id); }); } // Minuteur récurrent (Phase 3) : contrairement aux autres déclencheurs // (réagissent à quelque chose), celui-ci fait avancer le jeu tout seul, à // intervalle régulier — géré PAR ÉCRAN (appelé depuis showScreen(), // static/js/play/screens.js) : n'existe QUE tant que l'écran qui le // porte est affiché. On arrête d'abord tout minuteur de l'affichage // précédent (même principe que runAnimationTimeline) — sans ça, revenir // plusieurs fois sur le même écran accumulerait des setInterval en // double, chacun exécutant le graphe en plus des autres. var activeTimerTriggerIntervals = []; function runScreenTimerTriggers(screenId) { activeTimerTriggerIntervals.forEach(function(id) { clearInterval(id); }); activeTimerTriggerIntervals = []; const flow = gameData.flows[String(screenId)]; if (!flow) return; flow.nodes .filter(function(n){ return n.node_type === 'trigger' && n.trigger_event === 'minuteur' && n.trigger_interval_ms > 0; }) .forEach(function(n){ var intervalId = setInterval(function(){ runFlowFrom(n.id, screenId); }, n.trigger_interval_ms); activeTimerTriggerIntervals.push(intervalId); }); } // Trouve, pour un élément et un type de déclencheur ("clic"/"soumission"), // TOUS les nœuds Déclencheur qui lui sont rattachés — ce sont les points // d'entrée des graphes à exécuter. Renvoie un TABLEAU (jamais un seul // nœud) : rien n'empêche de poser plusieurs déclencheurs indépendants // "Au clic" sur le MÊME élément (ex. l'un ouvre une ligne de Répéteur, // l'autre éteint une surbrillance) — un seul clic doit alors exécuter // CHACUN de ces graphes, pas seulement le premier trouvé (régression // vécue : un second déclencheur posé sur un élément qui en avait déjà un // restait silencieusement inerte, aucune erreur, juste jamais exécuté). // // On cherche dans TOUS les écrans (pas seulement l'écran courant) : // bindClicks() attache les gestionnaires une seule fois, sur tous les // éléments de tous les écrans (déjà tous présents dans le DOM, seuls les // écrans non actifs sont masqués) — chaque identifiant d'élément est // unique dans tout le jeu, donc pas d'ambiguïté possible. Cette recherche // globale couvre aussi les écrans-MODÈLES (un élément de jeu réutilisable, // ex. "mail content", posé sur une scène) : un déclencheur "Au clic"/"Au // survol" posé dans l'éditeur du modèle, sur un de SES PROPRES enfants, // est donc retrouvé même quand ce modèle est utilisé ailleurs — d'où le // besoin de renvoyer aussi l'écran d'origine de CHAQUE nœud (screenIdKey), // pas seulement le nœud : runFlowFrom() doit exécuter le graphe dans CET // écran-là (celui du modèle), jamais dans celui réellement affiché, sans // quoi le nœud trouvé n'existerait pas dans le graphe utilisé et l'action // ne se déclencherait jamais. function findTriggerNodess(elementId, event) { const found = []; for (const screenIdKey in gameData.flows) { gameData.flows[screenIdKey].nodes.forEach(function(n) { if (n.node_type === 'trigger' && n.trigger_element_id === elementId && (n.trigger_event || 'clic') === event) { found.push({ node: n, screenId: screenIdKey }); } }); } return found; } // Ré-attache les gestionnaires de clic sur chaque élément — appelé au // chargement, puis à nouveau après un rafraîchissement des données (le // DOM d'un écran est reconstruit après une action "Modifier une donnée", // les nœuds précédents et leurs écouteurs disparaissent avec). // 3.1 (Confort) — interactions au survol : échange le texte affiché // contre data-hover-text pendant que la souris survole l'élément, puis le // restaure au départ de la souris. On ignore volontairement les éléments // qui ont des enfants (ex. un conteneur) pour ne jamais écraser une mise // en page imbriquée avec du texte brut — cette fonctionnalité vise les // éléments de texte simples (nom, lien...), pas les conteneurs. function bindHoverTexts() { document.querySelectorAll('[data-hover-text]').forEach(function(el) { if (el.dataset.hoverBound) return; if (el.children.length > 0) return; el.dataset.hoverBound = '1'; var original = el.textContent; el.addEventListener('mouseenter', function(){ el.textContent = el.dataset.hoverText; }); el.addEventListener('mouseleave', function(){ el.textContent = original; }); }); } // RGAA/WCAG 2.1.1 (clavier) : un objet "Au clic" n'était auparavant // actionnable qu'à la souris (simple
/, jamais un