@hex-core/components 1.7.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_tsup-dts-rollup.d.ts +1561 -0
- package/dist/arc.d.ts +4 -0
- package/dist/arc.js +147 -0
- package/dist/arc.js.map +1 -0
- package/dist/audio-player.d.ts +2 -0
- package/dist/audio-player.js +119 -0
- package/dist/audio-player.js.map +1 -0
- package/dist/audio-waveform.d.ts +2 -0
- package/dist/audio-waveform.js +72 -0
- package/dist/audio-waveform.js.map +1 -0
- package/dist/canvas.d.ts +2 -0
- package/dist/canvas.js +73 -0
- package/dist/canvas.js.map +1 -0
- package/dist/chord.d.ts +4 -0
- package/dist/chord.js +230 -0
- package/dist/chord.js.map +1 -0
- package/dist/cloze.d.ts +3 -0
- package/dist/cloze.js +98 -0
- package/dist/cloze.js.map +1 -0
- package/dist/compare-table.d.ts +4 -0
- package/dist/compare-table.js +109 -0
- package/dist/compare-table.js.map +1 -0
- package/dist/deck.d.ts +3 -0
- package/dist/deck.js +231 -0
- package/dist/deck.js.map +1 -0
- package/dist/dendrogram.d.ts +3 -0
- package/dist/dendrogram.js +162 -0
- package/dist/dendrogram.js.map +1 -0
- package/dist/diagram.d.ts +2 -0
- package/dist/diagram.js +70 -0
- package/dist/diagram.js.map +1 -0
- package/dist/flashcard.d.ts +2 -0
- package/dist/flashcard.js +107 -0
- package/dist/flashcard.js.map +1 -0
- package/dist/flowchart.d.ts +4 -0
- package/dist/flowchart.js +275 -0
- package/dist/flowchart.js.map +1 -0
- package/dist/funnel.d.ts +3 -0
- package/dist/funnel.js +157 -0
- package/dist/funnel.js.map +1 -0
- package/dist/gantt.d.ts +3 -0
- package/dist/gantt.js +279 -0
- package/dist/gantt.js.map +1 -0
- package/dist/image-occlusion.d.ts +3 -0
- package/dist/image-occlusion.js +106 -0
- package/dist/image-occlusion.js.map +1 -0
- package/dist/index.d.ts +84 -0
- package/dist/index.js +3946 -2
- package/dist/index.js.map +1 -1
- package/dist/matrix.d.ts +3 -0
- package/dist/matrix.js +155 -0
- package/dist/matrix.js.map +1 -0
- package/dist/mind-map.d.ts +3 -0
- package/dist/mind-map.js +167 -0
- package/dist/mind-map.js.map +1 -0
- package/dist/org-chart.d.ts +3 -0
- package/dist/org-chart.js +215 -0
- package/dist/org-chart.js.map +1 -0
- package/dist/pyramid.d.ts +3 -0
- package/dist/pyramid.js +150 -0
- package/dist/pyramid.js.map +1 -0
- package/dist/quiz.d.ts +3 -0
- package/dist/quiz.js +128 -0
- package/dist/quiz.js.map +1 -0
- package/dist/sankey.d.ts +4 -0
- package/dist/sankey.js +190 -0
- package/dist/sankey.js.map +1 -0
- package/dist/schemas.d.ts +23 -0
- package/dist/schemas.js +2208 -1
- package/dist/schemas.js.map +1 -1
- package/dist/sequence.d.ts +4 -0
- package/dist/sequence.js +229 -0
- package/dist/sequence.js.map +1 -0
- package/dist/spaced-repetition.d.ts +3 -0
- package/dist/spaced-repetition.js +73 -0
- package/dist/spaced-repetition.js.map +1 -0
- package/dist/sunburst.d.ts +3 -0
- package/dist/sunburst.js +205 -0
- package/dist/sunburst.js.map +1 -0
- package/dist/terminal.d.ts +2 -0
- package/dist/terminal.js +153 -0
- package/dist/terminal.js.map +1 -0
- package/dist/time-axis.d.ts +3 -0
- package/dist/time-axis.js +233 -0
- package/dist/time-axis.js.map +1 -0
- package/dist/tool-call.js +6 -1
- package/dist/tool-call.js.map +1 -1
- package/dist/tree-map.d.ts +3 -0
- package/dist/tree-map.js +171 -0
- package/dist/tree-map.js.map +1 -0
- package/dist/venn.d.ts +3 -0
- package/dist/venn.js +196 -0
- package/dist/venn.js.map +1 -0
- package/package.json +47 -3
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
|
-
|
|
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
|