@hex-core/components 1.7.0 → 1.8.1

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 (98) hide show
  1. package/dist/_tsup-dts-rollup.d.ts +1566 -5
  2. package/dist/arc.d.ts +4 -0
  3. package/dist/arc.js +147 -0
  4. package/dist/arc.js.map +1 -0
  5. package/dist/audio-player.d.ts +2 -0
  6. package/dist/audio-player.js +119 -0
  7. package/dist/audio-player.js.map +1 -0
  8. package/dist/audio-waveform.d.ts +2 -0
  9. package/dist/audio-waveform.js +72 -0
  10. package/dist/audio-waveform.js.map +1 -0
  11. package/dist/canvas.d.ts +2 -0
  12. package/dist/canvas.js +73 -0
  13. package/dist/canvas.js.map +1 -0
  14. package/dist/chord.d.ts +4 -0
  15. package/dist/chord.js +230 -0
  16. package/dist/chord.js.map +1 -0
  17. package/dist/cloze.d.ts +3 -0
  18. package/dist/cloze.js +98 -0
  19. package/dist/cloze.js.map +1 -0
  20. package/dist/color-picker.js.map +1 -1
  21. package/dist/compare-table.d.ts +4 -0
  22. package/dist/compare-table.js +109 -0
  23. package/dist/compare-table.js.map +1 -0
  24. package/dist/data-table.js.map +1 -1
  25. package/dist/deck.d.ts +3 -0
  26. package/dist/deck.js +231 -0
  27. package/dist/deck.js.map +1 -0
  28. package/dist/dendrogram.d.ts +3 -0
  29. package/dist/dendrogram.js +162 -0
  30. package/dist/dendrogram.js.map +1 -0
  31. package/dist/diagram.d.ts +2 -0
  32. package/dist/diagram.js +70 -0
  33. package/dist/diagram.js.map +1 -0
  34. package/dist/flashcard.d.ts +2 -0
  35. package/dist/flashcard.js +107 -0
  36. package/dist/flashcard.js.map +1 -0
  37. package/dist/flowchart.d.ts +4 -0
  38. package/dist/flowchart.js +275 -0
  39. package/dist/flowchart.js.map +1 -0
  40. package/dist/funnel.d.ts +3 -0
  41. package/dist/funnel.js +157 -0
  42. package/dist/funnel.js.map +1 -0
  43. package/dist/gantt.d.ts +3 -0
  44. package/dist/gantt.js +279 -0
  45. package/dist/gantt.js.map +1 -0
  46. package/dist/image-occlusion.d.ts +3 -0
  47. package/dist/image-occlusion.js +106 -0
  48. package/dist/image-occlusion.js.map +1 -0
  49. package/dist/index.d.ts +84 -0
  50. package/dist/index.js +3946 -2
  51. package/dist/index.js.map +1 -1
  52. package/dist/matrix.d.ts +3 -0
  53. package/dist/matrix.js +155 -0
  54. package/dist/matrix.js.map +1 -0
  55. package/dist/mind-map.d.ts +3 -0
  56. package/dist/mind-map.js +167 -0
  57. package/dist/mind-map.js.map +1 -0
  58. package/dist/org-chart.d.ts +3 -0
  59. package/dist/org-chart.js +215 -0
  60. package/dist/org-chart.js.map +1 -0
  61. package/dist/pyramid.d.ts +3 -0
  62. package/dist/pyramid.js +150 -0
  63. package/dist/pyramid.js.map +1 -0
  64. package/dist/quiz.d.ts +3 -0
  65. package/dist/quiz.js +128 -0
  66. package/dist/quiz.js.map +1 -0
  67. package/dist/sankey.d.ts +4 -0
  68. package/dist/sankey.js +190 -0
  69. package/dist/sankey.js.map +1 -0
  70. package/dist/schemas.d.ts +23 -0
  71. package/dist/schemas.js +2210 -3
  72. package/dist/schemas.js.map +1 -1
  73. package/dist/sequence.d.ts +4 -0
  74. package/dist/sequence.js +229 -0
  75. package/dist/sequence.js.map +1 -0
  76. package/dist/sonner.js.map +1 -1
  77. package/dist/spaced-repetition.d.ts +3 -0
  78. package/dist/spaced-repetition.js +73 -0
  79. package/dist/spaced-repetition.js.map +1 -0
  80. package/dist/sunburst.d.ts +3 -0
  81. package/dist/sunburst.js +205 -0
  82. package/dist/sunburst.js.map +1 -0
  83. package/dist/terminal.d.ts +2 -0
  84. package/dist/terminal.js +153 -0
  85. package/dist/terminal.js.map +1 -0
  86. package/dist/textarea.js.map +1 -1
  87. package/dist/time-axis.d.ts +3 -0
  88. package/dist/time-axis.js +233 -0
  89. package/dist/time-axis.js.map +1 -0
  90. package/dist/tool-call.js +6 -1
  91. package/dist/tool-call.js.map +1 -1
  92. package/dist/tree-map.d.ts +3 -0
  93. package/dist/tree-map.js +171 -0
  94. package/dist/tree-map.js.map +1 -0
  95. package/dist/venn.d.ts +3 -0
  96. package/dist/venn.js +196 -0
  97. package/dist/venn.js.map +1 -0
  98. package/package.json +49 -5
package/dist/index.js CHANGED
@@ -5116,6 +5116,401 @@ function SpeechRecognition({
5116
5116
  }
5117
5117
  );
5118
5118
  }
5119
+ var DARK_FALLBACK = {
5120
+ background: "#0a0a0a",
5121
+ foreground: "#e5e5e5",
5122
+ selectionBackground: "#404040"
5123
+ };
5124
+ var LIGHT_FALLBACK = {
5125
+ background: "#fafafa",
5126
+ foreground: "#171717",
5127
+ selectionBackground: "#d4d4d4"
5128
+ };
5129
+ function readCssVarAsHex(name) {
5130
+ if (typeof document === "undefined") return null;
5131
+ const triplet = getComputedStyle(document.documentElement).getPropertyValue(`--${name}`).trim();
5132
+ if (!triplet) return null;
5133
+ return hslTripletToHex(triplet);
5134
+ }
5135
+ function Terminal({
5136
+ output,
5137
+ onInput,
5138
+ cols = 80,
5139
+ rows = 24,
5140
+ theme = "dark",
5141
+ cursorBlink = true,
5142
+ disableInput = false,
5143
+ className,
5144
+ ...rest
5145
+ }) {
5146
+ const containerRef = React44.useRef(null);
5147
+ const termRef = React44.useRef(null);
5148
+ const writtenRef = React44.useRef("");
5149
+ const onInputRef = React44.useRef(onInput);
5150
+ onInputRef.current = onInput;
5151
+ const outputRef = React44.useRef(output);
5152
+ outputRef.current = output;
5153
+ React44.useEffect(() => {
5154
+ if (!containerRef.current) return;
5155
+ let disposed = false;
5156
+ let inputDispose = null;
5157
+ void (async () => {
5158
+ const xtermModule = await import('@xterm/xterm');
5159
+ if (disposed || !containerRef.current) return;
5160
+ const fallback = theme === "dark" ? DARK_FALLBACK : LIGHT_FALLBACK;
5161
+ const bgHex = readCssVarAsHex("background") ?? fallback.background;
5162
+ const fgHex = readCssVarAsHex("foreground") ?? fallback.foreground;
5163
+ const term = new xtermModule.Terminal({
5164
+ cols,
5165
+ rows,
5166
+ cursorBlink,
5167
+ disableStdin: disableInput,
5168
+ theme: {
5169
+ background: bgHex,
5170
+ foreground: fgHex,
5171
+ cursor: fgHex,
5172
+ selectionBackground: fallback.selectionBackground
5173
+ },
5174
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
5175
+ fontSize: 13
5176
+ });
5177
+ term.open(containerRef.current);
5178
+ termRef.current = term;
5179
+ const latest = normalizeOutput(outputRef.current);
5180
+ if (latest) {
5181
+ term.write(latest);
5182
+ writtenRef.current = latest;
5183
+ }
5184
+ inputDispose = term.onData((data) => {
5185
+ onInputRef.current?.(data);
5186
+ });
5187
+ })();
5188
+ return () => {
5189
+ disposed = true;
5190
+ inputDispose?.dispose();
5191
+ termRef.current?.dispose();
5192
+ termRef.current = null;
5193
+ writtenRef.current = "";
5194
+ };
5195
+ }, []);
5196
+ React44.useEffect(() => {
5197
+ const term = termRef.current;
5198
+ if (!term) return;
5199
+ const next = normalizeOutput(output);
5200
+ if (next === writtenRef.current) return;
5201
+ if (next.startsWith(writtenRef.current)) {
5202
+ const delta = next.slice(writtenRef.current.length);
5203
+ if (delta) term.write(delta);
5204
+ } else {
5205
+ term.reset();
5206
+ if (next) term.write(next);
5207
+ }
5208
+ writtenRef.current = next;
5209
+ }, [output]);
5210
+ const fallbackTriplet = theme === "dark" ? "0 0% 4%" : "0 0% 98%";
5211
+ const themeBg = `hsl(var(--background, ${fallbackTriplet}))`;
5212
+ return /* @__PURE__ */ jsx(
5213
+ "div",
5214
+ {
5215
+ ...rest,
5216
+ ref: containerRef,
5217
+ "data-hex-terminal": true,
5218
+ "data-theme": theme,
5219
+ style: { backgroundColor: themeBg, ...rest.style ?? {} },
5220
+ className: cn(
5221
+ "overflow-hidden rounded-md border p-2",
5222
+ "font-mono text-sm leading-tight",
5223
+ className
5224
+ )
5225
+ }
5226
+ );
5227
+ }
5228
+ function normalizeOutput(value) {
5229
+ if (value == null) return "";
5230
+ return Array.isArray(value) ? value.join("") : value;
5231
+ }
5232
+ function Canvas({
5233
+ nodes,
5234
+ edges,
5235
+ onNodesChange,
5236
+ onEdgesChange,
5237
+ onConnect,
5238
+ hideControls = false,
5239
+ hideBackground = false,
5240
+ fitView = true,
5241
+ children,
5242
+ className,
5243
+ ...rest
5244
+ }) {
5245
+ const [rf, setRf] = React44.useState(null);
5246
+ React44.useEffect(() => {
5247
+ let cancelled = false;
5248
+ void import('reactflow').then((mod) => {
5249
+ if (!cancelled) setRf(mod);
5250
+ });
5251
+ return () => {
5252
+ cancelled = true;
5253
+ };
5254
+ }, []);
5255
+ if (!rf) {
5256
+ return /* @__PURE__ */ jsx(
5257
+ "div",
5258
+ {
5259
+ ...rest,
5260
+ "data-hex-canvas-loading": true,
5261
+ className: cn("h-full w-full bg-muted/20", className),
5262
+ "aria-busy": "true"
5263
+ }
5264
+ );
5265
+ }
5266
+ const { ReactFlow, Background, Controls } = rf;
5267
+ return /* @__PURE__ */ jsx(
5268
+ "div",
5269
+ {
5270
+ ...rest,
5271
+ "data-hex-canvas": true,
5272
+ className: cn("h-full w-full", className),
5273
+ children: /* @__PURE__ */ jsxs(
5274
+ ReactFlow,
5275
+ {
5276
+ nodes,
5277
+ edges,
5278
+ onNodesChange,
5279
+ onEdgesChange,
5280
+ onConnect,
5281
+ fitView,
5282
+ children: [
5283
+ !hideBackground && /* @__PURE__ */ jsx(Background, {}),
5284
+ !hideControls && /* @__PURE__ */ jsx(Controls, {}),
5285
+ children
5286
+ ]
5287
+ }
5288
+ )
5289
+ }
5290
+ );
5291
+ }
5292
+ function AudioPlayer({ src, autoPlay = false, onEnded, onError, className, ...rest }) {
5293
+ const containerRef = React44.useRef(null);
5294
+ const wsRef = React44.useRef(null);
5295
+ const onEndedRef = React44.useRef(onEnded);
5296
+ const onErrorRef = React44.useRef(onError);
5297
+ onEndedRef.current = onEnded;
5298
+ onErrorRef.current = onError;
5299
+ const [isPlaying, setIsPlaying] = React44.useState(false);
5300
+ const [duration, setDuration] = React44.useState(0);
5301
+ const [currentTime, setCurrentTime] = React44.useState(0);
5302
+ const [isReady, setIsReady] = React44.useState(false);
5303
+ React44.useEffect(() => {
5304
+ if (!containerRef.current) return;
5305
+ let disposed = false;
5306
+ const objectUrl = src instanceof Blob ? URL.createObjectURL(src) : null;
5307
+ void (async () => {
5308
+ const { default: WaveSurfer } = await import('wavesurfer.js');
5309
+ if (disposed || !containerRef.current) return;
5310
+ const ws = WaveSurfer.create({
5311
+ container: containerRef.current,
5312
+ waveColor: "#a3a3a3",
5313
+ progressColor: "#171717",
5314
+ cursorColor: "transparent",
5315
+ height: 32,
5316
+ barWidth: 2,
5317
+ barGap: 1,
5318
+ barRadius: 1,
5319
+ normalize: true
5320
+ });
5321
+ ws.on("ready", () => {
5322
+ setIsReady(true);
5323
+ setDuration(ws.getDuration());
5324
+ if (autoPlay) void ws.play();
5325
+ });
5326
+ ws.on("play", () => setIsPlaying(true));
5327
+ ws.on("pause", () => setIsPlaying(false));
5328
+ ws.on("finish", () => {
5329
+ setIsPlaying(false);
5330
+ onEndedRef.current?.();
5331
+ });
5332
+ ws.on("timeupdate", (t) => setCurrentTime(t));
5333
+ ws.on("error", (err) => {
5334
+ onErrorRef.current?.(err.message);
5335
+ });
5336
+ void ws.load(objectUrl ?? src);
5337
+ wsRef.current = ws;
5338
+ })();
5339
+ return () => {
5340
+ disposed = true;
5341
+ wsRef.current?.destroy();
5342
+ wsRef.current = null;
5343
+ if (objectUrl) URL.revokeObjectURL(objectUrl);
5344
+ };
5345
+ }, [src]);
5346
+ function togglePlay() {
5347
+ const ws = wsRef.current;
5348
+ if (!ws || !isReady) return;
5349
+ void ws.playPause();
5350
+ }
5351
+ return /* @__PURE__ */ jsxs(
5352
+ "div",
5353
+ {
5354
+ ...rest,
5355
+ "data-hex-audio-player": true,
5356
+ className: cn("flex items-center gap-3 rounded-md border bg-background p-2", className),
5357
+ children: [
5358
+ /* @__PURE__ */ jsx(
5359
+ "button",
5360
+ {
5361
+ type: "button",
5362
+ onClick: togglePlay,
5363
+ disabled: !isReady,
5364
+ "aria-label": isPlaying ? "Pause" : "Play",
5365
+ className: cn(
5366
+ "inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-md",
5367
+ "bg-foreground text-background transition-opacity",
5368
+ "hover:opacity-80 disabled:cursor-not-allowed disabled:opacity-50"
5369
+ ),
5370
+ children: isPlaying ? /* @__PURE__ */ jsx(PauseIcon, {}) : /* @__PURE__ */ jsx(PlayIcon, {})
5371
+ }
5372
+ ),
5373
+ /* @__PURE__ */ jsx("div", { ref: containerRef, className: "flex-1 min-w-0" }),
5374
+ /* @__PURE__ */ jsxs("span", { className: "shrink-0 text-xs tabular-nums text-muted-foreground", children: [
5375
+ formatTime(currentTime),
5376
+ " / ",
5377
+ formatTime(duration)
5378
+ ] })
5379
+ ]
5380
+ }
5381
+ );
5382
+ }
5383
+ function formatTime(seconds) {
5384
+ if (!Number.isFinite(seconds) || seconds < 0) return "0:00";
5385
+ const m = Math.floor(seconds / 60);
5386
+ const s = Math.floor(seconds % 60);
5387
+ return `${m}:${s.toString().padStart(2, "0")}`;
5388
+ }
5389
+ function PlayIcon() {
5390
+ return /* @__PURE__ */ jsx("svg", { "aria-hidden": true, viewBox: "0 0 16 16", width: "12", height: "12", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M5 3.5v9l8-4.5z" }) });
5391
+ }
5392
+ function PauseIcon() {
5393
+ return /* @__PURE__ */ jsxs("svg", { "aria-hidden": true, viewBox: "0 0 16 16", width: "12", height: "12", fill: "currentColor", children: [
5394
+ /* @__PURE__ */ jsx("rect", { x: "4", y: "3", width: "3", height: "10", rx: "0.5" }),
5395
+ /* @__PURE__ */ jsx("rect", { x: "9", y: "3", width: "3", height: "10", rx: "0.5" })
5396
+ ] });
5397
+ }
5398
+ function AudioWaveform({
5399
+ src,
5400
+ height = 48,
5401
+ waveColor = "#a3a3a3",
5402
+ progressColor,
5403
+ onReady,
5404
+ onError,
5405
+ className,
5406
+ ...rest
5407
+ }) {
5408
+ const containerRef = React44.useRef(null);
5409
+ const onReadyRef = React44.useRef(onReady);
5410
+ const onErrorRef = React44.useRef(onError);
5411
+ onReadyRef.current = onReady;
5412
+ onErrorRef.current = onError;
5413
+ React44.useEffect(() => {
5414
+ if (!containerRef.current) return;
5415
+ let disposed = false;
5416
+ let ws = null;
5417
+ const objectUrl = src instanceof Blob ? URL.createObjectURL(src) : null;
5418
+ void (async () => {
5419
+ const { default: WaveSurfer } = await import('wavesurfer.js');
5420
+ if (disposed || !containerRef.current) return;
5421
+ ws = WaveSurfer.create({
5422
+ container: containerRef.current,
5423
+ waveColor,
5424
+ progressColor: progressColor ?? waveColor,
5425
+ cursorColor: "transparent",
5426
+ height,
5427
+ barWidth: 2,
5428
+ barGap: 1,
5429
+ barRadius: 1,
5430
+ normalize: true,
5431
+ interact: false
5432
+ });
5433
+ ws.on("ready", () => {
5434
+ if (ws) onReadyRef.current?.(ws.getDuration());
5435
+ });
5436
+ ws.on("error", (err) => {
5437
+ onErrorRef.current?.(err.message);
5438
+ });
5439
+ void ws.load(objectUrl ?? src);
5440
+ })();
5441
+ return () => {
5442
+ disposed = true;
5443
+ ws?.destroy();
5444
+ if (objectUrl) URL.revokeObjectURL(objectUrl);
5445
+ };
5446
+ }, [src, height, waveColor, progressColor]);
5447
+ return /* @__PURE__ */ jsx(
5448
+ "div",
5449
+ {
5450
+ ...rest,
5451
+ ref: containerRef,
5452
+ "data-hex-audio-waveform": true,
5453
+ className: cn("w-full", className)
5454
+ }
5455
+ );
5456
+ }
5457
+ function Diagram({ children: source, theme = "default", id, onError, className, ...rest }) {
5458
+ const [svg, setSvg] = React44.useState("");
5459
+ const [error, setError] = React44.useState(null);
5460
+ const onErrorRef = React44.useRef(onError);
5461
+ onErrorRef.current = onError;
5462
+ const reactId = React44.useId();
5463
+ const stableId = id ?? `hex-diagram-${reactId.replace(/:/g, "-")}`;
5464
+ React44.useEffect(() => {
5465
+ let cancelled = false;
5466
+ setError(null);
5467
+ void (async () => {
5468
+ try {
5469
+ const { default: mermaid } = await import('mermaid');
5470
+ if (cancelled) return;
5471
+ mermaid.initialize({ startOnLoad: false, theme, securityLevel: "strict" });
5472
+ const { svg: rendered } = await mermaid.render(stableId, source);
5473
+ if (!cancelled) setSvg(rendered);
5474
+ } catch (err) {
5475
+ const message = err instanceof Error ? err.message : String(err);
5476
+ if (cancelled) return;
5477
+ setError(message);
5478
+ onErrorRef.current?.(message);
5479
+ }
5480
+ })();
5481
+ return () => {
5482
+ cancelled = true;
5483
+ };
5484
+ }, [source, theme, stableId]);
5485
+ if (error) {
5486
+ return /* @__PURE__ */ jsxs(
5487
+ "div",
5488
+ {
5489
+ ...rest,
5490
+ "data-hex-diagram-error": true,
5491
+ role: "alert",
5492
+ className: cn(
5493
+ "rounded-md border border-destructive/40 bg-destructive/5 p-3 text-sm text-destructive",
5494
+ className
5495
+ ),
5496
+ children: [
5497
+ /* @__PURE__ */ jsx("div", { className: "font-medium", children: "Diagram failed to render" }),
5498
+ /* @__PURE__ */ jsx("pre", { className: "mt-1 whitespace-pre-wrap text-xs opacity-80", children: error })
5499
+ ]
5500
+ }
5501
+ );
5502
+ }
5503
+ return /* @__PURE__ */ jsx(
5504
+ "div",
5505
+ {
5506
+ ...rest,
5507
+ "data-hex-diagram": true,
5508
+ "data-theme": theme,
5509
+ className: cn("overflow-auto rounded-md border bg-background p-3", className),
5510
+ dangerouslySetInnerHTML: { __html: svg }
5511
+ }
5512
+ );
5513
+ }
5119
5514
  var loadingIndicatorVariants = cva("inline-flex items-center gap-2 text-muted-foreground", {
5120
5515
  variants: {
5121
5516
  size: {
@@ -5217,7 +5612,12 @@ var STATE_LABEL = {
5217
5612
  };
5218
5613
  var STATE_CLASSES = {
5219
5614
  pending: "bg-muted text-muted-foreground",
5220
- running: "bg-primary/15 text-primary animate-pulse",
5615
+ // Solid primary bg + primary-foreground text in dark mode — the
5616
+ // previously tinted `bg-primary/15` made the badge fg/bg too close in
5617
+ // hue at 10px to clear WCAG AA (was 4.41:1, fails the 4.5 floor).
5618
+ // Light mode keeps the soft tint where contrast holds; dark mode uses
5619
+ // the solid surface for guaranteed ≥7:1 contrast.
5620
+ running: "bg-primary/15 text-primary dark:bg-primary dark:text-primary-foreground animate-pulse",
5221
5621
  result: "bg-accent/30 text-accent-foreground",
5222
5622
  error: "bg-destructive/15 text-destructive"
5223
5623
  };
@@ -5808,7 +6208,3551 @@ function FileIcon2(props) {
5808
6208
  }
5809
6209
  );
5810
6210
  }
6211
+ function MindMap({
6212
+ root,
6213
+ orientation = "radial",
6214
+ width = 600,
6215
+ height = 400,
6216
+ onNodeClick,
6217
+ className,
6218
+ ...rest
6219
+ }) {
6220
+ const [d3h, setD3h] = React44.useState(null);
6221
+ React44.useEffect(() => {
6222
+ let cancelled = false;
6223
+ void import('d3-hierarchy').then((mod) => {
6224
+ if (!cancelled) setD3h(mod);
6225
+ });
6226
+ return () => {
6227
+ cancelled = true;
6228
+ };
6229
+ }, []);
6230
+ if (!d3h) {
6231
+ return /* @__PURE__ */ jsx(
6232
+ "div",
6233
+ {
6234
+ "data-hex-mind-map-loading": true,
6235
+ "aria-busy": "true",
6236
+ className: cn("inline-block bg-muted/20", className),
6237
+ style: { width, height }
6238
+ }
6239
+ );
6240
+ }
6241
+ const { nodes, links, viewBox } = layout(d3h, root, orientation, width, height);
6242
+ const nodeCount = nodes.length;
6243
+ const desc = `Mind map with ${nodeCount} node${nodeCount === 1 ? "" : "s"}, rooted at "${root.label}"`;
6244
+ return /* @__PURE__ */ jsxs(
6245
+ "svg",
6246
+ {
6247
+ ...rest,
6248
+ "data-hex-mind-map": true,
6249
+ "data-orientation": orientation,
6250
+ role: "img",
6251
+ viewBox,
6252
+ width,
6253
+ height,
6254
+ className: cn("block", className),
6255
+ children: [
6256
+ /* @__PURE__ */ jsx("title", { children: "Mind map" }),
6257
+ /* @__PURE__ */ jsx("desc", { children: desc }),
6258
+ /* @__PURE__ */ jsx("g", { "data-hex-mind-map-links": true, children: links.map((l) => /* @__PURE__ */ jsx(
6259
+ "path",
6260
+ {
6261
+ d: linkPath(l, orientation),
6262
+ fill: "none",
6263
+ stroke: "hsl(var(--muted-foreground))",
6264
+ strokeOpacity: 0.7,
6265
+ strokeWidth: 1.5
6266
+ },
6267
+ l.id
6268
+ )) }),
6269
+ /* @__PURE__ */ jsx("g", { "data-hex-mind-map-nodes": true, children: nodes.map((n) => /* @__PURE__ */ jsxs(
6270
+ "g",
6271
+ {
6272
+ "data-hex-mind-map-node": true,
6273
+ "data-depth": n.depth,
6274
+ transform: `translate(${n.x},${n.y})`,
6275
+ style: onNodeClick ? { cursor: "pointer" } : void 0,
6276
+ onClick: onNodeClick ? () => onNodeClick(n.node) : void 0,
6277
+ children: [
6278
+ /* @__PURE__ */ jsx(
6279
+ "circle",
6280
+ {
6281
+ r: 4,
6282
+ fill: "hsl(var(--primary))",
6283
+ stroke: "hsl(var(--background))",
6284
+ strokeWidth: 2
6285
+ }
6286
+ ),
6287
+ /* @__PURE__ */ jsx(
6288
+ "text",
6289
+ {
6290
+ x: 8,
6291
+ y: 4,
6292
+ fontSize: 12,
6293
+ fill: "hsl(var(--foreground))",
6294
+ style: { paintOrder: "stroke" },
6295
+ stroke: "hsl(var(--background))",
6296
+ strokeWidth: 3,
6297
+ strokeLinejoin: "round",
6298
+ children: n.node.label
6299
+ }
6300
+ )
6301
+ ]
6302
+ },
6303
+ n.node.id
6304
+ )) })
6305
+ ]
6306
+ }
6307
+ );
6308
+ }
6309
+ function layout(d3h, root, orientation, width, height) {
6310
+ const hierarchy = d3h.hierarchy(root);
6311
+ if (orientation === "radial") {
6312
+ const radius = Math.min(width, height) / 2 - 16;
6313
+ const layoutRoot2 = d3h.tree().size([2 * Math.PI, radius])(hierarchy);
6314
+ const nodes2 = [];
6315
+ const links2 = [];
6316
+ layoutRoot2.each((d) => {
6317
+ const point = polarToCartesian(d.x, d.y);
6318
+ nodes2.push({ node: d.data, x: point.x, y: point.y, depth: d.depth });
6319
+ });
6320
+ layoutRoot2.links().forEach((link, i) => {
6321
+ const sPt = polarToCartesian(link.source.x, link.source.y);
6322
+ const tPt = polarToCartesian(link.target.x, link.target.y);
6323
+ links2.push({ source: sPt, target: tPt, id: `l-${i}` });
6324
+ });
6325
+ const half = Math.min(width, height) / 2;
6326
+ return {
6327
+ nodes: nodes2,
6328
+ links: links2,
6329
+ viewBox: `${-half} ${-half} ${2 * half} ${2 * half}`
6330
+ };
6331
+ }
6332
+ const layoutRoot = d3h.tree().size([height - 32, width - 200])(hierarchy);
6333
+ const nodes = [];
6334
+ const links = [];
6335
+ layoutRoot.each((d) => {
6336
+ nodes.push({ node: d.data, x: d.y, y: d.x, depth: d.depth });
6337
+ });
6338
+ layoutRoot.links().forEach((link, i) => {
6339
+ links.push({
6340
+ source: { x: link.source.y, y: link.source.x },
6341
+ target: { x: link.target.y, y: link.target.x },
6342
+ id: `l-${i}`
6343
+ });
6344
+ });
6345
+ return {
6346
+ nodes,
6347
+ links,
6348
+ viewBox: `0 0 ${width} ${height}`
6349
+ };
6350
+ }
6351
+ function polarToCartesian(angle, radius) {
6352
+ return {
6353
+ x: radius * Math.cos(angle - Math.PI / 2),
6354
+ y: radius * Math.sin(angle - Math.PI / 2)
6355
+ };
6356
+ }
6357
+ function linkPath(link, orientation) {
6358
+ const { source: s, target: t } = link;
6359
+ if (orientation === "horizontal") {
6360
+ const mx = (s.x + t.x) / 2;
6361
+ return `M${s.x},${s.y} C${mx},${s.y} ${mx},${t.y} ${t.x},${t.y}`;
6362
+ }
6363
+ return `M${s.x},${s.y} C${s.x},${(s.y + t.y) / 2} ${t.x},${(s.y + t.y) / 2} ${t.x},${t.y}`;
6364
+ }
6365
+
6366
+ // src/lib/chart-palette.ts
6367
+ var CHART_PALETTE = [
6368
+ "hsl(var(--chart-1, var(--primary)))",
6369
+ "hsl(var(--chart-2, var(--primary)))",
6370
+ "hsl(var(--chart-3, var(--primary)))",
6371
+ "hsl(var(--chart-4, var(--primary)))",
6372
+ "hsl(var(--chart-5, var(--primary)))",
6373
+ "hsl(var(--chart-6, var(--primary)))"
6374
+ ];
6375
+ function pickChartHue(index) {
6376
+ const safe = (index % CHART_PALETTE.length + CHART_PALETTE.length) % CHART_PALETTE.length;
6377
+ return CHART_PALETTE[safe];
6378
+ }
6379
+ function TreeMap({
6380
+ root,
6381
+ width = 600,
6382
+ height = 400,
6383
+ padding = 2,
6384
+ tile = "squarify",
6385
+ colorBy = "depth",
6386
+ onLeafClick,
6387
+ className,
6388
+ ...rest
6389
+ }) {
6390
+ const [d3h, setD3h] = React44.useState(null);
6391
+ React44.useEffect(() => {
6392
+ let cancelled = false;
6393
+ void import('d3-hierarchy').then((mod) => {
6394
+ if (!cancelled) setD3h(mod);
6395
+ });
6396
+ return () => {
6397
+ cancelled = true;
6398
+ };
6399
+ }, []);
6400
+ if (!d3h) {
6401
+ return /* @__PURE__ */ jsx(
6402
+ "div",
6403
+ {
6404
+ "data-hex-tree-map-loading": true,
6405
+ "aria-busy": "true",
6406
+ className: cn("inline-block bg-muted/20", className),
6407
+ style: { width, height }
6408
+ }
6409
+ );
6410
+ }
6411
+ const leaves = layout2(d3h, root, width, height, padding, tile);
6412
+ const maxValue = leaves.reduce((m, l) => l.value > m ? l.value : m, 0) || 1;
6413
+ const desc = `Tree map of ${leaves.length} leaf${leaves.length === 1 ? "" : "s"}, rooted at "${root.label}"`;
6414
+ return /* @__PURE__ */ jsxs(
6415
+ "svg",
6416
+ {
6417
+ ...rest,
6418
+ "data-hex-tree-map": true,
6419
+ "data-tile": tile,
6420
+ role: "img",
6421
+ width,
6422
+ height,
6423
+ viewBox: `0 0 ${width} ${height}`,
6424
+ className: cn("block", className),
6425
+ children: [
6426
+ /* @__PURE__ */ jsx("title", { children: "Tree map" }),
6427
+ /* @__PURE__ */ jsx("desc", { children: desc }),
6428
+ leaves.map((l) => {
6429
+ const w = l.x1 - l.x0;
6430
+ const h = l.y1 - l.y0;
6431
+ const fill = resolveFill(l, maxValue, colorBy);
6432
+ return /* @__PURE__ */ jsxs(
6433
+ "g",
6434
+ {
6435
+ "data-hex-tree-map-leaf": true,
6436
+ "data-depth": l.depth,
6437
+ transform: `translate(${l.x0},${l.y0})`,
6438
+ style: onLeafClick ? { cursor: "pointer" } : void 0,
6439
+ onClick: onLeafClick ? () => onLeafClick(l.node) : void 0,
6440
+ children: [
6441
+ /* @__PURE__ */ jsx(
6442
+ "rect",
6443
+ {
6444
+ width: w,
6445
+ height: h,
6446
+ fill,
6447
+ fillOpacity: 0.85,
6448
+ stroke: "hsl(var(--background))",
6449
+ strokeWidth: 1
6450
+ }
6451
+ ),
6452
+ w > 40 && h > 16 ? /* @__PURE__ */ jsx(
6453
+ "text",
6454
+ {
6455
+ x: 6,
6456
+ y: 16,
6457
+ fontSize: 12,
6458
+ fontWeight: 500,
6459
+ fill: "hsl(var(--background))",
6460
+ style: {
6461
+ pointerEvents: "none",
6462
+ paintOrder: "stroke",
6463
+ stroke: "hsl(var(--foreground) / 0.35)",
6464
+ strokeWidth: 2
6465
+ },
6466
+ children: l.node.label
6467
+ }
6468
+ ) : null,
6469
+ w > 60 && h > 32 ? /* @__PURE__ */ jsx(
6470
+ "text",
6471
+ {
6472
+ x: 6,
6473
+ y: 32,
6474
+ fontSize: 11,
6475
+ fill: "hsl(var(--background) / 0.85)",
6476
+ style: {
6477
+ pointerEvents: "none",
6478
+ paintOrder: "stroke",
6479
+ stroke: "hsl(var(--foreground) / 0.3)",
6480
+ strokeWidth: 1.5
6481
+ },
6482
+ children: l.value.toLocaleString()
6483
+ }
6484
+ ) : null
6485
+ ]
6486
+ },
6487
+ l.node.id
6488
+ );
6489
+ })
6490
+ ]
6491
+ }
6492
+ );
6493
+ }
6494
+ function layout2(d3h, root, width, height, padding, tile) {
6495
+ const tileFn = tile === "binary" ? d3h.treemapBinary : tile === "slice-dice" ? d3h.treemapSliceDice : d3h.treemapSquarify;
6496
+ const hierarchy = d3h.hierarchy(root).sum((d) => d.children && d.children.length > 0 ? 0 : d.value ?? 0).sort((a, b) => (b.value ?? 0) - (a.value ?? 0));
6497
+ const layoutRoot = d3h.treemap().tile(tileFn).size([width, height]).padding(padding)(hierarchy);
6498
+ return layoutRoot.leaves().map((leaf, leafIdx) => {
6499
+ let cursor = leaf;
6500
+ while (cursor && cursor.depth > 1) cursor = cursor.parent;
6501
+ const ancestorIdx = cursor?.parent?.children?.indexOf(cursor) ?? leafIdx;
6502
+ return {
6503
+ node: leaf.data,
6504
+ depth: leaf.depth,
6505
+ x0: leaf.x0,
6506
+ y0: leaf.y0,
6507
+ x1: leaf.x1,
6508
+ y1: leaf.y1,
6509
+ value: leaf.value ?? 0,
6510
+ leafIdx,
6511
+ rootSiblingIdx: Math.max(0, ancestorIdx)
6512
+ };
6513
+ });
6514
+ }
6515
+ function resolveFill(leaf, maxValue, colorBy) {
6516
+ if (typeof colorBy === "function") return colorBy(leaf.node, leaf.depth);
6517
+ if (colorBy === "value") {
6518
+ const t = Math.max(0.2, Math.min(1, leaf.value / maxValue));
6519
+ return `hsl(var(--chart-1) / ${t.toFixed(2)})`;
6520
+ }
6521
+ const idx = leaf.depth > 1 ? leaf.rootSiblingIdx : leaf.leafIdx;
6522
+ return pickChartHue(idx);
6523
+ }
6524
+ function OrgChart({
6525
+ root,
6526
+ collapsible = true,
6527
+ defaultExpandedDepth = Number.POSITIVE_INFINITY,
6528
+ nodeWidth = 180,
6529
+ nodeHeight = 64,
6530
+ width = 800,
6531
+ height = 480,
6532
+ onNodeClick,
6533
+ className,
6534
+ ...rest
6535
+ }) {
6536
+ const [d3h, setD3h] = React44.useState(null);
6537
+ const [collapsed, setCollapsed] = React44.useState(() => seedCollapsed(root, defaultExpandedDepth));
6538
+ React44.useEffect(() => {
6539
+ let cancelled = false;
6540
+ void import('d3-hierarchy').then((mod) => {
6541
+ if (!cancelled) setD3h(mod);
6542
+ });
6543
+ return () => {
6544
+ cancelled = true;
6545
+ };
6546
+ }, []);
6547
+ React44.useEffect(() => {
6548
+ setCollapsed(seedCollapsed(root, defaultExpandedDepth));
6549
+ }, [root, defaultExpandedDepth]);
6550
+ if (!d3h) {
6551
+ return /* @__PURE__ */ jsx(
6552
+ "div",
6553
+ {
6554
+ "data-hex-org-chart-loading": true,
6555
+ "aria-busy": "true",
6556
+ className: cn("inline-block bg-muted/20", className),
6557
+ style: { width, height }
6558
+ }
6559
+ );
6560
+ }
6561
+ const { nodes, links } = layout3(d3h, root, collapsed, width, height, nodeHeight);
6562
+ const desc = `Organizational chart with ${nodes.length} visible node${nodes.length === 1 ? "" : "s"}, rooted at "${root.label}"`;
6563
+ const handleClick = (node) => {
6564
+ onNodeClick?.(node);
6565
+ if (!collapsible) return;
6566
+ const hasChildren = node.children && node.children.length > 0;
6567
+ if (!hasChildren) return;
6568
+ setCollapsed((prev) => {
6569
+ const next = new Set(prev);
6570
+ if (next.has(node.id)) next.delete(node.id);
6571
+ else next.add(node.id);
6572
+ return next;
6573
+ });
6574
+ };
6575
+ return /* @__PURE__ */ jsxs(
6576
+ "svg",
6577
+ {
6578
+ ...rest,
6579
+ "data-hex-org-chart": true,
6580
+ role: "img",
6581
+ width,
6582
+ height,
6583
+ viewBox: `0 0 ${width} ${height}`,
6584
+ className: cn("block", className),
6585
+ children: [
6586
+ /* @__PURE__ */ jsx("title", { children: "Org chart" }),
6587
+ /* @__PURE__ */ jsx("desc", { children: desc }),
6588
+ /* @__PURE__ */ jsx("g", { "data-hex-org-chart-links": true, children: links.map((l) => /* @__PURE__ */ jsx(
6589
+ "path",
6590
+ {
6591
+ d: `M${l.source.x},${l.source.y} C${l.source.x},${(l.source.y + l.target.y) / 2} ${l.target.x},${(l.source.y + l.target.y) / 2} ${l.target.x},${l.target.y}`,
6592
+ fill: "none",
6593
+ stroke: "hsl(var(--muted-foreground))",
6594
+ strokeOpacity: 0.5,
6595
+ strokeWidth: 1
6596
+ },
6597
+ l.id
6598
+ )) }),
6599
+ /* @__PURE__ */ jsx("g", { "data-hex-org-chart-nodes": true, children: nodes.map((n) => /* @__PURE__ */ jsxs(
6600
+ "g",
6601
+ {
6602
+ "data-hex-org-chart-node": true,
6603
+ "data-depth": n.depth,
6604
+ transform: `translate(${n.x - nodeWidth / 2},${n.y - nodeHeight / 2})`,
6605
+ style: collapsible || onNodeClick ? { cursor: "pointer" } : void 0,
6606
+ onClick: collapsible || onNodeClick ? () => handleClick(n.node) : void 0,
6607
+ children: [
6608
+ /* @__PURE__ */ jsx(
6609
+ "rect",
6610
+ {
6611
+ width: nodeWidth,
6612
+ height: nodeHeight,
6613
+ rx: 8,
6614
+ ry: 8,
6615
+ fill: "hsl(var(--card))",
6616
+ stroke: "hsl(var(--border))",
6617
+ strokeWidth: 1
6618
+ }
6619
+ ),
6620
+ /* @__PURE__ */ jsx(
6621
+ "text",
6622
+ {
6623
+ x: 12,
6624
+ y: 24,
6625
+ fontSize: 13,
6626
+ fontWeight: 600,
6627
+ fill: "hsl(var(--foreground))",
6628
+ children: truncate(n.node.label, Math.floor((nodeWidth - 24) / 7))
6629
+ }
6630
+ ),
6631
+ n.node.subtitle ? /* @__PURE__ */ jsx(
6632
+ "text",
6633
+ {
6634
+ x: 12,
6635
+ y: 42,
6636
+ fontSize: 11,
6637
+ fill: "hsl(var(--muted-foreground))",
6638
+ children: truncate(n.node.subtitle, Math.floor((nodeWidth - 24) / 6))
6639
+ }
6640
+ ) : null,
6641
+ n.collapsedCount > 0 ? /* @__PURE__ */ jsxs("g", { transform: `translate(${nodeWidth - 28},${nodeHeight - 18})`, children: [
6642
+ /* @__PURE__ */ jsx(
6643
+ "rect",
6644
+ {
6645
+ width: 20,
6646
+ height: 14,
6647
+ rx: 7,
6648
+ ry: 7,
6649
+ fill: "hsl(var(--primary))",
6650
+ opacity: 0.85
6651
+ }
6652
+ ),
6653
+ /* @__PURE__ */ jsx(
6654
+ "text",
6655
+ {
6656
+ x: 10,
6657
+ y: 10,
6658
+ fontSize: 9,
6659
+ fontWeight: 600,
6660
+ fill: "hsl(var(--primary-foreground))",
6661
+ textAnchor: "middle",
6662
+ children: `+${n.collapsedCount}`
6663
+ }
6664
+ )
6665
+ ] }) : null
6666
+ ]
6667
+ },
6668
+ n.node.id
6669
+ )) })
6670
+ ]
6671
+ }
6672
+ );
6673
+ }
6674
+ function seedCollapsed(root, maxDepth) {
6675
+ if (!Number.isFinite(maxDepth)) return /* @__PURE__ */ new Set();
6676
+ const collapsed = /* @__PURE__ */ new Set();
6677
+ const walk = (n, depth) => {
6678
+ if (depth >= maxDepth && n.children && n.children.length > 0) {
6679
+ collapsed.add(n.id);
6680
+ return;
6681
+ }
6682
+ n.children?.forEach((c) => walk(c, depth + 1));
6683
+ };
6684
+ walk(root, 0);
6685
+ return collapsed;
6686
+ }
6687
+ function layout3(d3h, root, collapsed, width, height, nodeHeight) {
6688
+ const indexById = /* @__PURE__ */ new Map();
6689
+ indexNodes(root, indexById);
6690
+ const visible = pruneCollapsed(root, collapsed);
6691
+ const hierarchy = d3h.hierarchy(visible);
6692
+ const layoutRoot = d3h.tree().size([width - 32, height - nodeHeight])(hierarchy);
6693
+ const nodes = [];
6694
+ layoutRoot.each((d) => {
6695
+ const original = indexById.get(d.data.id);
6696
+ const collapsedCount = collapsed.has(d.data.id) && original ? countDescendants(original) : 0;
6697
+ nodes.push({ node: d.data, x: d.x + 16, y: d.y + nodeHeight / 2, depth: d.depth, collapsedCount });
6698
+ });
6699
+ const links = layoutRoot.links().map((link, i) => ({
6700
+ source: { x: link.source.x + 16, y: link.source.y + nodeHeight / 2 },
6701
+ target: { x: link.target.x + 16, y: link.target.y + nodeHeight / 2 },
6702
+ id: `l-${i}`
6703
+ }));
6704
+ return { nodes, links };
6705
+ }
6706
+ function pruneCollapsed(node, collapsed) {
6707
+ if (collapsed.has(node.id)) {
6708
+ return { ...node, children: void 0 };
6709
+ }
6710
+ if (!node.children || node.children.length === 0) return node;
6711
+ return { ...node, children: node.children.map((c) => pruneCollapsed(c, collapsed)) };
6712
+ }
6713
+ function indexNodes(node, out) {
6714
+ out.set(node.id, node);
6715
+ if (!node.children) return;
6716
+ for (const c of node.children) indexNodes(c, out);
6717
+ }
6718
+ function countDescendants(node) {
6719
+ if (!node.children || node.children.length === 0) return 0;
6720
+ return node.children.reduce((sum, c) => sum + 1 + countDescendants(c), 0);
6721
+ }
6722
+ function truncate(s, max) {
6723
+ if (s.length <= max) return s;
6724
+ return `${s.slice(0, Math.max(1, max - 1))}\u2026`;
6725
+ }
6726
+ function depthOpacity(depth, maxDepth) {
6727
+ if (maxDepth <= 1) return 0.85;
6728
+ const t = (depth - 1) / Math.max(1, maxDepth - 1);
6729
+ return 0.85 - t * 0.4;
6730
+ }
6731
+ function Sunburst({
6732
+ root,
6733
+ drillable = true,
6734
+ centerLabel,
6735
+ size = 400,
6736
+ onSegmentClick,
6737
+ className,
6738
+ ...rest
6739
+ }) {
6740
+ const [d3h, setD3h] = React44.useState(null);
6741
+ const [d3s, setD3s] = React44.useState(null);
6742
+ const [focusId, setFocusId] = React44.useState(root.id);
6743
+ React44.useEffect(() => {
6744
+ let cancelled = false;
6745
+ void Promise.all([import('d3-hierarchy'), import('d3-shape')]).then(([h, s]) => {
6746
+ if (cancelled) return;
6747
+ setD3h(h);
6748
+ setD3s(s);
6749
+ });
6750
+ return () => {
6751
+ cancelled = true;
6752
+ };
6753
+ }, []);
6754
+ React44.useEffect(() => {
6755
+ setFocusId(root.id);
6756
+ }, [root]);
6757
+ if (!d3h || !d3s) {
6758
+ return /* @__PURE__ */ jsx(
6759
+ "div",
6760
+ {
6761
+ "data-hex-sunburst-loading": true,
6762
+ "aria-busy": "true",
6763
+ className: cn("inline-block bg-muted/20", className),
6764
+ style: { width: size, height: size }
6765
+ }
6766
+ );
6767
+ }
6768
+ const focused = findNode(root, focusId) ?? root;
6769
+ const radius = size / 2;
6770
+ const segments = layout4(d3h, focused, radius);
6771
+ const maxDepth = segments.reduce((m, s) => Math.max(m, s.depth), 1);
6772
+ const arc = d3s.arc().startAngle((s) => s.x0).endAngle((s) => s.x1).innerRadius((s) => s.y0).outerRadius((s) => s.y1).padAngle(5e-3).padRadius(radius);
6773
+ const handleSegmentClick = (segment) => {
6774
+ onSegmentClick?.(segment.node);
6775
+ if (!drillable) return;
6776
+ const hasChildren = segment.node.children && segment.node.children.length > 0;
6777
+ if (!hasChildren) return;
6778
+ setFocusId(segment.node.id);
6779
+ };
6780
+ const handleCenterClick = () => {
6781
+ if (!drillable || focusId === root.id) return;
6782
+ setFocusId(root.id);
6783
+ };
6784
+ const desc = `Sunburst with ${segments.length} segments, focused on "${focused.label}"`;
6785
+ return /* @__PURE__ */ jsxs(
6786
+ "svg",
6787
+ {
6788
+ ...rest,
6789
+ "data-hex-sunburst": true,
6790
+ "data-focus-id": focusId,
6791
+ role: "img",
6792
+ width: size,
6793
+ height: size,
6794
+ viewBox: `${-radius} ${-radius} ${size} ${size}`,
6795
+ className: cn("block", className),
6796
+ children: [
6797
+ /* @__PURE__ */ jsx("title", { children: "Sunburst" }),
6798
+ /* @__PURE__ */ jsx("desc", { children: desc }),
6799
+ /* @__PURE__ */ jsx("g", { "data-hex-sunburst-segments": true, children: segments.filter((s) => s.depth > 0).map((s) => /* @__PURE__ */ jsx(
6800
+ "path",
6801
+ {
6802
+ "data-hex-sunburst-segment": true,
6803
+ "data-depth": s.depth,
6804
+ d: arc(s) ?? "",
6805
+ fill: pickChartHue(s.rootSiblingIdx),
6806
+ fillOpacity: depthOpacity(s.depth, maxDepth),
6807
+ stroke: "hsl(var(--background))",
6808
+ strokeWidth: 1,
6809
+ style: drillable || onSegmentClick ? { cursor: "pointer" } : void 0,
6810
+ onClick: drillable || onSegmentClick ? () => handleSegmentClick(s) : void 0
6811
+ },
6812
+ s.node.id
6813
+ )) }),
6814
+ /* @__PURE__ */ jsx("g", { "data-hex-sunburst-labels": true, "aria-hidden": "true", pointerEvents: "none", children: segments.filter((s) => s.depth > 0 && s.x1 - s.x0 > 0.18).map((s) => {
6815
+ const angle = (s.x0 + s.x1) / 2;
6816
+ const rMid = (s.y0 + s.y1) / 2;
6817
+ const cx = Math.sin(angle) * rMid;
6818
+ const cy = -Math.cos(angle) * rMid;
6819
+ return /* @__PURE__ */ jsx(
6820
+ "text",
6821
+ {
6822
+ x: cx,
6823
+ y: cy,
6824
+ textAnchor: "middle",
6825
+ dy: "0.35em",
6826
+ fontSize: 11,
6827
+ fontWeight: 500,
6828
+ fill: "hsl(var(--background))",
6829
+ style: { paintOrder: "stroke", stroke: "hsl(var(--foreground) / 0.25)", strokeWidth: 2 },
6830
+ children: s.node.label
6831
+ },
6832
+ `${s.node.id}-label`
6833
+ );
6834
+ }) }),
6835
+ /* @__PURE__ */ jsxs(
6836
+ "g",
6837
+ {
6838
+ "data-hex-sunburst-center": true,
6839
+ style: drillable && focusId !== root.id ? { cursor: "pointer" } : void 0,
6840
+ onClick: drillable ? handleCenterClick : void 0,
6841
+ children: [
6842
+ /* @__PURE__ */ jsx("circle", { r: radius / 4, fill: "hsl(var(--card))", stroke: "hsl(var(--border))", strokeWidth: 1 }),
6843
+ centerLabel ? /* @__PURE__ */ jsx("foreignObject", { x: -radius / 4, y: -radius / 4, width: radius / 2, height: radius / 2, children: /* @__PURE__ */ jsx(
6844
+ "div",
6845
+ {
6846
+ style: {
6847
+ width: "100%",
6848
+ height: "100%",
6849
+ display: "flex",
6850
+ alignItems: "center",
6851
+ justifyContent: "center",
6852
+ textAlign: "center",
6853
+ fontSize: 12,
6854
+ padding: 4
6855
+ },
6856
+ children: centerLabel
6857
+ }
6858
+ ) }) : /* @__PURE__ */ jsx(
6859
+ "text",
6860
+ {
6861
+ textAnchor: "middle",
6862
+ dy: "0.35em",
6863
+ fontSize: 12,
6864
+ fontWeight: 600,
6865
+ fill: "hsl(var(--foreground))",
6866
+ children: focused.label
6867
+ }
6868
+ )
6869
+ ]
6870
+ }
6871
+ )
6872
+ ]
6873
+ }
6874
+ );
6875
+ }
6876
+ function layout4(d3h, focused, radius) {
6877
+ const hierarchy = d3h.hierarchy(focused).sum((d) => d.children && d.children.length > 0 ? 0 : d.value ?? 0).sort((a, b) => (b.value ?? 0) - (a.value ?? 0));
6878
+ const layoutRoot = d3h.partition().size([2 * Math.PI, radius])(hierarchy);
6879
+ const segments = [];
6880
+ layoutRoot.each((d) => {
6881
+ let cursor = d;
6882
+ while (cursor && cursor.depth > 1) cursor = cursor.parent;
6883
+ const ancestorIdx = cursor?.parent?.children?.indexOf(cursor) ?? 0;
6884
+ segments.push({
6885
+ node: d.data,
6886
+ depth: d.depth,
6887
+ x0: d.x0,
6888
+ x1: d.x1,
6889
+ y0: d.y0,
6890
+ y1: d.y1,
6891
+ rootSiblingIdx: Math.max(0, ancestorIdx)
6892
+ });
6893
+ });
6894
+ return segments;
6895
+ }
6896
+ function findNode(root, id) {
6897
+ if (root.id === id) return root;
6898
+ if (!root.children) return null;
6899
+ for (const c of root.children) {
6900
+ const f = findNode(c, id);
6901
+ if (f) return f;
6902
+ }
6903
+ return null;
6904
+ }
6905
+ function Dendrogram({
6906
+ root,
6907
+ orientation = "horizontal",
6908
+ linkShape = "step",
6909
+ width = 600,
6910
+ height = 400,
6911
+ onLeafClick,
6912
+ className,
6913
+ ...rest
6914
+ }) {
6915
+ const [d3h, setD3h] = React44.useState(null);
6916
+ React44.useEffect(() => {
6917
+ let cancelled = false;
6918
+ void import('d3-hierarchy').then((mod) => {
6919
+ if (!cancelled) setD3h(mod);
6920
+ });
6921
+ return () => {
6922
+ cancelled = true;
6923
+ };
6924
+ }, []);
6925
+ if (!d3h) {
6926
+ return /* @__PURE__ */ jsx(
6927
+ "div",
6928
+ {
6929
+ "data-hex-dendrogram-loading": true,
6930
+ "aria-busy": "true",
6931
+ className: cn("inline-block bg-muted/20", className),
6932
+ style: { width, height }
6933
+ }
6934
+ );
6935
+ }
6936
+ const { nodes, links } = layout5(d3h, root, orientation, width, height);
6937
+ const leafCount = nodes.filter((n) => n.isLeaf).length;
6938
+ const desc = `Dendrogram with ${leafCount} leaves, rooted at "${root.label}"`;
6939
+ return /* @__PURE__ */ jsxs(
6940
+ "svg",
6941
+ {
6942
+ ...rest,
6943
+ "data-hex-dendrogram": true,
6944
+ "data-orientation": orientation,
6945
+ "data-link-shape": linkShape,
6946
+ role: "img",
6947
+ width,
6948
+ height,
6949
+ viewBox: `0 0 ${width} ${height}`,
6950
+ className: cn("block", className),
6951
+ children: [
6952
+ /* @__PURE__ */ jsx("title", { children: "Dendrogram" }),
6953
+ /* @__PURE__ */ jsx("desc", { children: desc }),
6954
+ /* @__PURE__ */ jsx("g", { "data-hex-dendrogram-links": true, children: links.map((l) => /* @__PURE__ */ jsx(
6955
+ "path",
6956
+ {
6957
+ d: linkPath2(l, orientation, linkShape),
6958
+ fill: "none",
6959
+ stroke: "hsl(var(--muted-foreground))",
6960
+ strokeOpacity: 0.8,
6961
+ strokeWidth: 1
6962
+ },
6963
+ l.id
6964
+ )) }),
6965
+ /* @__PURE__ */ jsx("g", { "data-hex-dendrogram-nodes": true, children: nodes.map((n) => /* @__PURE__ */ jsxs(
6966
+ "g",
6967
+ {
6968
+ "data-hex-dendrogram-node": true,
6969
+ "data-leaf": n.isLeaf ? "true" : "false",
6970
+ "data-depth": n.depth,
6971
+ transform: `translate(${n.x},${n.y})`,
6972
+ style: n.isLeaf && onLeafClick ? { cursor: "pointer" } : void 0,
6973
+ onClick: n.isLeaf && onLeafClick ? () => onLeafClick(n.node) : void 0,
6974
+ children: [
6975
+ /* @__PURE__ */ jsx(
6976
+ "circle",
6977
+ {
6978
+ r: n.isLeaf ? 3 : 2,
6979
+ fill: n.isLeaf ? "hsl(var(--primary))" : "hsl(var(--muted-foreground))"
6980
+ }
6981
+ ),
6982
+ n.isLeaf ? /* @__PURE__ */ jsx(
6983
+ "text",
6984
+ {
6985
+ x: orientation === "horizontal" ? 6 : 0,
6986
+ y: orientation === "horizontal" ? 4 : 14,
6987
+ textAnchor: orientation === "horizontal" ? "start" : "middle",
6988
+ fontSize: 11,
6989
+ fill: "hsl(var(--foreground))",
6990
+ style: { paintOrder: "stroke" },
6991
+ stroke: "hsl(var(--background))",
6992
+ strokeWidth: 3,
6993
+ strokeLinejoin: "round",
6994
+ children: n.node.label
6995
+ }
6996
+ ) : null
6997
+ ]
6998
+ },
6999
+ n.node.id
7000
+ )) })
7001
+ ]
7002
+ }
7003
+ );
7004
+ }
7005
+ function layout5(d3h, root, orientation, width, height) {
7006
+ const hierarchy = d3h.hierarchy(root);
7007
+ const clusterFn = d3h.cluster();
7008
+ if (orientation === "horizontal") {
7009
+ clusterFn.size([height - 32, width - 120]);
7010
+ } else {
7011
+ clusterFn.size([width - 32, height - 64]);
7012
+ }
7013
+ const layoutRoot = clusterFn(hierarchy);
7014
+ const nodes = [];
7015
+ layoutRoot.each((d) => {
7016
+ const isLeaf = !d.children || d.children.length === 0;
7017
+ if (orientation === "horizontal") {
7018
+ nodes.push({ node: d.data, x: d.y + 16, y: d.x + 16, depth: d.depth, isLeaf });
7019
+ } else {
7020
+ nodes.push({ node: d.data, x: d.x + 16, y: d.y + 16, depth: d.depth, isLeaf });
7021
+ }
7022
+ });
7023
+ const links = layoutRoot.links().map((link, i) => {
7024
+ if (orientation === "horizontal") {
7025
+ return {
7026
+ source: { x: link.source.y + 16, y: link.source.x + 16 },
7027
+ target: { x: link.target.y + 16, y: link.target.x + 16 },
7028
+ id: `l-${i}`
7029
+ };
7030
+ }
7031
+ return {
7032
+ source: { x: link.source.x + 16, y: link.source.y + 16 },
7033
+ target: { x: link.target.x + 16, y: link.target.y + 16 },
7034
+ id: `l-${i}`
7035
+ };
7036
+ });
7037
+ return { nodes, links };
7038
+ }
7039
+ function linkPath2(link, orientation, linkShape) {
7040
+ const { source: s, target: t } = link;
7041
+ if (linkShape === "step") {
7042
+ if (orientation === "horizontal") {
7043
+ return `M${s.x},${s.y} H${t.x} V${t.y}`;
7044
+ }
7045
+ return `M${s.x},${s.y} V${t.y} H${t.x}`;
7046
+ }
7047
+ if (orientation === "horizontal") {
7048
+ const mx = (s.x + t.x) / 2;
7049
+ return `M${s.x},${s.y} C${mx},${s.y} ${mx},${t.y} ${t.x},${t.y}`;
7050
+ }
7051
+ const my = (s.y + t.y) / 2;
7052
+ return `M${s.x},${s.y} C${s.x},${my} ${t.x},${my} ${t.x},${t.y}`;
7053
+ }
7054
+ function Sankey({
7055
+ nodes,
7056
+ links,
7057
+ width = 720,
7058
+ height = 420,
7059
+ nodeAlign = "justify",
7060
+ nodeWidth = 12,
7061
+ nodePadding = 8,
7062
+ onLinkHover,
7063
+ onNodeClick,
7064
+ className,
7065
+ ...rest
7066
+ }) {
7067
+ const [d3s, setD3s] = React44.useState(null);
7068
+ React44.useEffect(() => {
7069
+ let cancelled = false;
7070
+ void import('d3-sankey').then((mod) => {
7071
+ if (!cancelled) setD3s(mod);
7072
+ });
7073
+ return () => {
7074
+ cancelled = true;
7075
+ };
7076
+ }, []);
7077
+ const laidOut = React44.useMemo(() => {
7078
+ if (!d3s) return null;
7079
+ return layout6(d3s, nodes, links, width, height, nodeAlign, nodeWidth, nodePadding);
7080
+ }, [d3s, nodes, links, width, height, nodeAlign, nodeWidth, nodePadding]);
7081
+ if (!d3s || !laidOut) {
7082
+ return /* @__PURE__ */ jsx(
7083
+ "div",
7084
+ {
7085
+ "data-hex-sankey-loading": true,
7086
+ "aria-busy": "true",
7087
+ "aria-label": "Loading Sankey diagram",
7088
+ className: cn("inline-block bg-muted/20", className),
7089
+ style: { width, height }
7090
+ }
7091
+ );
7092
+ }
7093
+ const { nodes: laidOutNodes, links: laidOutLinks } = laidOut;
7094
+ const desc = `Sankey diagram with ${nodes.length} node${nodes.length === 1 ? "" : "s"} and ${links.length} link${links.length === 1 ? "" : "s"}`;
7095
+ return /* @__PURE__ */ jsxs(
7096
+ "svg",
7097
+ {
7098
+ ...rest,
7099
+ "data-hex-sankey": true,
7100
+ "data-node-align": nodeAlign,
7101
+ role: "img",
7102
+ width,
7103
+ height,
7104
+ viewBox: `0 0 ${width} ${height}`,
7105
+ className: cn("block", className),
7106
+ children: [
7107
+ /* @__PURE__ */ jsx("title", { children: "Sankey diagram" }),
7108
+ /* @__PURE__ */ jsx("desc", { children: desc }),
7109
+ /* @__PURE__ */ jsx("g", { "data-hex-sankey-links": true, fill: "none", children: laidOutLinks.map((l, i) => {
7110
+ const interactive = Boolean(onLinkHover);
7111
+ const fireHover = (link) => onLinkHover?.(link);
7112
+ const stroke = pickChartHue(l.sourceIdx);
7113
+ return /* @__PURE__ */ jsx(
7114
+ "path",
7115
+ {
7116
+ "data-hex-sankey-link": true,
7117
+ d: l.d,
7118
+ stroke,
7119
+ strokeOpacity: 0.45,
7120
+ strokeWidth: Math.max(1, l.width),
7121
+ role: interactive ? "button" : void 0,
7122
+ tabIndex: interactive ? 0 : void 0,
7123
+ "aria-label": interactive ? `Flow: ${l.original.source} \u2192 ${l.original.target} (${l.original.value})` : void 0,
7124
+ style: {
7125
+ cursor: interactive ? "pointer" : void 0,
7126
+ transition: "stroke-opacity 120ms ease"
7127
+ },
7128
+ onMouseEnter: interactive ? () => fireHover(l.original) : void 0,
7129
+ onMouseLeave: interactive ? () => fireHover(null) : void 0,
7130
+ onFocus: interactive ? () => fireHover(l.original) : void 0,
7131
+ onBlur: interactive ? () => fireHover(null) : void 0
7132
+ },
7133
+ `${l.original.source}-${l.original.target}-${i}`
7134
+ );
7135
+ }) }),
7136
+ /* @__PURE__ */ jsx("g", { "data-hex-sankey-nodes": true, children: laidOutNodes.map((n) => {
7137
+ const w = n.x1 - n.x0;
7138
+ const h = n.y1 - n.y0;
7139
+ const isRightSide = n.x0 > width / 2;
7140
+ const interactive = Boolean(onNodeClick);
7141
+ const handleActivate = () => onNodeClick?.(n.original);
7142
+ const fill = pickChartHue(n.idx);
7143
+ return /* @__PURE__ */ jsxs(
7144
+ "g",
7145
+ {
7146
+ "data-hex-sankey-node": true,
7147
+ transform: `translate(${n.x0},${n.y0})`,
7148
+ role: interactive ? "button" : void 0,
7149
+ tabIndex: interactive ? 0 : void 0,
7150
+ "aria-label": interactive ? n.original.label : void 0,
7151
+ style: interactive ? { cursor: "pointer" } : void 0,
7152
+ onClick: interactive ? handleActivate : void 0,
7153
+ onKeyDown: interactive ? (e) => activateOnKey(e, handleActivate) : void 0,
7154
+ children: [
7155
+ /* @__PURE__ */ jsx("rect", { width: w, height: h, fill, stroke: "hsl(var(--background))" }),
7156
+ /* @__PURE__ */ jsx(
7157
+ "text",
7158
+ {
7159
+ x: isRightSide ? -6 : w + 6,
7160
+ y: h / 2,
7161
+ dy: "0.35em",
7162
+ fontSize: 12,
7163
+ fontWeight: 500,
7164
+ fill: "hsl(var(--foreground))",
7165
+ textAnchor: isRightSide ? "end" : "start",
7166
+ style: { pointerEvents: "none" },
7167
+ children: n.original.label
7168
+ }
7169
+ )
7170
+ ]
7171
+ },
7172
+ n.original.id
7173
+ );
7174
+ }) })
7175
+ ]
7176
+ }
7177
+ );
7178
+ }
7179
+ function layout6(d3s, nodes, links, width, height, align, nodeWidth, nodePadding) {
7180
+ const alignFn = align === "left" ? d3s.sankeyLeft : align === "right" ? d3s.sankeyRight : align === "center" ? d3s.sankeyCenter : d3s.sankeyJustify;
7181
+ const nodesClone = nodes.map((n) => ({ ...n }));
7182
+ const linksClone = links.map((l) => ({ ...l }));
7183
+ const sankeyGen = d3s.sankey().nodeId((d) => d.id).nodeAlign(alignFn).nodeWidth(nodeWidth).nodePadding(nodePadding).extent([
7184
+ [1, 1],
7185
+ [width - 1, height - 1]
7186
+ ]);
7187
+ const result = sankeyGen({ nodes: nodesClone, links: linksClone });
7188
+ const linkPath3 = d3s.sankeyLinkHorizontal();
7189
+ const idByIndex = /* @__PURE__ */ new Map();
7190
+ nodes.forEach((n, i) => idByIndex.set(n.id, i));
7191
+ return {
7192
+ nodes: result.nodes.map((n, i) => ({
7193
+ original: { ...nodes[i] },
7194
+ x0: n.x0 ?? 0,
7195
+ x1: n.x1 ?? 0,
7196
+ y0: n.y0 ?? 0,
7197
+ y1: n.y1 ?? 0,
7198
+ idx: i
7199
+ })),
7200
+ links: result.links.map((l, i) => {
7201
+ const sourceId = typeof l.source === "string" ? l.source : l.source.id;
7202
+ const targetId = typeof l.target === "string" ? l.target : l.target.id;
7203
+ return {
7204
+ original: { ...links[i], source: sourceId, target: targetId, value: l.value },
7205
+ d: linkPath3(l) ?? "",
7206
+ width: l.width ?? 1,
7207
+ sourceIdx: idByIndex.get(sourceId) ?? 0
7208
+ };
7209
+ })
7210
+ };
7211
+ }
7212
+ function activateOnKey(e, fn) {
7213
+ if (e.key === "Enter" || e.key === " ") {
7214
+ e.preventDefault();
7215
+ fn();
7216
+ }
7217
+ }
7218
+ function Funnel({
7219
+ stages,
7220
+ width = 480,
7221
+ height = 360,
7222
+ gap = 4,
7223
+ showConversion = true,
7224
+ onStageClick,
7225
+ className,
7226
+ ...rest
7227
+ }) {
7228
+ const laidOut = layout7(stages, width, height, gap);
7229
+ const desc = `Funnel with ${stages.length} stage${stages.length === 1 ? "" : "s"}, peak value ${stages[0]?.value ?? 0}`;
7230
+ return /* @__PURE__ */ jsxs(
7231
+ "svg",
7232
+ {
7233
+ ...rest,
7234
+ "data-hex-funnel": true,
7235
+ role: "img",
7236
+ width,
7237
+ height,
7238
+ viewBox: `0 0 ${width} ${height}`,
7239
+ className: cn("block", className),
7240
+ children: [
7241
+ /* @__PURE__ */ jsx("title", { children: "Funnel chart" }),
7242
+ /* @__PURE__ */ jsx("desc", { children: desc }),
7243
+ /* @__PURE__ */ jsx("g", { "data-hex-funnel-stages": true, children: laidOut.map((s) => {
7244
+ const interactive = Boolean(onStageClick);
7245
+ const handleActivate = () => onStageClick?.(s.stage);
7246
+ return /* @__PURE__ */ jsxs(
7247
+ "g",
7248
+ {
7249
+ "data-hex-funnel-stage": true,
7250
+ "data-depth": s.depth,
7251
+ role: interactive ? "button" : void 0,
7252
+ tabIndex: interactive ? 0 : void 0,
7253
+ "aria-label": interactive ? `${s.stage.label}: ${s.stage.value}` : void 0,
7254
+ style: interactive ? { cursor: "pointer" } : void 0,
7255
+ onClick: interactive ? handleActivate : void 0,
7256
+ onKeyDown: interactive ? (e) => activateOnKey2(e, handleActivate) : void 0,
7257
+ children: [
7258
+ /* @__PURE__ */ jsx(
7259
+ "polygon",
7260
+ {
7261
+ points: `${s.topLeft},${s.yTop} ${s.topRight},${s.yTop} ${s.bottomRight},${s.yBottom} ${s.bottomLeft},${s.yBottom}`,
7262
+ fill: pickChartHue(s.depth),
7263
+ fillOpacity: 0.85,
7264
+ stroke: "hsl(var(--background))",
7265
+ strokeWidth: 1
7266
+ }
7267
+ ),
7268
+ /* @__PURE__ */ jsx(
7269
+ "text",
7270
+ {
7271
+ x: width / 2,
7272
+ y: (s.yTop + s.yBottom) / 2,
7273
+ dy: "0.35em",
7274
+ textAnchor: "middle",
7275
+ fontSize: 12,
7276
+ fontWeight: 600,
7277
+ fill: "hsl(var(--background))",
7278
+ style: {
7279
+ pointerEvents: "none",
7280
+ paintOrder: "stroke",
7281
+ stroke: "hsl(var(--foreground) / 0.45)",
7282
+ strokeWidth: 2
7283
+ },
7284
+ children: `${s.stage.label} \xB7 ${s.stage.value.toLocaleString()}`
7285
+ }
7286
+ )
7287
+ ]
7288
+ },
7289
+ s.stage.id
7290
+ );
7291
+ }) }),
7292
+ showConversion ? /* @__PURE__ */ jsx("g", { "data-hex-funnel-conversions": true, children: laidOut.map(
7293
+ (s) => s.conversionFromPrev != null ? /* @__PURE__ */ jsx(
7294
+ "text",
7295
+ {
7296
+ "data-hex-funnel-conversion": true,
7297
+ x: width - 4,
7298
+ y: s.yTop - 2,
7299
+ textAnchor: "end",
7300
+ fontSize: 10,
7301
+ fill: "hsl(var(--muted-foreground))",
7302
+ children: formatPct(s.conversionFromPrev)
7303
+ },
7304
+ `conv-${s.stage.id}`
7305
+ ) : null
7306
+ ) }) : null
7307
+ ]
7308
+ }
7309
+ );
7310
+ }
7311
+ function layout7(stages, width, height, gap) {
7312
+ if (stages.length === 0) return [];
7313
+ const peak = Math.max(...stages.map((s) => s.value), 0) || 1;
7314
+ const usableHeight = height - gap * Math.max(0, stages.length - 1);
7315
+ const stageHeight = usableHeight / stages.length;
7316
+ const cx = width / 2;
7317
+ return stages.map((stage, i) => {
7318
+ const yTop = i * (stageHeight + gap);
7319
+ const yBottom = yTop + stageHeight;
7320
+ const topRatio = stage.value / peak;
7321
+ const next = stages[i + 1];
7322
+ const bottomRatio = next ? next.value / peak : topRatio;
7323
+ const topHalfWidth = width / 2 * Math.max(0, Math.min(1, topRatio));
7324
+ const bottomHalfWidth = width / 2 * Math.max(0, Math.min(1, bottomRatio));
7325
+ const prev = stages[i - 1];
7326
+ const conversionFromPrev = prev && prev.value > 0 ? stage.value / prev.value : null;
7327
+ return {
7328
+ stage,
7329
+ depth: i,
7330
+ topLeft: cx - topHalfWidth,
7331
+ topRight: cx + topHalfWidth,
7332
+ bottomLeft: cx - bottomHalfWidth,
7333
+ bottomRight: cx + bottomHalfWidth,
7334
+ yTop,
7335
+ yBottom,
7336
+ conversionFromPrev
7337
+ };
7338
+ });
7339
+ }
7340
+ function formatPct(ratio) {
7341
+ if (!Number.isFinite(ratio)) return "\u2014";
7342
+ return `${(ratio * 100).toFixed(ratio < 0.1 ? 1 : 0)}%`;
7343
+ }
7344
+ function activateOnKey2(e, fn) {
7345
+ if (e.key === "Enter" || e.key === " ") {
7346
+ e.preventDefault();
7347
+ fn();
7348
+ }
7349
+ }
7350
+ function Pyramid({
7351
+ tiers,
7352
+ shape = "widening",
7353
+ width = 480,
7354
+ height = 360,
7355
+ gap = 4,
7356
+ showValues = true,
7357
+ onTierClick,
7358
+ className,
7359
+ ...rest
7360
+ }) {
7361
+ const laidOut = layout8(tiers, shape, width, height, gap);
7362
+ const desc = `Pyramid with ${tiers.length} tier${tiers.length === 1 ? "" : "s"} (${shape})`;
7363
+ const warnedRef = React44.useRef(false);
7364
+ const nodeEnv = globalThis.process?.env?.NODE_ENV;
7365
+ if (nodeEnv !== "production" && !warnedRef.current && tiers.length > 7) {
7366
+ warnedRef.current = true;
7367
+ console.warn(
7368
+ `[hex-core/Pyramid] ${tiers.length} tiers \u2014 labels become unreadable past ~7. Group adjacent tiers or switch to TreeMap / OrgChart.`
7369
+ );
7370
+ }
7371
+ return /* @__PURE__ */ jsxs(
7372
+ "svg",
7373
+ {
7374
+ ...rest,
7375
+ "data-hex-pyramid": true,
7376
+ "data-shape": shape,
7377
+ role: "img",
7378
+ width,
7379
+ height,
7380
+ viewBox: `0 0 ${width} ${height}`,
7381
+ className: cn("block", className),
7382
+ children: [
7383
+ /* @__PURE__ */ jsx("title", { children: "Pyramid chart" }),
7384
+ /* @__PURE__ */ jsx("desc", { children: desc }),
7385
+ /* @__PURE__ */ jsx("g", { "data-hex-pyramid-tiers": true, children: laidOut.map((t) => {
7386
+ const valueText = showValues && t.tier.value != null ? ` \xB7 ${t.tier.value.toLocaleString()}` : "";
7387
+ const interactive = Boolean(onTierClick);
7388
+ const handleActivate = () => onTierClick?.(t.tier);
7389
+ return /* @__PURE__ */ jsxs(
7390
+ "g",
7391
+ {
7392
+ "data-hex-pyramid-tier": true,
7393
+ "data-depth": t.depth,
7394
+ role: interactive ? "button" : void 0,
7395
+ tabIndex: interactive ? 0 : void 0,
7396
+ "aria-label": interactive ? `${t.tier.label}${valueText}` : void 0,
7397
+ style: interactive ? { cursor: "pointer" } : void 0,
7398
+ onClick: interactive ? handleActivate : void 0,
7399
+ onKeyDown: interactive ? (e) => activateOnKey3(e, handleActivate) : void 0,
7400
+ children: [
7401
+ /* @__PURE__ */ jsx(
7402
+ "polygon",
7403
+ {
7404
+ points: `${t.topLeft},${t.yTop} ${t.topRight},${t.yTop} ${t.bottomRight},${t.yBottom} ${t.bottomLeft},${t.yBottom}`,
7405
+ fill: pickChartHue(t.depth),
7406
+ fillOpacity: 0.85,
7407
+ stroke: "hsl(var(--background))",
7408
+ strokeWidth: 1
7409
+ }
7410
+ ),
7411
+ /* @__PURE__ */ jsx(
7412
+ "text",
7413
+ {
7414
+ x: width / 2,
7415
+ y: (t.yTop + t.yBottom) / 2,
7416
+ dy: "0.35em",
7417
+ textAnchor: "middle",
7418
+ fontSize: 12,
7419
+ fontWeight: 600,
7420
+ fill: "hsl(var(--background))",
7421
+ style: {
7422
+ pointerEvents: "none",
7423
+ paintOrder: "stroke",
7424
+ stroke: "hsl(var(--foreground) / 0.45)",
7425
+ strokeWidth: 2
7426
+ },
7427
+ children: `${t.tier.label}${valueText}`
7428
+ }
7429
+ )
7430
+ ]
7431
+ },
7432
+ t.tier.id
7433
+ );
7434
+ }) })
7435
+ ]
7436
+ }
7437
+ );
7438
+ }
7439
+ function layout8(tiers, shape, width, height, gap) {
7440
+ if (tiers.length === 0) return [];
7441
+ const usableHeight = height - gap * Math.max(0, tiers.length - 1);
7442
+ const tierHeight = usableHeight / tiers.length;
7443
+ const cx = width / 2;
7444
+ const total = tiers.length;
7445
+ const ratioAt = (i) => {
7446
+ const t = total === 1 ? 0 : i / (total - 1);
7447
+ return shape === "widening" ? 0.2 + 0.8 * t : 1 - 0.8 * t;
7448
+ };
7449
+ return tiers.map((tier, i) => {
7450
+ const yTop = i * (tierHeight + gap);
7451
+ const yBottom = yTop + tierHeight;
7452
+ const topRatio = ratioAt(i);
7453
+ const bottomRatio = i + 1 < total ? ratioAt(i + 1) : topRatio;
7454
+ const topHalfWidth = width / 2 * topRatio;
7455
+ const bottomHalfWidth = width / 2 * bottomRatio;
7456
+ return {
7457
+ tier,
7458
+ depth: i,
7459
+ topLeft: cx - topHalfWidth,
7460
+ topRight: cx + topHalfWidth,
7461
+ bottomLeft: cx - bottomHalfWidth,
7462
+ bottomRight: cx + bottomHalfWidth,
7463
+ yTop,
7464
+ yBottom
7465
+ };
7466
+ });
7467
+ }
7468
+ function activateOnKey3(e, fn) {
7469
+ if (e.key === "Enter" || e.key === " ") {
7470
+ e.preventDefault();
7471
+ fn();
7472
+ }
7473
+ }
7474
+ function Flowchart({
7475
+ nodes,
7476
+ edges,
7477
+ direction = "vertical",
7478
+ width = 720,
7479
+ height = 480,
7480
+ nodeWidth = 140,
7481
+ nodeHeight = 48,
7482
+ onNodeClick,
7483
+ className,
7484
+ ...rest
7485
+ }) {
7486
+ const { nodes: laidOutNodes, edges: laidOutEdges } = React44.useMemo(
7487
+ () => layout9(nodes, edges, direction, width, height, nodeWidth, nodeHeight),
7488
+ [nodes, edges, direction, width, height, nodeWidth, nodeHeight]
7489
+ );
7490
+ const desc = `Flowchart with ${nodes.length} node${nodes.length === 1 ? "" : "s"} and ${edges.length} edge${edges.length === 1 ? "" : "s"}, laid out ${direction}`;
7491
+ const arrowId = React44.useId().replace(/:/g, "-");
7492
+ return /* @__PURE__ */ jsxs(
7493
+ "svg",
7494
+ {
7495
+ ...rest,
7496
+ "data-hex-flowchart": true,
7497
+ "data-direction": direction,
7498
+ role: "img",
7499
+ width,
7500
+ height,
7501
+ viewBox: `0 0 ${width} ${height}`,
7502
+ className: cn("block", className),
7503
+ children: [
7504
+ /* @__PURE__ */ jsx("title", { children: "Flowchart" }),
7505
+ /* @__PURE__ */ jsx("desc", { children: desc }),
7506
+ /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx(
7507
+ "marker",
7508
+ {
7509
+ id: `hex-flowchart-arrow-${arrowId}`,
7510
+ viewBox: "0 0 10 10",
7511
+ refX: "10",
7512
+ refY: "5",
7513
+ markerWidth: "6",
7514
+ markerHeight: "6",
7515
+ orient: "auto-start-reverse",
7516
+ children: /* @__PURE__ */ jsx("path", { d: "M 0 0 L 10 5 L 0 10 z", fill: "hsl(var(--muted-foreground))" })
7517
+ }
7518
+ ) }),
7519
+ /* @__PURE__ */ jsx("g", { "data-hex-flowchart-edges": true, children: laidOutEdges.map((e, i) => /* @__PURE__ */ jsxs("g", { "data-hex-flowchart-edge": true, children: [
7520
+ /* @__PURE__ */ jsx(
7521
+ "path",
7522
+ {
7523
+ d: edgePath(e.from, e.to, direction),
7524
+ fill: "none",
7525
+ stroke: "hsl(var(--muted-foreground))",
7526
+ strokeOpacity: 0.7,
7527
+ strokeWidth: 1.25,
7528
+ markerEnd: `url(#hex-flowchart-arrow-${arrowId})`
7529
+ }
7530
+ ),
7531
+ e.edge.label ? /* @__PURE__ */ jsx(
7532
+ "text",
7533
+ {
7534
+ x: (e.from.x + e.to.x) / 2,
7535
+ y: (e.from.y + e.to.y) / 2 - 4,
7536
+ textAnchor: "middle",
7537
+ fontSize: 10,
7538
+ fill: "hsl(var(--muted-foreground))",
7539
+ style: {
7540
+ paintOrder: "stroke"
7541
+ },
7542
+ stroke: "hsl(var(--background))",
7543
+ strokeWidth: 3,
7544
+ strokeLinejoin: "round",
7545
+ children: e.edge.label
7546
+ }
7547
+ ) : null
7548
+ ] }, `${e.edge.source}-${e.edge.target}-${i}`)) }),
7549
+ /* @__PURE__ */ jsx("g", { "data-hex-flowchart-nodes": true, children: laidOutNodes.map((n) => {
7550
+ const shape = n.node.shape ?? "rect";
7551
+ const interactive = Boolean(onNodeClick);
7552
+ const handleActivate = () => onNodeClick?.(n.node);
7553
+ const truncated = truncate2(n.node.label, Math.floor((nodeWidth - 16) / 7));
7554
+ const isTruncated = truncated !== n.node.label;
7555
+ return /* @__PURE__ */ jsxs(
7556
+ "g",
7557
+ {
7558
+ "data-hex-flowchart-node": true,
7559
+ "data-shape": shape,
7560
+ "data-rank": n.rank,
7561
+ transform: `translate(${n.x - nodeWidth / 2},${n.y - nodeHeight / 2})`,
7562
+ role: interactive ? "button" : void 0,
7563
+ tabIndex: interactive ? 0 : void 0,
7564
+ "aria-label": interactive ? n.node.label : void 0,
7565
+ style: interactive ? { cursor: "pointer" } : void 0,
7566
+ onClick: interactive ? handleActivate : void 0,
7567
+ onKeyDown: interactive ? (e) => activateOnKey4(e, handleActivate) : void 0,
7568
+ children: [
7569
+ isTruncated ? /* @__PURE__ */ jsx("title", { children: n.node.label }) : null,
7570
+ shape === "diamond" ? /* @__PURE__ */ jsx(
7571
+ "polygon",
7572
+ {
7573
+ points: `${nodeWidth / 2},0 ${nodeWidth},${nodeHeight / 2} ${nodeWidth / 2},${nodeHeight} 0,${nodeHeight / 2}`,
7574
+ fill: "hsl(var(--card))",
7575
+ stroke: "hsl(var(--border))",
7576
+ strokeWidth: 1
7577
+ }
7578
+ ) : /* @__PURE__ */ jsx(
7579
+ "rect",
7580
+ {
7581
+ width: nodeWidth,
7582
+ height: nodeHeight,
7583
+ rx: shape === "round" ? nodeHeight / 2 : 6,
7584
+ ry: shape === "round" ? nodeHeight / 2 : 6,
7585
+ fill: "hsl(var(--card))",
7586
+ stroke: "hsl(var(--border))",
7587
+ strokeWidth: 1
7588
+ }
7589
+ ),
7590
+ /* @__PURE__ */ jsx(
7591
+ "text",
7592
+ {
7593
+ x: nodeWidth / 2,
7594
+ y: nodeHeight / 2,
7595
+ dy: "0.35em",
7596
+ textAnchor: "middle",
7597
+ fontSize: 12,
7598
+ fontWeight: 500,
7599
+ fill: "hsl(var(--foreground))",
7600
+ style: { pointerEvents: "none" },
7601
+ children: truncated
7602
+ }
7603
+ )
7604
+ ]
7605
+ },
7606
+ n.node.id
7607
+ );
7608
+ }) })
7609
+ ]
7610
+ }
7611
+ );
7612
+ }
7613
+ function layout9(nodes, edges, direction, width, height, nodeWidth, nodeHeight) {
7614
+ if (nodes.length === 0) return { nodes: [], edges: [] };
7615
+ const byId = new Map(nodes.map((n) => [n.id, n]));
7616
+ const ranks = computeRanks(nodes, edges, byId);
7617
+ const maxRank = Math.max(...ranks.values(), 0);
7618
+ const rankGroups = /* @__PURE__ */ new Map();
7619
+ for (const n of nodes) {
7620
+ const r = ranks.get(n.id) ?? 0;
7621
+ const arr = rankGroups.get(r) ?? [];
7622
+ arr.push(n.id);
7623
+ rankGroups.set(r, arr);
7624
+ }
7625
+ const parentsOf = /* @__PURE__ */ new Map();
7626
+ for (const n of nodes) parentsOf.set(n.id, []);
7627
+ for (const e of edges) {
7628
+ const arr = parentsOf.get(e.target);
7629
+ if (arr && byId.has(e.source)) arr.push(e.source);
7630
+ }
7631
+ const orderInRank = /* @__PURE__ */ new Map();
7632
+ for (let r = 0; r <= maxRank; r++) {
7633
+ const group = rankGroups.get(r) ?? [];
7634
+ if (r > 0) {
7635
+ group.sort((a, b) => meanParentIndex(a, parentsOf, orderInRank) - meanParentIndex(b, parentsOf, orderInRank));
7636
+ }
7637
+ group.forEach((id, i) => orderInRank.set(id, i));
7638
+ }
7639
+ const positions = /* @__PURE__ */ new Map();
7640
+ const margin = 32;
7641
+ const usableMain = (direction === "vertical" ? height : width) - margin * 2;
7642
+ const usableCross = (direction === "vertical" ? width : height) - margin * 2;
7643
+ const rankCount = maxRank + 1;
7644
+ const mainStep = rankCount > 1 ? usableMain / (rankCount - 1) : 0;
7645
+ for (let r = 0; r <= maxRank; r++) {
7646
+ const group = rankGroups.get(r) ?? [];
7647
+ const crossStep = group.length > 0 ? usableCross / (group.length + 1) : 0;
7648
+ group.forEach((id, i) => {
7649
+ const cross = margin + crossStep * (i + 1);
7650
+ const main = margin + mainStep * r;
7651
+ if (direction === "vertical") {
7652
+ positions.set(id, { x: cross, y: main, rank: r });
7653
+ } else {
7654
+ positions.set(id, { x: main, y: cross, rank: r });
7655
+ }
7656
+ });
7657
+ }
7658
+ const laidOutNodes = nodes.map((n) => {
7659
+ const p = positions.get(n.id) ?? { x: 0, y: 0, rank: 0 };
7660
+ return { node: n, x: p.x, y: p.y, rank: p.rank };
7661
+ });
7662
+ const laidOutEdges = edges.map((edge) => {
7663
+ const sp = positions.get(edge.source);
7664
+ const tp = positions.get(edge.target);
7665
+ if (!sp || !tp) return null;
7666
+ const from = direction === "vertical" ? { x: sp.x, y: sp.y + nodeHeight / 2 } : { x: sp.x + nodeWidth / 2, y: sp.y };
7667
+ const to = direction === "vertical" ? { x: tp.x, y: tp.y - nodeHeight / 2 } : { x: tp.x - nodeWidth / 2, y: tp.y };
7668
+ return { edge, from, to };
7669
+ }).filter((e) => e !== null);
7670
+ return { nodes: laidOutNodes, edges: laidOutEdges };
7671
+ }
7672
+ function meanParentIndex(id, parentsOf, orderInRank) {
7673
+ const parents = parentsOf.get(id) ?? [];
7674
+ if (parents.length === 0) return Number.POSITIVE_INFINITY;
7675
+ let sum = 0;
7676
+ let count = 0;
7677
+ for (const p of parents) {
7678
+ const idx = orderInRank.get(p);
7679
+ if (idx != null) {
7680
+ sum += idx;
7681
+ count++;
7682
+ }
7683
+ }
7684
+ return count === 0 ? Number.POSITIVE_INFINITY : sum / count;
7685
+ }
7686
+ function computeRanks(nodes, edges, byId) {
7687
+ const ranks = /* @__PURE__ */ new Map();
7688
+ const incoming = /* @__PURE__ */ new Map();
7689
+ for (const n of nodes) incoming.set(n.id, []);
7690
+ for (const e of edges) {
7691
+ const arr = incoming.get(e.target);
7692
+ if (arr && byId.has(e.source)) arr.push(e.source);
7693
+ }
7694
+ const memo = /* @__PURE__ */ new Map();
7695
+ const visiting = /* @__PURE__ */ new Set();
7696
+ const rankOf = (id) => {
7697
+ const cached = memo.get(id);
7698
+ if (cached !== void 0) return cached;
7699
+ if (visiting.has(id)) return 0;
7700
+ visiting.add(id);
7701
+ try {
7702
+ const node = byId.get(id);
7703
+ if (node?.rank != null) {
7704
+ memo.set(id, node.rank);
7705
+ return node.rank;
7706
+ }
7707
+ const parents = incoming.get(id) ?? [];
7708
+ const r = parents.length === 0 ? 0 : 1 + Math.max(...parents.map((p) => rankOf(p)));
7709
+ memo.set(id, r);
7710
+ return r;
7711
+ } finally {
7712
+ visiting.delete(id);
7713
+ }
7714
+ };
7715
+ for (const n of nodes) ranks.set(n.id, rankOf(n.id));
7716
+ return ranks;
7717
+ }
7718
+ function edgePath(from, to, direction) {
7719
+ if (direction === "vertical") {
7720
+ const my = (from.y + to.y) / 2;
7721
+ return `M${from.x},${from.y} C${from.x},${my} ${to.x},${my} ${to.x},${to.y}`;
7722
+ }
7723
+ const mx = (from.x + to.x) / 2;
7724
+ return `M${from.x},${from.y} C${mx},${from.y} ${mx},${to.y} ${to.x},${to.y}`;
7725
+ }
7726
+ function truncate2(s, max) {
7727
+ if (s.length <= max) return s;
7728
+ return `${s.slice(0, Math.max(1, max - 1))}\u2026`;
7729
+ }
7730
+ function activateOnKey4(e, fn) {
7731
+ if (e.key === "Enter" || e.key === " ") {
7732
+ e.preventDefault();
7733
+ fn();
7734
+ }
7735
+ }
7736
+ function Venn({
7737
+ sets,
7738
+ size = 360,
7739
+ onSetClick,
7740
+ className,
7741
+ ...rest
7742
+ }) {
7743
+ const circles = React44.useMemo(() => layout10(sets, size), [sets, size]);
7744
+ const desc = `Venn diagram with ${sets.length} set${sets.length === 1 ? "" : "s"}: ${sets.map((s) => s.label).join(", ") || "(empty)"}`;
7745
+ const warnedRef = React44.useRef(false);
7746
+ const nodeEnv = globalThis.process?.env?.NODE_ENV;
7747
+ if (nodeEnv !== "production" && !warnedRef.current && (sets.length === 0 || sets.length > 3)) {
7748
+ warnedRef.current = true;
7749
+ console.warn(
7750
+ `[hex-core/Venn] Got ${sets.length} sets \u2014 Venn supports 2 or 3. Use TreeMap or Matrix for higher-arity overlaps.`
7751
+ );
7752
+ }
7753
+ if (sets.length === 0 || sets.length > 3) {
7754
+ return /* @__PURE__ */ jsxs(
7755
+ "svg",
7756
+ {
7757
+ ...rest,
7758
+ "data-hex-venn": true,
7759
+ "data-set-count": sets.length,
7760
+ role: "img",
7761
+ width: size,
7762
+ height: size,
7763
+ viewBox: `0 0 ${size} ${size}`,
7764
+ className: cn("block", className),
7765
+ children: [
7766
+ /* @__PURE__ */ jsx("title", { children: "Venn diagram" }),
7767
+ /* @__PURE__ */ jsx("desc", { children: desc }),
7768
+ /* @__PURE__ */ jsx(
7769
+ "text",
7770
+ {
7771
+ x: size / 2,
7772
+ y: size / 2,
7773
+ textAnchor: "middle",
7774
+ dy: "0.35em",
7775
+ fontSize: 12,
7776
+ fill: "hsl(var(--muted-foreground))",
7777
+ children: sets.length === 0 ? "No sets" : `Venn supports 2\u20133 sets (got ${sets.length})`
7778
+ }
7779
+ )
7780
+ ]
7781
+ }
7782
+ );
7783
+ }
7784
+ return /* @__PURE__ */ jsxs(
7785
+ "svg",
7786
+ {
7787
+ ...rest,
7788
+ "data-hex-venn": true,
7789
+ "data-set-count": sets.length,
7790
+ role: "img",
7791
+ width: size,
7792
+ height: size,
7793
+ viewBox: `0 0 ${size} ${size}`,
7794
+ className: cn("block", className),
7795
+ children: [
7796
+ /* @__PURE__ */ jsx("title", { children: "Venn diagram" }),
7797
+ /* @__PURE__ */ jsx("desc", { children: desc }),
7798
+ /* @__PURE__ */ jsx("g", { "data-hex-venn-sets": true, children: circles.map((c) => {
7799
+ const interactive = Boolean(onSetClick);
7800
+ const handleActivate = () => onSetClick?.(c.set);
7801
+ return /* @__PURE__ */ jsx(
7802
+ "g",
7803
+ {
7804
+ "data-hex-venn-set": true,
7805
+ "data-depth": c.depth,
7806
+ role: interactive ? "button" : void 0,
7807
+ tabIndex: interactive ? 0 : void 0,
7808
+ "aria-label": interactive ? c.set.label : void 0,
7809
+ style: interactive ? { cursor: "pointer" } : void 0,
7810
+ onClick: interactive ? handleActivate : void 0,
7811
+ onKeyDown: interactive ? (e) => activateOnKey5(e, handleActivate) : void 0,
7812
+ children: /* @__PURE__ */ jsx(
7813
+ "circle",
7814
+ {
7815
+ cx: c.cx,
7816
+ cy: c.cy,
7817
+ r: c.r,
7818
+ fill: pickChartHue(c.depth),
7819
+ fillOpacity: 0.45,
7820
+ stroke: "hsl(var(--background))",
7821
+ strokeWidth: 1.5
7822
+ }
7823
+ )
7824
+ },
7825
+ c.set.id
7826
+ );
7827
+ }) }),
7828
+ /* @__PURE__ */ jsx("g", { "data-hex-venn-labels": true, children: circles.map((c) => /* @__PURE__ */ jsx(
7829
+ "text",
7830
+ {
7831
+ "data-hex-venn-label": true,
7832
+ x: c.labelX,
7833
+ y: c.labelY,
7834
+ textAnchor: "middle",
7835
+ dy: "0.35em",
7836
+ fontSize: 12,
7837
+ fontWeight: 600,
7838
+ fill: "hsl(var(--foreground))",
7839
+ style: { paintOrder: "stroke", pointerEvents: "none" },
7840
+ stroke: "hsl(var(--background))",
7841
+ strokeWidth: 3,
7842
+ strokeLinejoin: "round",
7843
+ children: `${c.set.label}${c.set.value != null ? ` (${c.set.value})` : ""}`
7844
+ },
7845
+ `label-${c.set.id}`
7846
+ )) })
7847
+ ]
7848
+ }
7849
+ );
7850
+ }
7851
+ function layout10(sets, size) {
7852
+ if (sets.length === 0 || sets.length > 3) return [];
7853
+ const cx = size / 2;
7854
+ const cy = size / 2;
7855
+ const r = size * 0.22;
7856
+ const sep = size * 0.12;
7857
+ if (sets.length === 1) {
7858
+ return [{ set: sets[0], cx, cy, r, labelX: cx, labelY: cy - r - 8, depth: 0 }];
7859
+ }
7860
+ if (sets.length === 2) {
7861
+ return sets.map((s, i) => {
7862
+ const dx = i === 0 ? -sep : sep;
7863
+ return {
7864
+ set: s,
7865
+ cx: cx + dx,
7866
+ cy,
7867
+ r,
7868
+ labelX: cx + dx + (i === 0 ? -r * 0.6 : r * 0.6),
7869
+ labelY: cy - r - 8,
7870
+ depth: i
7871
+ };
7872
+ });
7873
+ }
7874
+ const verticalOffset = sep * 0.866;
7875
+ const triangle = [
7876
+ { dx: -sep, dy: verticalOffset },
7877
+ // bottom-left
7878
+ { dx: sep, dy: verticalOffset },
7879
+ // bottom-right
7880
+ { dx: 0, dy: -verticalOffset }
7881
+ // top
7882
+ ];
7883
+ const LABEL_PUSH = 1.7;
7884
+ const TOP_LABEL_NUDGE = -8;
7885
+ const BOTTOM_LABEL_NUDGE = 16;
7886
+ return sets.map((s, i) => {
7887
+ const { dx, dy } = triangle[i] ?? triangle[0];
7888
+ const isTop = i === 2;
7889
+ return {
7890
+ set: s,
7891
+ cx: cx + dx,
7892
+ cy: cy + dy,
7893
+ r,
7894
+ labelX: cx + dx * LABEL_PUSH,
7895
+ labelY: cy + dy * LABEL_PUSH + (isTop ? TOP_LABEL_NUDGE : BOTTOM_LABEL_NUDGE),
7896
+ depth: i
7897
+ };
7898
+ });
7899
+ }
7900
+ function activateOnKey5(e, fn) {
7901
+ if (e.key === "Enter" || e.key === " ") {
7902
+ e.preventDefault();
7903
+ fn();
7904
+ }
7905
+ }
7906
+ function Chord({
7907
+ nodes,
7908
+ matrix,
7909
+ size = 480,
7910
+ padAngle = 0.04,
7911
+ onChordHover,
7912
+ onNodeClick,
7913
+ className,
7914
+ ...rest
7915
+ }) {
7916
+ const [d3c, setD3c] = React44.useState(null);
7917
+ const [d3s, setD3s] = React44.useState(null);
7918
+ const [importError, setImportError] = React44.useState(false);
7919
+ React44.useEffect(() => {
7920
+ let cancelled = false;
7921
+ void Promise.all([import('d3-chord'), import('d3-shape')]).then(
7922
+ ([c, s]) => {
7923
+ if (cancelled) return;
7924
+ setD3c(c);
7925
+ setD3s(s);
7926
+ },
7927
+ () => {
7928
+ if (!cancelled) setImportError(true);
7929
+ }
7930
+ );
7931
+ return () => {
7932
+ cancelled = true;
7933
+ };
7934
+ }, []);
7935
+ const laidOut = React44.useMemo(() => {
7936
+ if (!d3c || !d3s) return null;
7937
+ return layout11(d3c, d3s, nodes, matrix, size, padAngle);
7938
+ }, [d3c, d3s, nodes, matrix, size, padAngle]);
7939
+ if (importError) {
7940
+ return /* @__PURE__ */ jsxs(
7941
+ "div",
7942
+ {
7943
+ "data-hex-chord-error": true,
7944
+ role: "alert",
7945
+ className: cn(
7946
+ "inline-flex items-center justify-center rounded-md border border-destructive/40 bg-destructive/5 p-3 text-sm text-destructive",
7947
+ className
7948
+ ),
7949
+ style: { width: size, height: size },
7950
+ children: [
7951
+ "Install ",
7952
+ /* @__PURE__ */ jsx("code", { className: "mx-1", children: "d3-chord" }),
7953
+ " to view this chord diagram."
7954
+ ]
7955
+ }
7956
+ );
7957
+ }
7958
+ if (!laidOut) {
7959
+ return /* @__PURE__ */ jsx(
7960
+ "div",
7961
+ {
7962
+ "data-hex-chord-loading": true,
7963
+ "aria-busy": "true",
7964
+ "aria-label": "Loading chord diagram",
7965
+ className: cn("inline-block bg-muted/20", className),
7966
+ style: { width: size, height: size }
7967
+ }
7968
+ );
7969
+ }
7970
+ const { arcs, chords } = laidOut;
7971
+ const desc = `Chord diagram with ${nodes.length} node${nodes.length === 1 ? "" : "s"} and ${chords.length} ribbon${chords.length === 1 ? "" : "s"}`;
7972
+ const radius = size / 2;
7973
+ return /* @__PURE__ */ jsxs(
7974
+ "svg",
7975
+ {
7976
+ ...rest,
7977
+ "data-hex-chord": true,
7978
+ role: "img",
7979
+ width: size,
7980
+ height: size,
7981
+ viewBox: `${-radius} ${-radius} ${size} ${size}`,
7982
+ className: cn("block", className),
7983
+ children: [
7984
+ /* @__PURE__ */ jsx("title", { children: "Chord diagram" }),
7985
+ /* @__PURE__ */ jsx("desc", { children: desc }),
7986
+ /* @__PURE__ */ jsx("g", { "data-hex-chord-ribbons": true, children: chords.map((c, i) => {
7987
+ const interactive = Boolean(onChordHover);
7988
+ const payload = {
7989
+ source: c.source,
7990
+ target: c.target,
7991
+ sourceValue: c.sourceValue,
7992
+ targetValue: c.targetValue
7993
+ };
7994
+ const fireHover = (chord) => onChordHover?.(chord);
7995
+ return /* @__PURE__ */ jsx(
7996
+ "path",
7997
+ {
7998
+ "data-hex-chord-ribbon": true,
7999
+ d: c.d,
8000
+ fill: pickChartHue(c.sourceIdx),
8001
+ fillOpacity: 0.55,
8002
+ stroke: "hsl(var(--background))",
8003
+ strokeWidth: 0.5,
8004
+ role: interactive ? "button" : void 0,
8005
+ tabIndex: interactive ? 0 : void 0,
8006
+ "aria-label": interactive ? `Flow from ${c.source.label} to ${c.target.label}, value ${c.sourceValue}; reverse value ${c.targetValue}` : void 0,
8007
+ style: {
8008
+ cursor: interactive ? "pointer" : void 0,
8009
+ transition: "fill-opacity 120ms ease"
8010
+ },
8011
+ onMouseEnter: interactive ? () => fireHover(payload) : void 0,
8012
+ onMouseLeave: interactive ? () => fireHover(null) : void 0,
8013
+ onFocus: interactive ? () => fireHover(payload) : void 0,
8014
+ onBlur: interactive ? () => fireHover(null) : void 0
8015
+ },
8016
+ `${c.source.id}-${c.target.id}-${i}`
8017
+ );
8018
+ }) }),
8019
+ /* @__PURE__ */ jsx("g", { "data-hex-chord-arcs": true, children: arcs.map((a) => {
8020
+ const interactive = Boolean(onNodeClick);
8021
+ const handleActivate = () => onNodeClick?.(a.node);
8022
+ const flip = a.labelAngle > Math.PI / 2 && a.labelAngle < 3 * Math.PI / 2;
8023
+ return /* @__PURE__ */ jsxs(
8024
+ "g",
8025
+ {
8026
+ "data-hex-chord-arc": true,
8027
+ "data-depth": a.depth,
8028
+ role: interactive ? "button" : void 0,
8029
+ tabIndex: interactive ? 0 : void 0,
8030
+ "aria-label": interactive ? a.node.label : void 0,
8031
+ style: interactive ? { cursor: "pointer" } : void 0,
8032
+ onClick: interactive ? handleActivate : void 0,
8033
+ onKeyDown: interactive ? (e) => activateOnKey6(e, handleActivate) : void 0,
8034
+ children: [
8035
+ /* @__PURE__ */ jsx(
8036
+ "path",
8037
+ {
8038
+ d: a.d,
8039
+ fill: pickChartHue(a.depth),
8040
+ stroke: "hsl(var(--background))",
8041
+ strokeWidth: 1
8042
+ }
8043
+ ),
8044
+ /* @__PURE__ */ jsx(
8045
+ "text",
8046
+ {
8047
+ x: a.labelX,
8048
+ y: a.labelY,
8049
+ textAnchor: flip ? "end" : "start",
8050
+ dy: "0.35em",
8051
+ transform: flip ? `rotate(${a.labelAngle * 180 / Math.PI - 270} ${a.labelX} ${a.labelY})` : `rotate(${a.labelAngle * 180 / Math.PI - 90} ${a.labelX} ${a.labelY})`,
8052
+ fontSize: 11,
8053
+ fill: "hsl(var(--foreground))",
8054
+ style: { pointerEvents: "none" },
8055
+ children: a.node.label
8056
+ }
8057
+ )
8058
+ ]
8059
+ },
8060
+ a.node.id
8061
+ );
8062
+ }) })
8063
+ ]
8064
+ }
8065
+ );
8066
+ }
8067
+ function layout11(d3c, d3s, nodes, matrix, size, padAngle) {
8068
+ const longestLabel = nodes.reduce((m, n) => Math.max(m, n.label.length), 0);
8069
+ const labelMargin = Math.max(40, longestLabel * 6 + 16);
8070
+ const radius = size / 2 - labelMargin;
8071
+ const innerRadius = radius - 12;
8072
+ const outerRadius = radius;
8073
+ const chordGen = d3c.chord().padAngle(padAngle).sortSubgroups((a, b) => a < b ? 1 : a > b ? -1 : 0);
8074
+ const result = chordGen(matrix);
8075
+ const arc = d3s.arc().innerRadius(innerRadius).outerRadius(outerRadius);
8076
+ const ribbon = d3c.ribbon().radius(innerRadius);
8077
+ const arcs = result.groups.map((g) => {
8078
+ const node = nodes[g.index] ?? { id: `_${g.index}`, label: `Set ${g.index}` };
8079
+ const midAngle = (g.startAngle + g.endAngle) / 2;
8080
+ const labelRadius = outerRadius + 6;
8081
+ return {
8082
+ node,
8083
+ depth: g.index,
8084
+ d: arc({ startAngle: g.startAngle, endAngle: g.endAngle, innerRadius, outerRadius }) ?? "",
8085
+ labelX: labelRadius * Math.sin(midAngle),
8086
+ labelY: -labelRadius * Math.cos(midAngle),
8087
+ labelAngle: midAngle
8088
+ };
8089
+ });
8090
+ const buildRibbonInput = (c) => ({
8091
+ source: { ...c.source, radius: innerRadius },
8092
+ target: { ...c.target, radius: innerRadius }
8093
+ });
8094
+ const chords = result.filter((c) => c.source.value > 0 || c.target.value > 0).map((c) => ({
8095
+ source: nodes[c.source.index] ?? { id: `_${c.source.index}`, label: `Set ${c.source.index}` },
8096
+ target: nodes[c.target.index] ?? { id: `_${c.target.index}`, label: `Set ${c.target.index}` },
8097
+ sourceValue: c.source.value,
8098
+ targetValue: c.target.value,
8099
+ d: ribbon(buildRibbonInput(c)) ?? "",
8100
+ sourceIdx: c.source.index
8101
+ }));
8102
+ return { arcs, chords };
8103
+ }
8104
+ function activateOnKey6(e, fn) {
8105
+ if (e.key === "Enter" || e.key === " ") {
8106
+ e.preventDefault();
8107
+ fn();
8108
+ }
8109
+ }
8110
+ function Arc({
8111
+ nodes,
8112
+ edges,
8113
+ width = 720,
8114
+ height = 360,
8115
+ nodeRadius = 5,
8116
+ onEdgeHover,
8117
+ onNodeClick,
8118
+ className,
8119
+ ...rest
8120
+ }) {
8121
+ const laidOut = React44.useMemo(() => layout12(nodes, edges, width, height), [nodes, edges, width, height]);
8122
+ const desc = `Arc diagram with ${nodes.length} node${nodes.length === 1 ? "" : "s"} and ${edges.length} edge${edges.length === 1 ? "" : "s"}`;
8123
+ return /* @__PURE__ */ jsxs(
8124
+ "svg",
8125
+ {
8126
+ ...rest,
8127
+ "data-hex-arc": true,
8128
+ role: "img",
8129
+ width,
8130
+ height,
8131
+ viewBox: `0 0 ${width} ${height}`,
8132
+ className: cn("block", className),
8133
+ children: [
8134
+ /* @__PURE__ */ jsx("title", { children: "Arc diagram" }),
8135
+ /* @__PURE__ */ jsx("desc", { children: desc }),
8136
+ /* @__PURE__ */ jsx("g", { "data-hex-arc-edges": true, fill: "none", children: laidOut.edges.map((e, i) => {
8137
+ const interactive = Boolean(onEdgeHover);
8138
+ const fireHover = (edge) => onEdgeHover?.(edge);
8139
+ return /* @__PURE__ */ jsx(
8140
+ "path",
8141
+ {
8142
+ "data-hex-arc-edge": true,
8143
+ d: e.d,
8144
+ stroke: "hsl(var(--primary))",
8145
+ strokeOpacity: 0.5,
8146
+ strokeWidth: Math.max(1, e.width),
8147
+ role: interactive ? "button" : void 0,
8148
+ tabIndex: interactive ? 0 : void 0,
8149
+ "aria-label": interactive ? `Edge between ${e.edge.source} and ${e.edge.target}${e.edge.value != null ? `, value ${e.edge.value}` : ""}` : void 0,
8150
+ style: {
8151
+ cursor: interactive ? "pointer" : void 0,
8152
+ transition: "stroke-opacity 120ms ease"
8153
+ },
8154
+ onMouseEnter: interactive ? () => fireHover(e.edge) : void 0,
8155
+ onMouseLeave: interactive ? () => fireHover(null) : void 0,
8156
+ onFocus: interactive ? () => fireHover(e.edge) : void 0,
8157
+ onBlur: interactive ? () => fireHover(null) : void 0
8158
+ },
8159
+ `${e.edge.source}-${e.edge.target}-${i}`
8160
+ );
8161
+ }) }),
8162
+ /* @__PURE__ */ jsx("g", { "data-hex-arc-nodes": true, children: laidOut.nodes.map((n) => {
8163
+ const interactive = Boolean(onNodeClick);
8164
+ const handleActivate = () => onNodeClick?.(n.node);
8165
+ return /* @__PURE__ */ jsxs(
8166
+ "g",
8167
+ {
8168
+ "data-hex-arc-node": true,
8169
+ "data-depth": n.depth,
8170
+ role: interactive ? "button" : void 0,
8171
+ tabIndex: interactive ? 0 : void 0,
8172
+ "aria-label": interactive ? n.node.label : void 0,
8173
+ style: interactive ? { cursor: "pointer" } : void 0,
8174
+ onClick: interactive ? handleActivate : void 0,
8175
+ onKeyDown: interactive ? (e) => activateOnKey7(e, handleActivate) : void 0,
8176
+ children: [
8177
+ /* @__PURE__ */ jsx(
8178
+ "circle",
8179
+ {
8180
+ cx: n.x,
8181
+ cy: n.y,
8182
+ r: nodeRadius,
8183
+ fill: "hsl(var(--primary))",
8184
+ stroke: "hsl(var(--background))",
8185
+ strokeWidth: 2
8186
+ }
8187
+ ),
8188
+ /* @__PURE__ */ jsx(
8189
+ "text",
8190
+ {
8191
+ x: n.x,
8192
+ y: n.y + nodeRadius + 14,
8193
+ textAnchor: "middle",
8194
+ fontSize: 10,
8195
+ fill: "hsl(var(--foreground))",
8196
+ style: { pointerEvents: "none" },
8197
+ children: n.node.label
8198
+ }
8199
+ )
8200
+ ]
8201
+ },
8202
+ n.node.id
8203
+ );
8204
+ }) })
8205
+ ]
8206
+ }
8207
+ );
8208
+ }
8209
+ function layout12(nodes, edges, width, height) {
8210
+ if (nodes.length === 0) return { nodes: [], edges: [] };
8211
+ const margin = 32;
8212
+ const baselineY = height - 32;
8213
+ const usable = width - margin * 2;
8214
+ const step = nodes.length > 1 ? usable / (nodes.length - 1) : 0;
8215
+ const positions = /* @__PURE__ */ new Map();
8216
+ const laidOutNodes = nodes.map((node, i) => {
8217
+ const x = nodes.length === 1 ? width / 2 : margin + step * i;
8218
+ positions.set(node.id, { x, y: baselineY, index: i });
8219
+ return { node, x, y: baselineY, depth: i };
8220
+ });
8221
+ const maxValue = edges.reduce((m, e) => Math.max(m, e.value ?? 1), 1);
8222
+ const laidOutEdges = edges.map((edge) => {
8223
+ const sp = positions.get(edge.source);
8224
+ const tp = positions.get(edge.target);
8225
+ if (!sp || !tp) return null;
8226
+ const [a, b] = sp.x < tp.x ? [sp, tp] : [tp, sp];
8227
+ const span = (b.x - a.x) / 2;
8228
+ const ctrlY = baselineY - span;
8229
+ const d = `M${a.x},${baselineY} C${a.x},${ctrlY} ${b.x},${ctrlY} ${b.x},${baselineY}`;
8230
+ return {
8231
+ edge,
8232
+ d,
8233
+ width: 1 + (edge.value ?? 1) / maxValue * 3
8234
+ };
8235
+ }).filter((e) => e !== null);
8236
+ return { nodes: laidOutNodes, edges: laidOutEdges };
8237
+ }
8238
+ function activateOnKey7(e, fn) {
8239
+ if (e.key === "Enter" || e.key === " ") {
8240
+ e.preventDefault();
8241
+ fn();
8242
+ }
8243
+ }
8244
+ var MIN_CELL_SIZE_FOR_VALUE = 28;
8245
+ function Matrix({
8246
+ nodes,
8247
+ matrix,
8248
+ size = 480,
8249
+ labelMargin = 80,
8250
+ showValues = true,
8251
+ onCellHover,
8252
+ onCellClick,
8253
+ className,
8254
+ ...rest
8255
+ }) {
8256
+ const cells = React44.useMemo(() => layout13(nodes, matrix, size, labelMargin), [nodes, matrix, size, labelMargin]);
8257
+ const desc = `Matrix with ${nodes.length} node${nodes.length === 1 ? "" : "s"} (${nodes.length}\xD7${nodes.length} cells)`;
8258
+ const cellSize = nodes.length > 0 ? (size - labelMargin) / nodes.length : 0;
8259
+ return /* @__PURE__ */ jsxs(
8260
+ "svg",
8261
+ {
8262
+ ...rest,
8263
+ "data-hex-matrix": true,
8264
+ role: "img",
8265
+ width: size,
8266
+ height: size,
8267
+ viewBox: `0 0 ${size} ${size}`,
8268
+ className: cn("block", className),
8269
+ children: [
8270
+ /* @__PURE__ */ jsx("title", { children: "Adjacency matrix" }),
8271
+ /* @__PURE__ */ jsx("desc", { children: desc }),
8272
+ /* @__PURE__ */ jsx("g", { "data-hex-matrix-rows": true, children: nodes.map((n, i) => /* @__PURE__ */ jsx(
8273
+ "text",
8274
+ {
8275
+ x: labelMargin - 4,
8276
+ y: labelMargin + cellSize * i + cellSize / 2,
8277
+ dy: "0.35em",
8278
+ textAnchor: "end",
8279
+ fontSize: 10,
8280
+ fill: "hsl(var(--foreground))",
8281
+ children: n.label
8282
+ },
8283
+ `row-${n.id}`
8284
+ )) }),
8285
+ /* @__PURE__ */ jsx("g", { "data-hex-matrix-cols": true, children: nodes.map((n, i) => /* @__PURE__ */ jsx(
8286
+ "text",
8287
+ {
8288
+ x: labelMargin + cellSize * i + cellSize / 2,
8289
+ y: labelMargin - 4,
8290
+ textAnchor: "end",
8291
+ fontSize: 10,
8292
+ fill: "hsl(var(--foreground))",
8293
+ transform: `rotate(-45 ${labelMargin + cellSize * i + cellSize / 2} ${labelMargin - 4})`,
8294
+ children: n.label
8295
+ },
8296
+ `col-${n.id}`
8297
+ )) }),
8298
+ /* @__PURE__ */ jsx("g", { "data-hex-matrix-cells": true, children: cells.map((c) => {
8299
+ const interactive = Boolean(onCellHover || onCellClick);
8300
+ const cellPayload = { row: c.row, col: c.col, value: c.value };
8301
+ const fireHover = (cell) => onCellHover?.(cell);
8302
+ const handleActivate = () => onCellClick?.(cellPayload);
8303
+ return /* @__PURE__ */ jsxs(
8304
+ "g",
8305
+ {
8306
+ "data-hex-matrix-cell": true,
8307
+ "data-row": c.rowIndex,
8308
+ "data-col": c.colIndex,
8309
+ role: interactive ? "button" : void 0,
8310
+ tabIndex: interactive ? 0 : void 0,
8311
+ "aria-label": interactive ? `${c.row.label} \u2192 ${c.col.label}: ${c.value}` : void 0,
8312
+ style: interactive ? { cursor: "pointer" } : void 0,
8313
+ onMouseEnter: onCellHover ? () => fireHover(cellPayload) : void 0,
8314
+ onMouseLeave: onCellHover ? () => fireHover(null) : void 0,
8315
+ onFocus: onCellHover ? () => fireHover(cellPayload) : void 0,
8316
+ onBlur: onCellHover ? () => fireHover(null) : void 0,
8317
+ onClick: onCellClick ? handleActivate : void 0,
8318
+ onKeyDown: onCellClick ? (e) => activateOnKey8(e, handleActivate) : void 0,
8319
+ children: [
8320
+ /* @__PURE__ */ jsx(
8321
+ "rect",
8322
+ {
8323
+ x: c.x,
8324
+ y: c.y,
8325
+ width: cellSize,
8326
+ height: cellSize,
8327
+ fill: "hsl(var(--chart-1, var(--primary)))",
8328
+ fillOpacity: 0.08 + 0.87 * c.intensity,
8329
+ stroke: "hsl(var(--background))",
8330
+ strokeWidth: 0.5
8331
+ }
8332
+ ),
8333
+ showValues && cellSize > MIN_CELL_SIZE_FOR_VALUE && c.value !== 0 ? /* @__PURE__ */ jsx(
8334
+ "text",
8335
+ {
8336
+ x: c.x + cellSize / 2,
8337
+ y: c.y + cellSize / 2,
8338
+ dy: "0.35em",
8339
+ textAnchor: "middle",
8340
+ fontSize: 9,
8341
+ fill: c.intensity > 0.55 ? "hsl(var(--background))" : "hsl(var(--foreground))",
8342
+ style: { pointerEvents: "none" },
8343
+ children: c.value
8344
+ }
8345
+ ) : null
8346
+ ]
8347
+ },
8348
+ `${c.rowIndex}-${c.colIndex}`
8349
+ );
8350
+ }) })
8351
+ ]
8352
+ }
8353
+ );
8354
+ }
8355
+ function layout13(nodes, matrix, size, labelMargin) {
8356
+ if (nodes.length === 0) return [];
8357
+ const cellSize = (size - labelMargin) / nodes.length;
8358
+ let maxValue = 0;
8359
+ for (const row of matrix) {
8360
+ for (const v of row) if (v > maxValue) maxValue = v;
8361
+ }
8362
+ const cells = [];
8363
+ for (let i = 0; i < nodes.length; i++) {
8364
+ for (let j = 0; j < nodes.length; j++) {
8365
+ const value = matrix[i]?.[j] ?? 0;
8366
+ cells.push({
8367
+ row: nodes[i],
8368
+ col: nodes[j],
8369
+ rowIndex: i,
8370
+ colIndex: j,
8371
+ value,
8372
+ x: labelMargin + cellSize * j,
8373
+ y: labelMargin + cellSize * i,
8374
+ intensity: maxValue > 0 ? value / maxValue : 0
8375
+ });
8376
+ }
8377
+ }
8378
+ return cells;
8379
+ }
8380
+ function activateOnKey8(e, fn) {
8381
+ if (e.key === "Enter" || e.key === " ") {
8382
+ e.preventDefault();
8383
+ fn();
8384
+ }
8385
+ }
8386
+ function TimeAxis({
8387
+ events,
8388
+ start,
8389
+ end,
8390
+ width = 720,
8391
+ height = 200,
8392
+ tickCount = 6,
8393
+ onEventClick,
8394
+ className,
8395
+ ...rest
8396
+ }) {
8397
+ const laidOut = React44.useMemo(
8398
+ () => layout14(events, start, end, width, height, tickCount),
8399
+ [events, start, end, width, height, tickCount]
8400
+ );
8401
+ const desc = events.length === 0 ? "Empty time axis" : `Time axis with ${events.length} event${events.length === 1 ? "" : "s"}, range ${laidOut.startLabel} to ${laidOut.endLabel}`;
8402
+ return /* @__PURE__ */ jsxs(
8403
+ "svg",
8404
+ {
8405
+ ...rest,
8406
+ "data-hex-time-axis": true,
8407
+ role: "img",
8408
+ width,
8409
+ height,
8410
+ viewBox: `0 0 ${width} ${height}`,
8411
+ className: cn("block", className),
8412
+ children: [
8413
+ /* @__PURE__ */ jsx("title", { children: "Time axis" }),
8414
+ /* @__PURE__ */ jsx("desc", { children: desc }),
8415
+ /* @__PURE__ */ jsxs("g", { "data-hex-time-axis-axis": true, children: [
8416
+ /* @__PURE__ */ jsx(
8417
+ "line",
8418
+ {
8419
+ x1: laidOut.axisLeft,
8420
+ x2: laidOut.axisRight,
8421
+ y1: laidOut.axisY,
8422
+ y2: laidOut.axisY,
8423
+ stroke: "hsl(var(--muted-foreground))",
8424
+ strokeWidth: 1
8425
+ }
8426
+ ),
8427
+ laidOut.ticks.map((tick, i) => /* @__PURE__ */ jsxs("g", { "data-hex-time-axis-tick": true, children: [
8428
+ /* @__PURE__ */ jsx(
8429
+ "line",
8430
+ {
8431
+ x1: tick.x,
8432
+ x2: tick.x,
8433
+ y1: laidOut.axisY - 4,
8434
+ y2: laidOut.axisY + 4,
8435
+ stroke: "hsl(var(--muted-foreground))",
8436
+ strokeWidth: 1
8437
+ }
8438
+ ),
8439
+ /* @__PURE__ */ jsx(
8440
+ "text",
8441
+ {
8442
+ x: tick.x,
8443
+ y: laidOut.axisY + 18,
8444
+ textAnchor: "middle",
8445
+ fontSize: 10,
8446
+ fill: "hsl(var(--muted-foreground))",
8447
+ children: tick.label
8448
+ }
8449
+ )
8450
+ ] }, `tick-${i}`))
8451
+ ] }),
8452
+ /* @__PURE__ */ jsx("g", { "data-hex-time-axis-events": true, children: laidOut.events.map((e) => {
8453
+ const interactive = Boolean(onEventClick);
8454
+ const handleActivate = () => onEventClick?.(e.event);
8455
+ return /* @__PURE__ */ jsxs(
8456
+ "g",
8457
+ {
8458
+ "data-hex-time-axis-event": true,
8459
+ "data-row": e.rowIndex,
8460
+ role: interactive ? "button" : void 0,
8461
+ tabIndex: interactive ? 0 : void 0,
8462
+ "aria-label": interactive ? `${e.event.label} on ${formatDate(toDate(e.event.date))}` : void 0,
8463
+ style: interactive ? { cursor: "pointer" } : void 0,
8464
+ onClick: interactive ? handleActivate : void 0,
8465
+ onKeyDown: interactive ? (k) => activateOnKey9(k, handleActivate) : void 0,
8466
+ children: [
8467
+ /* @__PURE__ */ jsx(
8468
+ "line",
8469
+ {
8470
+ x1: e.x,
8471
+ x2: e.x,
8472
+ y1: e.y,
8473
+ y2: laidOut.axisY,
8474
+ stroke: "hsl(var(--muted-foreground))",
8475
+ strokeOpacity: 0.65,
8476
+ strokeWidth: 1
8477
+ }
8478
+ ),
8479
+ /* @__PURE__ */ jsx(
8480
+ "circle",
8481
+ {
8482
+ cx: e.x,
8483
+ cy: e.y,
8484
+ r: 5,
8485
+ fill: "hsl(var(--primary))",
8486
+ stroke: "hsl(var(--background))",
8487
+ strokeWidth: 2
8488
+ }
8489
+ ),
8490
+ /* @__PURE__ */ jsx(
8491
+ "text",
8492
+ {
8493
+ x: e.x + 8,
8494
+ y: e.y + 4,
8495
+ fontSize: 11,
8496
+ fill: "hsl(var(--foreground))",
8497
+ style: { paintOrder: "stroke", pointerEvents: "none" },
8498
+ stroke: "hsl(var(--background))",
8499
+ strokeWidth: 3,
8500
+ strokeLinejoin: "round",
8501
+ children: e.event.label
8502
+ }
8503
+ )
8504
+ ]
8505
+ },
8506
+ e.event.id
8507
+ );
8508
+ }) })
8509
+ ]
8510
+ }
8511
+ );
8512
+ }
8513
+ function layout14(events, start, end, width, height, tickCount) {
8514
+ const margin = 32;
8515
+ const axisLeft = margin;
8516
+ const axisRight = width - margin;
8517
+ const axisY = height - 40;
8518
+ const usableWidth = axisRight - axisLeft;
8519
+ const validEvents = events.filter((e) => Number.isFinite(toDate(e.date).getTime()));
8520
+ if (validEvents.length === 0) {
8521
+ return {
8522
+ events: [],
8523
+ ticks: [],
8524
+ axisY,
8525
+ axisLeft,
8526
+ axisRight,
8527
+ startLabel: "\u2014",
8528
+ endLabel: "\u2014"
8529
+ };
8530
+ }
8531
+ const dates = validEvents.map((e) => toDate(e.date).getTime());
8532
+ const explicitMin = start != null ? toDate(start).getTime() : NaN;
8533
+ const explicitMax = end != null ? toDate(end).getTime() : NaN;
8534
+ const minTs = Number.isFinite(explicitMin) ? explicitMin : Math.min(...dates);
8535
+ const maxTs = Number.isFinite(explicitMax) ? explicitMax : Math.max(...dates);
8536
+ const span = Math.max(1, maxTs - minTs);
8537
+ const tToX = (t) => axisLeft + (t - minTs) / span * usableWidth;
8538
+ const MIN_GAP_PX = 48;
8539
+ const ROW_HEIGHT_PX = 22;
8540
+ const maxRows = Math.max(1, Math.floor((axisY - 10 - 24) / ROW_HEIGHT_PX) + 1);
8541
+ const rowsLastX = [];
8542
+ const positioned = validEvents.map((event) => ({ event, t: toDate(event.date).getTime() })).sort((a, b) => a.t - b.t || a.event.id.localeCompare(b.event.id)).map(({ event, t }) => {
8543
+ const x = tToX(t);
8544
+ let rowIndex = rowsLastX.findIndex((lastX) => x - lastX >= MIN_GAP_PX);
8545
+ if (rowIndex === -1) {
8546
+ if (rowsLastX.length < maxRows) {
8547
+ rowIndex = rowsLastX.length;
8548
+ rowsLastX.push(x);
8549
+ } else {
8550
+ rowIndex = maxRows - 1;
8551
+ rowsLastX[rowIndex] = x;
8552
+ }
8553
+ } else {
8554
+ rowsLastX[rowIndex] = x;
8555
+ }
8556
+ return { event, x, y: 24 + rowIndex * ROW_HEIGHT_PX, rowIndex };
8557
+ });
8558
+ const rawTicks = [];
8559
+ for (let i = 0; i < tickCount; i++) {
8560
+ const t = minTs + i / Math.max(1, tickCount - 1) * span;
8561
+ rawTicks.push({ x: tToX(t), label: formatTick(new Date(t), span) });
8562
+ }
8563
+ const ticks = rawTicks.map((t, i) => ({
8564
+ x: t.x,
8565
+ label: i > 0 && rawTicks[i - 1]?.label === t.label ? "" : t.label
8566
+ }));
8567
+ return {
8568
+ events: positioned,
8569
+ ticks,
8570
+ axisY,
8571
+ axisLeft,
8572
+ axisRight,
8573
+ startLabel: formatDate(new Date(minTs)),
8574
+ endLabel: formatDate(new Date(maxTs))
8575
+ };
8576
+ }
8577
+ function toDate(v) {
8578
+ if (v instanceof Date) return v;
8579
+ if (typeof v === "number") return new Date(v);
8580
+ return new Date(v);
8581
+ }
8582
+ function formatDate(d) {
8583
+ if (Number.isNaN(d.getTime())) return "\u2014";
8584
+ return d.toISOString().slice(0, 10);
8585
+ }
8586
+ function formatTick(d, spanMs) {
8587
+ if (Number.isNaN(d.getTime())) return "\u2014";
8588
+ const ONE_DAY = 24 * 60 * 60 * 1e3;
8589
+ if (spanMs <= 7 * ONE_DAY) {
8590
+ return d.toISOString().slice(0, 10);
8591
+ }
8592
+ if (spanMs <= 90 * ONE_DAY) {
8593
+ return d.toISOString().slice(5, 10);
8594
+ }
8595
+ if (spanMs <= 730 * ONE_DAY) {
8596
+ return d.toISOString().slice(0, 7);
8597
+ }
8598
+ return String(d.getUTCFullYear());
8599
+ }
8600
+ function activateOnKey9(e, fn) {
8601
+ if (e.key === "Enter" || e.key === " ") {
8602
+ e.preventDefault();
8603
+ fn();
8604
+ }
8605
+ }
8606
+ var HEADER_HEIGHT = 40;
8607
+ var ROW_PADDING = 6;
8608
+ function Gantt({
8609
+ tasks,
8610
+ width = 800,
8611
+ rowHeight = 32,
8612
+ labelMargin = 140,
8613
+ tickCount = 6,
8614
+ onTaskClick,
8615
+ className,
8616
+ ...rest
8617
+ }) {
8618
+ const laidOut = React44.useMemo(
8619
+ () => layout15(tasks, width, rowHeight, labelMargin, tickCount),
8620
+ [tasks, width, rowHeight, labelMargin, tickCount]
8621
+ );
8622
+ const totalHeight = HEADER_HEIGHT + tasks.length * rowHeight + ROW_PADDING;
8623
+ const desc = `Gantt with ${tasks.length} task${tasks.length === 1 ? "" : "s"}, range ${laidOut.startLabel} to ${laidOut.endLabel}`;
8624
+ const arrowId = React44.useId().replace(/:/g, "-");
8625
+ return /* @__PURE__ */ jsxs(
8626
+ "svg",
8627
+ {
8628
+ ...rest,
8629
+ "data-hex-gantt": true,
8630
+ role: "img",
8631
+ width,
8632
+ height: totalHeight,
8633
+ viewBox: `0 0 ${width} ${totalHeight}`,
8634
+ className: cn("block", className),
8635
+ children: [
8636
+ /* @__PURE__ */ jsx("title", { children: "Gantt chart" }),
8637
+ /* @__PURE__ */ jsx("desc", { children: desc }),
8638
+ /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx(
8639
+ "marker",
8640
+ {
8641
+ id: `hex-gantt-arrow-${arrowId}`,
8642
+ viewBox: "0 0 10 10",
8643
+ refX: "10",
8644
+ refY: "5",
8645
+ markerWidth: "5",
8646
+ markerHeight: "5",
8647
+ orient: "auto-start-reverse",
8648
+ children: /* @__PURE__ */ jsx("path", { d: "M 0 0 L 10 5 L 0 10 z", fill: "hsl(var(--muted-foreground))" })
8649
+ }
8650
+ ) }),
8651
+ /* @__PURE__ */ jsxs("g", { "data-hex-gantt-axis": true, children: [
8652
+ /* @__PURE__ */ jsx(
8653
+ "line",
8654
+ {
8655
+ x1: labelMargin,
8656
+ x2: width - 16,
8657
+ y1: HEADER_HEIGHT - 8,
8658
+ y2: HEADER_HEIGHT - 8,
8659
+ stroke: "hsl(var(--muted-foreground))",
8660
+ strokeWidth: 1
8661
+ }
8662
+ ),
8663
+ laidOut.ticks.map((t, i) => /* @__PURE__ */ jsxs("g", { "data-hex-gantt-tick": true, children: [
8664
+ /* @__PURE__ */ jsx(
8665
+ "line",
8666
+ {
8667
+ x1: t.x,
8668
+ x2: t.x,
8669
+ y1: HEADER_HEIGHT - 8,
8670
+ y2: totalHeight - 4,
8671
+ stroke: "hsl(var(--muted-foreground))",
8672
+ strokeOpacity: 0.15,
8673
+ strokeWidth: 1
8674
+ }
8675
+ ),
8676
+ /* @__PURE__ */ jsx(
8677
+ "text",
8678
+ {
8679
+ x: t.x,
8680
+ y: HEADER_HEIGHT - 14,
8681
+ textAnchor: "middle",
8682
+ fontSize: 10,
8683
+ fill: "hsl(var(--muted-foreground))",
8684
+ children: t.label
8685
+ }
8686
+ )
8687
+ ] }, `tick-${i}`))
8688
+ ] }),
8689
+ /* @__PURE__ */ jsx("g", { "data-hex-gantt-labels": true, children: laidOut.tasks.map((t) => /* @__PURE__ */ jsx(
8690
+ "text",
8691
+ {
8692
+ x: labelMargin - 8,
8693
+ y: t.y + t.h / 2,
8694
+ dy: "0.35em",
8695
+ textAnchor: "end",
8696
+ fontSize: 11,
8697
+ fill: "hsl(var(--foreground))",
8698
+ children: t.task.label
8699
+ },
8700
+ `label-${t.task.id}`
8701
+ )) }),
8702
+ /* @__PURE__ */ jsx("g", { "data-hex-gantt-deps": true, fill: "none", children: laidOut.deps.map((d) => {
8703
+ const ELBOW = 8;
8704
+ const needsJog = d.to.x < d.from.x + ELBOW;
8705
+ const path = needsJog ? `M${d.from.x},${d.from.y} L${d.from.x + ELBOW},${d.from.y} L${d.from.x + ELBOW},${d.to.y} L${d.to.x},${d.to.y}` : `M${d.from.x},${d.from.y} L${(d.from.x + d.to.x) / 2},${d.from.y} L${(d.from.x + d.to.x) / 2},${d.to.y} L${d.to.x},${d.to.y}`;
8706
+ return /* @__PURE__ */ jsx(
8707
+ "path",
8708
+ {
8709
+ "data-hex-gantt-dep": true,
8710
+ d: path,
8711
+ stroke: "hsl(var(--muted-foreground))",
8712
+ strokeOpacity: 0.55,
8713
+ strokeWidth: 1,
8714
+ markerEnd: `url(#hex-gantt-arrow-${arrowId})`
8715
+ },
8716
+ d.id
8717
+ );
8718
+ }) }),
8719
+ /* @__PURE__ */ jsx("g", { "data-hex-gantt-tasks": true, children: laidOut.tasks.map((t) => {
8720
+ const interactive = Boolean(onTaskClick);
8721
+ const handleActivate = () => onTaskClick?.(t.task);
8722
+ return /* @__PURE__ */ jsxs(
8723
+ "g",
8724
+ {
8725
+ "data-hex-gantt-task": true,
8726
+ "data-row": t.rowIndex,
8727
+ role: interactive ? "button" : void 0,
8728
+ tabIndex: interactive ? 0 : void 0,
8729
+ "aria-label": interactive ? `${t.task.label}, ${formatDate2(toDate2(t.task.start))} to ${formatDate2(toDate2(t.task.end))}${t.task.progress != null ? `, ${Math.round((t.task.progress ?? 0) * 100)}% complete` : ""}` : void 0,
8730
+ style: interactive ? { cursor: "pointer" } : void 0,
8731
+ onClick: interactive ? handleActivate : void 0,
8732
+ onKeyDown: interactive ? (e) => activateOnKey10(e, handleActivate) : void 0,
8733
+ children: [
8734
+ /* @__PURE__ */ jsx(
8735
+ "rect",
8736
+ {
8737
+ x: t.x,
8738
+ y: t.y,
8739
+ width: Math.max(2, t.w),
8740
+ height: t.h,
8741
+ rx: 4,
8742
+ ry: 4,
8743
+ fill: "hsl(var(--primary))",
8744
+ fillOpacity: 0.25,
8745
+ stroke: "hsl(var(--primary))",
8746
+ strokeWidth: 1
8747
+ }
8748
+ ),
8749
+ t.progressW > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
8750
+ /* @__PURE__ */ jsx(
8751
+ "rect",
8752
+ {
8753
+ x: t.x,
8754
+ y: t.y,
8755
+ width: t.progressW,
8756
+ height: t.h,
8757
+ rx: 4,
8758
+ ry: 4,
8759
+ fill: "hsl(var(--primary))",
8760
+ fillOpacity: 0.7
8761
+ }
8762
+ ),
8763
+ t.progressW < t.w - 0.5 ? /* @__PURE__ */ jsx(
8764
+ "line",
8765
+ {
8766
+ x1: t.x + t.progressW,
8767
+ x2: t.x + t.progressW,
8768
+ y1: t.y,
8769
+ y2: t.y + t.h,
8770
+ stroke: "hsl(var(--primary))",
8771
+ strokeWidth: 1
8772
+ }
8773
+ ) : null
8774
+ ] }) : null
8775
+ ]
8776
+ },
8777
+ t.task.id
8778
+ );
8779
+ }) })
8780
+ ]
8781
+ }
8782
+ );
8783
+ }
8784
+ function layout15(tasks, width, rowHeight, labelMargin, tickCount) {
8785
+ if (tasks.length === 0) {
8786
+ return { tasks: [], deps: [], ticks: [], startLabel: "\u2014", endLabel: "\u2014" };
8787
+ }
8788
+ const axisLeft = labelMargin;
8789
+ const axisRight = width - 16;
8790
+ const usable = axisRight - axisLeft;
8791
+ const allTimes = tasks.flatMap((t) => [toDate2(t.start).getTime(), toDate2(t.end).getTime()]).filter((t) => Number.isFinite(t));
8792
+ if (allTimes.length === 0) {
8793
+ return { tasks: [], deps: [], ticks: [], startLabel: "\u2014", endLabel: "\u2014" };
8794
+ }
8795
+ const minTs = Math.min(...allTimes);
8796
+ const maxTs = Math.max(...allTimes);
8797
+ const span = Math.max(1, maxTs - minTs);
8798
+ const tToX = (t) => axisLeft + (t - minTs) / span * usable;
8799
+ const laidOutTasks = tasks.map((task, i) => {
8800
+ const startTs = toDate2(task.start).getTime();
8801
+ const endTs = toDate2(task.end).getTime();
8802
+ const x = tToX(startTs);
8803
+ const w = Math.max(0, tToX(endTs) - x);
8804
+ const y = HEADER_HEIGHT + i * rowHeight + ROW_PADDING / 2;
8805
+ const h = rowHeight - ROW_PADDING;
8806
+ const progress = clamp01(task.progress ?? 0);
8807
+ return {
8808
+ task,
8809
+ rowIndex: i,
8810
+ x,
8811
+ y,
8812
+ w,
8813
+ h,
8814
+ progressW: w * progress
8815
+ };
8816
+ });
8817
+ const byId = new Map(laidOutTasks.map((t) => [t.task.id, t]));
8818
+ const deps = [];
8819
+ laidOutTasks.forEach((t) => {
8820
+ (t.task.dependencies ?? []).forEach((depId, k) => {
8821
+ const from = byId.get(depId);
8822
+ if (!from) return;
8823
+ deps.push({
8824
+ from: { x: from.x + from.w, y: from.y + from.h / 2 },
8825
+ to: { x: t.x, y: t.y + t.h / 2 },
8826
+ id: `${depId}-${t.task.id}-${k}`
8827
+ });
8828
+ });
8829
+ });
8830
+ const rawTicks = [];
8831
+ for (let i = 0; i < tickCount; i++) {
8832
+ const ts = minTs + i / Math.max(1, tickCount - 1) * span;
8833
+ rawTicks.push({ x: tToX(ts), label: formatTick2(new Date(ts), span) });
8834
+ }
8835
+ const ticks = rawTicks.map((t, i) => ({
8836
+ x: t.x,
8837
+ label: i > 0 && rawTicks[i - 1]?.label === t.label ? "" : t.label
8838
+ }));
8839
+ return {
8840
+ tasks: laidOutTasks,
8841
+ deps,
8842
+ ticks,
8843
+ startLabel: formatDate2(new Date(minTs)),
8844
+ endLabel: formatDate2(new Date(maxTs))
8845
+ };
8846
+ }
8847
+ function toDate2(v) {
8848
+ if (v instanceof Date) return v;
8849
+ if (typeof v === "number") return new Date(v);
8850
+ return new Date(v);
8851
+ }
8852
+ function clamp01(v) {
8853
+ return v < 0 ? 0 : v > 1 ? 1 : v;
8854
+ }
8855
+ function formatDate2(d) {
8856
+ if (Number.isNaN(d.getTime())) return "\u2014";
8857
+ return d.toISOString().slice(0, 10);
8858
+ }
8859
+ function formatTick2(d, spanMs) {
8860
+ if (Number.isNaN(d.getTime())) return "\u2014";
8861
+ const ONE_DAY = 24 * 60 * 60 * 1e3;
8862
+ if (spanMs <= 90 * ONE_DAY) return d.toISOString().slice(5, 10);
8863
+ if (spanMs <= 730 * ONE_DAY) return d.toISOString().slice(0, 7);
8864
+ return String(d.getUTCFullYear());
8865
+ }
8866
+ function activateOnKey10(e, fn) {
8867
+ if (e.key === "Enter" || e.key === " ") {
8868
+ e.preventDefault();
8869
+ fn();
8870
+ }
8871
+ }
8872
+ var HEADER_PADDING = 16;
8873
+ var MESSAGE_TOP_PADDING = 24;
8874
+ function Sequence({
8875
+ actors,
8876
+ messages,
8877
+ width = 720,
8878
+ headerHeight = 40,
8879
+ messageGap = 36,
8880
+ onActorClick,
8881
+ onMessageClick,
8882
+ className,
8883
+ ...rest
8884
+ }) {
8885
+ const laidOut = React44.useMemo(
8886
+ () => layout16(actors, messages, width, headerHeight, messageGap),
8887
+ [actors, messages, width, headerHeight, messageGap]
8888
+ );
8889
+ const desc = `Sequence diagram with ${actors.length} actor${actors.length === 1 ? "" : "s"} and ${messages.length} message${messages.length === 1 ? "" : "s"}`;
8890
+ const arrowId = React44.useId().replace(/:/g, "-");
8891
+ const SELF_CALL_DROP = Math.min(14, Math.max(6, messageGap * 0.4));
8892
+ const SELF_CALL_OUT = 28;
8893
+ return /* @__PURE__ */ jsxs(
8894
+ "svg",
8895
+ {
8896
+ ...rest,
8897
+ "data-hex-sequence": true,
8898
+ role: "img",
8899
+ width,
8900
+ height: laidOut.totalHeight,
8901
+ viewBox: `0 0 ${width} ${laidOut.totalHeight}`,
8902
+ className: cn("block", className),
8903
+ children: [
8904
+ /* @__PURE__ */ jsx("title", { children: "Sequence diagram" }),
8905
+ /* @__PURE__ */ jsx("desc", { children: desc }),
8906
+ /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx(
8907
+ "marker",
8908
+ {
8909
+ id: `hex-sequence-arrow-${arrowId}`,
8910
+ viewBox: "0 0 10 10",
8911
+ refX: "10",
8912
+ refY: "5",
8913
+ markerWidth: "6",
8914
+ markerHeight: "6",
8915
+ orient: "auto-start-reverse",
8916
+ children: /* @__PURE__ */ jsx("path", { d: "M 0 0 L 10 5 L 0 10 z", fill: "hsl(var(--foreground))" })
8917
+ }
8918
+ ) }),
8919
+ /* @__PURE__ */ jsx("g", { "data-hex-sequence-lifelines": true, children: laidOut.actors.map((a) => /* @__PURE__ */ jsx(
8920
+ "line",
8921
+ {
8922
+ "data-hex-sequence-lifeline": true,
8923
+ x1: a.x,
8924
+ x2: a.x,
8925
+ y1: headerHeight,
8926
+ y2: laidOut.totalHeight - 8,
8927
+ stroke: "hsl(var(--muted-foreground))",
8928
+ strokeOpacity: 0.7,
8929
+ strokeDasharray: "3 4",
8930
+ strokeWidth: 1
8931
+ },
8932
+ `lifeline-${a.actor.id}`
8933
+ )) }),
8934
+ /* @__PURE__ */ jsx("g", { "data-hex-sequence-actors": true, children: laidOut.actors.map((a) => {
8935
+ const interactive = Boolean(onActorClick);
8936
+ const handleActivate = () => onActorClick?.(a.actor);
8937
+ return /* @__PURE__ */ jsxs(
8938
+ "g",
8939
+ {
8940
+ "data-hex-sequence-actor": true,
8941
+ "data-depth": a.depth,
8942
+ role: interactive ? "button" : void 0,
8943
+ tabIndex: interactive ? 0 : void 0,
8944
+ "aria-label": interactive ? a.actor.label : void 0,
8945
+ style: interactive ? { cursor: "pointer" } : void 0,
8946
+ onClick: interactive ? handleActivate : void 0,
8947
+ onKeyDown: interactive ? (e) => activateOnKey11(e, handleActivate) : void 0,
8948
+ children: [
8949
+ /* @__PURE__ */ jsx(
8950
+ "rect",
8951
+ {
8952
+ x: a.x - 64,
8953
+ y: 4,
8954
+ width: 128,
8955
+ height: headerHeight - 8,
8956
+ rx: 4,
8957
+ ry: 4,
8958
+ fill: "hsl(var(--card))",
8959
+ stroke: "hsl(var(--border))",
8960
+ strokeWidth: 1
8961
+ }
8962
+ ),
8963
+ /* @__PURE__ */ jsx(
8964
+ "text",
8965
+ {
8966
+ x: a.x,
8967
+ y: headerHeight / 2,
8968
+ dy: "0.35em",
8969
+ textAnchor: "middle",
8970
+ fontSize: 12,
8971
+ fontWeight: 600,
8972
+ fill: "hsl(var(--foreground))",
8973
+ style: { pointerEvents: "none" },
8974
+ children: a.actor.label
8975
+ }
8976
+ )
8977
+ ]
8978
+ },
8979
+ a.actor.id
8980
+ );
8981
+ }) }),
8982
+ /* @__PURE__ */ jsx("g", { "data-hex-sequence-messages": true, children: laidOut.messages.map((m, i) => {
8983
+ const interactive = Boolean(onMessageClick);
8984
+ const handleActivate = () => onMessageClick?.(m.message);
8985
+ const stroke = "hsl(var(--foreground))";
8986
+ const dashed = m.message.type === "return" ? "4 3" : void 0;
8987
+ const strokeWidth = m.message.type === "async" ? 1 : 1.25;
8988
+ return /* @__PURE__ */ jsxs(
8989
+ "g",
8990
+ {
8991
+ "data-hex-sequence-message": true,
8992
+ "data-depth": m.depth,
8993
+ "data-type": m.message.type ?? "sync",
8994
+ role: interactive ? "button" : void 0,
8995
+ tabIndex: interactive ? 0 : void 0,
8996
+ "aria-label": interactive ? `Message ${i + 1}: from ${m.from.actor.label} to ${m.to.actor.label}${m.message.label ? `, "${m.message.label}"` : ""}` : void 0,
8997
+ style: interactive ? { cursor: "pointer" } : void 0,
8998
+ onClick: interactive ? handleActivate : void 0,
8999
+ onKeyDown: interactive ? (e) => activateOnKey11(e, handleActivate) : void 0,
9000
+ children: [
9001
+ m.isSelfCall ? (
9002
+ // Loopback: out + down + back. Drop scales with messageGap.
9003
+ /* @__PURE__ */ jsx(
9004
+ "path",
9005
+ {
9006
+ d: `M${m.from.x},${m.y} L${m.from.x + SELF_CALL_OUT},${m.y} L${m.from.x + SELF_CALL_OUT},${m.y + SELF_CALL_DROP} L${m.from.x + 4},${m.y + SELF_CALL_DROP}`,
9007
+ fill: "none",
9008
+ stroke,
9009
+ strokeWidth,
9010
+ strokeDasharray: dashed,
9011
+ markerEnd: `url(#hex-sequence-arrow-${arrowId})`
9012
+ }
9013
+ )
9014
+ ) : /* @__PURE__ */ jsx(
9015
+ "line",
9016
+ {
9017
+ x1: m.from.x,
9018
+ x2: m.to.x,
9019
+ y1: m.y,
9020
+ y2: m.y,
9021
+ stroke,
9022
+ strokeWidth,
9023
+ strokeDasharray: dashed,
9024
+ markerEnd: `url(#hex-sequence-arrow-${arrowId})`
9025
+ }
9026
+ ),
9027
+ m.message.label ? /* @__PURE__ */ jsx(
9028
+ "text",
9029
+ {
9030
+ x: m.isSelfCall ? m.from.x + SELF_CALL_OUT + 4 : (m.from.x + m.to.x) / 2,
9031
+ y: m.y - 4,
9032
+ textAnchor: m.isSelfCall ? "start" : "middle",
9033
+ fontSize: 11,
9034
+ fill: "hsl(var(--foreground))",
9035
+ style: {
9036
+ paintOrder: "stroke",
9037
+ pointerEvents: "none"
9038
+ },
9039
+ stroke: "hsl(var(--background))",
9040
+ strokeWidth: 3,
9041
+ strokeLinejoin: "round",
9042
+ children: m.message.label
9043
+ }
9044
+ ) : null
9045
+ ]
9046
+ },
9047
+ `${m.message.from}-${m.message.to}-${i}`
9048
+ );
9049
+ }) })
9050
+ ]
9051
+ }
9052
+ );
9053
+ }
9054
+ function layout16(actors, messages, width, headerHeight, messageGap) {
9055
+ if (actors.length === 0) {
9056
+ return { actors: [], messages: [], totalHeight: headerHeight + 16 };
9057
+ }
9058
+ const usableWidth = width - HEADER_PADDING * 2;
9059
+ const colStep = actors.length > 1 ? usableWidth / (actors.length - 1) : 0;
9060
+ const actorPositions = actors.map((a, i) => ({
9061
+ actor: a,
9062
+ x: HEADER_PADDING + colStep * i,
9063
+ depth: i
9064
+ }));
9065
+ const byId = new Map(actorPositions.map((a) => [a.actor.id, a]));
9066
+ const laidOutMessages = messages.map((message, i) => {
9067
+ const from = byId.get(message.from);
9068
+ const to = byId.get(message.to);
9069
+ if (!from || !to) return null;
9070
+ return {
9071
+ message,
9072
+ from,
9073
+ to,
9074
+ y: headerHeight + MESSAGE_TOP_PADDING + i * messageGap,
9075
+ depth: i,
9076
+ isSelfCall: message.from === message.to
9077
+ };
9078
+ }).filter((m) => m !== null);
9079
+ const totalHeight = headerHeight + MESSAGE_TOP_PADDING + Math.max(0, laidOutMessages.length - 1) * messageGap + 32;
9080
+ return { actors: actorPositions, messages: laidOutMessages, totalHeight };
9081
+ }
9082
+ function activateOnKey11(e, fn) {
9083
+ if (e.key === "Enter" || e.key === " ") {
9084
+ e.preventDefault();
9085
+ fn();
9086
+ }
9087
+ }
9088
+ function Flashcard({
9089
+ front,
9090
+ back,
9091
+ defaultFlipped = false,
9092
+ flipped: flippedProp,
9093
+ onFlipChange,
9094
+ width = 360,
9095
+ height = 240,
9096
+ flipDurationMs = 500,
9097
+ className,
9098
+ ...rest
9099
+ }) {
9100
+ const [internalFlipped, setInternalFlipped] = React44.useState(defaultFlipped);
9101
+ const isControlled = flippedProp !== void 0;
9102
+ const flipped = isControlled ? flippedProp : internalFlipped;
9103
+ const toggle = React44.useCallback(() => {
9104
+ const next = !flipped;
9105
+ if (!isControlled) setInternalFlipped(next);
9106
+ onFlipChange?.(next);
9107
+ }, [flipped, isControlled, onFlipChange]);
9108
+ const handleKey = React44.useCallback(
9109
+ (e) => {
9110
+ if (e.key === "Enter" || e.key === " ") {
9111
+ e.preventDefault();
9112
+ toggle();
9113
+ }
9114
+ },
9115
+ [toggle]
9116
+ );
9117
+ return /* @__PURE__ */ jsx(
9118
+ "div",
9119
+ {
9120
+ ...rest,
9121
+ "data-hex-flashcard": true,
9122
+ "data-flipped": flipped,
9123
+ role: "button",
9124
+ tabIndex: 0,
9125
+ "aria-pressed": flipped,
9126
+ "aria-label": flipped ? "Flashcard, back side. Activate to flip to front." : "Flashcard, front side. Activate to reveal back.",
9127
+ onClick: toggle,
9128
+ onKeyDown: handleKey,
9129
+ className: cn(
9130
+ "relative inline-block cursor-pointer select-none focus:outline-none focus-visible:ring-2 focus-visible:ring-ring",
9131
+ className
9132
+ ),
9133
+ style: {
9134
+ width,
9135
+ height,
9136
+ perspective: 1e3
9137
+ },
9138
+ children: /* @__PURE__ */ jsxs(
9139
+ "div",
9140
+ {
9141
+ "data-hex-flashcard-inner": true,
9142
+ className: "relative h-full w-full transition-transform",
9143
+ style: {
9144
+ transformStyle: "preserve-3d",
9145
+ transform: flipped ? "rotateY(180deg)" : "rotateY(0deg)",
9146
+ transitionDuration: `${flipDurationMs}ms`
9147
+ },
9148
+ children: [
9149
+ /* @__PURE__ */ jsx(
9150
+ "div",
9151
+ {
9152
+ "data-hex-flashcard-face": true,
9153
+ "data-side": "front",
9154
+ className: "absolute inset-0 flex items-center justify-center rounded-lg border bg-card p-4 text-center text-card-foreground shadow-sm",
9155
+ style: {
9156
+ backfaceVisibility: "hidden",
9157
+ WebkitBackfaceVisibility: "hidden"
9158
+ },
9159
+ children: front
9160
+ }
9161
+ ),
9162
+ /* @__PURE__ */ jsx(
9163
+ "div",
9164
+ {
9165
+ "data-hex-flashcard-face": true,
9166
+ "data-side": "back",
9167
+ className: "absolute inset-0 flex items-center justify-center rounded-lg border bg-card p-4 text-center text-card-foreground shadow-sm",
9168
+ style: {
9169
+ backfaceVisibility: "hidden",
9170
+ WebkitBackfaceVisibility: "hidden",
9171
+ transform: "rotateY(180deg)"
9172
+ },
9173
+ children: back
9174
+ }
9175
+ )
9176
+ ]
9177
+ }
9178
+ )
9179
+ }
9180
+ );
9181
+ }
9182
+ var AUTO_ID_PREFIX = "__hex_cloze_auto_";
9183
+ function normalize(parts) {
9184
+ let blankIndex = 0;
9185
+ const fragments = parts.map((p) => {
9186
+ if (typeof p === "string") return p;
9187
+ const id = p.id ?? `${AUTO_ID_PREFIX}${blankIndex}`;
9188
+ const out = { hidden: p.hidden, id, blankIndex };
9189
+ blankIndex++;
9190
+ return out;
9191
+ });
9192
+ return { fragments, total: blankIndex };
9193
+ }
9194
+ function Cloze({ parts, revealMode = "click", onReveal, className, ...rest }) {
9195
+ const { fragments, total } = React44.useMemo(() => normalize(parts), [parts]);
9196
+ const [revealed, setRevealed] = React44.useState(() => /* @__PURE__ */ new Set());
9197
+ const onRevealRef = React44.useRef(onReveal);
9198
+ onRevealRef.current = onReveal;
9199
+ const update = React44.useCallback((next) => {
9200
+ setRevealed(next);
9201
+ onRevealRef.current?.(Array.from(next));
9202
+ }, []);
9203
+ const toggleBlank = React44.useCallback(
9204
+ (id) => {
9205
+ const next = new Set(revealed);
9206
+ if (next.has(id)) next.delete(id);
9207
+ else next.add(id);
9208
+ update(next);
9209
+ },
9210
+ [revealed, update]
9211
+ );
9212
+ const allRevealed = total > 0 && revealed.size === total;
9213
+ const toggleAll = React44.useCallback(() => {
9214
+ if (allRevealed) {
9215
+ update(/* @__PURE__ */ new Set());
9216
+ } else {
9217
+ const next = /* @__PURE__ */ new Set();
9218
+ for (const f of fragments) if (typeof f !== "string") next.add(f.id);
9219
+ update(next);
9220
+ }
9221
+ }, [allRevealed, fragments, update]);
9222
+ return /* @__PURE__ */ jsxs(
9223
+ "div",
9224
+ {
9225
+ ...rest,
9226
+ "data-hex-cloze": true,
9227
+ "data-all-revealed": allRevealed,
9228
+ className: cn("text-base leading-relaxed", className),
9229
+ children: [
9230
+ /* @__PURE__ */ jsx("p", { "data-hex-cloze-text": true, children: fragments.map((f, i) => {
9231
+ if (typeof f === "string") return /* @__PURE__ */ jsx(React44.Fragment, { children: f }, `s-${i}`);
9232
+ const isRevealed = revealed.has(f.id);
9233
+ return /* @__PURE__ */ jsx(
9234
+ "button",
9235
+ {
9236
+ type: "button",
9237
+ "data-hex-cloze-blank": true,
9238
+ "data-blank-id": f.id,
9239
+ "data-revealed": isRevealed,
9240
+ "aria-pressed": isRevealed,
9241
+ "aria-label": isRevealed ? `Blank ${f.blankIndex + 1} revealed: ${f.hidden}. Activate to hide.` : `Blank ${f.blankIndex + 1} of ${total}. Activate to reveal.`,
9242
+ onClick: () => toggleBlank(f.id),
9243
+ className: cn(
9244
+ "mx-0.5 inline-block rounded px-1.5 py-0 align-baseline transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-ring",
9245
+ isRevealed ? "bg-primary/10 text-foreground underline decoration-primary decoration-dotted underline-offset-4" : "select-none bg-muted text-transparent"
9246
+ ),
9247
+ children: f.hidden
9248
+ },
9249
+ f.id
9250
+ );
9251
+ }) }),
9252
+ revealMode === "all" && total > 0 ? /* @__PURE__ */ jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsx(
9253
+ "button",
9254
+ {
9255
+ type: "button",
9256
+ "data-hex-cloze-toggle-all": true,
9257
+ "aria-label": allRevealed ? "Hide all blanks" : "Reveal all blanks",
9258
+ onClick: toggleAll,
9259
+ className: "rounded-md border bg-background px-3 py-1 text-xs font-medium text-foreground transition-colors hover:bg-muted focus:outline-none focus-visible:ring-2 focus-visible:ring-ring",
9260
+ children: allRevealed ? "Hide all" : "Reveal all"
9261
+ }
9262
+ ) }) : null
9263
+ ]
9264
+ }
9265
+ );
9266
+ }
9267
+ function ImageOcclusion({
9268
+ src,
9269
+ alt,
9270
+ regions,
9271
+ onRegionReveal,
9272
+ className,
9273
+ ...rest
9274
+ }) {
9275
+ const [revealed, setRevealed] = React44.useState(() => /* @__PURE__ */ new Set());
9276
+ const onRevealRef = React44.useRef(onRegionReveal);
9277
+ onRevealRef.current = onRegionReveal;
9278
+ const warnedRef = React44.useRef(false);
9279
+ const nodeEnv = globalThis.process?.env?.NODE_ENV;
9280
+ if (nodeEnv !== "production" && !warnedRef.current) {
9281
+ const offender = regions.find(
9282
+ (r) => r.x < 0 || r.y < 0 || r.x + r.width > 1.0001 || r.y + r.height > 1.0001
9283
+ );
9284
+ if (offender) {
9285
+ warnedRef.current = true;
9286
+ console.warn(
9287
+ `[hex-core/ImageOcclusion] Region "${offender.id}" coords escape [0, 1]. Pass fractions, not pixels \u2014 e.g. x: 0.42 (not 168).`
9288
+ );
9289
+ }
9290
+ }
9291
+ const toggle = React44.useCallback((id) => {
9292
+ setRevealed((prev) => {
9293
+ const next = new Set(prev);
9294
+ if (next.has(id)) {
9295
+ next.delete(id);
9296
+ } else {
9297
+ next.add(id);
9298
+ onRevealRef.current?.(id);
9299
+ }
9300
+ return next;
9301
+ });
9302
+ }, []);
9303
+ return /* @__PURE__ */ jsxs(
9304
+ "div",
9305
+ {
9306
+ ...rest,
9307
+ "data-hex-image-occlusion": true,
9308
+ className: cn("relative inline-block", className),
9309
+ children: [
9310
+ /* @__PURE__ */ jsx(
9311
+ "img",
9312
+ {
9313
+ src,
9314
+ alt,
9315
+ "data-hex-image-occlusion-img": true,
9316
+ className: "block h-auto w-full select-none",
9317
+ draggable: false
9318
+ }
9319
+ ),
9320
+ /* @__PURE__ */ jsx(
9321
+ "div",
9322
+ {
9323
+ "data-hex-image-occlusion-overlay": true,
9324
+ className: "pointer-events-none absolute inset-0",
9325
+ children: regions.map((r, i) => {
9326
+ const isRevealed = revealed.has(r.id);
9327
+ return /* @__PURE__ */ jsx(
9328
+ "button",
9329
+ {
9330
+ type: "button",
9331
+ "data-hex-image-occlusion-region": true,
9332
+ "data-region-id": r.id,
9333
+ "data-revealed": isRevealed,
9334
+ "aria-pressed": isRevealed,
9335
+ "aria-label": r.label ? isRevealed ? `Region ${i + 1} revealed: ${r.label}. Activate to hide.` : `Region ${i + 1} of ${regions.length}, hidden. Activate to reveal: ${r.label}.` : isRevealed ? `Region ${i + 1} revealed. Activate to hide.` : `Region ${i + 1} of ${regions.length}, hidden. Activate to reveal.`,
9336
+ onClick: () => toggle(r.id),
9337
+ className: cn(
9338
+ "pointer-events-auto absolute rounded-sm border-2 border-primary/60 transition-opacity focus:outline-none focus-visible:ring-2 focus-visible:ring-ring",
9339
+ isRevealed ? "bg-transparent opacity-30 hover:opacity-60" : "bg-primary opacity-95 hover:bg-primary/90"
9340
+ ),
9341
+ style: {
9342
+ left: `${r.x * 100}%`,
9343
+ top: `${r.y * 100}%`,
9344
+ width: `${r.width * 100}%`,
9345
+ height: `${r.height * 100}%`,
9346
+ // Explicit z-index = array index makes stacking deterministic:
9347
+ // later array entries always render (and click-catch) on top.
9348
+ zIndex: i
9349
+ }
9350
+ },
9351
+ r.id
9352
+ );
9353
+ })
9354
+ }
9355
+ )
9356
+ ]
9357
+ }
9358
+ );
9359
+ }
9360
+ function Quiz({
9361
+ question,
9362
+ options,
9363
+ selectionMode = "single",
9364
+ submitLabel = "Submit",
9365
+ onAnswer,
9366
+ className,
9367
+ ...rest
9368
+ }) {
9369
+ const [selected, setSelected] = React44.useState(() => /* @__PURE__ */ new Set());
9370
+ const [submitted, setSubmitted] = React44.useState(false);
9371
+ const onAnswerRef = React44.useRef(onAnswer);
9372
+ onAnswerRef.current = onAnswer;
9373
+ const groupName = React44.useId();
9374
+ const correctIds = React44.useMemo(
9375
+ () => new Set(options.filter((o) => o.correct).map((o) => o.id)),
9376
+ [options]
9377
+ );
9378
+ const allCorrect = React44.useMemo(() => {
9379
+ if (selected.size !== correctIds.size) return false;
9380
+ for (const id of correctIds) if (!selected.has(id)) return false;
9381
+ return true;
9382
+ }, [selected, correctIds]);
9383
+ const stateFor = (option) => {
9384
+ if (!submitted) return "unanswered";
9385
+ const picked = selected.has(option.id);
9386
+ const isCorrect = correctIds.has(option.id);
9387
+ if (picked && isCorrect) return "correct";
9388
+ if (picked && !isCorrect) return "incorrect";
9389
+ if (!picked && isCorrect) return "missed";
9390
+ return "unanswered";
9391
+ };
9392
+ const toggle = (id) => {
9393
+ setSelected((prev) => {
9394
+ if (selectionMode === "single") return /* @__PURE__ */ new Set([id]);
9395
+ const next = new Set(prev);
9396
+ if (next.has(id)) next.delete(id);
9397
+ else next.add(id);
9398
+ return next;
9399
+ });
9400
+ };
9401
+ const handleSubmit = () => {
9402
+ setSubmitted(true);
9403
+ onAnswerRef.current?.(Array.from(selected), allCorrect);
9404
+ };
9405
+ const canSubmit = selected.size > 0;
9406
+ const statusText = submitted ? allCorrect ? "Correct \u2014 well done." : "Some selections are incorrect or missed. Review the highlighted options." : "";
9407
+ return /* @__PURE__ */ jsxs(
9408
+ "div",
9409
+ {
9410
+ ...rest,
9411
+ "data-hex-quiz": true,
9412
+ "data-submitted": submitted,
9413
+ "data-all-correct": submitted && allCorrect,
9414
+ className: cn("rounded-lg border bg-card p-4 text-card-foreground", className),
9415
+ children: [
9416
+ /* @__PURE__ */ jsx("div", { "data-hex-quiz-question": true, className: "mb-3 text-base font-medium", children: question }),
9417
+ /* @__PURE__ */ jsx("div", { "data-hex-quiz-options": true, className: "space-y-2", role: selectionMode === "single" ? "radiogroup" : "group", children: options.map((option) => {
9418
+ const state = stateFor(option);
9419
+ const isPicked = selected.has(option.id);
9420
+ return /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsxs(
9421
+ "label",
9422
+ {
9423
+ "data-hex-quiz-option": true,
9424
+ "data-state": state,
9425
+ "data-picked": isPicked,
9426
+ className: cn(
9427
+ "flex cursor-pointer items-start gap-2 rounded-md border bg-background p-2 transition-colors",
9428
+ "hover:bg-muted/50",
9429
+ state === "correct" && "border-primary bg-primary/10",
9430
+ state === "incorrect" && "border-destructive bg-destructive/10",
9431
+ state === "missed" && "border-primary/60 bg-primary/5 ring-1 ring-primary/40"
9432
+ ),
9433
+ children: [
9434
+ /* @__PURE__ */ jsx(
9435
+ "input",
9436
+ {
9437
+ type: selectionMode === "single" ? "radio" : "checkbox",
9438
+ name: selectionMode === "single" ? groupName : void 0,
9439
+ value: option.id,
9440
+ checked: isPicked,
9441
+ onChange: () => toggle(option.id),
9442
+ className: "mt-0.5"
9443
+ }
9444
+ ),
9445
+ /* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
9446
+ /* @__PURE__ */ jsx("div", { "data-hex-quiz-label": true, children: option.label }),
9447
+ submitted && option.explanation && (state === "correct" || state === "incorrect" || state === "missed") ? /* @__PURE__ */ jsx("div", { "data-hex-quiz-explanation": true, className: "mt-1 text-xs text-muted-foreground", children: option.explanation }) : null
9448
+ ] })
9449
+ ]
9450
+ }
9451
+ ) }, option.id);
9452
+ }) }),
9453
+ /* @__PURE__ */ jsxs("div", { className: "mt-3 flex items-center gap-3", children: [
9454
+ /* @__PURE__ */ jsx(
9455
+ "button",
9456
+ {
9457
+ type: "button",
9458
+ "data-hex-quiz-submit": true,
9459
+ disabled: !canSubmit,
9460
+ onClick: handleSubmit,
9461
+ className: cn(
9462
+ "inline-flex h-9 items-center justify-center rounded-md bg-primary px-3 text-sm font-medium text-primary-foreground transition-opacity",
9463
+ "hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-50",
9464
+ "focus:outline-none focus-visible:ring-2 focus-visible:ring-ring"
9465
+ ),
9466
+ children: submitLabel
9467
+ }
9468
+ ),
9469
+ /* @__PURE__ */ jsx("div", { "data-hex-quiz-status": true, role: "status", "aria-live": "polite", className: "text-sm text-muted-foreground", children: statusText })
9470
+ ] })
9471
+ ]
9472
+ }
9473
+ );
9474
+ }
9475
+ var EMPTY_PLACEHOLDER = "\u2014";
9476
+ function CompareTable({
9477
+ subjects,
9478
+ attributes,
9479
+ highlightDifferences = false,
9480
+ onCellClick,
9481
+ className,
9482
+ ...rest
9483
+ }) {
9484
+ const warnedRef = React44.useRef(false);
9485
+ const nodeEnv = globalThis.process?.env?.NODE_ENV;
9486
+ if (nodeEnv !== "production" && !warnedRef.current) {
9487
+ const subjectIds = new Set(subjects.map((s) => s.id));
9488
+ for (const attr of attributes) {
9489
+ for (const valueId of Object.keys(attr.values)) {
9490
+ if (!subjectIds.has(valueId)) {
9491
+ warnedRef.current = true;
9492
+ console.warn(
9493
+ `[hex-core/CompareTable] Attribute "${attr.id}" has a value for subjectId "${valueId}" that isn't in the subjects array. The cell will not render.`
9494
+ );
9495
+ break;
9496
+ }
9497
+ }
9498
+ if (warnedRef.current) break;
9499
+ }
9500
+ }
9501
+ return /* @__PURE__ */ jsx(
9502
+ "div",
9503
+ {
9504
+ ...rest,
9505
+ "data-hex-compare-table": true,
9506
+ className: cn("overflow-x-auto rounded-lg border", className),
9507
+ children: /* @__PURE__ */ jsxs("table", { className: "w-full border-collapse text-sm", children: [
9508
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { "data-hex-compare-table-header": true, children: [
9509
+ /* @__PURE__ */ jsx(
9510
+ "th",
9511
+ {
9512
+ scope: "col",
9513
+ className: "sticky left-0 z-10 border-b bg-card px-3 py-2 text-left text-xs font-medium text-muted-foreground"
9514
+ }
9515
+ ),
9516
+ subjects.map((subject) => /* @__PURE__ */ jsx(
9517
+ "th",
9518
+ {
9519
+ scope: "col",
9520
+ "data-hex-compare-table-subject": true,
9521
+ "data-subject-id": subject.id,
9522
+ className: "border-b bg-card px-3 py-2 text-left font-semibold",
9523
+ children: subject.label
9524
+ },
9525
+ subject.id
9526
+ ))
9527
+ ] }) }),
9528
+ /* @__PURE__ */ jsx("tbody", { children: attributes.map((attr) => {
9529
+ const reference = highlightDifferences ? subjects.map((s) => attr.values[s.id]).find((v) => v != null && v !== "") : void 0;
9530
+ return /* @__PURE__ */ jsxs("tr", { "data-hex-compare-table-row": true, "data-attribute-id": attr.id, children: [
9531
+ /* @__PURE__ */ jsx(
9532
+ "th",
9533
+ {
9534
+ scope: "row",
9535
+ className: "sticky left-0 z-10 border-b bg-card px-3 py-2 text-left font-medium",
9536
+ children: attr.label
9537
+ }
9538
+ ),
9539
+ subjects.map((subject) => {
9540
+ const raw = attr.values[subject.id];
9541
+ const isEmpty = raw == null || raw === "";
9542
+ const displayValue = isEmpty ? EMPTY_PLACEHOLDER : raw;
9543
+ const isComparable = (typeof raw === "string" || typeof raw === "number" || typeof raw === "boolean") && (typeof reference === "string" || typeof reference === "number" || typeof reference === "boolean");
9544
+ const differs = highlightDifferences && !isEmpty && reference !== void 0 && isComparable && String(raw) !== String(reference);
9545
+ const interactive = Boolean(onCellClick);
9546
+ return /* @__PURE__ */ jsx(
9547
+ "td",
9548
+ {
9549
+ "data-hex-compare-table-cell": true,
9550
+ "data-subject-id": subject.id,
9551
+ "data-attribute-id": attr.id,
9552
+ "data-differs": differs,
9553
+ className: cn(
9554
+ "border-b px-3 py-2 align-top",
9555
+ differs && "bg-accent/30 text-accent-foreground",
9556
+ isEmpty && "text-muted-foreground",
9557
+ interactive && "cursor-pointer hover:bg-muted/40"
9558
+ ),
9559
+ onClick: interactive ? () => onCellClick?.(subject.id, attr.id) : void 0,
9560
+ children: displayValue
9561
+ },
9562
+ subject.id
9563
+ );
9564
+ })
9565
+ ] }, attr.id);
9566
+ }) })
9567
+ ] })
9568
+ }
9569
+ );
9570
+ }
9571
+ function shuffleArray(arr) {
9572
+ const out = arr.slice();
9573
+ for (let i = out.length - 1; i > 0; i--) {
9574
+ const j = Math.floor(Math.random() * (i + 1));
9575
+ [out[i], out[j]] = [out[j], out[i]];
9576
+ }
9577
+ return out;
9578
+ }
9579
+ function Deck({
9580
+ cards,
9581
+ shuffle = false,
9582
+ ratingSlot,
9583
+ onCardChange,
9584
+ cardWidth = 360,
9585
+ cardHeight = 240,
9586
+ className,
9587
+ ...rest
9588
+ }) {
9589
+ const order = React44.useMemo(() => shuffle ? shuffleArray(cards) : cards.slice(), [cards, shuffle]);
9590
+ const [index, setIndex] = React44.useState(0);
9591
+ const [flipped, setFlipped] = React44.useState(false);
9592
+ const onCardChangeRef = React44.useRef(onCardChange);
9593
+ onCardChangeRef.current = onCardChange;
9594
+ React44.useEffect(() => {
9595
+ setIndex(0);
9596
+ setFlipped(false);
9597
+ }, [order]);
9598
+ const total = order.length;
9599
+ const currentCard = order[index];
9600
+ const goPrev = React44.useCallback(() => {
9601
+ if (index === 0) return;
9602
+ const next = index - 1;
9603
+ setIndex(next);
9604
+ setFlipped(false);
9605
+ const card = order[next];
9606
+ if (card) onCardChangeRef.current?.(next, card);
9607
+ }, [index, order]);
9608
+ const goNext = React44.useCallback(() => {
9609
+ if (index >= total - 1) return;
9610
+ const next = index + 1;
9611
+ setIndex(next);
9612
+ setFlipped(false);
9613
+ const card = order[next];
9614
+ if (card) onCardChangeRef.current?.(next, card);
9615
+ }, [index, order, total]);
9616
+ if (total === 0) {
9617
+ return /* @__PURE__ */ jsx(
9618
+ "div",
9619
+ {
9620
+ ...rest,
9621
+ "data-hex-deck": true,
9622
+ "data-empty": "true",
9623
+ className: cn("rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground", className),
9624
+ children: "No cards in this deck."
9625
+ }
9626
+ );
9627
+ }
9628
+ const progressPercent = total > 0 ? (index + 1) / total * 100 : 0;
9629
+ return /* @__PURE__ */ jsxs(
9630
+ "div",
9631
+ {
9632
+ ...rest,
9633
+ "data-hex-deck": true,
9634
+ "data-index": index,
9635
+ "data-total": total,
9636
+ className: cn("inline-flex flex-col items-center gap-4", className),
9637
+ children: [
9638
+ currentCard ? /* @__PURE__ */ jsx(
9639
+ Flashcard,
9640
+ {
9641
+ front: currentCard.front,
9642
+ back: currentCard.back,
9643
+ flipped,
9644
+ onFlipChange: setFlipped,
9645
+ width: cardWidth,
9646
+ height: cardHeight
9647
+ },
9648
+ currentCard.id
9649
+ ) : null,
9650
+ /* @__PURE__ */ jsxs("div", { "data-hex-deck-controls": true, className: "flex w-full items-center gap-3", style: { width: cardWidth }, children: [
9651
+ /* @__PURE__ */ jsx(
9652
+ "button",
9653
+ {
9654
+ type: "button",
9655
+ "data-hex-deck-prev": true,
9656
+ disabled: index === 0,
9657
+ onClick: goPrev,
9658
+ "aria-label": `Previous card. Currently ${index + 1} of ${total}.`,
9659
+ className: "inline-flex h-9 items-center justify-center rounded-md border bg-background px-3 text-sm font-medium transition-colors hover:bg-muted disabled:cursor-not-allowed disabled:opacity-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring",
9660
+ children: "Prev"
9661
+ }
9662
+ ),
9663
+ /* @__PURE__ */ jsxs("div", { "data-hex-deck-progress": true, className: "flex-1", "aria-hidden": "true", children: [
9664
+ /* @__PURE__ */ jsx("div", { className: "h-1.5 overflow-hidden rounded-full bg-muted", children: /* @__PURE__ */ jsx(
9665
+ "div",
9666
+ {
9667
+ className: "h-full bg-primary transition-all",
9668
+ style: { width: `${progressPercent}%` }
9669
+ }
9670
+ ) }),
9671
+ /* @__PURE__ */ jsxs("div", { className: "mt-1 text-center text-xs text-muted-foreground", children: [
9672
+ index + 1,
9673
+ " / ",
9674
+ total
9675
+ ] })
9676
+ ] }),
9677
+ /* @__PURE__ */ jsx(
9678
+ "button",
9679
+ {
9680
+ type: "button",
9681
+ "data-hex-deck-next": true,
9682
+ disabled: index >= total - 1,
9683
+ onClick: goNext,
9684
+ "aria-label": `Next card. Currently ${index + 1} of ${total}.`,
9685
+ className: "inline-flex h-9 items-center justify-center rounded-md border bg-background px-3 text-sm font-medium transition-colors hover:bg-muted disabled:cursor-not-allowed disabled:opacity-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring",
9686
+ children: "Next"
9687
+ }
9688
+ )
9689
+ ] }),
9690
+ ratingSlot && currentCard ? /* @__PURE__ */ jsx("div", { "data-hex-deck-rating-slot": true, className: "w-full", style: { width: cardWidth }, children: ratingSlot(currentCard) }) : null
9691
+ ]
9692
+ }
9693
+ );
9694
+ }
9695
+ var RATINGS = ["again", "hard", "good", "easy"];
9696
+ var DEFAULT_LABELS = {
9697
+ again: "Again",
9698
+ hard: "Hard",
9699
+ good: "Good",
9700
+ easy: "Easy"
9701
+ };
9702
+ var RATING_HINT = {
9703
+ again: "Couldn't recall \u2014 show this card again soon.",
9704
+ hard: "Recalled with effort \u2014 review sooner than usual.",
9705
+ good: "Recalled correctly \u2014 keep the standard interval.",
9706
+ easy: "Recalled instantly \u2014 push the next review further out."
9707
+ };
9708
+ var RATING_CLASSES = {
9709
+ again: "border-destructive/40 bg-destructive/10 text-destructive hover:bg-destructive/15 focus-visible:ring-destructive/40",
9710
+ hard: "border-secondary bg-secondary text-secondary-foreground hover:bg-secondary/80",
9711
+ good: "border-primary/40 bg-primary/10 text-foreground hover:bg-primary/15 focus-visible:ring-primary/40",
9712
+ easy: "border-accent bg-accent/10 text-accent-foreground hover:bg-accent/20 focus-visible:ring-accent/40"
9713
+ };
9714
+ function SpacedRepetition({
9715
+ cardId,
9716
+ onRate,
9717
+ labels,
9718
+ className,
9719
+ ...rest
9720
+ }) {
9721
+ const onRateRef = React44.useRef(onRate);
9722
+ onRateRef.current = onRate;
9723
+ return /* @__PURE__ */ jsx(
9724
+ "div",
9725
+ {
9726
+ ...rest,
9727
+ "data-hex-spaced-repetition": true,
9728
+ "data-card-id": cardId,
9729
+ role: "group",
9730
+ "aria-label": "Confidence rating",
9731
+ className: cn("inline-flex flex-wrap items-center gap-2", className),
9732
+ children: RATINGS.map((rating) => {
9733
+ const label = labels?.[rating] ?? DEFAULT_LABELS[rating];
9734
+ return /* @__PURE__ */ jsx(
9735
+ "button",
9736
+ {
9737
+ type: "button",
9738
+ "data-hex-spaced-repetition-button": true,
9739
+ "data-rating": rating,
9740
+ "aria-label": `${label}: ${RATING_HINT[rating]}`,
9741
+ onClick: () => onRateRef.current(rating, cardId),
9742
+ className: cn(
9743
+ "inline-flex h-9 items-center justify-center rounded-md border px-3 text-sm font-medium transition-colors",
9744
+ "focus:outline-none focus-visible:ring-2 focus-visible:ring-ring",
9745
+ RATING_CLASSES[rating]
9746
+ ),
9747
+ children: label
9748
+ },
9749
+ rating
9750
+ );
9751
+ })
9752
+ }
9753
+ );
9754
+ }
5811
9755
 
5812
- export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, AlertTitle, AspectRatio, Attachment, Avatar, AvatarFallback, AvatarImage, Badge, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, Calendar, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, Citation, Cluster, CodeBlock, CodeBlockCopy2 as CodeBlockCopy, Collapsible, CollapsibleContent2 as CollapsibleContent, CollapsibleTrigger2 as CollapsibleTrigger, ColorPicker, Combobox, Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut, Composer, Container, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuTrigger, DataTable, DatePicker, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerPortal, DrawerTitle, DrawerTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuTrigger, Dropzone, Empty, ErrorState, FileTree, Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, Grid, HoverCard, HoverCardContent, HoverCardTrigger, Input, InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, Label, Loading, LoadingIndicator, Markdown, Menubar, MenubarContent, MenubarGroup, MenubarItem, MenubarLabel, MenubarMenu, MenubarPortal, MenubarRadioGroup, MenubarSeparator, MenubarShortcut, MenubarTrigger, Message, MessageActions, MessageList, MultiCombobox, NavigationMenu, NavigationMenuContent, NavigationMenuIndicator, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger, NavigationMenuViewport, Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, Progress, RadioGroup, RadioGroupItem, Reasoning, ResizableHandle, ResizablePanel, ResizablePanelGroup, ScrollArea, ScrollBar, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectSeparator, SelectTrigger, SelectValue, Separator, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, Sidebar, SidebarContent, SidebarFooter, SidebarHeader, SidebarItem, SidebarProvider, SidebarTrigger, Skeleton, Slider, Spacer, SpeechRecognition, Stack, Stepper, Suggestion, Switch, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Tag, Textarea, TimePicker, Timeline, Toaster, Toggle, ToggleGroup, ToggleGroupItem, ToolCall, Toolbar, ToolbarButton, ToolbarLink, ToolbarSeparator, ToolbarToggleGroup, ToolbarToggleItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, Tree, alertVariants, attachmentVariants, badgeVariants, buttonVariants, clusterVariants, cn, containerVariants, emptyVariants, errorStateVariants, formatHslTriplet, gridVariants, hexToHslTriplet, hslToRgb, hslTripletToHex, loadingIndicatorVariants, loadingVariants, messageVariants, navigationMenuTriggerStyle, parseHslTriplet, rgbToHsl, spacerVariants, stackVariants, tagVariants, toggleVariants, toolbarVariants, useFormField, useSidebar };
9756
+ export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, AlertTitle, Arc, AspectRatio, Attachment, AudioPlayer, AudioWaveform, Avatar, AvatarFallback, AvatarImage, Badge, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, Calendar, Canvas, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, Chord, Citation, Cloze, Cluster, CodeBlock, CodeBlockCopy2 as CodeBlockCopy, Collapsible, CollapsibleContent2 as CollapsibleContent, CollapsibleTrigger2 as CollapsibleTrigger, ColorPicker, Combobox, Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut, CompareTable, Composer, Container, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuTrigger, DataTable, DatePicker, Deck, Dendrogram, Diagram, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerPortal, DrawerTitle, DrawerTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuTrigger, Dropzone, Empty, ErrorState, FileTree, Flashcard, Flowchart, Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, Funnel, Gantt, Grid, HoverCard, HoverCardContent, HoverCardTrigger, ImageOcclusion, Input, InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, Label, Loading, LoadingIndicator, Markdown, Matrix, Menubar, MenubarContent, MenubarGroup, MenubarItem, MenubarLabel, MenubarMenu, MenubarPortal, MenubarRadioGroup, MenubarSeparator, MenubarShortcut, MenubarTrigger, Message, MessageActions, MessageList, MindMap, MultiCombobox, NavigationMenu, NavigationMenuContent, NavigationMenuIndicator, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger, NavigationMenuViewport, OrgChart, Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, Progress, Pyramid, Quiz, RadioGroup, RadioGroupItem, Reasoning, ResizableHandle, ResizablePanel, ResizablePanelGroup, Sankey, ScrollArea, ScrollBar, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectSeparator, SelectTrigger, SelectValue, Separator, Sequence, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, Sidebar, SidebarContent, SidebarFooter, SidebarHeader, SidebarItem, SidebarProvider, SidebarTrigger, Skeleton, Slider, SpacedRepetition, Spacer, SpeechRecognition, Stack, Stepper, Suggestion, Sunburst, Switch, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Tag, Terminal, Textarea, TimeAxis, TimePicker, Timeline, Toaster, Toggle, ToggleGroup, ToggleGroupItem, ToolCall, Toolbar, ToolbarButton, ToolbarLink, ToolbarSeparator, ToolbarToggleGroup, ToolbarToggleItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, Tree, TreeMap, Venn, alertVariants, attachmentVariants, badgeVariants, buttonVariants, clusterVariants, cn, containerVariants, emptyVariants, errorStateVariants, formatHslTriplet, gridVariants, hexToHslTriplet, hslToRgb, hslTripletToHex, loadingIndicatorVariants, loadingVariants, messageVariants, navigationMenuTriggerStyle, parseHslTriplet, rgbToHsl, spacerVariants, stackVariants, tagVariants, toggleVariants, toolbarVariants, useFormField, useSidebar };
5813
9757
  //# sourceMappingURL=index.js.map
5814
9758
  //# sourceMappingURL=index.js.map