footprint-explainable-ui 0.25.2 → 0.25.4

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.
package/dist/index.cjs CHANGED
@@ -82,6 +82,9 @@ function tokensToCSSVars(tokens) {
82
82
  if (c.success) vars["--fp-color-success"] = c.success;
83
83
  if (c.error) vars["--fp-color-error"] = c.error;
84
84
  if (c.warning) vars["--fp-color-warning"] = c.warning;
85
+ if (c.nodeCursor) vars["--fp-node-cursor"] = c.nodeCursor;
86
+ if (c.nodeVisited) vars["--fp-node-visited"] = c.nodeVisited;
87
+ if (c.nodeMain) vars["--fp-node-main"] = c.nodeMain;
85
88
  if (c.bgPrimary) vars["--fp-bg-primary"] = c.bgPrimary;
86
89
  if (c.bgSecondary) vars["--fp-bg-secondary"] = c.bgSecondary;
87
90
  if (c.bgTertiary) vars["--fp-bg-tertiary"] = c.bgTertiary;
@@ -101,6 +104,9 @@ var rawDefaults = {
101
104
  success: "#22c55e",
102
105
  error: "#ef4444",
103
106
  warning: "#f59e0b",
107
+ nodeCursor: "#f59e0b",
108
+ nodeVisited: "#22c55e",
109
+ nodeMain: "#6366f1",
104
110
  bgPrimary: "#0f172a",
105
111
  bgSecondary: "#1e293b",
106
112
  bgTertiary: "#334155",
@@ -121,6 +127,9 @@ var defaultTokens = {
121
127
  success: `var(--fp-color-success, ${rawDefaults.colors.success})`,
122
128
  error: `var(--fp-color-error, ${rawDefaults.colors.error})`,
123
129
  warning: `var(--fp-color-warning, ${rawDefaults.colors.warning})`,
130
+ nodeCursor: `var(--fp-node-cursor, ${rawDefaults.colors.nodeCursor})`,
131
+ nodeVisited: `var(--fp-node-visited, ${rawDefaults.colors.nodeVisited})`,
132
+ nodeMain: `var(--fp-node-main, ${rawDefaults.colors.nodeMain})`,
124
133
  bgPrimary: `var(--fp-bg-primary, ${rawDefaults.colors.bgPrimary})`,
125
134
  bgSecondary: `var(--fp-bg-secondary, ${rawDefaults.colors.bgSecondary})`,
126
135
  bgTertiary: `var(--fp-bg-tertiary, ${rawDefaults.colors.bgTertiary})`,
@@ -163,6 +172,15 @@ var theme = {
163
172
  success: v("--fp-color-success", "#22c55e"),
164
173
  error: v("--fp-color-error", "#ef4444"),
165
174
  warning: v("--fp-color-warning", "#f59e0b"),
175
+ // Semantic NODE-STATE colors — first-class, themeable roles a runtime overlay
176
+ // maps onto (scrub cursor / executed / a group's lead node). Distinct from the
177
+ // generic `primary` accent so the three read as three different things.
178
+ nodeCursor: v("--fp-node-cursor", "#f59e0b"),
179
+ // the current / scrubbed-to step
180
+ nodeVisited: v("--fp-node-visited", "#22c55e"),
181
+ // executed up to the cursor
182
+ nodeMain: v("--fp-node-main", "#6366f1"),
183
+ // the lead / "hero" node of a group
166
184
  bgPrimary: v("--fp-bg-primary", "#0f172a"),
167
185
  bgSecondary: v("--fp-bg-secondary", "#1e293b"),
168
186
  bgTertiary: v("--fp-bg-tertiary", "#334155"),
@@ -3631,6 +3649,18 @@ function centerForkParents(graph, options = {}) {
3631
3649
  }
3632
3650
  return minX <= maxX ? Math.max(minX, Math.min(maxX, desiredX)) : x0;
3633
3651
  };
3652
+ const evenFanKids = (forkCenter, kids) => {
3653
+ if (kids.length < 2) return;
3654
+ const sorted = [...kids].sort((a, b) => centerX(a) - centerX(b));
3655
+ let gap = 0;
3656
+ for (let i = 0; i < sorted.length - 1; i++) {
3657
+ gap = Math.max(gap, width.get(sorted[i]) / 2 + nodeSep + width.get(sorted[i + 1]) / 2);
3658
+ }
3659
+ const mid = (sorted.length - 1) / 2;
3660
+ for (let i = 0; i < sorted.length; i++) {
3661
+ workingX.set(sorted[i], forkCenter + (i - mid) * gap - width.get(sorted[i]) / 2);
3662
+ }
3663
+ };
3634
3664
  const order = [...graph.nodes].sort(
3635
3665
  (a, b) => b.position.y - a.position.y || a.position.x - b.position.x || a.id.localeCompare(b.id)
3636
3666
  );
@@ -3649,6 +3679,11 @@ function centerForkParents(graph, options = {}) {
3649
3679
  const wN = width.get(n.id);
3650
3680
  const span = (Math.min(...centers) + Math.max(...centers)) / 2;
3651
3681
  workingX.set(n.id, clampX(n.id, span - wN / 2));
3682
+ if (isFork) {
3683
+ const succSets = kin.map((k) => childrenOf.get(k) ?? []);
3684
+ const isDiamond = kin.length >= 2 && succSets[0].some((s) => succSets.every((ss) => ss.includes(s)));
3685
+ if (isDiamond) evenFanKids(centerX(n.id), kin);
3686
+ }
3652
3687
  const stepOf = isFork ? predsOf : childrenOf;
3653
3688
  let curId = n.id;
3654
3689
  const walked = /* @__PURE__ */ new Set([curId]);
@@ -3665,6 +3700,28 @@ function centerForkParents(graph, options = {}) {
3665
3700
  curId = m;
3666
3701
  }
3667
3702
  }
3703
+ for (const n of order) {
3704
+ const outD = outDegree.get(n.id) ?? 0;
3705
+ const inD = inDegree.get(n.id) ?? 0;
3706
+ if (!(outD >= 2 && inD <= 1)) continue;
3707
+ const kids = (childrenOf.get(n.id) ?? []).filter(
3708
+ (k) => byId.get(k)?.parentId === n.parentId
3709
+ );
3710
+ if (kids.length < 2) continue;
3711
+ const succSets = kids.map((k) => childrenOf.get(k) ?? []);
3712
+ const isDiamond = succSets[0].some((s) => succSets.every((ss) => ss.includes(s)));
3713
+ if (isDiamond) continue;
3714
+ const ps = predsOf.get(n.id);
3715
+ if (!ps || ps.length !== 1) continue;
3716
+ const pred = ps[0];
3717
+ if ((outDegree.get(pred) ?? 0) !== 1) continue;
3718
+ if (byId.get(pred)?.parentId !== byId.get(n.id)?.parentId) continue;
3719
+ const before = centerX(n.id);
3720
+ workingX.set(n.id, clampX(n.id, centerX(pred) - width.get(n.id) / 2));
3721
+ const delta = centerX(n.id) - before;
3722
+ if (delta === 0) continue;
3723
+ for (const k of kids) workingX.set(k, clampX(k, workingX.get(k) + delta));
3724
+ }
3668
3725
  const nodes = graph.nodes.map(
3669
3726
  (n) => workingX.get(n.id) === n.position.x ? n : { ...n, position: { x: workingX.get(n.id), y: n.position.y } }
3670
3727
  );
@@ -3897,12 +3954,12 @@ var StageNode = (0, import_react15.memo)(function StageNode2({
3897
3954
  const isHero = data.emphasis === "hero";
3898
3955
  const isMuted = data.emphasis === "muted";
3899
3956
  const sizeScale = data.size === "lg" ? 1.3 : data.size === "sm" ? 0.85 : 1;
3900
- const restingBg = isHero ? `color-mix(in srgb, ${theme.primary} 12%, ${theme.bgSecondary})` : theme.bgSecondary;
3901
- const restingBorder = isHero ? theme.primary : theme.border;
3902
- const restingShadow = isHero ? `0 0 10px color-mix(in srgb, ${theme.primary} 22%, transparent)` : `0 2px 8px rgba(0,0,0,0.15)`;
3903
- const bg = active ? theme.primary : done ? theme.success : error ? theme.error : restingBg;
3904
- const borderColor = active ? theme.primary : done ? theme.success : error ? theme.error : restingBorder;
3905
- const shadow = active ? `0 0 22px color-mix(in srgb, ${theme.primary} 55%, transparent)` : done ? `0 0 8px color-mix(in srgb, ${theme.success} 20%, transparent)` : error ? `0 0 12px color-mix(in srgb, ${theme.error} 30%, transparent)` : restingShadow;
3957
+ const restingBg = isHero ? `color-mix(in srgb, ${theme.nodeMain} 12%, ${theme.bgSecondary})` : theme.bgSecondary;
3958
+ const restingBorder = isHero ? theme.nodeMain : theme.border;
3959
+ const restingShadow = isHero ? `0 0 10px color-mix(in srgb, ${theme.nodeMain} 22%, transparent)` : `0 2px 8px rgba(0,0,0,0.15)`;
3960
+ const bg = active ? theme.nodeCursor : done ? theme.nodeVisited : error ? theme.error : restingBg;
3961
+ const borderColor = active ? theme.nodeCursor : done ? theme.nodeVisited : error ? theme.error : restingBorder;
3962
+ const shadow = active ? `0 0 22px color-mix(in srgb, ${theme.nodeCursor} 55%, transparent)` : done ? `0 0 8px color-mix(in srgb, ${theme.nodeVisited} 20%, transparent)` : error ? `0 0 12px color-mix(in srgb, ${theme.error} 30%, transparent)` : restingShadow;
3906
3963
  const textColor = active || done || error ? "#fff" : theme.textPrimary;
3907
3964
  return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
3908
3965
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_react16.Handle, { type: "target", position: import_react16.Position.Top, style: { opacity: 0 } }),
@@ -3932,8 +3989,8 @@ var StageNode = (0, import_react15.memo)(function StageNode2({
3932
3989
  },
3933
3990
  children: stepNumbers.map((num, i) => {
3934
3991
  const isLatest = i === stepNumbers.length - 1;
3935
- const badgeBg = isLatest && active ? theme.primary : theme.success;
3936
- const glow = isLatest && active ? `color-mix(in srgb, ${theme.primary} 50%, transparent)` : `color-mix(in srgb, ${theme.success} 40%, transparent)`;
3992
+ const badgeBg = isLatest && active ? theme.nodeCursor : theme.nodeVisited;
3993
+ const glow = isLatest && active ? `color-mix(in srgb, ${theme.nodeCursor} 50%, transparent)` : `color-mix(in srgb, ${theme.nodeVisited} 40%, transparent)`;
3937
3994
  return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3938
3995
  "div",
3939
3996
  {
@@ -3979,7 +4036,7 @@ var StageNode = (0, import_react15.memo)(function StageNode2({
3979
4036
  inset: -6,
3980
4037
  borderRadius: isDecider ? 0 : `calc(${theme.radius} + 4px)`,
3981
4038
  clipPath: isDecider ? "polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%)" : void 0,
3982
- border: `2px solid ${theme.primary}`,
4039
+ border: `2px solid ${theme.nodeCursor}`,
3983
4040
  opacity: 0.3,
3984
4041
  animation: "fp-pulse 1.5s ease-out infinite"
3985
4042
  }