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