@micheeeeel/yinyangapp 2.2.1 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (22) hide show
  1. package/README.md +3 -0
  2. package/dist/bundle.js +1 -1
  3. package/dist/types/animations/AnimationsMngr.d.ts +10 -0
  4. package/dist/types/api.d.ts +7 -0
  5. package/dist/types/components/controller/YinYangController.d.ts +167 -5
  6. package/dist/types/components/controller/action-stack/ActionStackController.d.ts +15 -0
  7. package/dist/types/components/controller/actions/ChildActionMenuController.d.ts +40 -0
  8. package/dist/types/components/controller/children/ChildInteractionController.d.ts +9 -0
  9. package/dist/types/components/controller/force-field/ForceFieldDetailsPanel.d.ts +59 -8
  10. package/dist/types/components/controller/force-field/ForceFieldLifecycleController.d.ts +17 -0
  11. package/dist/types/components/controller/force-field/ForceFieldRenderer.d.ts +25 -0
  12. package/dist/types/components/controller/force-field/types.d.ts +16 -0
  13. package/dist/types/components/controller/odd/OddHighlightEffects.d.ts +78 -0
  14. package/dist/types/components/controller/odd/OddViewManager.d.ts +92 -1
  15. package/dist/types/components/controller/odd/childHighlightStyles.d.ts +79 -0
  16. package/dist/types/components/controller/search/SearchPanelSynchronizer.d.ts +9 -0
  17. package/dist/types/components/controller/state/YinYangControllerState.d.ts +23 -0
  18. package/dist/types/components/view/SpikeDrawer.d.ts +28 -0
  19. package/dist/types/components/workspace/workspaceTemplate.d.ts +1 -1
  20. package/dist/types/config/animationConfig.d.ts +7 -0
  21. package/dist/types/config/propagationCometConfig.d.ts +41 -0
  22. package/package.json +1 -1
@@ -14,6 +14,16 @@ export declare class AnimationsMngr {
14
14
  * les changements de composant).
15
15
  */
16
16
  stopRotation(component: IYinYangController, rampMs?: number): void;
17
+ /**
18
+ * Désabonne un composant en fin de vie : coupe sa boucle rAF et libère son
19
+ * verrou d'animation, SANS toucher au DOM. À utiliser (et non stopRotation)
20
+ * quand les éléments vont être détruits : finalizeStop remettrait les
21
+ * transforms des lobes yin/yang à la position de base (resetRotationFrame)
22
+ * or ces groupes vivent dans globalGroup, pas dans le yinYangGroup que
23
+ * clearChildren retire — le reset ressusciterait donc à l'écran des
24
+ * satellites pourtant retirés.
25
+ */
26
+ unsubscribe(component: IYinYangController): void;
17
27
  /**
18
28
  * Arrête la rotation en rejoignant la position de base dans une durée
19
29
  * FIXE `durationMs`, quelle que soit la distance restante — troisième mode
@@ -44,6 +44,13 @@ export declare function mountYinYang(config: YinYangMountConfig): YinYangMountHa
44
44
  export declare function createYinYangSvg(config: YinYangSvgConfig): SVGSVGElement | null;
45
45
  /** Variante en lot : une seule zone hors écran pour tous les rendus. */
46
46
  export declare function createYinYangSvgs(configs: YinYangSvgConfig[]): (SVGSVGElement | null)[];
47
+ /** Durée d'un tour de rotation yin/yang du diagramme (ms), telle qu'elle est
48
+ * configurée côté Sphères (`animationConfig.rotationDurationMs`). Un hôte qui
49
+ * rejoue la même cinématique ailleurs — un film de marker cuit avec
50
+ * `yinYangRotationMs`, ou la variable CSS `--yy-yinyang-rotation` — doit LIRE
51
+ * cette valeur plutôt que la recopier : une vitesse changée ici doit se voir
52
+ * partout, sans resynchronisation manuelle. */
53
+ export declare const YINYANG_ROTATION_DURATION_MS: number;
47
54
  export { createYinYangFilmSvg, createYinYangFilmSvgAsync } from "./api-film";
48
55
  export type { YinYangFilmConfig, YinYangFilmAsyncOptions } from "./api-film";
49
56
  export { createRandomCause } from "./api-random";
@@ -710,11 +710,47 @@ export declare class YinYangController implements IYinYangController {
710
710
  private _computeCrossComponentCascadeRows;
711
711
  private renderForceFieldDetails;
712
712
  private flashChildCuttings;
713
- /** Anime un point rouge vif traversant `waypoints` en séquence segment par
714
- * segment. `onWaypointArrival` est appelé à chaque arrivée (sauf la source).
715
- * `onSegmentStart` est appelé au début de chaque segment (utile pour pulser
716
- * le lien force-field correspondant). */
717
- private _animatePropagationArc;
713
+ /** Waypoint d'un satellite de la facette courante (par index orbital). Le
714
+ * contrôleur peut être passé quand l'appelant l'a déjà sous la main. */
715
+ private _resolveOrbitWaypoint;
716
+ /** Waypoint sur l'arc de bordure d'une autre facette (cible inter-facettes). */
717
+ private _resolveBorderWaypoint;
718
+ /** `color` avec l'opacité `alpha` (chaîne CSS). */
719
+ private static _withAlpha;
720
+ /**
721
+ * Couleur de la comète : celle de l'action portant l'emphase (aperçu ??
722
+ * focus) quand le réglage le demande et qu'elle est identifiable, sinon la
723
+ * couleur fixe du réglage.
724
+ */
725
+ private _resolvePropagationColor;
726
+ /** Anneau doux (couleur de la comète) autour d'un satellite atteint :
727
+ * un cercle qui s'élargit en s'éteignant, plus une brève lueur pleine.
728
+ * Intensité réglable (ringOpacity, 0 = rien). */
729
+ private _drawFlashRings;
730
+ /**
731
+ * Joue une passe de propagation sur des waypoints déjà résolus : lueur sur
732
+ * la source, comète parcourant tout le chemin d'un seul mouvement (en
733
+ * pulsant chaque lien de champ de force à son entrée), anneau à chaque
734
+ * arrivée. Partagée par le clic sur une ligne du panneau
735
+ * (flashChildCuttings) et par la boucle de l'action en focus
736
+ * (flashSourcePropagationTree). Retourne la durée totale de la passe en ms
737
+ * (comète éteinte et dernier anneau fondu), pour cadencer la boucle.
738
+ */
739
+ private _animateResolvedPropagation;
740
+ /**
741
+ * Comète parcourant `waypoints` (polyligne) d'un seul mouvement : vitesse
742
+ * de croisière réglable, départ et arrivée adoucis (easeSinInOut sur tout
743
+ * le trajet), fondu d'apparition/extinction, halo flouté, et queue fondue
744
+ * reconstituée à partir des positions récentes (elle épouse donc les
745
+ * virages aux étapes au lieu de se couper à chaque segment). Tout est
746
+ * déterministe en fonction du temps écoulé : la queue se calcule sans
747
+ * historique, en rééchantillonnant la position à t − Δ.
748
+ * `onWaypointArrival(wp)` à chaque étape atteinte (sauf la source),
749
+ * `onSegmentStart(i)` à l'entrée dans chaque segment. `isCancelled` lu à
750
+ * chaque frame : vrai → la comète disparaît net. Retourne la durée du
751
+ * trajet (ms), fondu de sortie non compris.
752
+ */
753
+ private _animateComet;
718
754
  private getCurrentComponent;
719
755
  private getCurrentComponentOrUndefined;
720
756
  private getCurrentComponentTitle;
@@ -769,6 +805,9 @@ export declare class YinYangController implements IYinYangController {
769
805
  private clearProjectedActionState;
770
806
  private applyEmptyGhostProjectionState;
771
807
  private updateChildActionMenu;
808
+ /** Sélectionne (ouvre) le satellite `childKey` de la facette courante,
809
+ * comme un clic dessus — sans toggle-close s'il est déjà actif. */
810
+ private selectSatelliteByKey;
772
811
  /**
773
812
  * Appelé par YinYangDrawer quand le petit cercle "œil" du lobe dominant
774
813
  * (yin ou yang, selon Composante.orientation) est cliqué — jamais l'autre
@@ -969,7 +1008,130 @@ export declare class YinYangController implements IYinYangController {
969
1008
  * rejoignant leur position de base dans une durée fixe `durationMs`
970
1009
  * (troisième mode d'arrêt — voir AnimationsMngr.stopRotationFast). */
971
1010
  stopRotationAllFast(durationMs: number): void;
1011
+ /**
1012
+ * Satellites atteints par la propagation de l'action en cours (source
1013
+ * racine comprise), résolus depuis les liens du champ de force via leur
1014
+ * index orbital — la même correspondance index → satellite qu'utilise
1015
+ * ForceFieldRenderer pour tracer les segments. Ils gardent leur apparence
1016
+ * normale quand une mise en valeur éteint le reste de la constellation.
1017
+ */
1018
+ private getPropagationTargetChildKeys;
1019
+ /**
1020
+ * Rejoue tout ce qui dépend de l'onglet actif (RECHERCHE vs STACK) : la
1021
+ * mise en valeur des ODD et du satellite sélectionné, réservée à
1022
+ * RECHERCHE, l'anneau orange du satellite sélectionné, réservé à STACK, et
1023
+ * les boutons d'action, qui ne se déploient plus en STACK. Appelé par
1024
+ * switchToTab — le mode se lit dans le DOM (isStackModeActive), il n'y a
1025
+ * donc rien à observer, seulement à resynchroniser au basculement.
1026
+ */
1027
+ refreshModeDependentVisuals(): void;
972
1028
  highlightOddInChildren(oddId: string | null): void;
1029
+ /**
1030
+ * Aperçu de mise en valeur d'un ODD, quelle qu'en soit l'origine : arc du
1031
+ * diagramme, chip de la légende du panneau RECHERCHE ou du panneau STACK.
1032
+ * Une sortie de survol (`null`) retombe sur l'ODD actif plutôt que
1033
+ * d'effacer la sélection en cours — même priorité actif/survol que celle
1034
+ * que YinYangDrawer applique déjà à l'arc lui-même.
1035
+ */
1036
+ previewOddHighlight(oddId: string | null): void;
1037
+ /** Focus persistant — null pour l'effacer. */
1038
+ private setFocusedStackItem;
1039
+ /**
1040
+ * Clic sur le libellé d'un item déjà en focus, alors que son bloc est
1041
+ * visible dans le panneau (satellite porteur sélectionné) : désactive le
1042
+ * focus et retourne true — l'appelant n'a alors ni navigation ni scroll à
1043
+ * faire. Si le bloc n'est pas visible (l'utilisateur a navigué ailleurs
1044
+ * entre-temps), le clic redevient une sélection normale : false.
1045
+ */
1046
+ toggleStackItemFocusFromStack(stackItemId: string): boolean;
1047
+ /** Aperçu par survol — null à la sortie, qui retombe sur le focus. */
1048
+ private setStackItemEmphasisPreview;
1049
+ /**
1050
+ * Point d'accroche unique de la mise en valeur du satellite source de
1051
+ * l'action portant l'emphase (aperçu ?? focus). Réutilise pour l'instant
1052
+ * le glow de highlightOddByStackItemId (ou le segment de bordure si la
1053
+ * source est dans une autre facette) — l'anneau animé viendra ici.
1054
+ * `clearIfNone` : sans emphase, efface la mise en valeur (sorties de
1055
+ * survol, défocus) ; false pour les simples re-applications (satellites
1056
+ * recréés, fin d'un survol de satellite) afin de ne pas écraser d'autres
1057
+ * highlights en cours.
1058
+ */
1059
+ private applyFocusedSourceHighlight;
1060
+ /** Memo à une entrée de resolveEmphasisSourceChildIndex (lu chaque frame). */
1061
+ private _emphasisSourceMemo;
1062
+ /**
1063
+ * Index orbital du satellite source de l'action portant l'emphase, ou null
1064
+ * si rien ne doit être atténué : hors mode STACK (un aperçu RECHERCHE ne
1065
+ * doit pas hériter d'un focus STACK résiduel), item absent, Événement Clé
1066
+ * (pas de champ de force), ou source dans une autre facette (tout reste
1067
+ * plein, la bordure source est surlignée par highlightOddByStackItemId).
1068
+ * Résolution par oddId + cuttingIndex (buildForceFieldSourceIndicesFromStackItems),
1069
+ * robuste au rebuild de childOddMap ; la clé du memo suit la convention de
1070
+ * ForceFieldLifecycleController._buildPropCacheKey.
1071
+ */
1072
+ private resolveEmphasisSourceChildIndex;
1073
+ /** Identité stable (sourceOriginKey) du satellite source d'un item de la
1074
+ * pile — null pour un Événement Clé ou une facette introuvable. */
1075
+ private _stackItemSourceOriginKey;
1076
+ private _emphasisKeyMemo;
1077
+ /** Clé de la source de l'action portant l'emphase (aperçu ?? focus), quelle
1078
+ * que soit sa facette ; null hors STACK ou sans emphase. Lue à chaque frame. */
1079
+ private resolveEmphasisSourceOriginKey;
1080
+ /** Memo à une entrée de resolveFocusedActionSource (lu à chaque update du menu). */
1081
+ private _focusSourceMemo;
1082
+ /**
1083
+ * Action en focus (focus persistant seulement, pas l'aperçu par survol —
1084
+ * une bulle qui apparaît/disparaît au survol serait du bruit), résolue en
1085
+ * (actionId, clé et index orbital du satellite source) dans la facette
1086
+ * courante. null hors mode STACK, sans focus, pour un Événement Clé, ou si
1087
+ * la source vit dans une autre facette.
1088
+ */
1089
+ private resolveFocusedActionSource;
1090
+ /**
1091
+ * Point de départ « bulle » du champ de force de l'action en focus : la
1092
+ * bulle de son action dans le menu radial si le satellite source est celui
1093
+ * sélectionné, sinon la bulle détachée posée sur la source. null si
1094
+ * aucune bulle n'est visible (le champ démarre alors au satellite).
1095
+ */
1096
+ private resolveFocusBubbleStub;
1097
+ private _focusLoopTimer;
1098
+ /** Jeton d'annulation : incrémenté à chaque (re)départ/arrêt, les passes en
1099
+ * vol comparent leur jeton à chaque frame et s'effacent si dépassées. */
1100
+ private _focusLoopRun;
1101
+ /** Pause entre deux passes (propagationCometConfig.loopGapMs, panneau
1102
+ * Debug). Un timer en attente est reposé avec la nouvelle valeur pour que
1103
+ * le réglage se sente tout de suite. */
1104
+ setFocusPropagationLoopGapMs(ms: number): void;
1105
+ private restartFocusPropagationLoop;
1106
+ private stopFocusPropagationLoop;
1107
+ private _runFocusPropagationLoopPass;
1108
+ /** Liens sortants par index orbital de départ (segments chaînés inclus),
1109
+ * restreints à ceux qui mènent quelque part (satellite ou autre facette). */
1110
+ private _buildOutgoingLinkMap;
1111
+ /** Tous les chemins `prefix` + descente depuis `node` jusqu'aux feuilles
1112
+ * (satellite sans lien sortant, ou arc d'une autre facette), sans cycle. */
1113
+ private _walkPropagationPaths;
1114
+ /** Anime simultanément chaque chemin (waypoints `head` puis la cible de
1115
+ * chaque lien) ; retourne la durée de la passe la plus longue. */
1116
+ private _animatePropagationPaths;
1117
+ /**
1118
+ * Joue une passe de propagation sur l'arbre complet des champs de force
1119
+ * d'une source de la facette courante : chaque chemin racine → feuille est
1120
+ * animé simultanément, comme le clic sur l'en-tête d'un bloc source.
1121
+ * L'arbre est lu dans state.forceField.links via le tag sourceChildIndex
1122
+ * (segments chaînés inclus : startIndex = cible précédente). La comète part
1123
+ * de la bulle d'action quand elle est visible. Retourne la durée de la
1124
+ * passe (0 si rien à animer).
1125
+ */
1126
+ private flashSourcePropagationTree;
1127
+ /**
1128
+ * Même passe pour une action dont la source vit dans une autre facette :
1129
+ * ses liens entrants ici (tagués sourceOriginKey, recopié par
1130
+ * restoreIncomingForceFieldContext) partent de l'arc de la facette source ;
1131
+ * chaque entrée est prolongée par sa cascade intra-facette. Retourne la
1132
+ * durée de la passe (0 si cette action n'a aucun lien entrant ici).
1133
+ */
1134
+ private flashIncomingPropagationTree;
973
1135
  highlightOddByStackItemId(stackItemId: string | null): void;
974
1136
  /**
975
1137
  * Retourne les actions applicables au cutting associé à `childKey`,
@@ -14,6 +14,9 @@ export declare class ActionStackController {
14
14
  private fallingStackItemIds;
15
15
  private keyboardListenersBound;
16
16
  private _linkedHoverId;
17
+ /** Item en focus (action en focus côté panneau) : style persistant,
18
+ * synchronisé par YinYangController.setFocusedStackItem. */
19
+ private _focusedItemId;
17
20
  private _onBlockHighlightCb?;
18
21
  private _onSatelliteHighlightCb?;
19
22
  private activeComboHighlight;
@@ -50,6 +53,18 @@ export declare class ActionStackController {
50
53
  setSatelliteHighlightCallback(cb: (id: string | null) => void): void;
51
54
  getItemById(id: string): ActionStackItem | undefined;
52
55
  highlightByStackItemId(id: string | null): void;
56
+ /** Focus persistant (miroir du bloc en focus dans le panneau) — survit aux
57
+ * re-rendus, updateVisual relisant _focusedItemId. */
58
+ setFocusedItemId(id: string | null): void;
59
+ /**
60
+ * Style du cadre d'un item selon son emphase, par priorité décroissante :
61
+ * survol lié (aperçu) > focus (persistant, teinte pleine + halo) > coché
62
+ * (halo discret) > repos. Source unique pour updateVisual,
63
+ * highlightByStackItemId et setFocusedItemId, afin qu'un re-rendu ne
64
+ * fasse jamais retomber le focus.
65
+ */
66
+ private _hitboxEmphasisStyle;
67
+ private _applyHitboxEmphasisStyles;
53
68
  /**
54
69
  * Affiche temporairement le highlight combo (cercles de fenêtre) pendant ~1.8 s.
55
70
  * Sans effet si un highlight permanent est déjà actif.
@@ -3,6 +3,17 @@ import { IYinYangView } from "../../../interfaces/IYinYangView";
3
3
  import { type ActionDefinition } from "../../../data/yasuni_actions";
4
4
  import { ChildrenOrbitEntry } from "../utils/childrenOrbit";
5
5
  import { type Point } from "./radialItemMenu";
6
+ type DetachedBubbleContext = {
7
+ orbitData: ChildrenOrbitEntry[];
8
+ /** Satellite source porteur de la bulle — null = aucune bulle détachée. */
9
+ sourceChildKey: string | null;
10
+ action: ActionDefinition | null;
11
+ childActionMenuItemDistance: number;
12
+ childActionMenuItemScale: number;
13
+ childActionIconFontScale: number;
14
+ /** Clic sur la bulle : typiquement, sélectionner le satellite source. */
15
+ onClick: (sourceChildKey: string, actionId: string) => void;
16
+ };
6
17
  type ChildActionMenuContext = {
7
18
  orbitData: ChildrenOrbitEntry[];
8
19
  activeActionMenuChildKey: string | null;
@@ -44,6 +55,18 @@ export declare class ChildActionMenuController {
44
55
  * le contexte courant) et rejoué au survol : rallumer une impulsion ne
45
56
  * doit pas relancer la chorégraphie d'ouverture. */
46
57
  private linkStyleGetter;
58
+ /** Positions du dernier update() — exposées au champ de force (le segment
59
+ * « bulle → source » démarre au centre de la bulle de l'action en focus). */
60
+ private lastPositions;
61
+ private detachedGroup;
62
+ private detachedLineLayer;
63
+ private lastDetachedCenter;
64
+ private lastDetachedPosition;
65
+ /** Centre (coordonnées du globalGroup) de la bulle `actionId` du menu radial
66
+ * ouvert, ou null si elle n'y est pas. */
67
+ getItemCenter(actionId: string): Point | null;
68
+ /** Centre de la bulle détachée si elle porte `actionId`, sinon null. */
69
+ getDetachedCenter(actionId: string): Point | null;
47
70
  constructor(view: IYinYangView, getChildRenderedCenter: (childController: IYinYangController) => Point | null, refreshActionStackVisual: () => void,
48
71
  /** Aperçu — 1er clic sur une action : affiche sa carte, ne joue rien. */
49
72
  activateActionFromChild: (childController: IYinYangController, actionId: string, label?: string) => void,
@@ -69,6 +92,9 @@ export declare class ChildActionMenuController {
69
92
  private setHoveredAction;
70
93
  retractAndDispose(): void;
71
94
  clearImmediately(): void;
95
+ /** Retire la bulle détachée et son faisceau, tout de suite (0) ou après un
96
+ * délai (fin d'une transition de facette). */
97
+ private disposeDetached;
72
98
  /**
73
99
  * Starts the fold-out animation on the visible action buttons, then removes
74
100
  * the group once every item has finished animating. Nulls the group
@@ -76,6 +102,20 @@ export declare class ChildActionMenuController {
76
102
  * that follows is a no-op and won't cancel the in-flight transition.
77
103
  */
78
104
  animateClose(): void;
105
+ /** Replie la bulle détachée (chorégraphie de fermeture), groupes conservés. */
106
+ private closeDetached;
107
+ /**
108
+ * Bulle détachée de l'action en focus : quand un bloc de propagation est
109
+ * en focus, le satellite sélectionné n'est pas la source et son menu ne
110
+ * montre donc pas la bulle de l'action. On pose alors, sur le satellite
111
+ * source, la seule bulle de cette action (pas ses sœurs), en style
112
+ * « regardée », avec son faisceau — le champ de force peut ainsi toujours
113
+ * démarrer à la bulle. Un clic sélectionne le satellite source (le menu
114
+ * complet prend alors le relais et la bulle détachée se replie).
115
+ * Cluster indépendant du menu radial : update() n'en sait rien.
116
+ */
117
+ updateDetached(context: DetachedBubbleContext): void;
118
+ private ensureDetachedGroups;
79
119
  update(context: ChildActionMenuContext): void;
80
120
  private ensureChildActionMenuGroup;
81
121
  private ensureLineLayer;
@@ -1,6 +1,7 @@
1
1
  import { IYinYangController } from "../../../interfaces/IYinYangController";
2
2
  import { IYinYangView } from "../../../interfaces/IYinYangView";
3
3
  import { YinYangInternalModel } from "../../../interfaces/IYinYangInternalModel";
4
+ import { ChildHighlightState } from "../odd/childHighlightStyles";
4
5
  type ChildOddMeta = {
5
6
  oddId: string;
6
7
  score: number;
@@ -19,6 +20,11 @@ type ChildInteractionControllerDeps = {
19
20
  onUserCuttingClicked?: (childController: IYinYangController) => void;
20
21
  /** Appelé quand la souris entre/sort d'un satellite (null = sortie). */
21
22
  onChildHover?: (oddId: string | null, cuttingIndex?: number) => void;
23
+ /** ODD actuellement mis en valeur (OddViewManager) — le style "satellite
24
+ * actif" et le survol doivent le respecter au lieu de l'écraser. */
25
+ getOddHighlight: () => ChildHighlightState;
26
+ /** Satellites atteints par la propagation de l'action en cours. */
27
+ getPropagationTargetKeys: () => Set<string>;
22
28
  };
23
29
  export declare class ChildInteractionController {
24
30
  private deps;
@@ -33,6 +39,9 @@ export declare class ChildInteractionController {
33
39
  clearActiveChild(childControllers: IYinYangController[]): void;
34
40
  animateChildren(childControllers: IYinYangController[], show: boolean, onBeforeShow?: () => void, onBeforeHide?: () => void): Promise<void>;
35
41
  private applyChildActiveStyle;
42
+ /** Filtre courant d'un satellite : la mise en valeur d'un ODD prime sur le
43
+ * style "actif", qui prime lui-même sur l'état neutre. */
44
+ private resolveFilterFor;
36
45
  private getChildOffset;
37
46
  }
38
47
  export {};
@@ -123,12 +123,25 @@ export declare class ForceFieldDetailsPanel {
123
123
  private _onDiagramElementHover?;
124
124
  /** Called when hovering a cumulative block — notifies the stack to highlight the matching item. */
125
125
  private _onStackItemHighlightCb?;
126
+ /** Clic sur l'en-tête d'un bloc → action en focus (YinYangController). */
127
+ private _onStackItemFocusCb?;
128
+ /** Miroir de state.forceField.focusedStackItemId — sert uniquement à
129
+ * ré-appliquer .ff-block--focused après chaque innerHTML. */
130
+ private _focusedStackItemId;
131
+ /** Bloc actuellement survolé — pour détecter un survol orphelin quand le
132
+ * bloc est détruit par un re-rendu sans que mouseleave n'ait été émis. */
133
+ private _hoveredBlockStackItemId;
126
134
  /** Id d'un bloc "Effets cumulés" à centrer (scroll animé) dès qu'il apparaît
127
135
  * dans le panneau — voir requestScrollToStackItemBlock. Le rendu déclencheur
128
136
  * (renderComposanteInfo / renderCumulativeActionDetails) peut survenir de
129
137
  * façon asynchrone après la demande (ouverture animée du satellite). */
130
138
  private _pendingBlockScrollStackItemId;
131
139
  private _pendingBlockScrollTimeout;
140
+ /** Dernier bloc ciblé par un clic-libellé de la pile — encadré persistant
141
+ * (.ff-block--selected) pour savoir à tout instant quel bloc est sélectionné,
142
+ * là où le focus-flash s'éteint en fin d'animation. Remplacé au prochain
143
+ * clic-libellé ; ré-appliqué après chaque innerHTML comme le focus. */
144
+ private _selectedStackItemId;
132
145
  /** Notifie le header (chip STACK) du badge + nom de l'élément affiché par les
133
146
  * cartes d'info minimales (Sphère, ODD) — ces cartes ne répètent plus ce
134
147
  * titre dans leur corps. Facette et Satellite restent gérés par leurs
@@ -166,11 +179,13 @@ export declare class ForceFieldDetailsPanel {
166
179
  * celle d'une tout autre carte, ou une position absente du snapshot
167
180
  * précédent, ce qui la replierait par défaut au lieu de suivre l'auto-fit. */
168
181
  private _hasStableCardKey;
169
- /** Un clic "replier" (par opposition à un clic qui ouvre/surligne) — sur le
170
- * badge delta habituel, ou sur le header pour une carte qui n'en a plus :
171
- * la carte source Événement Clé n'a aucun delta à elle (celui de l'effet
172
- * vit désormais uniquement sur la carte "Actions amplifiées" sœur), donc
173
- * aucun badge à cliquer — le header sert de poignée de repli à sa place. */
182
+ /** Un clic "poignée" (ouvrir/replier la carte, rien d'autre — pas de flash
183
+ * de propagation, pas de surlignage combo) : le badge delta en haut à
184
+ * droite, le chip de poids, et l'en-tête de la carte — même rôle pour tous
185
+ * les types de cartes. L'en-tête portait autrefois le flash de propagation
186
+ * des cartes source ; ce flash tourne désormais en boucle sur l'action en
187
+ * focus (YinYangController.flashSourcePropagationTree) et reste accessible
188
+ * ligne par ligne dans le corps de la carte. */
174
189
  private _isCardCollapseTrigger;
175
190
  private _attachComboHighlightHandlers;
176
191
  /** Contexte combo (backward) ou amplificateur (forward) déduit de
@@ -289,10 +304,16 @@ export declare class ForceFieldDetailsPanel {
289
304
  * bloc combo/amplificateur, pour que ce bloc-ci ne fasse pas exception.
290
305
  */
291
306
  private _buildDelayedTargetsCardHtml;
292
- /** Badge et note de provenance d'un bloc action de la vue cumulative :
293
- * propagée (depuis son satellite source), adverse (son acteur d'origine)
294
- * ou directe. */
307
+ /** Badge et note de provenance d'un bloc action de la vue cumulative.
308
+ * Seule une action propagée porte une note dans l'en-tête (le satellite
309
+ * dont elle provient) : pour une action directe — adverse comprise — on
310
+ * est déjà sur le satellite porteur, l'information serait redondante.
311
+ * L'acteur d'origine d'une action adverse est rendu à part (`origin`),
312
+ * dans le corps du bloc, pour ne pas concurrencer le nom. */
295
313
  private _pickActionBlockBadge;
314
+ /** Ligne « Acteur : … » placée en tête du corps d'un bloc adverse (action
315
+ * ou Événement) — remplace l'ancienne note d'en-tête. */
316
+ private _buildBlockOriginHtml;
296
317
  /**
297
318
  * En-tête générique et repliable d'un .ff-cumulative-block — icône, badge
298
319
  * de type, nom, note de provenance optionnelle ("rationale"), et une
@@ -461,6 +482,20 @@ export declare class ForceFieldDetailsPanel {
461
482
  }): void;
462
483
  private _attachBlockLinkedHoverHandlers;
463
484
  setStackItemHighlightCallback(cb: (id: string | null) => void): void;
485
+ /** Vrai si le panneau affiche actuellement le bloc « Effets cumulés » de cet
486
+ * item de pile (le satellite/la facette porteuse est sélectionné). */
487
+ hasBlockForStackItem(stackItemId: string): boolean;
488
+ setStackItemFocusCallback(cb: (id: string | null) => void): void;
489
+ /** Miroir du focus (source de vérité : YinYangController / state). */
490
+ setFocusedStackItemId(id: string | null): void;
491
+ /**
492
+ * Ré-applique .ff-block--focused sur le bloc en focus — à appeler après
493
+ * chaque rendu de blocs (innerHTML), comme _consumePendingBlockScroll.
494
+ * Profite du passage pour clore un survol orphelin : un bloc survolé puis
495
+ * détruit par le re-rendu n'émet jamais mouseleave, et son aperçu
496
+ * primerait indéfiniment sur le focus.
497
+ */
498
+ private _applyFocusedBlockClass;
464
499
  highlightBlockByStackItemId(id: string | null): void;
465
500
  /**
466
501
  * Demande de centrer (scroll animé) le bloc "Effets cumulés" de cet item de
@@ -475,6 +510,22 @@ export declare class ForceFieldDetailsPanel {
475
510
  */
476
511
  requestScrollToStackItemBlock(stackItemId: string): void;
477
512
  private _consumePendingBlockScroll;
513
+ /**
514
+ * Déplie la carte source du premier rail d'un bloc — même désignation que
515
+ * _applyAutoFitToGrids (carte marquée source, sinon premier non-combo), qui
516
+ * ne la replie jamais quelle que soit la phase : l'ouvrir ici ne sera donc
517
+ * pas défait par l'auto-fit déclenché ensuite (rAF / ResizeObserver). Si le
518
+ * rail déborde, il scrolle — comme après un clic utilisateur sur la carte.
519
+ */
520
+ private _expandSourceCard;
521
+ /**
522
+ * Mise en valeur appuyée (halo pulsé aux couleurs de l'item, cf.
523
+ * .ff-block--focus-flash) du bloc que le clic-libellé vient de centrer, pour
524
+ * qu'il se distingue nettement de ses voisins une fois le scroll terminé.
525
+ * La classe est retirée en fin d'animation ; un nouveau focus sur le même
526
+ * bloc relance l'animation depuis le début (retrait + reflow + ré-ajout).
527
+ */
528
+ private _flashFocusedBlock;
478
529
  private getTargetName;
479
530
  private getGhostDetailsContainer;
480
531
  private getForceFieldDetailsContainer;
@@ -35,6 +35,23 @@ type ForceFieldLifecycleControllerDeps = {
35
35
  x: number;
36
36
  y: number;
37
37
  } | null;
38
+ /** Index orbital du satellite source de l'action en focus/aperçu (voir
39
+ * YinYangController.resolveEmphasisSourceChildIndex), null sans emphase.
40
+ * Lu à chaque frame : le renderer lisse lui-même la transition. */
41
+ getEmphasisSourceChildIndex: () => number | null;
42
+ /** Identité (sourceOriginKey) du satellite source de l'action en
43
+ * focus/aperçu, même quand il vit dans une autre facette — c'est elle qui
44
+ * atténue les liens entrants des autres actions. null sans emphase. */
45
+ getEmphasisSourceOriginKey: () => string | null;
46
+ /** Bulle d'action de l'action en focus (menu radial du satellite source,
47
+ * ou bulle détachée) — le champ de cette source démarre à la bulle. */
48
+ getFocusBubbleStub: () => {
49
+ sourceChildIndex: number;
50
+ center: {
51
+ x: number;
52
+ y: number;
53
+ };
54
+ } | null;
38
55
  };
39
56
  export declare class ForceFieldLifecycleController {
40
57
  private deps;
@@ -22,16 +22,41 @@ export declare class ForceFieldRenderer {
22
22
  * Called by YinYangController while the propagation arc travels the matching segment.
23
23
  */
24
24
  pulseLink(linkIndex: number, durationMs: number): void;
25
+ /** Horodatage du dernier updateVisual — updateVisual ne reçoit pas de Δt,
26
+ * on le dérive ici pour le lissage de l'emphase. */
27
+ private _lastVisualNow;
28
+ /** Identité du segment « bulle → source » courant et origine de sa
29
+ * révélation (voir updateVisual / bubbleStub). */
30
+ private _stubKey;
31
+ private _stubRevealAt;
25
32
  hasActiveGroup(): boolean;
26
33
  ensureElements(params: {
27
34
  globalGroupNode: SVGGElement;
28
35
  requiredCount: number;
29
36
  }): void;
37
+ /** Ajuste le nombre de layers au nombre de segments à dessiner (liens +
38
+ * éventuel segment « bulle → source », voir updateVisual). */
39
+ private _ensureLayerCount;
30
40
  updateVisual(params: {
31
41
  forceFieldOrbitData: ChildrenOrbitEntry[] | null;
32
42
  forceFieldLinks: ForceFieldSegment[];
33
43
  forceFieldRevealStartedAt?: number;
34
44
  forceFieldPhase: number;
45
+ /** Index orbital du satellite source de l'action en focus (ou en aperçu
46
+ * par survol) ; null = pas d'emphase, tous les liens restent pleins. */
47
+ emphasisSourceChildIndex: number | null;
48
+ /** Identité stable (sourceOriginKey) de la source de l'action en
49
+ * focus/aperçu — prime sur l'index quand le lien la porte, et seule
50
+ * façon d'atténuer les liens entrants (source dans une autre facette). */
51
+ emphasisSourceOriginKey: string | null;
52
+ /** Bulle d'action de l'action en focus : quand elle est connue, le champ
53
+ * de cette source « démarre » à la bulle — un segment supplémentaire
54
+ * bulle → satellite source est dessiné avec la grammaire des liens
55
+ * (port d'émission à la bulle, éclat au satellite). null = aucun. */
56
+ bubbleStub: {
57
+ sourceChildIndex: number;
58
+ center: Point;
59
+ } | null;
35
60
  getChildRenderedCenter: (childController: IYinYangController) => Point | null;
36
61
  getComponentBorderTargetPoint: (componentId: string) => Point | null;
37
62
  }): void;
@@ -31,8 +31,24 @@ export type ForceFieldLink = {
31
31
  potentialGain: number;
32
32
  /** Delay before revealing this link, used for chained intra-ODD propagation. */
33
33
  revealDelayMs?: number;
34
+ /** Index orbital du satellite source racine ayant produit ce lien (posé par
35
+ * ForceFieldComputationService.build, un appel par source). Pour un segment
36
+ * chaîné, startIndex est la cible précédente : seul ce champ dit à quelle
37
+ * action le lien appartient — c'est la clé de l'emphase par action en focus
38
+ * (ForceFieldRenderer). Absent pour les liens injectés en incomingMode
39
+ * (buildFromSource) → jamais atténués. */
40
+ sourceChildIndex?: number;
41
+ /** Identité stable du satellite source racine de l'action (voir
42
+ * buildSourceOriginKey). Contrairement à sourceChildIndex (index orbital,
43
+ * local à une facette), elle survit au changement de facette : recopiée
44
+ * sur les liens entrants (restoreIncomingForceFieldContext), elle permet à
45
+ * l'emphase et à la comète de l'action en focus de suivre sa propagation
46
+ * inter-facettes depuis l'arc de la facette source. */
47
+ sourceOriginKey?: string;
34
48
  detail: ForceFieldLinkDetail;
35
49
  };
50
+ /** Clé d'identité d'un satellite source racine : `composante|odd|cutting`. */
51
+ export declare function buildSourceOriginKey(componentId: string, oddId: string, cuttingId: number | undefined): string;
36
52
  /**
37
53
  * Représentation pure-rendu d'un segment de champ de force.
38
54
  * Identique à ForceFieldLink mais sans le champ `detail` (domaine métier).
@@ -0,0 +1,78 @@
1
+ /** Un satellite membre de l'ODD mis en valeur, dans le repère du globalGroup
2
+ * de la facette parente (mêmes coordonnées que
3
+ * WeightPreviewRingRenderer : centre orbital courant + rayon du satellite). */
4
+ export type OddHighlightTarget = {
5
+ id: string;
6
+ x: number;
7
+ y: number;
8
+ radius: number;
9
+ };
10
+ /**
11
+ * Effets "sélection d'ODD" posés sur les satellites membres, dans le
12
+ * globalGroup de la facette parente — donc dans le même repère que les
13
+ * satellites eux-mêmes, qui continuent d'orbiter dessous (les positions sont
14
+ * relues à chaque frame via `getTargets`, jamais figées à la construction).
15
+ *
16
+ * Trois couches, toutes peintes dans la couleur de l'ODD :
17
+ * - un anneau de visée pointillé dont les tirets dérivent lentement,
18
+ * doublé de quatre crochets d'angle qui tournent en sens inverse ;
19
+ * - un "ping" sonar qui s'échappe de chaque satellite en boucle, décalé
20
+ * d'un satellite à l'autre ;
21
+ * - une onde de scan unique partant de la couronne de la facette, tirée une
22
+ * seule fois quand un nouvel ODD passe en sélection (voir `oddKey`).
23
+ *
24
+ * Réservé à l'intensité "full" (clic) : le survol n'allume que le halo
25
+ * coloré des satellites, sans rien de tout ceci — voir OddViewManager.
26
+ *
27
+ * Aucun filtre CSS ici, à dessein : un `drop-shadow` sur un élément dont
28
+ * un attribut change à chaque frame (rotation des crochets, dérive des
29
+ * tirets, rayon du ping) force une passe de flou par frame et par élément —
30
+ * mesuré à 65 ms/frame avec cinq satellites en orbite. La lueur est rendue
31
+ * par un second trait, plus large et translucide, dessiné sous le trait
32
+ * net : même lecture, coût nul. De même, les styles constants ne sont posés
33
+ * qu'à la création (enter), seule la géométrie est écrite par frame.
34
+ */
35
+ export declare class OddHighlightEffects {
36
+ private layerNode;
37
+ private getTargets;
38
+ private color;
39
+ private frameId;
40
+ private scan;
41
+ /** ODD actuellement mis en valeur — sert à ne retirer l'onde de scan
42
+ * qu'une seule fois par sélection, et non à chaque re-render. */
43
+ private currentOddKey;
44
+ render(params: {
45
+ globalGroupNode: SVGGElement | null;
46
+ getTargets: () => OddHighlightTarget[];
47
+ color: string;
48
+ oddKey: string;
49
+ parentCenterX: number;
50
+ /** Rayon de départ de l'onde de scan : le bord externe de la couronne,
51
+ * et non le centre — la moitié intérieure de la course serait cachée
52
+ * sous le disque de la facette, et l'onde doit se lire comme partant de
53
+ * l'arc de l'ODD vers ses satellites. */
54
+ parentCenterY: number;
55
+ scanStartRadius: number;
56
+ scanRadius: number;
57
+ }): void;
58
+ /**
59
+ * Retire toutes les couches. `keepOddMemory` conserve l'ODD mémorisé pour
60
+ * la déduplication de l'onde de scan : on l'utilise quand la mise en
61
+ * valeur n'est que *rétrogradée* en "soft" (survol d'un arc voisin alors
62
+ * qu'un ODD est sélectionné) — sinon le simple fait de repasser la souris
63
+ * rejouerait l'onde à chaque retour sur la sélection.
64
+ */
65
+ clear(keepOddMemory?: boolean): void;
66
+ private ensureLayer;
67
+ private ensureLoop;
68
+ private stopLoop;
69
+ /** Jointure d'un couple lueur + trait net pour une forme donnée : la lueur
70
+ * est le même élément, dessiné d'abord, plus large et translucide. */
71
+ private joinPair;
72
+ private drawReticles;
73
+ /** Quatre arcs courts aux diagonales — le "target lock" qui encadre
74
+ * l'anneau de visée sans le fermer. */
75
+ private buildBracketPath;
76
+ private drawPings;
77
+ private drawScan;
78
+ }