diff --git a/templates/play.html b/templates/play.html index a0509424..4a97faf8 100644 --- a/templates/play.html +++ b/templates/play.html @@ -265,28 +265,39 @@ } // Trouve, pour un élément et un type de déclencheur ("clic"/"soumission"), - // le nœud Déclencheur qui lui est rattaché — c'est le point d'entrée du - // graphe à exécuter. 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 du - // 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 findTriggerNode(elementId, event) { + // 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) { - const flow = gameData.flows[screenIdKey]; - const found = flow.nodes.find(n => n.node_type === 'trigger' && n.trigger_element_id === elementId && (n.trigger_event || 'clic') === event); - if (found) return { node: found, screenId: screenIdKey }; + 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 null; + return found; } // Ré-attache les gestionnaires de clic sur chaque élément — appelé au @@ -313,8 +324,8 @@ function bindClicks() { document.querySelectorAll('[data-element-id]').forEach(elDiv => { const elementId = parseInt(elDiv.dataset.elementId, 10); - const trigger = findTriggerNode(elementId, 'clic'); - if (trigger) { + const triggers = findTriggerNodess(elementId, 'clic'); + if (triggers.length) { // Garde-fou anti-doublon : refreshRuntimeData() rappelle bindClicks() // après chaque changement de donnée, mais un élément qui n'est ni un // Répéteur ni une Jauge garde le MÊME nœud DOM d'un rafraîchissement @@ -337,7 +348,9 @@ const rowEl = ev.target.closest('.repeaterItem'); window.lastClickedRowId = rowEl ? parseInt(rowEl.dataset.rowId, 10) : null; window.lastClickedDefinitionId = elDiv.dataset.definitionId ? parseInt(elDiv.dataset.definitionId, 10) : null; - runFlowFrom(trigger.node.id, trigger.screenId); + // Exécute CHAQUE déclencheur "Au clic" posé sur cet élément, pas + // seulement le premier (voir le commentaire de findTriggerNodess). + triggers.forEach(trigger => runFlowFrom(trigger.node.id, trigger.screenId)); }); } }); @@ -354,12 +367,15 @@ document.querySelectorAll('[data-element-id]').forEach(elDiv => { if (elDiv.dataset.hoverTriggerBound) return; const elementId = parseInt(elDiv.dataset.elementId, 10); - const enterTrigger = findTriggerNode(elementId, 'survol'); - const leaveTrigger = findTriggerNode(elementId, 'fin_survol'); - if (!enterTrigger && !leaveTrigger) return; + const enterTriggers = findTriggerNodess(elementId, 'survol'); + const leaveTriggers = findTriggerNodess(elementId, 'fin_survol'); + if (!enterTriggers.length && !leaveTriggers.length) return; elDiv.dataset.hoverTriggerBound = '1'; - if (enterTrigger) elDiv.addEventListener('mouseenter', () => runFlowFrom(enterTrigger.node.id, enterTrigger.screenId)); - if (leaveTrigger) elDiv.addEventListener('mouseleave', () => runFlowFrom(leaveTrigger.node.id, leaveTrigger.screenId)); + // Exécute CHAQUE déclencheur trouvé, pas seulement le premier — voir + // le commentaire de findTriggerNodess (plusieurs déclencheurs + // indépendants sur le même élément/événement doivent tous s'exécuter). + if (enterTriggers.length) elDiv.addEventListener('mouseenter', () => enterTriggers.forEach(t => runFlowFrom(t.node.id, t.screenId))); + if (leaveTriggers.length) elDiv.addEventListener('mouseleave', () => leaveTriggers.forEach(t => runFlowFrom(t.node.id, t.screenId))); }); } @@ -982,10 +998,10 @@ // continue sur son (ou ses) fil de sortie ; chaque nœud Condition évalue // une donnée du jeu et suit le(s) fil(s) "Vrai" ou "Faux" en conséquence. // flowScreenId : l'écran PROPRIÉTAIRE du graphe à exécuter — par défaut - // l'écran affiché, mais peut être un écran-MODÈLE (voir findTriggerNode) + // l'écran affiché, mais peut être un écran-MODÈLE (voir findTriggerNodes) // quand le nœud de départ vient d'un déclencheur posé dans l'éditeur d'un // élément de jeu réutilisable, sur un de ses propres enfants. Sans ce - // paramètre, un tel déclencheur était bien retrouvé (findTriggerNode + // paramètre, un tel déclencheur était bien retrouvé (findTriggerNodes // cherche dans tous les écrans) mais son graphe n'existait jamais dans // l'écran réellement affiché : rien ne se déclenchait, silencieusement. function runFlowFrom(nodeId, flowScreenId) { @@ -1018,7 +1034,7 @@ // Déclenche un événement personnalisé (voir screens/custom_events/, // action "declencher_evenement") : une pure NOTIFICATION, sans aucun // paramètre — scan GLOBAL de gameData.flows, même principe que - // findTriggerNode()/runScreenShowTriggers() ci-dessus, puisqu'un + // findTriggerNodes()/runScreenShowTriggers() ci-dessus, puisqu'un // écouteur (nœud Déclencheur trigger_event="evenement") peut vivre sur // N'IMPORTE QUELLE autre scène ou modèle, pas seulement l'écran affiché. // C'est à CHAQUE écouteur de décider quoi faire ensuite (via sa propre