@hex-core/components 1.6.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.
Files changed (97) hide show
  1. package/dist/_tsup-dts-rollup.d.ts +1597 -0
  2. package/dist/arc.d.ts +4 -0
  3. package/dist/arc.js +147 -0
  4. package/dist/arc.js.map +1 -0
  5. package/dist/audio-player.d.ts +2 -0
  6. package/dist/audio-player.js +119 -0
  7. package/dist/audio-player.js.map +1 -0
  8. package/dist/audio-waveform.d.ts +2 -0
  9. package/dist/audio-waveform.js +72 -0
  10. package/dist/audio-waveform.js.map +1 -0
  11. package/dist/canvas.d.ts +2 -0
  12. package/dist/canvas.js +73 -0
  13. package/dist/canvas.js.map +1 -0
  14. package/dist/chord.d.ts +4 -0
  15. package/dist/chord.js +230 -0
  16. package/dist/chord.js.map +1 -0
  17. package/dist/cloze.d.ts +3 -0
  18. package/dist/cloze.js +98 -0
  19. package/dist/cloze.js.map +1 -0
  20. package/dist/compare-table.d.ts +4 -0
  21. package/dist/compare-table.js +109 -0
  22. package/dist/compare-table.js.map +1 -0
  23. package/dist/deck.d.ts +3 -0
  24. package/dist/deck.js +231 -0
  25. package/dist/deck.js.map +1 -0
  26. package/dist/dendrogram.d.ts +3 -0
  27. package/dist/dendrogram.js +162 -0
  28. package/dist/dendrogram.js.map +1 -0
  29. package/dist/diagram.d.ts +2 -0
  30. package/dist/diagram.js +70 -0
  31. package/dist/diagram.js.map +1 -0
  32. package/dist/flashcard.d.ts +2 -0
  33. package/dist/flashcard.js +107 -0
  34. package/dist/flashcard.js.map +1 -0
  35. package/dist/flowchart.d.ts +4 -0
  36. package/dist/flowchart.js +275 -0
  37. package/dist/flowchart.js.map +1 -0
  38. package/dist/funnel.d.ts +3 -0
  39. package/dist/funnel.js +157 -0
  40. package/dist/funnel.js.map +1 -0
  41. package/dist/gantt.d.ts +3 -0
  42. package/dist/gantt.js +279 -0
  43. package/dist/gantt.js.map +1 -0
  44. package/dist/image-occlusion.d.ts +3 -0
  45. package/dist/image-occlusion.js +106 -0
  46. package/dist/image-occlusion.js.map +1 -0
  47. package/dist/index.d.ts +86 -0
  48. package/dist/index.js +4085 -2
  49. package/dist/index.js.map +1 -1
  50. package/dist/matrix.d.ts +3 -0
  51. package/dist/matrix.js +155 -0
  52. package/dist/matrix.js.map +1 -0
  53. package/dist/mind-map.d.ts +3 -0
  54. package/dist/mind-map.js +167 -0
  55. package/dist/mind-map.js.map +1 -0
  56. package/dist/org-chart.d.ts +3 -0
  57. package/dist/org-chart.js +215 -0
  58. package/dist/org-chart.js.map +1 -0
  59. package/dist/pyramid.d.ts +3 -0
  60. package/dist/pyramid.js +150 -0
  61. package/dist/pyramid.js.map +1 -0
  62. package/dist/quiz.d.ts +3 -0
  63. package/dist/quiz.js +128 -0
  64. package/dist/quiz.js.map +1 -0
  65. package/dist/sankey.d.ts +4 -0
  66. package/dist/sankey.js +190 -0
  67. package/dist/sankey.js.map +1 -0
  68. package/dist/schemas.d.ts +23 -0
  69. package/dist/schemas.js +2211 -4
  70. package/dist/schemas.js.map +1 -1
  71. package/dist/sequence.d.ts +4 -0
  72. package/dist/sequence.js +229 -0
  73. package/dist/sequence.js.map +1 -0
  74. package/dist/spaced-repetition.d.ts +3 -0
  75. package/dist/spaced-repetition.js +73 -0
  76. package/dist/spaced-repetition.js.map +1 -0
  77. package/dist/speech-recognition.d.ts +2 -0
  78. package/dist/speech-recognition.js +152 -0
  79. package/dist/speech-recognition.js.map +1 -0
  80. package/dist/sunburst.d.ts +3 -0
  81. package/dist/sunburst.js +205 -0
  82. package/dist/sunburst.js.map +1 -0
  83. package/dist/terminal.d.ts +2 -0
  84. package/dist/terminal.js +153 -0
  85. package/dist/terminal.js.map +1 -0
  86. package/dist/time-axis.d.ts +3 -0
  87. package/dist/time-axis.js +233 -0
  88. package/dist/time-axis.js.map +1 -0
  89. package/dist/tool-call.js +6 -1
  90. package/dist/tool-call.js.map +1 -1
  91. package/dist/tree-map.d.ts +3 -0
  92. package/dist/tree-map.js +171 -0
  93. package/dist/tree-map.js.map +1 -0
  94. package/dist/venn.d.ts +3 -0
  95. package/dist/venn.js +196 -0
  96. package/dist/venn.js.map +1 -0
  97. package/package.json +47 -3
package/dist/deck.js ADDED
@@ -0,0 +1,231 @@
1
+ "use client";
2
+ import * as React2 from 'react';
3
+ import { clsx } from 'clsx';
4
+ import { twMerge } from 'tailwind-merge';
5
+ import { jsx, jsxs } from 'react/jsx-runtime';
6
+
7
+ function cn(...inputs) {
8
+ return twMerge(clsx(inputs));
9
+ }
10
+ function Flashcard({
11
+ front,
12
+ back,
13
+ defaultFlipped = false,
14
+ flipped: flippedProp,
15
+ onFlipChange,
16
+ width = 360,
17
+ height = 240,
18
+ flipDurationMs = 500,
19
+ className,
20
+ ...rest
21
+ }) {
22
+ const [internalFlipped, setInternalFlipped] = React2.useState(defaultFlipped);
23
+ const isControlled = flippedProp !== void 0;
24
+ const flipped = isControlled ? flippedProp : internalFlipped;
25
+ const toggle = React2.useCallback(() => {
26
+ const next = !flipped;
27
+ if (!isControlled) setInternalFlipped(next);
28
+ onFlipChange?.(next);
29
+ }, [flipped, isControlled, onFlipChange]);
30
+ const handleKey = React2.useCallback(
31
+ (e) => {
32
+ if (e.key === "Enter" || e.key === " ") {
33
+ e.preventDefault();
34
+ toggle();
35
+ }
36
+ },
37
+ [toggle]
38
+ );
39
+ return /* @__PURE__ */ jsx(
40
+ "div",
41
+ {
42
+ ...rest,
43
+ "data-hex-flashcard": true,
44
+ "data-flipped": flipped,
45
+ role: "button",
46
+ tabIndex: 0,
47
+ "aria-pressed": flipped,
48
+ "aria-label": flipped ? "Flashcard, back side. Activate to flip to front." : "Flashcard, front side. Activate to reveal back.",
49
+ onClick: toggle,
50
+ onKeyDown: handleKey,
51
+ className: cn(
52
+ "relative inline-block cursor-pointer select-none focus:outline-none focus-visible:ring-2 focus-visible:ring-ring",
53
+ className
54
+ ),
55
+ style: {
56
+ width,
57
+ height,
58
+ perspective: 1e3
59
+ },
60
+ children: /* @__PURE__ */ jsxs(
61
+ "div",
62
+ {
63
+ "data-hex-flashcard-inner": true,
64
+ className: "relative h-full w-full transition-transform",
65
+ style: {
66
+ transformStyle: "preserve-3d",
67
+ transform: flipped ? "rotateY(180deg)" : "rotateY(0deg)",
68
+ transitionDuration: `${flipDurationMs}ms`
69
+ },
70
+ children: [
71
+ /* @__PURE__ */ jsx(
72
+ "div",
73
+ {
74
+ "data-hex-flashcard-face": true,
75
+ "data-side": "front",
76
+ className: "absolute inset-0 flex items-center justify-center rounded-lg border bg-card p-4 text-center text-card-foreground shadow-sm",
77
+ style: {
78
+ backfaceVisibility: "hidden",
79
+ WebkitBackfaceVisibility: "hidden"
80
+ },
81
+ children: front
82
+ }
83
+ ),
84
+ /* @__PURE__ */ jsx(
85
+ "div",
86
+ {
87
+ "data-hex-flashcard-face": true,
88
+ "data-side": "back",
89
+ className: "absolute inset-0 flex items-center justify-center rounded-lg border bg-card p-4 text-center text-card-foreground shadow-sm",
90
+ style: {
91
+ backfaceVisibility: "hidden",
92
+ WebkitBackfaceVisibility: "hidden",
93
+ transform: "rotateY(180deg)"
94
+ },
95
+ children: back
96
+ }
97
+ )
98
+ ]
99
+ }
100
+ )
101
+ }
102
+ );
103
+ }
104
+ function shuffleArray(arr) {
105
+ const out = arr.slice();
106
+ for (let i = out.length - 1; i > 0; i--) {
107
+ const j = Math.floor(Math.random() * (i + 1));
108
+ [out[i], out[j]] = [out[j], out[i]];
109
+ }
110
+ return out;
111
+ }
112
+ function Deck({
113
+ cards,
114
+ shuffle = false,
115
+ ratingSlot,
116
+ onCardChange,
117
+ cardWidth = 360,
118
+ cardHeight = 240,
119
+ className,
120
+ ...rest
121
+ }) {
122
+ const order = React2.useMemo(() => shuffle ? shuffleArray(cards) : cards.slice(), [cards, shuffle]);
123
+ const [index, setIndex] = React2.useState(0);
124
+ const [flipped, setFlipped] = React2.useState(false);
125
+ const onCardChangeRef = React2.useRef(onCardChange);
126
+ onCardChangeRef.current = onCardChange;
127
+ React2.useEffect(() => {
128
+ setIndex(0);
129
+ setFlipped(false);
130
+ }, [order]);
131
+ const total = order.length;
132
+ const currentCard = order[index];
133
+ const goPrev = React2.useCallback(() => {
134
+ if (index === 0) return;
135
+ const next = index - 1;
136
+ setIndex(next);
137
+ setFlipped(false);
138
+ const card = order[next];
139
+ if (card) onCardChangeRef.current?.(next, card);
140
+ }, [index, order]);
141
+ const goNext = React2.useCallback(() => {
142
+ if (index >= total - 1) return;
143
+ const next = index + 1;
144
+ setIndex(next);
145
+ setFlipped(false);
146
+ const card = order[next];
147
+ if (card) onCardChangeRef.current?.(next, card);
148
+ }, [index, order, total]);
149
+ if (total === 0) {
150
+ return /* @__PURE__ */ jsx(
151
+ "div",
152
+ {
153
+ ...rest,
154
+ "data-hex-deck": true,
155
+ "data-empty": "true",
156
+ className: cn("rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground", className),
157
+ children: "No cards in this deck."
158
+ }
159
+ );
160
+ }
161
+ const progressPercent = total > 0 ? (index + 1) / total * 100 : 0;
162
+ return /* @__PURE__ */ jsxs(
163
+ "div",
164
+ {
165
+ ...rest,
166
+ "data-hex-deck": true,
167
+ "data-index": index,
168
+ "data-total": total,
169
+ className: cn("inline-flex flex-col items-center gap-4", className),
170
+ children: [
171
+ currentCard ? /* @__PURE__ */ jsx(
172
+ Flashcard,
173
+ {
174
+ front: currentCard.front,
175
+ back: currentCard.back,
176
+ flipped,
177
+ onFlipChange: setFlipped,
178
+ width: cardWidth,
179
+ height: cardHeight
180
+ },
181
+ currentCard.id
182
+ ) : null,
183
+ /* @__PURE__ */ jsxs("div", { "data-hex-deck-controls": true, className: "flex w-full items-center gap-3", style: { width: cardWidth }, children: [
184
+ /* @__PURE__ */ jsx(
185
+ "button",
186
+ {
187
+ type: "button",
188
+ "data-hex-deck-prev": true,
189
+ disabled: index === 0,
190
+ onClick: goPrev,
191
+ "aria-label": `Previous card. Currently ${index + 1} of ${total}.`,
192
+ 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",
193
+ children: "Prev"
194
+ }
195
+ ),
196
+ /* @__PURE__ */ jsxs("div", { "data-hex-deck-progress": true, className: "flex-1", "aria-hidden": "true", children: [
197
+ /* @__PURE__ */ jsx("div", { className: "h-1.5 overflow-hidden rounded-full bg-muted", children: /* @__PURE__ */ jsx(
198
+ "div",
199
+ {
200
+ className: "h-full bg-primary transition-all",
201
+ style: { width: `${progressPercent}%` }
202
+ }
203
+ ) }),
204
+ /* @__PURE__ */ jsxs("div", { className: "mt-1 text-center text-xs text-muted-foreground", children: [
205
+ index + 1,
206
+ " / ",
207
+ total
208
+ ] })
209
+ ] }),
210
+ /* @__PURE__ */ jsx(
211
+ "button",
212
+ {
213
+ type: "button",
214
+ "data-hex-deck-next": true,
215
+ disabled: index >= total - 1,
216
+ onClick: goNext,
217
+ "aria-label": `Next card. Currently ${index + 1} of ${total}.`,
218
+ 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",
219
+ children: "Next"
220
+ }
221
+ )
222
+ ] }),
223
+ ratingSlot && currentCard ? /* @__PURE__ */ jsx("div", { "data-hex-deck-rating-slot": true, className: "w-full", style: { width: cardWidth }, children: ratingSlot(currentCard) }) : null
224
+ ]
225
+ }
226
+ );
227
+ }
228
+
229
+ export { Deck };
230
+ //# sourceMappingURL=deck.js.map
231
+ //# sourceMappingURL=deck.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/utils.ts","../src/artifacts/flashcard/flashcard.tsx","../src/artifacts/deck/deck.tsx"],"names":["React","jsx","jsxs"],"mappings":"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","file":"deck.js","sourcesContent":["import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Merge class names with Tailwind CSS conflict resolution.\n * @param inputs - Class values (strings, arrays, objects) to merge\n * @returns A single merged class string with Tailwind conflicts resolved\n */\nexport function cn(...inputs: ClassValue[]) {\n\treturn twMerge(clsx(inputs));\n}\n","\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../../lib/utils.js\";\n\n/**\n * Flashcard — front/back card with a 3D flip animation. Click, Enter, or\n * Space to flip. Pure CSS 3D transform, no animation peer required.\n *\n * Headless on content: pass any ReactNode for `front` and `back`. Pair\n * with Deck (artifacts/deck) for shuffle / next / prev / progress, or\n * with SpacedRepetition (artifacts/spaced-repetition) for confidence\n * rating after each reveal.\n *\n * @example\n * <Flashcard\n * front={<>What is the capital of France?</>}\n * back={<>Paris</>}\n * />\n *\n * <Flashcard\n * flipped={isFlipped}\n * onFlipChange={setFlipped}\n * front={term}\n * back={definition}\n * />\n */\nexport interface FlashcardProps extends Omit<React.HTMLAttributes<HTMLDivElement>, \"children\"> {\n\t/** Content of the front face. */\n\tfront: React.ReactNode;\n\t/** Content of the back face. */\n\tback: React.ReactNode;\n\t/** Uncontrolled initial flipped state. Default false. */\n\tdefaultFlipped?: boolean;\n\t/** Controlled flipped state. */\n\tflipped?: boolean;\n\t/** Fired with the new flipped value when the user toggles. */\n\tonFlipChange?: (flipped: boolean) => void;\n\t/** Pixel width. Default 360. */\n\twidth?: number;\n\t/** Pixel height. Default 240. */\n\theight?: number;\n\t/** Flip animation duration in ms. Default 500. Set to 0 to disable the animation entirely. */\n\tflipDurationMs?: number;\n}\n\nfunction Flashcard({\n\tfront,\n\tback,\n\tdefaultFlipped = false,\n\tflipped: flippedProp,\n\tonFlipChange,\n\twidth = 360,\n\theight = 240,\n\tflipDurationMs = 500,\n\tclassName,\n\t...rest\n}: FlashcardProps) {\n\tconst [internalFlipped, setInternalFlipped] = React.useState(defaultFlipped);\n\tconst isControlled = flippedProp !== undefined;\n\tconst flipped = isControlled ? flippedProp : internalFlipped;\n\n\tconst toggle = React.useCallback(() => {\n\t\tconst next = !flipped;\n\t\tif (!isControlled) setInternalFlipped(next);\n\t\tonFlipChange?.(next);\n\t}, [flipped, isControlled, onFlipChange]);\n\n\tconst handleKey = React.useCallback(\n\t\t(e: React.KeyboardEvent) => {\n\t\t\tif (e.key === \"Enter\" || e.key === \" \") {\n\t\t\t\te.preventDefault();\n\t\t\t\ttoggle();\n\t\t\t}\n\t\t},\n\t\t[toggle],\n\t);\n\n\treturn (\n\t\t<div\n\t\t\t{...rest}\n\t\t\tdata-hex-flashcard\n\t\t\tdata-flipped={flipped}\n\t\t\trole=\"button\"\n\t\t\ttabIndex={0}\n\t\t\taria-pressed={flipped}\n\t\t\taria-label={flipped ? \"Flashcard, back side. Activate to flip to front.\" : \"Flashcard, front side. Activate to reveal back.\"}\n\t\t\tonClick={toggle}\n\t\t\tonKeyDown={handleKey}\n\t\t\tclassName={cn(\n\t\t\t\t\"relative inline-block cursor-pointer select-none focus:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\tstyle={{\n\t\t\t\twidth,\n\t\t\t\theight,\n\t\t\t\tperspective: 1000,\n\t\t\t}}\n\t\t>\n\t\t\t<div\n\t\t\t\tdata-hex-flashcard-inner\n\t\t\t\tclassName=\"relative h-full w-full transition-transform\"\n\t\t\t\tstyle={{\n\t\t\t\t\ttransformStyle: \"preserve-3d\",\n\t\t\t\t\ttransform: flipped ? \"rotateY(180deg)\" : \"rotateY(0deg)\",\n\t\t\t\t\ttransitionDuration: `${flipDurationMs}ms`,\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t<div\n\t\t\t\t\tdata-hex-flashcard-face\n\t\t\t\t\tdata-side=\"front\"\n\t\t\t\t\tclassName=\"absolute inset-0 flex items-center justify-center rounded-lg border bg-card p-4 text-center text-card-foreground shadow-sm\"\n\t\t\t\t\tstyle={{\n\t\t\t\t\t\tbackfaceVisibility: \"hidden\",\n\t\t\t\t\t\tWebkitBackfaceVisibility: \"hidden\",\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t{front}\n\t\t\t\t</div>\n\t\t\t\t<div\n\t\t\t\t\tdata-hex-flashcard-face\n\t\t\t\t\tdata-side=\"back\"\n\t\t\t\t\tclassName=\"absolute inset-0 flex items-center justify-center rounded-lg border bg-card p-4 text-center text-card-foreground shadow-sm\"\n\t\t\t\t\tstyle={{\n\t\t\t\t\t\tbackfaceVisibility: \"hidden\",\n\t\t\t\t\t\tWebkitBackfaceVisibility: \"hidden\",\n\t\t\t\t\t\ttransform: \"rotateY(180deg)\",\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t{back}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nexport { Flashcard };\n","\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../../lib/utils.js\";\nimport { Flashcard } from \"../flashcard/flashcard.js\";\n\n/**\n * Deck — a paged sequence of flashcards with optional shuffle, prev/next\n * navigation, a progress bar, and a slot for per-card SRS rating.\n * Composes [Flashcard] internally; consumers don't render Flashcard\n * themselves when they're inside a Deck.\n *\n * @example\n * <Deck\n * cards={[\n * { id: \"1\", front: \"Term 1\", back: \"Definition 1\" },\n * { id: \"2\", front: \"Term 2\", back: \"Definition 2\" },\n * ]}\n * shuffle\n * ratingSlot={(card) => (\n * <SpacedRepetition cardId={card.id} onRate={(rating) => save(rating, card.id)} />\n * )}\n * />\n */\nexport type DeckCard = {\n\tid: string;\n\tfront: React.ReactNode;\n\tback: React.ReactNode;\n};\n\nexport interface DeckProps extends Omit<React.HTMLAttributes<HTMLDivElement>, \"children\"> {\n\t/** Cards in order. */\n\tcards: DeckCard[];\n\t/** Initial shuffle. Default false (preserves order). */\n\tshuffle?: boolean;\n\t/** Optional render slot below the card; passed the current card. Useful for SpacedRepetition. */\n\tratingSlot?: (card: DeckCard) => React.ReactNode;\n\t/** Fired whenever the active card changes (after shuffle / prev / next). */\n\tonCardChange?: (index: number, card: DeckCard) => void;\n\t/** Pixel width of the inner Flashcard. Default 360. */\n\tcardWidth?: number;\n\t/** Pixel height of the inner Flashcard. Default 240. */\n\tcardHeight?: number;\n}\n\nfunction shuffleArray<T>(arr: T[]): T[] {\n\t// Fisher-Yates. Cheap and deterministic-shape (just non-deterministic\n\t// order on each call). Consumers wanting reproducibility wire their own\n\t// pre-shuffled array and pass `shuffle={false}`.\n\tconst out = arr.slice();\n\tfor (let i = out.length - 1; i > 0; i--) {\n\t\tconst j = Math.floor(Math.random() * (i + 1));\n\t\t[out[i], out[j]] = [out[j], out[i]];\n\t}\n\treturn out;\n}\n\nfunction Deck({\n\tcards,\n\tshuffle = false,\n\tratingSlot,\n\tonCardChange,\n\tcardWidth = 360,\n\tcardHeight = 240,\n\tclassName,\n\t...rest\n}: DeckProps) {\n\t// Order is recomputed only when `cards` identity OR `shuffle` flips —\n\t// not on every prev/next, so the user never gets re-shuffled mid-session.\n\tconst order = React.useMemo(() => (shuffle ? shuffleArray(cards) : cards.slice()), [cards, shuffle]);\n\tconst [index, setIndex] = React.useState(0);\n\tconst [flipped, setFlipped] = React.useState(false);\n\tconst onCardChangeRef = React.useRef(onCardChange);\n\tonCardChangeRef.current = onCardChange;\n\n\t// Reset to the first card when the order changes (cards swap, shuffle toggles).\n\tReact.useEffect(() => {\n\t\tsetIndex(0);\n\t\tsetFlipped(false);\n\t}, [order]);\n\n\tconst total = order.length;\n\tconst currentCard = order[index];\n\n\tconst goPrev = React.useCallback(() => {\n\t\tif (index === 0) return;\n\t\tconst next = index - 1;\n\t\tsetIndex(next);\n\t\tsetFlipped(false);\n\t\tconst card = order[next];\n\t\tif (card) onCardChangeRef.current?.(next, card);\n\t}, [index, order]);\n\n\tconst goNext = React.useCallback(() => {\n\t\tif (index >= total - 1) return;\n\t\tconst next = index + 1;\n\t\tsetIndex(next);\n\t\tsetFlipped(false);\n\t\tconst card = order[next];\n\t\tif (card) onCardChangeRef.current?.(next, card);\n\t}, [index, order, total]);\n\n\tif (total === 0) {\n\t\treturn (\n\t\t\t<div\n\t\t\t\t{...rest}\n\t\t\t\tdata-hex-deck\n\t\t\t\tdata-empty=\"true\"\n\t\t\t\tclassName={cn(\"rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground\", className)}\n\t\t\t>\n\t\t\t\tNo cards in this deck.\n\t\t\t</div>\n\t\t);\n\t}\n\n\tconst progressPercent = total > 0 ? ((index + 1) / total) * 100 : 0;\n\n\treturn (\n\t\t<div\n\t\t\t{...rest}\n\t\t\tdata-hex-deck\n\t\t\tdata-index={index}\n\t\t\tdata-total={total}\n\t\t\tclassName={cn(\"inline-flex flex-col items-center gap-4\", className)}\n\t\t>\n\t\t\t{currentCard ? (\n\t\t\t\t<Flashcard\n\t\t\t\t\tkey={currentCard.id}\n\t\t\t\t\tfront={currentCard.front}\n\t\t\t\t\tback={currentCard.back}\n\t\t\t\t\tflipped={flipped}\n\t\t\t\t\tonFlipChange={setFlipped}\n\t\t\t\t\twidth={cardWidth}\n\t\t\t\t\theight={cardHeight}\n\t\t\t\t/>\n\t\t\t) : null}\n\t\t\t<div data-hex-deck-controls className=\"flex w-full items-center gap-3\" style={{ width: cardWidth }}>\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tdata-hex-deck-prev\n\t\t\t\t\tdisabled={index === 0}\n\t\t\t\t\tonClick={goPrev}\n\t\t\t\t\taria-label={`Previous card. Currently ${index + 1} of ${total}.`}\n\t\t\t\t\tclassName=\"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\"\n\t\t\t\t>\n\t\t\t\t\tPrev\n\t\t\t\t</button>\n\t\t\t\t<div data-hex-deck-progress className=\"flex-1\" aria-hidden=\"true\">\n\t\t\t\t\t<div className=\"h-1.5 overflow-hidden rounded-full bg-muted\">\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tclassName=\"h-full bg-primary transition-all\"\n\t\t\t\t\t\t\tstyle={{ width: `${progressPercent}%` }}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"mt-1 text-center text-xs text-muted-foreground\">\n\t\t\t\t\t\t{index + 1} / {total}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tdata-hex-deck-next\n\t\t\t\t\tdisabled={index >= total - 1}\n\t\t\t\t\tonClick={goNext}\n\t\t\t\t\taria-label={`Next card. Currently ${index + 1} of ${total}.`}\n\t\t\t\t\tclassName=\"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\"\n\t\t\t\t>\n\t\t\t\t\tNext\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t\t{ratingSlot && currentCard ? (\n\t\t\t\t<div data-hex-deck-rating-slot className=\"w-full\" style={{ width: cardWidth }}>\n\t\t\t\t\t{ratingSlot(currentCard)}\n\t\t\t\t</div>\n\t\t\t) : null}\n\t\t</div>\n\t);\n}\n\nexport { Deck };\n"]}
@@ -0,0 +1,3 @@
1
+ export { DendrogramNode_alias_1 as DendrogramNode } from './_tsup-dts-rollup.js';
2
+ export { DendrogramProps_alias_1 as DendrogramProps } from './_tsup-dts-rollup.js';
3
+ export { Dendrogram_alias_1 as Dendrogram } from './_tsup-dts-rollup.js';
@@ -0,0 +1,162 @@
1
+ "use client";
2
+ import * as React from 'react';
3
+ import { clsx } from 'clsx';
4
+ import { twMerge } from 'tailwind-merge';
5
+ import { jsx, jsxs } from 'react/jsx-runtime';
6
+
7
+ function cn(...inputs) {
8
+ return twMerge(clsx(inputs));
9
+ }
10
+ function Dendrogram({
11
+ root,
12
+ orientation = "horizontal",
13
+ linkShape = "step",
14
+ width = 600,
15
+ height = 400,
16
+ onLeafClick,
17
+ className,
18
+ ...rest
19
+ }) {
20
+ const [d3h, setD3h] = React.useState(null);
21
+ React.useEffect(() => {
22
+ let cancelled = false;
23
+ void import('d3-hierarchy').then((mod) => {
24
+ if (!cancelled) setD3h(mod);
25
+ });
26
+ return () => {
27
+ cancelled = true;
28
+ };
29
+ }, []);
30
+ if (!d3h) {
31
+ return /* @__PURE__ */ jsx(
32
+ "div",
33
+ {
34
+ "data-hex-dendrogram-loading": true,
35
+ "aria-busy": "true",
36
+ className: cn("inline-block bg-muted/20", className),
37
+ style: { width, height }
38
+ }
39
+ );
40
+ }
41
+ const { nodes, links } = layout(d3h, root, orientation, width, height);
42
+ const leafCount = nodes.filter((n) => n.isLeaf).length;
43
+ const desc = `Dendrogram with ${leafCount} leaves, rooted at "${root.label}"`;
44
+ return /* @__PURE__ */ jsxs(
45
+ "svg",
46
+ {
47
+ ...rest,
48
+ "data-hex-dendrogram": true,
49
+ "data-orientation": orientation,
50
+ "data-link-shape": linkShape,
51
+ role: "img",
52
+ width,
53
+ height,
54
+ viewBox: `0 0 ${width} ${height}`,
55
+ className: cn("block", className),
56
+ children: [
57
+ /* @__PURE__ */ jsx("title", { children: "Dendrogram" }),
58
+ /* @__PURE__ */ jsx("desc", { children: desc }),
59
+ /* @__PURE__ */ jsx("g", { "data-hex-dendrogram-links": true, children: links.map((l) => /* @__PURE__ */ jsx(
60
+ "path",
61
+ {
62
+ d: linkPath(l, orientation, linkShape),
63
+ fill: "none",
64
+ stroke: "hsl(var(--muted-foreground))",
65
+ strokeOpacity: 0.8,
66
+ strokeWidth: 1
67
+ },
68
+ l.id
69
+ )) }),
70
+ /* @__PURE__ */ jsx("g", { "data-hex-dendrogram-nodes": true, children: nodes.map((n) => /* @__PURE__ */ jsxs(
71
+ "g",
72
+ {
73
+ "data-hex-dendrogram-node": true,
74
+ "data-leaf": n.isLeaf ? "true" : "false",
75
+ "data-depth": n.depth,
76
+ transform: `translate(${n.x},${n.y})`,
77
+ style: n.isLeaf && onLeafClick ? { cursor: "pointer" } : void 0,
78
+ onClick: n.isLeaf && onLeafClick ? () => onLeafClick(n.node) : void 0,
79
+ children: [
80
+ /* @__PURE__ */ jsx(
81
+ "circle",
82
+ {
83
+ r: n.isLeaf ? 3 : 2,
84
+ fill: n.isLeaf ? "hsl(var(--primary))" : "hsl(var(--muted-foreground))"
85
+ }
86
+ ),
87
+ n.isLeaf ? /* @__PURE__ */ jsx(
88
+ "text",
89
+ {
90
+ x: orientation === "horizontal" ? 6 : 0,
91
+ y: orientation === "horizontal" ? 4 : 14,
92
+ textAnchor: orientation === "horizontal" ? "start" : "middle",
93
+ fontSize: 11,
94
+ fill: "hsl(var(--foreground))",
95
+ style: { paintOrder: "stroke" },
96
+ stroke: "hsl(var(--background))",
97
+ strokeWidth: 3,
98
+ strokeLinejoin: "round",
99
+ children: n.node.label
100
+ }
101
+ ) : null
102
+ ]
103
+ },
104
+ n.node.id
105
+ )) })
106
+ ]
107
+ }
108
+ );
109
+ }
110
+ function layout(d3h, root, orientation, width, height) {
111
+ const hierarchy = d3h.hierarchy(root);
112
+ const clusterFn = d3h.cluster();
113
+ if (orientation === "horizontal") {
114
+ clusterFn.size([height - 32, width - 120]);
115
+ } else {
116
+ clusterFn.size([width - 32, height - 64]);
117
+ }
118
+ const layoutRoot = clusterFn(hierarchy);
119
+ const nodes = [];
120
+ layoutRoot.each((d) => {
121
+ const isLeaf = !d.children || d.children.length === 0;
122
+ if (orientation === "horizontal") {
123
+ nodes.push({ node: d.data, x: d.y + 16, y: d.x + 16, depth: d.depth, isLeaf });
124
+ } else {
125
+ nodes.push({ node: d.data, x: d.x + 16, y: d.y + 16, depth: d.depth, isLeaf });
126
+ }
127
+ });
128
+ const links = layoutRoot.links().map((link, i) => {
129
+ if (orientation === "horizontal") {
130
+ return {
131
+ source: { x: link.source.y + 16, y: link.source.x + 16 },
132
+ target: { x: link.target.y + 16, y: link.target.x + 16 },
133
+ id: `l-${i}`
134
+ };
135
+ }
136
+ return {
137
+ source: { x: link.source.x + 16, y: link.source.y + 16 },
138
+ target: { x: link.target.x + 16, y: link.target.y + 16 },
139
+ id: `l-${i}`
140
+ };
141
+ });
142
+ return { nodes, links };
143
+ }
144
+ function linkPath(link, orientation, linkShape) {
145
+ const { source: s, target: t } = link;
146
+ if (linkShape === "step") {
147
+ if (orientation === "horizontal") {
148
+ return `M${s.x},${s.y} H${t.x} V${t.y}`;
149
+ }
150
+ return `M${s.x},${s.y} V${t.y} H${t.x}`;
151
+ }
152
+ if (orientation === "horizontal") {
153
+ const mx = (s.x + t.x) / 2;
154
+ return `M${s.x},${s.y} C${mx},${s.y} ${mx},${t.y} ${t.x},${t.y}`;
155
+ }
156
+ const my = (s.y + t.y) / 2;
157
+ return `M${s.x},${s.y} C${s.x},${my} ${t.x},${my} ${t.x},${t.y}`;
158
+ }
159
+
160
+ export { Dendrogram };
161
+ //# sourceMappingURL=dendrogram.js.map
162
+ //# sourceMappingURL=dendrogram.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/utils.ts","../src/artifacts/dendrogram/dendrogram.tsx"],"names":[],"mappings":"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","file":"dendrogram.js","sourcesContent":["import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Merge class names with Tailwind CSS conflict resolution.\n * @param inputs - Class values (strings, arrays, objects) to merge\n * @returns A single merged class string with Tailwind conflicts resolved\n */\nexport function cn(...inputs: ClassValue[]) {\n\treturn twMerge(clsx(inputs));\n}\n","\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../../lib/utils.js\";\n\n/**\n * Clustering tree where every leaf sits at the same depth, regardless of\n * branch length — the visual signature of taxonomies, phylogenetic trees,\n * and hierarchical-clustering output. Backed by d3-hierarchy's `cluster`\n * layout (distinct from `tree`, which packs leaves variably).\n *\n * Heavy peer: requires `d3-hierarchy` (~3 KB gzip).\n *\n * @example\n * <Dendrogram\n * root={{\n * id: \"root\", label: \"Animals\",\n * children: [\n * { id: \"mammals\", label: \"Mammals\", children: [\n * { id: \"cat\", label: \"Cat\" },\n * { id: \"dog\", label: \"Dog\" },\n * ]},\n * { id: \"birds\", label: \"Birds\", children: [{ id: \"robin\", label: \"Robin\" }] },\n * ],\n * }}\n * />\n */\nexport type DendrogramNode = {\n\tid: string;\n\tlabel: string;\n\tchildren?: DendrogramNode[];\n};\n\nexport interface DendrogramProps extends Omit<React.SVGAttributes<SVGSVGElement>, \"children\"> {\n\t/** Root of the hierarchy. */\n\troot: DendrogramNode;\n\t/** \"horizontal\" runs root-to-leaves left→right; \"vertical\" runs top→bottom. Default \"horizontal\". */\n\torientation?: \"horizontal\" | \"vertical\";\n\t/** \"step\" draws right-angle elbow links (taxonomy aesthetic); \"diagonal\" uses smooth Bezier. Default \"step\". */\n\tlinkShape?: \"step\" | \"diagonal\";\n\t/** Pixel width of the rendered SVG. Default 600. */\n\twidth?: number;\n\t/** Pixel height of the rendered SVG. Default 400. */\n\theight?: number;\n\t/** Fired when a leaf is clicked. */\n\tonLeafClick?: (node: DendrogramNode) => void;\n}\n\ninterface LaidOutNode {\n\tnode: DendrogramNode;\n\tx: number;\n\ty: number;\n\tdepth: number;\n\tisLeaf: boolean;\n}\n\ninterface LaidOutLink {\n\tsource: { x: number; y: number };\n\ttarget: { x: number; y: number };\n\tid: string;\n}\n\ntype D3HierarchyMod = typeof import(\"d3-hierarchy\");\n\nfunction Dendrogram({\n\troot,\n\torientation = \"horizontal\",\n\tlinkShape = \"step\",\n\twidth = 600,\n\theight = 400,\n\tonLeafClick,\n\tclassName,\n\t...rest\n}: DendrogramProps) {\n\tconst [d3h, setD3h] = React.useState<D3HierarchyMod | null>(null);\n\n\tReact.useEffect(() => {\n\t\tlet cancelled = false;\n\t\tvoid import(\"d3-hierarchy\").then((mod) => {\n\t\t\tif (!cancelled) setD3h(mod);\n\t\t});\n\t\treturn () => {\n\t\t\tcancelled = true;\n\t\t};\n\t}, []);\n\n\tif (!d3h) {\n\t\treturn (\n\t\t\t<div\n\t\t\t\tdata-hex-dendrogram-loading\n\t\t\t\taria-busy=\"true\"\n\t\t\t\tclassName={cn(\"inline-block bg-muted/20\", className)}\n\t\t\t\tstyle={{ width, height }}\n\t\t\t/>\n\t\t);\n\t}\n\n\tconst { nodes, links } = layout(d3h, root, orientation, width, height);\n\tconst leafCount = nodes.filter((n) => n.isLeaf).length;\n\tconst desc = `Dendrogram with ${leafCount} leaves, rooted at \"${root.label}\"`;\n\n\treturn (\n\t\t<svg\n\t\t\t{...rest}\n\t\t\tdata-hex-dendrogram\n\t\t\tdata-orientation={orientation}\n\t\t\tdata-link-shape={linkShape}\n\t\t\trole=\"img\"\n\t\t\twidth={width}\n\t\t\theight={height}\n\t\t\tviewBox={`0 0 ${width} ${height}`}\n\t\t\tclassName={cn(\"block\", className)}\n\t\t>\n\t\t\t<title>Dendrogram</title>\n\t\t\t<desc>{desc}</desc>\n\t\t\t<g data-hex-dendrogram-links>\n\t\t\t\t{links.map((l) => (\n\t\t\t\t\t<path\n\t\t\t\t\t\tkey={l.id}\n\t\t\t\t\t\td={linkPath(l, orientation, linkShape)}\n\t\t\t\t\t\tfill=\"none\"\n\t\t\t\t\t\tstroke=\"hsl(var(--muted-foreground))\"\n\t\t\t\t\t\tstrokeOpacity={0.8}\n\t\t\t\t\t\tstrokeWidth={1}\n\t\t\t\t\t/>\n\t\t\t\t))}\n\t\t\t</g>\n\t\t\t<g data-hex-dendrogram-nodes>\n\t\t\t\t{nodes.map((n) => (\n\t\t\t\t\t<g\n\t\t\t\t\t\tkey={n.node.id}\n\t\t\t\t\t\tdata-hex-dendrogram-node\n\t\t\t\t\t\tdata-leaf={n.isLeaf ? \"true\" : \"false\"}\n\t\t\t\t\t\tdata-depth={n.depth}\n\t\t\t\t\t\ttransform={`translate(${n.x},${n.y})`}\n\t\t\t\t\t\tstyle={n.isLeaf && onLeafClick ? { cursor: \"pointer\" } : undefined}\n\t\t\t\t\t\tonClick={n.isLeaf && onLeafClick ? () => onLeafClick(n.node) : undefined}\n\t\t\t\t\t>\n\t\t\t\t\t\t<circle\n\t\t\t\t\t\t\tr={n.isLeaf ? 3 : 2}\n\t\t\t\t\t\t\tfill={n.isLeaf ? \"hsl(var(--primary))\" : \"hsl(var(--muted-foreground))\"}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t{n.isLeaf ? (\n\t\t\t\t\t\t\t<text\n\t\t\t\t\t\t\t\tx={orientation === \"horizontal\" ? 6 : 0}\n\t\t\t\t\t\t\t\ty={orientation === \"horizontal\" ? 4 : 14}\n\t\t\t\t\t\t\t\ttextAnchor={orientation === \"horizontal\" ? \"start\" : \"middle\"}\n\t\t\t\t\t\t\t\tfontSize={11}\n\t\t\t\t\t\t\t\tfill=\"hsl(var(--foreground))\"\n\t\t\t\t\t\t\t\tstyle={{ paintOrder: \"stroke\" }}\n\t\t\t\t\t\t\t\tstroke=\"hsl(var(--background))\"\n\t\t\t\t\t\t\t\tstrokeWidth={3}\n\t\t\t\t\t\t\t\tstrokeLinejoin=\"round\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{n.node.label}\n\t\t\t\t\t\t\t</text>\n\t\t\t\t\t\t) : null}\n\t\t\t\t\t</g>\n\t\t\t\t))}\n\t\t\t</g>\n\t\t</svg>\n\t);\n}\n\nfunction layout(\n\td3h: D3HierarchyMod,\n\troot: DendrogramNode,\n\torientation: \"horizontal\" | \"vertical\",\n\twidth: number,\n\theight: number,\n): { nodes: LaidOutNode[]; links: LaidOutLink[] } {\n\tconst hierarchy = d3h.hierarchy<DendrogramNode>(root);\n\tconst clusterFn = d3h.cluster<DendrogramNode>();\n\tif (orientation === \"horizontal\") {\n\t\tclusterFn.size([height - 32, width - 120]);\n\t} else {\n\t\tclusterFn.size([width - 32, height - 64]);\n\t}\n\t// cluster(hierarchy) returns HierarchyPointNode<T> with typed x/y.\n\tconst layoutRoot = clusterFn(hierarchy);\n\n\tconst nodes: LaidOutNode[] = [];\n\tlayoutRoot.each((d) => {\n\t\tconst isLeaf = !d.children || d.children.length === 0;\n\t\tif (orientation === \"horizontal\") {\n\t\t\tnodes.push({ node: d.data, x: d.y + 16, y: d.x + 16, depth: d.depth, isLeaf });\n\t\t} else {\n\t\t\tnodes.push({ node: d.data, x: d.x + 16, y: d.y + 16, depth: d.depth, isLeaf });\n\t\t}\n\t});\n\n\tconst links: LaidOutLink[] = layoutRoot.links().map((link, i) => {\n\t\tif (orientation === \"horizontal\") {\n\t\t\treturn {\n\t\t\t\tsource: { x: link.source.y + 16, y: link.source.x + 16 },\n\t\t\t\ttarget: { x: link.target.y + 16, y: link.target.x + 16 },\n\t\t\t\tid: `l-${i}`,\n\t\t\t};\n\t\t}\n\t\treturn {\n\t\t\tsource: { x: link.source.x + 16, y: link.source.y + 16 },\n\t\t\ttarget: { x: link.target.x + 16, y: link.target.y + 16 },\n\t\t\tid: `l-${i}`,\n\t\t};\n\t});\n\n\treturn { nodes, links };\n}\n\nfunction linkPath(\n\tlink: LaidOutLink,\n\torientation: \"horizontal\" | \"vertical\",\n\tlinkShape: \"step\" | \"diagonal\",\n): string {\n\tconst { source: s, target: t } = link;\n\tif (linkShape === \"step\") {\n\t\tif (orientation === \"horizontal\") {\n\t\t\treturn `M${s.x},${s.y} H${t.x} V${t.y}`;\n\t\t}\n\t\treturn `M${s.x},${s.y} V${t.y} H${t.x}`;\n\t}\n\tif (orientation === \"horizontal\") {\n\t\tconst mx = (s.x + t.x) / 2;\n\t\treturn `M${s.x},${s.y} C${mx},${s.y} ${mx},${t.y} ${t.x},${t.y}`;\n\t}\n\tconst my = (s.y + t.y) / 2;\n\treturn `M${s.x},${s.y} C${s.x},${my} ${t.x},${my} ${t.x},${t.y}`;\n}\n\nexport { Dendrogram };\n"]}
@@ -0,0 +1,2 @@
1
+ export { DiagramProps_alias_1 as DiagramProps } from './_tsup-dts-rollup.js';
2
+ export { Diagram_alias_1 as Diagram } from './_tsup-dts-rollup.js';
@@ -0,0 +1,70 @@
1
+ "use client";
2
+ import * as React from 'react';
3
+ import { clsx } from 'clsx';
4
+ import { twMerge } from 'tailwind-merge';
5
+ import { jsxs, jsx } from 'react/jsx-runtime';
6
+
7
+ function cn(...inputs) {
8
+ return twMerge(clsx(inputs));
9
+ }
10
+ function Diagram({ children: source, theme = "default", id, onError, className, ...rest }) {
11
+ const [svg, setSvg] = React.useState("");
12
+ const [error, setError] = React.useState(null);
13
+ const onErrorRef = React.useRef(onError);
14
+ onErrorRef.current = onError;
15
+ const reactId = React.useId();
16
+ const stableId = id ?? `hex-diagram-${reactId.replace(/:/g, "-")}`;
17
+ React.useEffect(() => {
18
+ let cancelled = false;
19
+ setError(null);
20
+ void (async () => {
21
+ try {
22
+ const { default: mermaid } = await import('mermaid');
23
+ if (cancelled) return;
24
+ mermaid.initialize({ startOnLoad: false, theme, securityLevel: "strict" });
25
+ const { svg: rendered } = await mermaid.render(stableId, source);
26
+ if (!cancelled) setSvg(rendered);
27
+ } catch (err) {
28
+ const message = err instanceof Error ? err.message : String(err);
29
+ if (cancelled) return;
30
+ setError(message);
31
+ onErrorRef.current?.(message);
32
+ }
33
+ })();
34
+ return () => {
35
+ cancelled = true;
36
+ };
37
+ }, [source, theme, stableId]);
38
+ if (error) {
39
+ return /* @__PURE__ */ jsxs(
40
+ "div",
41
+ {
42
+ ...rest,
43
+ "data-hex-diagram-error": true,
44
+ role: "alert",
45
+ className: cn(
46
+ "rounded-md border border-destructive/40 bg-destructive/5 p-3 text-sm text-destructive",
47
+ className
48
+ ),
49
+ children: [
50
+ /* @__PURE__ */ jsx("div", { className: "font-medium", children: "Diagram failed to render" }),
51
+ /* @__PURE__ */ jsx("pre", { className: "mt-1 whitespace-pre-wrap text-xs opacity-80", children: error })
52
+ ]
53
+ }
54
+ );
55
+ }
56
+ return /* @__PURE__ */ jsx(
57
+ "div",
58
+ {
59
+ ...rest,
60
+ "data-hex-diagram": true,
61
+ "data-theme": theme,
62
+ className: cn("overflow-auto rounded-md border bg-background p-3", className),
63
+ dangerouslySetInnerHTML: { __html: svg }
64
+ }
65
+ );
66
+ }
67
+
68
+ export { Diagram };
69
+ //# sourceMappingURL=diagram.js.map
70
+ //# sourceMappingURL=diagram.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/utils.ts","../src/ai/diagram/diagram.tsx"],"names":[],"mappings":";;;;;AAQO,SAAS,MAAM,MAAA,EAAsB;AAC3C,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC5B;ACyBA,SAAS,OAAA,CAAQ,EAAE,QAAA,EAAU,MAAA,EAAQ,KAAA,GAAQ,SAAA,EAAW,EAAA,EAAI,OAAA,EAAS,SAAA,EAAW,GAAG,IAAA,EAAK,EAAiB;AACxG,EAAA,MAAM,CAAC,GAAA,EAAK,MAAM,CAAA,GAAU,eAAiB,EAAE,CAAA;AAC/C,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAU,eAAwB,IAAI,CAAA;AAC5D,EAAA,MAAM,UAAA,GAAmB,aAAO,OAAO,CAAA;AACvC,EAAA,UAAA,CAAW,OAAA,GAAU,OAAA;AAMrB,EAAA,MAAM,UAAgB,KAAA,CAAA,KAAA,EAAM;AAC5B,EAAA,MAAM,WAAW,EAAA,IAAM,CAAA,YAAA,EAAe,QAAQ,OAAA,CAAQ,IAAA,EAAM,GAAG,CAAC,CAAA,CAAA;AAEhE,EAAM,gBAAU,MAAM;AACrB,IAAA,IAAI,SAAA,GAAY,KAAA;AAChB,IAAA,QAAA,CAAS,IAAI,CAAA;AACb,IAAA,KAAA,CAAM,YAAY;AACjB,MAAA,IAAI;AACH,QAAA,MAAM,EAAE,OAAA,EAAS,OAAA,EAAQ,GAAI,MAAM,OAAO,SAAS,CAAA;AACnD,QAAA,IAAI,SAAA,EAAW;AACf,QAAA,OAAA,CAAQ,WAAW,EAAE,WAAA,EAAa,OAAO,KAAA,EAAO,aAAA,EAAe,UAAU,CAAA;AACzE,QAAA,MAAM,EAAE,KAAK,QAAA,EAAS,GAAI,MAAM,OAAA,CAAQ,MAAA,CAAO,UAAU,MAAM,CAAA;AAC/D,QAAA,IAAI,CAAC,SAAA,EAAW,MAAA,CAAO,QAAQ,CAAA;AAAA,MAChC,SAAS,GAAA,EAAK;AACb,QAAA,MAAM,UAAU,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU,OAAO,GAAG,CAAA;AAC/D,QAAA,IAAI,SAAA,EAAW;AACf,QAAA,QAAA,CAAS,OAAO,CAAA;AAChB,QAAA,UAAA,CAAW,UAAU,OAAO,CAAA;AAAA,MAC7B;AAAA,IACD,CAAA,GAAG;AACH,IAAA,OAAO,MAAM;AACZ,MAAA,SAAA,GAAY,IAAA;AAAA,IACb,CAAA;AAAA,EACD,CAAA,EAAG,CAAC,MAAA,EAAQ,KAAA,EAAO,QAAQ,CAAC,CAAA;AAE5B,EAAA,IAAI,KAAA,EAAO;AACV,IAAA,uBACC,IAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,GAAG,IAAA;AAAA,QACJ,wBAAA,EAAsB,IAAA;AAAA,QACtB,IAAA,EAAK,OAAA;AAAA,QACL,SAAA,EAAW,EAAA;AAAA,UACV,uFAAA;AAAA,UACA;AAAA,SACD;AAAA,QAEA,QAAA,EAAA;AAAA,0BAAA,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,aAAA,EAAc,QAAA,EAAA,0BAAA,EAAwB,CAAA;AAAA,0BACrD,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,6CAAA,EAA+C,QAAA,EAAA,KAAA,EAAM;AAAA;AAAA;AAAA,KACrE;AAAA,EAEF;AAEA,EAAA,uBACC,GAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,GAAG,IAAA;AAAA,MACJ,kBAAA,EAAgB,IAAA;AAAA,MAChB,YAAA,EAAY,KAAA;AAAA,MACZ,SAAA,EAAW,EAAA,CAAG,mDAAA,EAAqD,SAAS,CAAA;AAAA,MAK5E,uBAAA,EAAyB,EAAE,MAAA,EAAQ,GAAA;AAAI;AAAA,GACxC;AAEF","file":"diagram.js","sourcesContent":["import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Merge class names with Tailwind CSS conflict resolution.\n * @param inputs - Class values (strings, arrays, objects) to merge\n * @returns A single merged class string with Tailwind conflicts resolved\n */\nexport function cn(...inputs: ClassValue[]) {\n\treturn twMerge(clsx(inputs));\n}\n","\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../../lib/utils.js\";\n\n/**\n * Render a Mermaid diagram from a source string. Useful for AI agent\n * outputs that emit flowcharts / sequence diagrams / class diagrams in\n * Markdown — pipe the code-fence body straight in.\n *\n * Heavy peer: requires `mermaid` (~700 KB gzip). The hex-core CLI's `add`\n * flow prompts before installing — this is the largest engine in the\n * AI Elements set, so the consumer should opt in deliberately.\n *\n * @example\n * <Diagram>{`flowchart LR\\n A --> B\\n B --> C`}</Diagram>\n *\n * <Diagram theme=\"dark\" id=\"agent-flow\">{mermaidSource}</Diagram>\n */\nexport interface DiagramProps extends Omit<React.HTMLAttributes<HTMLDivElement>, \"children\" | \"onError\"> {\n\t/** Mermaid source string. */\n\tchildren: string;\n\t/** Light or dark mermaid theme. Default \"default\" (light). */\n\ttheme?: \"default\" | \"dark\" | \"forest\" | \"neutral\";\n\t/** Stable ID for the rendered SVG. Required when multiple diagrams share a page; auto-generated when omitted. */\n\tid?: string;\n\t/** Called on parse/render failure with the engine's message. */\n\tonError?: (message: string) => void;\n}\n\n/**\n * Renders a Mermaid diagram from source.\n * @param props - Source string + theme + lifecycle callbacks\n * @returns A div containing the rendered SVG\n */\nfunction Diagram({ children: source, theme = \"default\", id, onError, className, ...rest }: DiagramProps) {\n\tconst [svg, setSvg] = React.useState<string>(\"\");\n\tconst [error, setError] = React.useState<string | null>(null);\n\tconst onErrorRef = React.useRef(onError);\n\tonErrorRef.current = onError;\n\n\t// useId is stable across SSR/hydration AND unique per component instance,\n\t// avoiding the module-singleton counter bug where two diagrams in\n\t// streamed AI output collide on mermaid's internal cache key. Mermaid\n\t// rejects \":\" in IDs (React's useId emits \":r0:\"-style) so strip it.\n\tconst reactId = React.useId();\n\tconst stableId = id ?? `hex-diagram-${reactId.replace(/:/g, \"-\")}`;\n\n\tReact.useEffect(() => {\n\t\tlet cancelled = false;\n\t\tsetError(null);\n\t\tvoid (async () => {\n\t\t\ttry {\n\t\t\t\tconst { default: mermaid } = await import(\"mermaid\");\n\t\t\t\tif (cancelled) return;\n\t\t\t\tmermaid.initialize({ startOnLoad: false, theme, securityLevel: \"strict\" });\n\t\t\t\tconst { svg: rendered } = await mermaid.render(stableId, source);\n\t\t\t\tif (!cancelled) setSvg(rendered);\n\t\t\t} catch (err) {\n\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\tif (cancelled) return;\n\t\t\t\tsetError(message);\n\t\t\t\tonErrorRef.current?.(message);\n\t\t\t}\n\t\t})();\n\t\treturn () => {\n\t\t\tcancelled = true;\n\t\t};\n\t}, [source, theme, stableId]);\n\n\tif (error) {\n\t\treturn (\n\t\t\t<div\n\t\t\t\t{...rest}\n\t\t\t\tdata-hex-diagram-error\n\t\t\t\trole=\"alert\"\n\t\t\t\tclassName={cn(\n\t\t\t\t\t\"rounded-md border border-destructive/40 bg-destructive/5 p-3 text-sm text-destructive\",\n\t\t\t\t\tclassName,\n\t\t\t\t)}\n\t\t\t>\n\t\t\t\t<div className=\"font-medium\">Diagram failed to render</div>\n\t\t\t\t<pre className=\"mt-1 whitespace-pre-wrap text-xs opacity-80\">{error}</pre>\n\t\t\t</div>\n\t\t);\n\t}\n\n\treturn (\n\t\t<div\n\t\t\t{...rest}\n\t\t\tdata-hex-diagram\n\t\t\tdata-theme={theme}\n\t\t\tclassName={cn(\"overflow-auto rounded-md border bg-background p-3\", className)}\n\t\t\t// dangerouslySetInnerHTML: mermaid sanitizes its own SVG output via\n\t\t\t// securityLevel=\"strict\" (no foreignObject, no script tags). Source\n\t\t\t// strings still come from the consumer — never from untrusted user\n\t\t\t// input without their own validation upstream.\n\t\t\tdangerouslySetInnerHTML={{ __html: svg }}\n\t\t/>\n\t);\n}\n\nexport { Diagram };\n"]}
@@ -0,0 +1,2 @@
1
+ export { FlashcardProps_alias_1 as FlashcardProps } from './_tsup-dts-rollup.js';
2
+ export { Flashcard_alias_1 as Flashcard } from './_tsup-dts-rollup.js';