@sentropic/design-system-svelte 0.12.0 → 0.13.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.
@@ -237,6 +237,18 @@
237
237
  * by `edgeCurve * dist * factor`. Defaults to a light 0.15.
238
238
  */
239
239
  edgeCurve?: number;
240
+ /**
241
+ * Repulsion multiplier controlling how spread out the layout is.
242
+ * >1 = graphe plus aéré, <1 = plus compact ; multiplie la force de
243
+ * répulsion sans toucher au fit-to-content. Defaults to 1. Clamped to
244
+ * [0.1, 10] internally to avoid layout explosions/collapses.
245
+ */
246
+ repulsion?: number;
247
+ /**
248
+ * Called when the user hovers (or keyboard-focuses) a node, and again with
249
+ * null when the hover/focus ends. Intended for syncing an external panel.
250
+ */
251
+ onNodeHover?: (node: ForceGraphNode | null) => void;
240
252
  class?: string;
241
253
  };
242
254
 
@@ -256,6 +268,8 @@
256
268
  onEdgeHover,
257
269
  legend,
258
270
  edgeCurve = 0.15,
271
+ repulsion = 1,
272
+ onNodeHover,
259
273
  class: className
260
274
  }: ForceGraphProps = $props();
261
275
 
@@ -311,7 +325,8 @@
311
325
  es: ForceGraphEdge[],
312
326
  w: number,
313
327
  h: number,
314
- ticks: number
328
+ ticks: number,
329
+ repulsionFactor: number
315
330
  ): Map<string, { x: number; y: number }> {
316
331
  const cx = w / 2;
317
332
  const cy = h / 2;
@@ -339,7 +354,11 @@
339
354
 
340
355
  const area = w * h;
341
356
  const k = Math.sqrt(area / Math.max(ns.length, 1)); // ideal node distance
342
- const repulsion = k * k * 0.9;
357
+ // Clamp the caller-supplied factor so extreme values can't explode or
358
+ // collapse the layout. >1 spreads nodes out, <1 packs them tighter; the
359
+ // fit-to-content viewBox is recomputed afterwards so spacing just fills space.
360
+ const clampedRepulsion = Math.min(Math.max(repulsionFactor, 0.1), 10);
361
+ const repulsion = k * k * 0.9 * clampedRepulsion;
343
362
  const restLength = k * 0.8;
344
363
  const springK = 0.04;
345
364
  const gravity = 0.012;
@@ -426,7 +445,7 @@
426
445
  // honouring the motion preference (no rAF loop, no jitter).
427
446
  const layout = $derived.by(() => {
428
447
  const ticks = Math.max(1, Math.round(iterations));
429
- return runSimulation(nodes, edges, width, height, ticks);
448
+ return runSimulation(nodes, edges, width, height, ticks, repulsion);
430
449
  });
431
450
 
432
451
  const positionedNodes = $derived.by(() =>
@@ -584,6 +603,38 @@
584
603
  return !(srcActive || tgtActive);
585
604
  }
586
605
 
606
+ // ---------------------------------------------------------------------------
607
+ // Hover-connexe (demand 7): hovering a node fades the rest of the graph the
608
+ // same way selection does — the hovered node and its direct neighbours stay
609
+ // full, every other node dims, and only edges incident to the hovered node
610
+ // keep their opacity. Composes with selection (predicates OR'd together).
611
+ // ---------------------------------------------------------------------------
612
+ const hoveredNodeId = $derived(
613
+ hoveredNodeIndex != null ? (positionedNodes[hoveredNodeIndex]?.node.id ?? null) : null
614
+ );
615
+ const hoverActiveSet = $derived.by(() => {
616
+ const set = new Set<string>();
617
+ if (hoveredNodeId == null) return set;
618
+ set.add(hoveredNodeId);
619
+ const nb = adjacency.get(hoveredNodeId);
620
+ if (nb) for (const n of nb) set.add(n);
621
+ return set;
622
+ });
623
+
624
+ // A node is dimmed by hover when a node is hovered and this one is neither
625
+ // the hovered node nor one of its direct neighbours.
626
+ function isHoverDimmedNode(id: string): boolean {
627
+ if (hoveredNodeId == null) return false;
628
+ return !hoverActiveSet.has(id);
629
+ }
630
+
631
+ // An edge is dimmed by hover when a node is hovered and the edge is not
632
+ // incident to it (keep only the hovered node's own edges full).
633
+ function isHoverDimmedEdge(e: ForceGraphEdge): boolean {
634
+ if (hoveredNodeId == null) return false;
635
+ return e.source !== hoveredNodeId && e.target !== hoveredNodeId;
636
+ }
637
+
587
638
  // Keyboard handler for a node circle: Space/Enter → onSelect, Enter → onOpenEntity.
588
639
  function handleNodeKeydown(id: string, e: KeyboardEvent) {
589
640
  if (e.key === "Enter" || e.key === " ") {
@@ -734,7 +785,7 @@
734
785
  class:st-forceGraph__edge--weak={e.edge.weak}
735
786
  class:st-forceGraph__edge--emphasis={e.edge.emphasis}
736
787
  class:st-forceGraph__edge--hovered={hoveredEdgeIndex === e.i}
737
- class:st-forceGraph__edge--dim={isEdgeSelectionDimmed(e.edge)}
788
+ class:st-forceGraph__edge--dim={isEdgeSelectionDimmed(e.edge) || isHoverDimmedEdge(e.edge)}
738
789
  d={e.path}
739
790
  fill="none"
740
791
  stroke-dasharray={e.dashArray}
@@ -747,7 +798,7 @@
747
798
  class:st-forceGraph__edge--weak={e.edge.weak}
748
799
  class:st-forceGraph__edge--emphasis={e.edge.emphasis}
749
800
  class:st-forceGraph__edge--hovered={hoveredEdgeIndex === e.i}
750
- class:st-forceGraph__edge--dim={isEdgeSelectionDimmed(e.edge)}
801
+ class:st-forceGraph__edge--dim={isEdgeSelectionDimmed(e.edge) || isHoverDimmedEdge(e.edge)}
751
802
  x1={e.x1}
752
803
  y1={e.y1}
753
804
  x2={e.x2}
@@ -764,7 +815,7 @@
764
815
  {#each positionedNodes as p (p.node.id)}
765
816
  <g
766
817
  class="st-forceGraph__node st-forceGraph__node--{p.tone}"
767
- class:st-forceGraph__node--dim={(hoveredNodeIndex !== null && hoveredNodeIndex !== p.i) || isSelectionDimmed(p.node.id)}
818
+ class:st-forceGraph__node--dim={isHoverDimmedNode(p.node.id) || isSelectionDimmed(p.node.id)}
768
819
  class:st-forceGraph__node--selected={selectedSet.has(p.node.id)}
769
820
  class:st-forceGraph__node--focus={focusId === p.node.id}
770
821
  transform="translate({p.x} {p.y})"
@@ -777,10 +828,10 @@
777
828
  role="button"
778
829
  aria-label="{p.title}{p.node.group !== undefined ? `: ${p.node.group}` : ''}"
779
830
  aria-pressed={selectedSet.has(p.node.id)}
780
- onmouseenter={() => (hoveredNodeIndex = p.i)}
781
- onmouseleave={() => (hoveredNodeIndex = null)}
782
- onfocus={() => (hoveredNodeIndex = p.i)}
783
- onblur={() => (hoveredNodeIndex = null)}
831
+ onmouseenter={() => { hoveredNodeIndex = p.i; onNodeHover?.(p.node); }}
832
+ onmouseleave={() => { hoveredNodeIndex = null; onNodeHover?.(null); }}
833
+ onfocus={() => { hoveredNodeIndex = p.i; onNodeHover?.(p.node); }}
834
+ onblur={() => { hoveredNodeIndex = null; onNodeHover?.(null); }}
784
835
  onclick={() => onSelect?.(p.node.id)}
785
836
  ondblclick={() => onOpenEntity?.(p.node.id)}
786
837
  onkeydown={(e) => handleNodeKeydown(p.node.id, e)}
@@ -793,10 +844,10 @@
793
844
  role="button"
794
845
  aria-label="{p.title}{p.node.group !== undefined ? `: ${p.node.group}` : ''}"
795
846
  aria-pressed={selectedSet.has(p.node.id)}
796
- onmouseenter={() => (hoveredNodeIndex = p.i)}
797
- onmouseleave={() => (hoveredNodeIndex = null)}
798
- onfocus={() => (hoveredNodeIndex = p.i)}
799
- onblur={() => (hoveredNodeIndex = null)}
847
+ onmouseenter={() => { hoveredNodeIndex = p.i; onNodeHover?.(p.node); }}
848
+ onmouseleave={() => { hoveredNodeIndex = null; onNodeHover?.(null); }}
849
+ onfocus={() => { hoveredNodeIndex = p.i; onNodeHover?.(p.node); }}
850
+ onblur={() => { hoveredNodeIndex = null; onNodeHover?.(null); }}
800
851
  onclick={() => onSelect?.(p.node.id)}
801
852
  ondblclick={() => onOpenEntity?.(p.node.id)}
802
853
  onkeydown={(e) => handleNodeKeydown(p.node.id, e)}
@@ -126,6 +126,18 @@ type ForceGraphProps = {
126
126
  * by `edgeCurve * dist * factor`. Defaults to a light 0.15.
127
127
  */
128
128
  edgeCurve?: number;
129
+ /**
130
+ * Repulsion multiplier controlling how spread out the layout is.
131
+ * >1 = graphe plus aéré, <1 = plus compact ; multiplie la force de
132
+ * répulsion sans toucher au fit-to-content. Defaults to 1. Clamped to
133
+ * [0.1, 10] internally to avoid layout explosions/collapses.
134
+ */
135
+ repulsion?: number;
136
+ /**
137
+ * Called when the user hovers (or keyboard-focuses) a node, and again with
138
+ * null when the hover/focus ends. Intended for syncing an external panel.
139
+ */
140
+ onNodeHover?: (node: ForceGraphNode | null) => void;
129
141
  class?: string;
130
142
  };
131
143
  declare const ForceGraph: import("svelte").Component<ForceGraphProps, {}, "">;
@@ -1 +1 @@
1
- {"version":3,"file":"ForceGraph.svelte.d.ts","sourceRoot":"","sources":["../src/lib/ForceGraph.svelte.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ForceGraph.svelte.d.ts","sourceRoot":"","sources":["../src/lib/ForceGraph.svelte.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentropic/design-system-svelte",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"