@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.
Files changed (94) hide show
  1. package/dist/_tsup-dts-rollup.d.ts +1561 -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 +84 -0
  48. package/dist/index.js +3946 -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 +2208 -1
  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/sunburst.d.ts +3 -0
  78. package/dist/sunburst.js +205 -0
  79. package/dist/sunburst.js.map +1 -0
  80. package/dist/terminal.d.ts +2 -0
  81. package/dist/terminal.js +153 -0
  82. package/dist/terminal.js.map +1 -0
  83. package/dist/time-axis.d.ts +3 -0
  84. package/dist/time-axis.js +233 -0
  85. package/dist/time-axis.js.map +1 -0
  86. package/dist/tool-call.js +6 -1
  87. package/dist/tool-call.js.map +1 -1
  88. package/dist/tree-map.d.ts +3 -0
  89. package/dist/tree-map.js +171 -0
  90. package/dist/tree-map.js.map +1 -0
  91. package/dist/venn.d.ts +3 -0
  92. package/dist/venn.js +196 -0
  93. package/dist/venn.js.map +1 -0
  94. package/package.json +47 -3
package/dist/chord.js ADDED
@@ -0,0 +1,230 @@
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
+ // src/lib/chart-palette.ts
8
+ var CHART_PALETTE = [
9
+ "hsl(var(--chart-1, var(--primary)))",
10
+ "hsl(var(--chart-2, var(--primary)))",
11
+ "hsl(var(--chart-3, var(--primary)))",
12
+ "hsl(var(--chart-4, var(--primary)))",
13
+ "hsl(var(--chart-5, var(--primary)))",
14
+ "hsl(var(--chart-6, var(--primary)))"
15
+ ];
16
+ function pickChartHue(index) {
17
+ const safe = (index % CHART_PALETTE.length + CHART_PALETTE.length) % CHART_PALETTE.length;
18
+ return CHART_PALETTE[safe];
19
+ }
20
+ function cn(...inputs) {
21
+ return twMerge(clsx(inputs));
22
+ }
23
+ function Chord({
24
+ nodes,
25
+ matrix,
26
+ size = 480,
27
+ padAngle = 0.04,
28
+ onChordHover,
29
+ onNodeClick,
30
+ className,
31
+ ...rest
32
+ }) {
33
+ const [d3c, setD3c] = React.useState(null);
34
+ const [d3s, setD3s] = React.useState(null);
35
+ const [importError, setImportError] = React.useState(false);
36
+ React.useEffect(() => {
37
+ let cancelled = false;
38
+ void Promise.all([import('d3-chord'), import('d3-shape')]).then(
39
+ ([c, s]) => {
40
+ if (cancelled) return;
41
+ setD3c(c);
42
+ setD3s(s);
43
+ },
44
+ () => {
45
+ if (!cancelled) setImportError(true);
46
+ }
47
+ );
48
+ return () => {
49
+ cancelled = true;
50
+ };
51
+ }, []);
52
+ const laidOut = React.useMemo(() => {
53
+ if (!d3c || !d3s) return null;
54
+ return layout(d3c, d3s, nodes, matrix, size, padAngle);
55
+ }, [d3c, d3s, nodes, matrix, size, padAngle]);
56
+ if (importError) {
57
+ return /* @__PURE__ */ jsxs(
58
+ "div",
59
+ {
60
+ "data-hex-chord-error": true,
61
+ role: "alert",
62
+ className: cn(
63
+ "inline-flex items-center justify-center rounded-md border border-destructive/40 bg-destructive/5 p-3 text-sm text-destructive",
64
+ className
65
+ ),
66
+ style: { width: size, height: size },
67
+ children: [
68
+ "Install ",
69
+ /* @__PURE__ */ jsx("code", { className: "mx-1", children: "d3-chord" }),
70
+ " to view this chord diagram."
71
+ ]
72
+ }
73
+ );
74
+ }
75
+ if (!laidOut) {
76
+ return /* @__PURE__ */ jsx(
77
+ "div",
78
+ {
79
+ "data-hex-chord-loading": true,
80
+ "aria-busy": "true",
81
+ "aria-label": "Loading chord diagram",
82
+ className: cn("inline-block bg-muted/20", className),
83
+ style: { width: size, height: size }
84
+ }
85
+ );
86
+ }
87
+ const { arcs, chords } = laidOut;
88
+ const desc = `Chord diagram with ${nodes.length} node${nodes.length === 1 ? "" : "s"} and ${chords.length} ribbon${chords.length === 1 ? "" : "s"}`;
89
+ const radius = size / 2;
90
+ return /* @__PURE__ */ jsxs(
91
+ "svg",
92
+ {
93
+ ...rest,
94
+ "data-hex-chord": true,
95
+ role: "img",
96
+ width: size,
97
+ height: size,
98
+ viewBox: `${-radius} ${-radius} ${size} ${size}`,
99
+ className: cn("block", className),
100
+ children: [
101
+ /* @__PURE__ */ jsx("title", { children: "Chord diagram" }),
102
+ /* @__PURE__ */ jsx("desc", { children: desc }),
103
+ /* @__PURE__ */ jsx("g", { "data-hex-chord-ribbons": true, children: chords.map((c, i) => {
104
+ const interactive = Boolean(onChordHover);
105
+ const payload = {
106
+ source: c.source,
107
+ target: c.target,
108
+ sourceValue: c.sourceValue,
109
+ targetValue: c.targetValue
110
+ };
111
+ const fireHover = (chord) => onChordHover?.(chord);
112
+ return /* @__PURE__ */ jsx(
113
+ "path",
114
+ {
115
+ "data-hex-chord-ribbon": true,
116
+ d: c.d,
117
+ fill: pickChartHue(c.sourceIdx),
118
+ fillOpacity: 0.55,
119
+ stroke: "hsl(var(--background))",
120
+ strokeWidth: 0.5,
121
+ role: interactive ? "button" : void 0,
122
+ tabIndex: interactive ? 0 : void 0,
123
+ "aria-label": interactive ? `Flow from ${c.source.label} to ${c.target.label}, value ${c.sourceValue}; reverse value ${c.targetValue}` : void 0,
124
+ style: {
125
+ cursor: interactive ? "pointer" : void 0,
126
+ transition: "fill-opacity 120ms ease"
127
+ },
128
+ onMouseEnter: interactive ? () => fireHover(payload) : void 0,
129
+ onMouseLeave: interactive ? () => fireHover(null) : void 0,
130
+ onFocus: interactive ? () => fireHover(payload) : void 0,
131
+ onBlur: interactive ? () => fireHover(null) : void 0
132
+ },
133
+ `${c.source.id}-${c.target.id}-${i}`
134
+ );
135
+ }) }),
136
+ /* @__PURE__ */ jsx("g", { "data-hex-chord-arcs": true, children: arcs.map((a) => {
137
+ const interactive = Boolean(onNodeClick);
138
+ const handleActivate = () => onNodeClick?.(a.node);
139
+ const flip = a.labelAngle > Math.PI / 2 && a.labelAngle < 3 * Math.PI / 2;
140
+ return /* @__PURE__ */ jsxs(
141
+ "g",
142
+ {
143
+ "data-hex-chord-arc": true,
144
+ "data-depth": a.depth,
145
+ role: interactive ? "button" : void 0,
146
+ tabIndex: interactive ? 0 : void 0,
147
+ "aria-label": interactive ? a.node.label : void 0,
148
+ style: interactive ? { cursor: "pointer" } : void 0,
149
+ onClick: interactive ? handleActivate : void 0,
150
+ onKeyDown: interactive ? (e) => activateOnKey(e, handleActivate) : void 0,
151
+ children: [
152
+ /* @__PURE__ */ jsx(
153
+ "path",
154
+ {
155
+ d: a.d,
156
+ fill: pickChartHue(a.depth),
157
+ stroke: "hsl(var(--background))",
158
+ strokeWidth: 1
159
+ }
160
+ ),
161
+ /* @__PURE__ */ jsx(
162
+ "text",
163
+ {
164
+ x: a.labelX,
165
+ y: a.labelY,
166
+ textAnchor: flip ? "end" : "start",
167
+ dy: "0.35em",
168
+ transform: flip ? `rotate(${a.labelAngle * 180 / Math.PI - 270} ${a.labelX} ${a.labelY})` : `rotate(${a.labelAngle * 180 / Math.PI - 90} ${a.labelX} ${a.labelY})`,
169
+ fontSize: 11,
170
+ fill: "hsl(var(--foreground))",
171
+ style: { pointerEvents: "none" },
172
+ children: a.node.label
173
+ }
174
+ )
175
+ ]
176
+ },
177
+ a.node.id
178
+ );
179
+ }) })
180
+ ]
181
+ }
182
+ );
183
+ }
184
+ function layout(d3c, d3s, nodes, matrix, size, padAngle) {
185
+ const longestLabel = nodes.reduce((m, n) => Math.max(m, n.label.length), 0);
186
+ const labelMargin = Math.max(40, longestLabel * 6 + 16);
187
+ const radius = size / 2 - labelMargin;
188
+ const innerRadius = radius - 12;
189
+ const outerRadius = radius;
190
+ const chordGen = d3c.chord().padAngle(padAngle).sortSubgroups((a, b) => a < b ? 1 : a > b ? -1 : 0);
191
+ const result = chordGen(matrix);
192
+ const arc = d3s.arc().innerRadius(innerRadius).outerRadius(outerRadius);
193
+ const ribbon = d3c.ribbon().radius(innerRadius);
194
+ const arcs = result.groups.map((g) => {
195
+ const node = nodes[g.index] ?? { id: `_${g.index}`, label: `Set ${g.index}` };
196
+ const midAngle = (g.startAngle + g.endAngle) / 2;
197
+ const labelRadius = outerRadius + 6;
198
+ return {
199
+ node,
200
+ depth: g.index,
201
+ d: arc({ startAngle: g.startAngle, endAngle: g.endAngle, innerRadius, outerRadius }) ?? "",
202
+ labelX: labelRadius * Math.sin(midAngle),
203
+ labelY: -labelRadius * Math.cos(midAngle),
204
+ labelAngle: midAngle
205
+ };
206
+ });
207
+ const buildRibbonInput = (c) => ({
208
+ source: { ...c.source, radius: innerRadius },
209
+ target: { ...c.target, radius: innerRadius }
210
+ });
211
+ const chords = result.filter((c) => c.source.value > 0 || c.target.value > 0).map((c) => ({
212
+ source: nodes[c.source.index] ?? { id: `_${c.source.index}`, label: `Set ${c.source.index}` },
213
+ target: nodes[c.target.index] ?? { id: `_${c.target.index}`, label: `Set ${c.target.index}` },
214
+ sourceValue: c.source.value,
215
+ targetValue: c.target.value,
216
+ d: ribbon(buildRibbonInput(c)) ?? "",
217
+ sourceIdx: c.source.index
218
+ }));
219
+ return { arcs, chords };
220
+ }
221
+ function activateOnKey(e, fn) {
222
+ if (e.key === "Enter" || e.key === " ") {
223
+ e.preventDefault();
224
+ fn();
225
+ }
226
+ }
227
+
228
+ export { Chord };
229
+ //# sourceMappingURL=chord.js.map
230
+ //# sourceMappingURL=chord.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/chart-palette.ts","../src/lib/utils.ts","../src/artifacts/chord/chord.tsx"],"names":[],"mappings":"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","file":"chord.js","sourcesContent":["/**\n * Categorical chart palette for diagram primitives that encode categorical\n * data (sunburst, treemap, sankey, chord, funnel, pyramid, venn, matrix).\n * Cycled by an integer key — node index, leaf index, depth-1 ancestor, etc.\n *\n * The values reach into `--chart-1` through `--chart-6`, the dedicated\n * diagram-encoding tokens added in `@hex-core/tokens` 1.4. Each token has a\n * `var(--primary)` fallback so consumers on theme presets that haven't been\n * updated to ship `--chart-N` (or who run a custom theme without the chart\n * family) still see a coherent monochrome rendering instead of black SVG\n * fills.\n *\n * Why a chart palette and not the semantic tokens: `--primary`, `--accent`,\n * `--secondary`, and `--muted` collapse to a single hue family in the\n * default monochrome theme — adjacent segments of a chart end up\n * indistinguishable. Chart tokens are tuned for perceptual differentiation.\n */\nexport const CHART_PALETTE = [\n\t\"hsl(var(--chart-1, var(--primary)))\",\n\t\"hsl(var(--chart-2, var(--primary)))\",\n\t\"hsl(var(--chart-3, var(--primary)))\",\n\t\"hsl(var(--chart-4, var(--primary)))\",\n\t\"hsl(var(--chart-5, var(--primary)))\",\n\t\"hsl(var(--chart-6, var(--primary)))\",\n] as const;\n\n/**\n * Return the chart hue at a stable index. Cycles modulo `CHART_PALETTE.length`\n * so the caller doesn't have to range-check.\n *\n * @param index - Integer key (node index, leaf index, depth-1 ancestor index, …).\n * @returns A `hsl(var(...))` string suitable for SVG `fill` / `stroke`.\n */\nexport function pickChartHue(index: number): string {\n\tconst safe = ((index % CHART_PALETTE.length) + CHART_PALETTE.length) % CHART_PALETTE.length;\n\t// `safe` is provably 0..CHART_PALETTE.length-1 — the assertion documents that.\n\treturn CHART_PALETTE[safe] as string;\n}\n","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 { pickChartHue } from \"../../lib/chart-palette.js\";\nimport { cn } from \"../../lib/utils.js\";\n\n/**\n * Chord diagram. Nodes sit on a ring; ribbons inside the ring encode\n * weighted bidirectional relationships between them. Common for\n * trade flows, migration, hyperlink graphs, citation networks —\n * anywhere \"A relates to B with weight w\" matters at scale.\n *\n * Heavy peers: requires `d3-chord` (~3 KB gzip) and `d3-shape` (~6 KB\n * gzip, already in the artifacts/ family). The hex-core CLI's `add`\n * flow prompts before installing.\n *\n * @example\n * <Chord\n * nodes={[\"A\", \"B\", \"C\", \"D\"].map((id) => ({ id, label: id }))}\n * matrix={[\n * [0, 5, 8, 1],\n * [3, 0, 2, 4],\n * [6, 0, 0, 7],\n * [2, 1, 9, 0],\n * ]}\n * />\n */\nexport type ChordNode = {\n\tid: string;\n\tlabel: string;\n};\n\n/**\n * Payload fired to `onChordHover`. `sourceValue` is the i→j flow;\n * `targetValue` is the j→i flow. They differ for asymmetric matrices and\n * are equal for symmetric ones.\n */\nexport type ChordHoverPayload = {\n\tsource: ChordNode;\n\ttarget: ChordNode;\n\tsourceValue: number;\n\ttargetValue: number;\n};\n\nexport interface ChordProps extends Omit<React.SVGAttributes<SVGSVGElement>, \"children\"> {\n\t/** Nodes (as ring segments). Order matches matrix rows/columns. */\n\tnodes: ChordNode[];\n\t/** Square N×N matrix of weights. matrix[i][j] = flow from node i to node j. */\n\tmatrix: number[][];\n\t/** Pixel size of the rendered SVG (it's square). Default 480. */\n\tsize?: number;\n\t/** Pixel padding between adjacent ring segments. Default 0.04 (radians, d3 convention). */\n\tpadAngle?: number;\n\t/** Fired when a ribbon is hovered (or hover ends, with `null`). */\n\tonChordHover?: (chord: ChordHoverPayload | null) => void;\n\t/** Fired when a node arc is clicked. */\n\tonNodeClick?: (node: ChordNode) => void;\n}\n\ninterface LaidOutArc {\n\tnode: ChordNode;\n\tdepth: number;\n\td: string;\n\tlabelX: number;\n\tlabelY: number;\n\tlabelAngle: number;\n}\n\ninterface LaidOutChord {\n\tsource: ChordNode;\n\ttarget: ChordNode;\n\tsourceValue: number;\n\ttargetValue: number;\n\td: string;\n\t/** Index of the source node — ribbons inherit the source arc's hue\n\t * so the eye can trace \"where did this flow originate\". */\n\tsourceIdx: number;\n}\n\ntype D3ChordMod = typeof import(\"d3-chord\");\ntype D3ShapeMod = typeof import(\"d3-shape\");\n\nfunction Chord({\n\tnodes,\n\tmatrix,\n\tsize = 480,\n\tpadAngle = 0.04,\n\tonChordHover,\n\tonNodeClick,\n\tclassName,\n\t...rest\n}: ChordProps) {\n\tconst [d3c, setD3c] = React.useState<D3ChordMod | null>(null);\n\tconst [d3s, setD3s] = React.useState<D3ShapeMod | null>(null);\n\tconst [importError, setImportError] = React.useState(false);\n\n\tReact.useEffect(() => {\n\t\tlet cancelled = false;\n\t\tvoid Promise.all([import(\"d3-chord\"), import(\"d3-shape\")]).then(\n\t\t\t([c, s]) => {\n\t\t\t\tif (cancelled) return;\n\t\t\t\tsetD3c(c);\n\t\t\t\tsetD3s(s);\n\t\t\t},\n\t\t\t() => {\n\t\t\t\tif (!cancelled) setImportError(true);\n\t\t\t},\n\t\t);\n\t\treturn () => {\n\t\t\tcancelled = true;\n\t\t};\n\t}, []);\n\n\tconst laidOut = React.useMemo(() => {\n\t\tif (!d3c || !d3s) return null;\n\t\treturn layout(d3c, d3s, nodes, matrix, size, padAngle);\n\t}, [d3c, d3s, nodes, matrix, size, padAngle]);\n\n\tif (importError) {\n\t\treturn (\n\t\t\t<div\n\t\t\t\tdata-hex-chord-error\n\t\t\t\trole=\"alert\"\n\t\t\t\tclassName={cn(\n\t\t\t\t\t\"inline-flex items-center justify-center 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\tstyle={{ width: size, height: size }}\n\t\t\t>\n\t\t\t\tInstall <code className=\"mx-1\">d3-chord</code> to view this chord diagram.\n\t\t\t</div>\n\t\t);\n\t}\n\n\tif (!laidOut) {\n\t\treturn (\n\t\t\t<div\n\t\t\t\tdata-hex-chord-loading\n\t\t\t\taria-busy=\"true\"\n\t\t\t\taria-label=\"Loading chord diagram\"\n\t\t\t\tclassName={cn(\"inline-block bg-muted/20\", className)}\n\t\t\t\tstyle={{ width: size, height: size }}\n\t\t\t/>\n\t\t);\n\t}\n\n\tconst { arcs, chords } = laidOut;\n\tconst desc = `Chord diagram with ${nodes.length} node${nodes.length === 1 ? \"\" : \"s\"} and ${chords.length} ribbon${chords.length === 1 ? \"\" : \"s\"}`;\n\tconst radius = size / 2;\n\n\treturn (\n\t\t<svg\n\t\t\t{...rest}\n\t\t\tdata-hex-chord\n\t\t\trole=\"img\"\n\t\t\twidth={size}\n\t\t\theight={size}\n\t\t\tviewBox={`${-radius} ${-radius} ${size} ${size}`}\n\t\t\tclassName={cn(\"block\", className)}\n\t\t>\n\t\t\t<title>Chord diagram</title>\n\t\t\t<desc>{desc}</desc>\n\t\t\t<g data-hex-chord-ribbons>\n\t\t\t\t{chords.map((c, i) => {\n\t\t\t\t\tconst interactive = Boolean(onChordHover);\n\t\t\t\t\tconst payload: ChordHoverPayload = {\n\t\t\t\t\t\tsource: c.source,\n\t\t\t\t\t\ttarget: c.target,\n\t\t\t\t\t\tsourceValue: c.sourceValue,\n\t\t\t\t\t\ttargetValue: c.targetValue,\n\t\t\t\t\t};\n\t\t\t\t\tconst fireHover = (chord: ChordHoverPayload | null) => onChordHover?.(chord);\n\t\t\t\t\treturn (\n\t\t\t\t\t\t<path\n\t\t\t\t\t\t\tkey={`${c.source.id}-${c.target.id}-${i}`}\n\t\t\t\t\t\t\tdata-hex-chord-ribbon\n\t\t\t\t\t\t\td={c.d}\n\t\t\t\t\t\t\tfill={pickChartHue(c.sourceIdx)}\n\t\t\t\t\t\t\tfillOpacity={0.55}\n\t\t\t\t\t\t\tstroke=\"hsl(var(--background))\"\n\t\t\t\t\t\t\tstrokeWidth={0.5}\n\t\t\t\t\t\t\trole={interactive ? \"button\" : undefined}\n\t\t\t\t\t\t\ttabIndex={interactive ? 0 : undefined}\n\t\t\t\t\t\t\taria-label={\n\t\t\t\t\t\t\t\tinteractive\n\t\t\t\t\t\t\t\t\t? `Flow from ${c.source.label} to ${c.target.label}, value ${c.sourceValue}; reverse value ${c.targetValue}`\n\t\t\t\t\t\t\t\t\t: undefined\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\t\tcursor: interactive ? \"pointer\" : undefined,\n\t\t\t\t\t\t\t\ttransition: \"fill-opacity 120ms ease\",\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tonMouseEnter={interactive ? () => fireHover(payload) : undefined}\n\t\t\t\t\t\t\tonMouseLeave={interactive ? () => fireHover(null) : undefined}\n\t\t\t\t\t\t\tonFocus={interactive ? () => fireHover(payload) : undefined}\n\t\t\t\t\t\t\tonBlur={interactive ? () => fireHover(null) : undefined}\n\t\t\t\t\t\t/>\n\t\t\t\t\t);\n\t\t\t\t})}\n\t\t\t</g>\n\t\t\t<g data-hex-chord-arcs>\n\t\t\t\t{arcs.map((a) => {\n\t\t\t\t\tconst interactive = Boolean(onNodeClick);\n\t\t\t\t\tconst handleActivate = () => onNodeClick?.(a.node);\n\t\t\t\t\tconst flip = a.labelAngle > Math.PI / 2 && a.labelAngle < (3 * Math.PI) / 2;\n\t\t\t\t\treturn (\n\t\t\t\t\t\t<g\n\t\t\t\t\t\t\tkey={a.node.id}\n\t\t\t\t\t\t\tdata-hex-chord-arc\n\t\t\t\t\t\t\tdata-depth={a.depth}\n\t\t\t\t\t\t\trole={interactive ? \"button\" : undefined}\n\t\t\t\t\t\t\ttabIndex={interactive ? 0 : undefined}\n\t\t\t\t\t\t\taria-label={interactive ? a.node.label : undefined}\n\t\t\t\t\t\t\tstyle={interactive ? { cursor: \"pointer\" } : undefined}\n\t\t\t\t\t\t\tonClick={interactive ? handleActivate : undefined}\n\t\t\t\t\t\t\tonKeyDown={interactive ? (e) => activateOnKey(e, handleActivate) : undefined}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<path\n\t\t\t\t\t\t\t\td={a.d}\n\t\t\t\t\t\t\t\tfill={pickChartHue(a.depth)}\n\t\t\t\t\t\t\t\tstroke=\"hsl(var(--background))\"\n\t\t\t\t\t\t\t\tstrokeWidth={1}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t<text\n\t\t\t\t\t\t\t\tx={a.labelX}\n\t\t\t\t\t\t\t\ty={a.labelY}\n\t\t\t\t\t\t\t\ttextAnchor={flip ? \"end\" : \"start\"}\n\t\t\t\t\t\t\t\tdy=\"0.35em\"\n\t\t\t\t\t\t\t\ttransform={\n\t\t\t\t\t\t\t\t\tflip\n\t\t\t\t\t\t\t\t\t\t? `rotate(${(a.labelAngle * 180) / Math.PI - 270} ${a.labelX} ${a.labelY})`\n\t\t\t\t\t\t\t\t\t\t: `rotate(${(a.labelAngle * 180) / Math.PI - 90} ${a.labelX} ${a.labelY})`\n\t\t\t\t\t\t\t\t}\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={{ pointerEvents: \"none\" }}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{a.node.label}\n\t\t\t\t\t\t\t</text>\n\t\t\t\t\t\t</g>\n\t\t\t\t\t);\n\t\t\t\t})}\n\t\t\t</g>\n\t\t</svg>\n\t);\n}\n\nfunction layout(\n\td3c: D3ChordMod,\n\td3s: D3ShapeMod,\n\tnodes: ChordNode[],\n\tmatrix: number[][],\n\tsize: number,\n\tpadAngle: number,\n): { arcs: LaidOutArc[]; chords: LaidOutChord[] } {\n\t// Reserve margin for label text scaled by the longest label, so words\n\t// like \"Americas\" / \"Manufacturing\" don't clip against the SVG edge.\n\t// 6 px/char + 16 px slack — empirically fits 12-char labels at fontSize 11.\n\tconst longestLabel = nodes.reduce((m, n) => Math.max(m, n.label.length), 0);\n\tconst labelMargin = Math.max(40, longestLabel * 6 + 16);\n\tconst radius = size / 2 - labelMargin;\n\tconst innerRadius = radius - 12;\n\tconst outerRadius = radius;\n\n\t// d3.descending lives in d3-array — inline the comparator to avoid pulling\n\t// another peer dep just for one helper.\n\tconst chordGen = d3c\n\t\t.chord()\n\t\t.padAngle(padAngle)\n\t\t.sortSubgroups((a, b) => (a < b ? 1 : a > b ? -1 : 0));\n\tconst result = chordGen(matrix);\n\n\tconst arc = d3s.arc().innerRadius(innerRadius).outerRadius(outerRadius);\n\t// `ribbon()` is exported from d3-chord, not d3-shape.\n\tconst ribbon = d3c.ribbon().radius(innerRadius);\n\n\tconst arcs: LaidOutArc[] = result.groups.map((g) => {\n\t\tconst node = nodes[g.index] ?? { id: `_${g.index}`, label: `Set ${g.index}` };\n\t\tconst midAngle = (g.startAngle + g.endAngle) / 2;\n\t\tconst labelRadius = outerRadius + 6;\n\t\treturn {\n\t\t\tnode,\n\t\t\tdepth: g.index,\n\t\t\td: arc({ startAngle: g.startAngle, endAngle: g.endAngle, innerRadius, outerRadius }) ?? \"\",\n\t\t\tlabelX: labelRadius * Math.sin(midAngle),\n\t\t\tlabelY: -labelRadius * Math.cos(midAngle),\n\t\t\tlabelAngle: midAngle,\n\t\t};\n\t});\n\n\t// Satisfies @types/d3-chord@3.0.6's `RibbonSubgroup.radius` requirement;\n\t// runtime ignores per-subgroup radius when the generator's `.radius()`\n\t// is set, so this spread is purely a typing accommodation.\n\tconst buildRibbonInput = (c: typeof result[number]) => ({\n\t\tsource: { ...c.source, radius: innerRadius },\n\t\ttarget: { ...c.target, radius: innerRadius },\n\t});\n\n\tconst chords: LaidOutChord[] = result\n\t\t// Drop chord pairs where both directions have zero flow — d3-chord\n\t\t// emits placeholder pairs for them which would otherwise render as\n\t\t// invisible-but-event-firing ribbons.\n\t\t.filter((c) => c.source.value > 0 || c.target.value > 0)\n\t\t.map((c) => ({\n\t\t\tsource: nodes[c.source.index] ?? { id: `_${c.source.index}`, label: `Set ${c.source.index}` },\n\t\t\ttarget: nodes[c.target.index] ?? { id: `_${c.target.index}`, label: `Set ${c.target.index}` },\n\t\t\tsourceValue: c.source.value,\n\t\t\ttargetValue: c.target.value,\n\t\t\td: ribbon(buildRibbonInput(c)) ?? \"\",\n\t\t\tsourceIdx: c.source.index,\n\t\t}));\n\n\treturn { arcs, chords };\n}\n\nfunction activateOnKey(e: React.KeyboardEvent, fn: () => void): void {\n\tif (e.key === \"Enter\" || e.key === \" \") {\n\t\te.preventDefault();\n\t\tfn();\n\t}\n}\n\nexport { Chord };\n"]}
@@ -0,0 +1,3 @@
1
+ export { ClozePart_alias_1 as ClozePart } from './_tsup-dts-rollup.js';
2
+ export { ClozeProps_alias_1 as ClozeProps } from './_tsup-dts-rollup.js';
3
+ export { Cloze_alias_1 as Cloze } from './_tsup-dts-rollup.js';
package/dist/cloze.js ADDED
@@ -0,0 +1,98 @@
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
+ var AUTO_ID_PREFIX = "__hex_cloze_auto_";
11
+ function normalize(parts) {
12
+ let blankIndex = 0;
13
+ const fragments = parts.map((p) => {
14
+ if (typeof p === "string") return p;
15
+ const id = p.id ?? `${AUTO_ID_PREFIX}${blankIndex}`;
16
+ const out = { hidden: p.hidden, id, blankIndex };
17
+ blankIndex++;
18
+ return out;
19
+ });
20
+ return { fragments, total: blankIndex };
21
+ }
22
+ function Cloze({ parts, revealMode = "click", onReveal, className, ...rest }) {
23
+ const { fragments, total } = React.useMemo(() => normalize(parts), [parts]);
24
+ const [revealed, setRevealed] = React.useState(() => /* @__PURE__ */ new Set());
25
+ const onRevealRef = React.useRef(onReveal);
26
+ onRevealRef.current = onReveal;
27
+ const update = React.useCallback((next) => {
28
+ setRevealed(next);
29
+ onRevealRef.current?.(Array.from(next));
30
+ }, []);
31
+ const toggleBlank = React.useCallback(
32
+ (id) => {
33
+ const next = new Set(revealed);
34
+ if (next.has(id)) next.delete(id);
35
+ else next.add(id);
36
+ update(next);
37
+ },
38
+ [revealed, update]
39
+ );
40
+ const allRevealed = total > 0 && revealed.size === total;
41
+ const toggleAll = React.useCallback(() => {
42
+ if (allRevealed) {
43
+ update(/* @__PURE__ */ new Set());
44
+ } else {
45
+ const next = /* @__PURE__ */ new Set();
46
+ for (const f of fragments) if (typeof f !== "string") next.add(f.id);
47
+ update(next);
48
+ }
49
+ }, [allRevealed, fragments, update]);
50
+ return /* @__PURE__ */ jsxs(
51
+ "div",
52
+ {
53
+ ...rest,
54
+ "data-hex-cloze": true,
55
+ "data-all-revealed": allRevealed,
56
+ className: cn("text-base leading-relaxed", className),
57
+ children: [
58
+ /* @__PURE__ */ jsx("p", { "data-hex-cloze-text": true, children: fragments.map((f, i) => {
59
+ if (typeof f === "string") return /* @__PURE__ */ jsx(React.Fragment, { children: f }, `s-${i}`);
60
+ const isRevealed = revealed.has(f.id);
61
+ return /* @__PURE__ */ jsx(
62
+ "button",
63
+ {
64
+ type: "button",
65
+ "data-hex-cloze-blank": true,
66
+ "data-blank-id": f.id,
67
+ "data-revealed": isRevealed,
68
+ "aria-pressed": isRevealed,
69
+ "aria-label": isRevealed ? `Blank ${f.blankIndex + 1} revealed: ${f.hidden}. Activate to hide.` : `Blank ${f.blankIndex + 1} of ${total}. Activate to reveal.`,
70
+ onClick: () => toggleBlank(f.id),
71
+ className: cn(
72
+ "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",
73
+ isRevealed ? "bg-primary/10 text-foreground underline decoration-primary decoration-dotted underline-offset-4" : "select-none bg-muted text-transparent"
74
+ ),
75
+ children: f.hidden
76
+ },
77
+ f.id
78
+ );
79
+ }) }),
80
+ revealMode === "all" && total > 0 ? /* @__PURE__ */ jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsx(
81
+ "button",
82
+ {
83
+ type: "button",
84
+ "data-hex-cloze-toggle-all": true,
85
+ "aria-label": allRevealed ? "Hide all blanks" : "Reveal all blanks",
86
+ onClick: toggleAll,
87
+ 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",
88
+ children: allRevealed ? "Hide all" : "Reveal all"
89
+ }
90
+ ) }) : null
91
+ ]
92
+ }
93
+ );
94
+ }
95
+
96
+ export { Cloze };
97
+ //# sourceMappingURL=cloze.js.map
98
+ //# sourceMappingURL=cloze.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/utils.ts","../src/artifacts/cloze/cloze.tsx"],"names":[],"mappings":"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","file":"cloze.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 * Cloze deletion — text with hidden segments the learner reveals one at\n * a time (or all at once). Each `{ hidden }` token in the `parts` array\n * renders as a redacted span; click / Enter / Space reveals just that\n * blank.\n *\n * Pure HTML; no heavy peer. Pair with Deck (artifacts/deck) to flow\n * through a sequence of cloze cards, or with SpacedRepetition for\n * confidence rating.\n *\n * @example\n * <Cloze parts={[\n * \"The mitochondria is the \",\n * { hidden: \"powerhouse\" },\n * \" of the cell.\",\n * ]} />\n */\nexport type ClozePart = string | { hidden: string; id?: string };\n\nexport interface ClozeProps extends Omit<React.HTMLAttributes<HTMLDivElement>, \"children\" | \"onReveal\"> {\n\t/** Mixed array: string fragments + { hidden } cloze tokens. */\n\tparts: ClozePart[];\n\t/** \"click\" reveals one blank at a time; \"all\" additionally surfaces a \"Reveal all\" toggle. Default \"click\". */\n\trevealMode?: \"click\" | \"all\";\n\t/** Fired with the cumulative set of revealed blank ids on every reveal change. */\n\tonReveal?: (revealedIds: string[]) => void;\n}\n\ninterface NormalizedBlank {\n\thidden: string;\n\tid: string;\n\tblankIndex: number;\n}\n\n// Namespace auto-ids with a prefix that's vanishingly unlikely to collide\n// with consumer-supplied explicit ids. Without this, `[{hidden:'a'},\n// {hidden:'b'}]` (auto-numbered \"blank-0\", \"blank-1\") would silently\n// alias with a future `{hidden:'c', id:'blank-1'}`.\nconst AUTO_ID_PREFIX = \"__hex_cloze_auto_\";\n\nfunction normalize(parts: ClozePart[]): { fragments: Array<string | NormalizedBlank>; total: number } {\n\tlet blankIndex = 0;\n\tconst fragments = parts.map((p) => {\n\t\tif (typeof p === \"string\") return p;\n\t\tconst id = p.id ?? `${AUTO_ID_PREFIX}${blankIndex}`;\n\t\tconst out: NormalizedBlank = { hidden: p.hidden, id, blankIndex };\n\t\tblankIndex++;\n\t\treturn out;\n\t});\n\treturn { fragments, total: blankIndex };\n}\n\nfunction Cloze({ parts, revealMode = \"click\", onReveal, className, ...rest }: ClozeProps) {\n\tconst { fragments, total } = React.useMemo(() => normalize(parts), [parts]);\n\tconst [revealed, setRevealed] = React.useState<Set<string>>(() => new Set());\n\tconst onRevealRef = React.useRef(onReveal);\n\tonRevealRef.current = onReveal;\n\n\tconst update = React.useCallback((next: Set<string>) => {\n\t\tsetRevealed(next);\n\t\tonRevealRef.current?.(Array.from(next));\n\t}, []);\n\n\tconst toggleBlank = React.useCallback(\n\t\t(id: string) => {\n\t\t\tconst next = new Set(revealed);\n\t\t\tif (next.has(id)) next.delete(id);\n\t\t\telse next.add(id);\n\t\t\tupdate(next);\n\t\t},\n\t\t[revealed, update],\n\t);\n\n\tconst allRevealed = total > 0 && revealed.size === total;\n\n\tconst toggleAll = React.useCallback(() => {\n\t\tif (allRevealed) {\n\t\t\tupdate(new Set());\n\t\t} else {\n\t\t\tconst next = new Set<string>();\n\t\t\tfor (const f of fragments) if (typeof f !== \"string\") next.add(f.id);\n\t\t\tupdate(next);\n\t\t}\n\t}, [allRevealed, fragments, update]);\n\n\treturn (\n\t\t<div\n\t\t\t{...rest}\n\t\t\tdata-hex-cloze\n\t\t\tdata-all-revealed={allRevealed}\n\t\t\tclassName={cn(\"text-base leading-relaxed\", className)}\n\t\t>\n\t\t\t<p data-hex-cloze-text>\n\t\t\t\t{fragments.map((f, i) => {\n\t\t\t\t\tif (typeof f === \"string\") return <React.Fragment key={`s-${i}`}>{f}</React.Fragment>;\n\t\t\t\t\tconst isRevealed = revealed.has(f.id);\n\t\t\t\t\treturn (\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tkey={f.id}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tdata-hex-cloze-blank\n\t\t\t\t\t\t\tdata-blank-id={f.id}\n\t\t\t\t\t\t\tdata-revealed={isRevealed}\n\t\t\t\t\t\t\taria-pressed={isRevealed}\n\t\t\t\t\t\t\taria-label={\n\t\t\t\t\t\t\t\tisRevealed\n\t\t\t\t\t\t\t\t\t? `Blank ${f.blankIndex + 1} revealed: ${f.hidden}. Activate to hide.`\n\t\t\t\t\t\t\t\t\t: `Blank ${f.blankIndex + 1} of ${total}. Activate to reveal.`\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tonClick={() => toggleBlank(f.id)}\n\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\"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\",\n\t\t\t\t\t\t\t\tisRevealed\n\t\t\t\t\t\t\t\t\t? \"bg-primary/10 text-foreground underline decoration-primary decoration-dotted underline-offset-4\"\n\t\t\t\t\t\t\t\t\t: \"select-none bg-muted text-transparent\",\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{/* Always render the hidden text so the visual width matches\n\t\t\t\t\t\t\t once revealed; transparent text when hidden. */}\n\t\t\t\t\t\t\t{f.hidden}\n\t\t\t\t\t\t</button>\n\t\t\t\t\t);\n\t\t\t\t})}\n\t\t\t</p>\n\t\t\t{revealMode === \"all\" && total > 0 ? (\n\t\t\t\t<div className=\"mt-3\">\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tdata-hex-cloze-toggle-all\n\t\t\t\t\t\taria-label={allRevealed ? \"Hide all blanks\" : \"Reveal all blanks\"}\n\t\t\t\t\t\tonClick={toggleAll}\n\t\t\t\t\t\tclassName=\"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\"\n\t\t\t\t\t>\n\t\t\t\t\t\t{allRevealed ? \"Hide all\" : \"Reveal all\"}\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t) : null}\n\t\t</div>\n\t);\n}\n\nexport { Cloze };\n"]}
@@ -0,0 +1,4 @@
1
+ export { CompareSubject_alias_1 as CompareSubject } from './_tsup-dts-rollup.js';
2
+ export { CompareAttribute_alias_1 as CompareAttribute } from './_tsup-dts-rollup.js';
3
+ export { CompareTableProps_alias_1 as CompareTableProps } from './_tsup-dts-rollup.js';
4
+ export { CompareTable_alias_1 as CompareTable } from './_tsup-dts-rollup.js';
@@ -0,0 +1,109 @@
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
+ var EMPTY_PLACEHOLDER = "\u2014";
11
+ function CompareTable({
12
+ subjects,
13
+ attributes,
14
+ highlightDifferences = false,
15
+ onCellClick,
16
+ className,
17
+ ...rest
18
+ }) {
19
+ const warnedRef = React.useRef(false);
20
+ const nodeEnv = globalThis.process?.env?.NODE_ENV;
21
+ if (nodeEnv !== "production" && !warnedRef.current) {
22
+ const subjectIds = new Set(subjects.map((s) => s.id));
23
+ for (const attr of attributes) {
24
+ for (const valueId of Object.keys(attr.values)) {
25
+ if (!subjectIds.has(valueId)) {
26
+ warnedRef.current = true;
27
+ console.warn(
28
+ `[hex-core/CompareTable] Attribute "${attr.id}" has a value for subjectId "${valueId}" that isn't in the subjects array. The cell will not render.`
29
+ );
30
+ break;
31
+ }
32
+ }
33
+ if (warnedRef.current) break;
34
+ }
35
+ }
36
+ return /* @__PURE__ */ jsx(
37
+ "div",
38
+ {
39
+ ...rest,
40
+ "data-hex-compare-table": true,
41
+ className: cn("overflow-x-auto rounded-lg border", className),
42
+ children: /* @__PURE__ */ jsxs("table", { className: "w-full border-collapse text-sm", children: [
43
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { "data-hex-compare-table-header": true, children: [
44
+ /* @__PURE__ */ jsx(
45
+ "th",
46
+ {
47
+ scope: "col",
48
+ className: "sticky left-0 z-10 border-b bg-card px-3 py-2 text-left text-xs font-medium text-muted-foreground"
49
+ }
50
+ ),
51
+ subjects.map((subject) => /* @__PURE__ */ jsx(
52
+ "th",
53
+ {
54
+ scope: "col",
55
+ "data-hex-compare-table-subject": true,
56
+ "data-subject-id": subject.id,
57
+ className: "border-b bg-card px-3 py-2 text-left font-semibold",
58
+ children: subject.label
59
+ },
60
+ subject.id
61
+ ))
62
+ ] }) }),
63
+ /* @__PURE__ */ jsx("tbody", { children: attributes.map((attr) => {
64
+ const reference = highlightDifferences ? subjects.map((s) => attr.values[s.id]).find((v) => v != null && v !== "") : void 0;
65
+ return /* @__PURE__ */ jsxs("tr", { "data-hex-compare-table-row": true, "data-attribute-id": attr.id, children: [
66
+ /* @__PURE__ */ jsx(
67
+ "th",
68
+ {
69
+ scope: "row",
70
+ className: "sticky left-0 z-10 border-b bg-card px-3 py-2 text-left font-medium",
71
+ children: attr.label
72
+ }
73
+ ),
74
+ subjects.map((subject) => {
75
+ const raw = attr.values[subject.id];
76
+ const isEmpty = raw == null || raw === "";
77
+ const displayValue = isEmpty ? EMPTY_PLACEHOLDER : raw;
78
+ const isComparable = (typeof raw === "string" || typeof raw === "number" || typeof raw === "boolean") && (typeof reference === "string" || typeof reference === "number" || typeof reference === "boolean");
79
+ const differs = highlightDifferences && !isEmpty && reference !== void 0 && isComparable && String(raw) !== String(reference);
80
+ const interactive = Boolean(onCellClick);
81
+ return /* @__PURE__ */ jsx(
82
+ "td",
83
+ {
84
+ "data-hex-compare-table-cell": true,
85
+ "data-subject-id": subject.id,
86
+ "data-attribute-id": attr.id,
87
+ "data-differs": differs,
88
+ className: cn(
89
+ "border-b px-3 py-2 align-top",
90
+ differs && "bg-accent/30 text-accent-foreground",
91
+ isEmpty && "text-muted-foreground",
92
+ interactive && "cursor-pointer hover:bg-muted/40"
93
+ ),
94
+ onClick: interactive ? () => onCellClick?.(subject.id, attr.id) : void 0,
95
+ children: displayValue
96
+ },
97
+ subject.id
98
+ );
99
+ })
100
+ ] }, attr.id);
101
+ }) })
102
+ ] })
103
+ }
104
+ );
105
+ }
106
+
107
+ export { CompareTable };
108
+ //# sourceMappingURL=compare-table.js.map
109
+ //# sourceMappingURL=compare-table.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/utils.ts","../src/artifacts/compare-table/compare-table.tsx"],"names":[],"mappings":"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","file":"compare-table.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 * Side-by-side comparison table. Subjects are columns; attributes are\n * rows; cells render the per-subject value for that attribute. With\n * `highlightDifferences`, cells whose value differs from the row's\n * first non-empty cell get a subtle accent — handy for vocab pairs\n * (term ↔ translation), feature matrices (Linux vs Mac vs Windows),\n * before/after comparisons. Pure HTML; no heavy peer.\n *\n * @example\n * <CompareTable\n * subjects={[\n * { id: \"linux\", label: \"Linux\" },\n * { id: \"mac\", label: \"Mac\" },\n * { id: \"win\", label: \"Windows\" },\n * ]}\n * attributes={[\n * { id: \"kernel\", label: \"Kernel\", values: { linux: \"Linux\", mac: \"Darwin\", win: \"NT\" } },\n * { id: \"fs\", label: \"Default FS\", values: { linux: \"ext4\", mac: \"APFS\", win: \"NTFS\" } },\n * ]}\n * />\n */\nexport type CompareSubject = {\n\tid: string;\n\tlabel: React.ReactNode;\n};\n\nexport type CompareAttribute = {\n\tid: string;\n\tlabel: React.ReactNode;\n\t/** Map of subjectId → cell content. Missing keys render as \"—\". */\n\tvalues: Record<string, React.ReactNode>;\n};\n\nexport interface CompareTableProps extends Omit<React.HTMLAttributes<HTMLDivElement>, \"children\"> {\n\t/** Columns. */\n\tsubjects: CompareSubject[];\n\t/** Rows. */\n\tattributes: CompareAttribute[];\n\t/** When true, cells whose value differs from the row's first non-empty cell get a subtle accent. */\n\thighlightDifferences?: boolean;\n\t/** Fired when a body cell is clicked. */\n\tonCellClick?: (subjectId: string, attributeId: string) => void;\n}\n\nconst EMPTY_PLACEHOLDER = \"—\";\n\nfunction CompareTable({\n\tsubjects,\n\tattributes,\n\thighlightDifferences = false,\n\tonCellClick,\n\tclassName,\n\t...rest\n}: CompareTableProps) {\n\t// Surface a developer-facing console.warn when an attribute references a\n\t// subjectId that isn't actually in `subjects` — easy mistake when the\n\t// consumer rebuilds rows from a different source than columns.\n\tconst warnedRef = React.useRef(false);\n\tconst nodeEnv = (globalThis as { process?: { env?: { NODE_ENV?: string } } }).process?.env?.NODE_ENV;\n\tif (nodeEnv !== \"production\" && !warnedRef.current) {\n\t\tconst subjectIds = new Set(subjects.map((s) => s.id));\n\t\tfor (const attr of attributes) {\n\t\t\tfor (const valueId of Object.keys(attr.values)) {\n\t\t\t\tif (!subjectIds.has(valueId)) {\n\t\t\t\t\twarnedRef.current = true;\n\t\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\t\tconsole.warn(\n\t\t\t\t\t\t`[hex-core/CompareTable] Attribute \"${attr.id}\" has a value for subjectId \"${valueId}\" that isn't in the subjects array. The cell will not render.`,\n\t\t\t\t\t);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (warnedRef.current) break;\n\t\t}\n\t}\n\n\treturn (\n\t\t<div\n\t\t\t{...rest}\n\t\t\tdata-hex-compare-table\n\t\t\tclassName={cn(\"overflow-x-auto rounded-lg border\", className)}\n\t\t>\n\t\t\t<table className=\"w-full border-collapse text-sm\">\n\t\t\t\t<thead>\n\t\t\t\t\t<tr data-hex-compare-table-header>\n\t\t\t\t\t\t<th\n\t\t\t\t\t\t\tscope=\"col\"\n\t\t\t\t\t\t\tclassName=\"sticky left-0 z-10 border-b bg-card px-3 py-2 text-left text-xs font-medium text-muted-foreground\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{/* Empty corner — attribute labels live here per row */}\n\t\t\t\t\t\t</th>\n\t\t\t\t\t\t{subjects.map((subject) => (\n\t\t\t\t\t\t\t<th\n\t\t\t\t\t\t\t\tkey={subject.id}\n\t\t\t\t\t\t\t\tscope=\"col\"\n\t\t\t\t\t\t\t\tdata-hex-compare-table-subject\n\t\t\t\t\t\t\t\tdata-subject-id={subject.id}\n\t\t\t\t\t\t\t\tclassName=\"border-b bg-card px-3 py-2 text-left font-semibold\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{subject.label}\n\t\t\t\t\t\t\t</th>\n\t\t\t\t\t\t))}\n\t\t\t\t\t</tr>\n\t\t\t\t</thead>\n\t\t\t\t<tbody>\n\t\t\t\t\t{attributes.map((attr) => {\n\t\t\t\t\t\t// Find the row's reference value (first non-empty cell among the\n\t\t\t\t\t\t// subjects we're rendering, preserving display order).\n\t\t\t\t\t\tconst reference = highlightDifferences\n\t\t\t\t\t\t\t? subjects.map((s) => attr.values[s.id]).find((v) => v != null && v !== \"\")\n\t\t\t\t\t\t\t: undefined;\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t<tr key={attr.id} data-hex-compare-table-row data-attribute-id={attr.id}>\n\t\t\t\t\t\t\t\t<th\n\t\t\t\t\t\t\t\t\tscope=\"row\"\n\t\t\t\t\t\t\t\t\tclassName=\"sticky left-0 z-10 border-b bg-card px-3 py-2 text-left font-medium\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t{attr.label}\n\t\t\t\t\t\t\t\t</th>\n\t\t\t\t\t\t\t\t{subjects.map((subject) => {\n\t\t\t\t\t\t\t\t\tconst raw = attr.values[subject.id];\n\t\t\t\t\t\t\t\t\tconst isEmpty = raw == null || raw === \"\";\n\t\t\t\t\t\t\t\t\tconst displayValue = isEmpty ? EMPTY_PLACEHOLDER : raw;\n\t\t\t\t\t\t\t\t\t// Skip diff for non-primitive values: ReactElements stringify\n\t\t\t\t\t\t\t\t\t// to \"[object Object]\" and would all flag as different. Only\n\t\t\t\t\t\t\t\t\t// run the comparison when both raw + reference are scalar.\n\t\t\t\t\t\t\t\t\tconst isComparable =\n\t\t\t\t\t\t\t\t\t\t(typeof raw === \"string\" || typeof raw === \"number\" || typeof raw === \"boolean\") &&\n\t\t\t\t\t\t\t\t\t\t(typeof reference === \"string\" ||\n\t\t\t\t\t\t\t\t\t\t\ttypeof reference === \"number\" ||\n\t\t\t\t\t\t\t\t\t\t\ttypeof reference === \"boolean\");\n\t\t\t\t\t\t\t\t\tconst differs =\n\t\t\t\t\t\t\t\t\t\thighlightDifferences &&\n\t\t\t\t\t\t\t\t\t\t!isEmpty &&\n\t\t\t\t\t\t\t\t\t\treference !== undefined &&\n\t\t\t\t\t\t\t\t\t\tisComparable &&\n\t\t\t\t\t\t\t\t\t\tString(raw) !== String(reference);\n\t\t\t\t\t\t\t\t\tconst interactive = Boolean(onCellClick);\n\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t<td\n\t\t\t\t\t\t\t\t\t\t\tkey={subject.id}\n\t\t\t\t\t\t\t\t\t\t\tdata-hex-compare-table-cell\n\t\t\t\t\t\t\t\t\t\t\tdata-subject-id={subject.id}\n\t\t\t\t\t\t\t\t\t\t\tdata-attribute-id={attr.id}\n\t\t\t\t\t\t\t\t\t\t\tdata-differs={differs}\n\t\t\t\t\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\t\t\t\t\"border-b px-3 py-2 align-top\",\n\t\t\t\t\t\t\t\t\t\t\t\tdiffers && \"bg-accent/30 text-accent-foreground\",\n\t\t\t\t\t\t\t\t\t\t\t\tisEmpty && \"text-muted-foreground\",\n\t\t\t\t\t\t\t\t\t\t\t\tinteractive && \"cursor-pointer hover:bg-muted/40\",\n\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\tonClick={interactive ? () => onCellClick?.(subject.id, attr.id) : undefined}\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t{displayValue}\n\t\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t);\n\t\t\t\t\t})}\n\t\t\t\t</tbody>\n\t\t\t</table>\n\t\t</div>\n\t);\n}\n\nexport { CompareTable };\n"]}
package/dist/deck.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { DeckCard_alias_1 as DeckCard } from './_tsup-dts-rollup.js';
2
+ export { DeckProps_alias_1 as DeckProps } from './_tsup-dts-rollup.js';
3
+ export { Deck_alias_1 as Deck } from './_tsup-dts-rollup.js';