@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.
- package/dist/_tsup-dts-rollup.d.ts +1597 -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 +86 -0
- package/dist/index.js +4085 -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 +2211 -4
- 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/speech-recognition.d.ts +2 -0
- package/dist/speech-recognition.js +152 -0
- package/dist/speech-recognition.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/sunburst.js
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
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
|
+
// 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 depthOpacity(depth, maxDepth) {
|
|
24
|
+
if (maxDepth <= 1) return 0.85;
|
|
25
|
+
const t = (depth - 1) / Math.max(1, maxDepth - 1);
|
|
26
|
+
return 0.85 - t * 0.4;
|
|
27
|
+
}
|
|
28
|
+
function Sunburst({
|
|
29
|
+
root,
|
|
30
|
+
drillable = true,
|
|
31
|
+
centerLabel,
|
|
32
|
+
size = 400,
|
|
33
|
+
onSegmentClick,
|
|
34
|
+
className,
|
|
35
|
+
...rest
|
|
36
|
+
}) {
|
|
37
|
+
const [d3h, setD3h] = React.useState(null);
|
|
38
|
+
const [d3s, setD3s] = React.useState(null);
|
|
39
|
+
const [focusId, setFocusId] = React.useState(root.id);
|
|
40
|
+
React.useEffect(() => {
|
|
41
|
+
let cancelled = false;
|
|
42
|
+
void Promise.all([import('d3-hierarchy'), import('d3-shape')]).then(([h, s]) => {
|
|
43
|
+
if (cancelled) return;
|
|
44
|
+
setD3h(h);
|
|
45
|
+
setD3s(s);
|
|
46
|
+
});
|
|
47
|
+
return () => {
|
|
48
|
+
cancelled = true;
|
|
49
|
+
};
|
|
50
|
+
}, []);
|
|
51
|
+
React.useEffect(() => {
|
|
52
|
+
setFocusId(root.id);
|
|
53
|
+
}, [root]);
|
|
54
|
+
if (!d3h || !d3s) {
|
|
55
|
+
return /* @__PURE__ */ jsx(
|
|
56
|
+
"div",
|
|
57
|
+
{
|
|
58
|
+
"data-hex-sunburst-loading": true,
|
|
59
|
+
"aria-busy": "true",
|
|
60
|
+
className: cn("inline-block bg-muted/20", className),
|
|
61
|
+
style: { width: size, height: size }
|
|
62
|
+
}
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
const focused = findNode(root, focusId) ?? root;
|
|
66
|
+
const radius = size / 2;
|
|
67
|
+
const segments = layout(d3h, focused, radius);
|
|
68
|
+
const maxDepth = segments.reduce((m, s) => Math.max(m, s.depth), 1);
|
|
69
|
+
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);
|
|
70
|
+
const handleSegmentClick = (segment) => {
|
|
71
|
+
onSegmentClick?.(segment.node);
|
|
72
|
+
if (!drillable) return;
|
|
73
|
+
const hasChildren = segment.node.children && segment.node.children.length > 0;
|
|
74
|
+
if (!hasChildren) return;
|
|
75
|
+
setFocusId(segment.node.id);
|
|
76
|
+
};
|
|
77
|
+
const handleCenterClick = () => {
|
|
78
|
+
if (!drillable || focusId === root.id) return;
|
|
79
|
+
setFocusId(root.id);
|
|
80
|
+
};
|
|
81
|
+
const desc = `Sunburst with ${segments.length} segments, focused on "${focused.label}"`;
|
|
82
|
+
return /* @__PURE__ */ jsxs(
|
|
83
|
+
"svg",
|
|
84
|
+
{
|
|
85
|
+
...rest,
|
|
86
|
+
"data-hex-sunburst": true,
|
|
87
|
+
"data-focus-id": focusId,
|
|
88
|
+
role: "img",
|
|
89
|
+
width: size,
|
|
90
|
+
height: size,
|
|
91
|
+
viewBox: `${-radius} ${-radius} ${size} ${size}`,
|
|
92
|
+
className: cn("block", className),
|
|
93
|
+
children: [
|
|
94
|
+
/* @__PURE__ */ jsx("title", { children: "Sunburst" }),
|
|
95
|
+
/* @__PURE__ */ jsx("desc", { children: desc }),
|
|
96
|
+
/* @__PURE__ */ jsx("g", { "data-hex-sunburst-segments": true, children: segments.filter((s) => s.depth > 0).map((s) => /* @__PURE__ */ jsx(
|
|
97
|
+
"path",
|
|
98
|
+
{
|
|
99
|
+
"data-hex-sunburst-segment": true,
|
|
100
|
+
"data-depth": s.depth,
|
|
101
|
+
d: arc(s) ?? "",
|
|
102
|
+
fill: pickChartHue(s.rootSiblingIdx),
|
|
103
|
+
fillOpacity: depthOpacity(s.depth, maxDepth),
|
|
104
|
+
stroke: "hsl(var(--background))",
|
|
105
|
+
strokeWidth: 1,
|
|
106
|
+
style: drillable || onSegmentClick ? { cursor: "pointer" } : void 0,
|
|
107
|
+
onClick: drillable || onSegmentClick ? () => handleSegmentClick(s) : void 0
|
|
108
|
+
},
|
|
109
|
+
s.node.id
|
|
110
|
+
)) }),
|
|
111
|
+
/* @__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) => {
|
|
112
|
+
const angle = (s.x0 + s.x1) / 2;
|
|
113
|
+
const rMid = (s.y0 + s.y1) / 2;
|
|
114
|
+
const cx = Math.sin(angle) * rMid;
|
|
115
|
+
const cy = -Math.cos(angle) * rMid;
|
|
116
|
+
return /* @__PURE__ */ jsx(
|
|
117
|
+
"text",
|
|
118
|
+
{
|
|
119
|
+
x: cx,
|
|
120
|
+
y: cy,
|
|
121
|
+
textAnchor: "middle",
|
|
122
|
+
dy: "0.35em",
|
|
123
|
+
fontSize: 11,
|
|
124
|
+
fontWeight: 500,
|
|
125
|
+
fill: "hsl(var(--background))",
|
|
126
|
+
style: { paintOrder: "stroke", stroke: "hsl(var(--foreground) / 0.25)", strokeWidth: 2 },
|
|
127
|
+
children: s.node.label
|
|
128
|
+
},
|
|
129
|
+
`${s.node.id}-label`
|
|
130
|
+
);
|
|
131
|
+
}) }),
|
|
132
|
+
/* @__PURE__ */ jsxs(
|
|
133
|
+
"g",
|
|
134
|
+
{
|
|
135
|
+
"data-hex-sunburst-center": true,
|
|
136
|
+
style: drillable && focusId !== root.id ? { cursor: "pointer" } : void 0,
|
|
137
|
+
onClick: drillable ? handleCenterClick : void 0,
|
|
138
|
+
children: [
|
|
139
|
+
/* @__PURE__ */ jsx("circle", { r: radius / 4, fill: "hsl(var(--card))", stroke: "hsl(var(--border))", strokeWidth: 1 }),
|
|
140
|
+
centerLabel ? /* @__PURE__ */ jsx("foreignObject", { x: -radius / 4, y: -radius / 4, width: radius / 2, height: radius / 2, children: /* @__PURE__ */ jsx(
|
|
141
|
+
"div",
|
|
142
|
+
{
|
|
143
|
+
style: {
|
|
144
|
+
width: "100%",
|
|
145
|
+
height: "100%",
|
|
146
|
+
display: "flex",
|
|
147
|
+
alignItems: "center",
|
|
148
|
+
justifyContent: "center",
|
|
149
|
+
textAlign: "center",
|
|
150
|
+
fontSize: 12,
|
|
151
|
+
padding: 4
|
|
152
|
+
},
|
|
153
|
+
children: centerLabel
|
|
154
|
+
}
|
|
155
|
+
) }) : /* @__PURE__ */ jsx(
|
|
156
|
+
"text",
|
|
157
|
+
{
|
|
158
|
+
textAnchor: "middle",
|
|
159
|
+
dy: "0.35em",
|
|
160
|
+
fontSize: 12,
|
|
161
|
+
fontWeight: 600,
|
|
162
|
+
fill: "hsl(var(--foreground))",
|
|
163
|
+
children: focused.label
|
|
164
|
+
}
|
|
165
|
+
)
|
|
166
|
+
]
|
|
167
|
+
}
|
|
168
|
+
)
|
|
169
|
+
]
|
|
170
|
+
}
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
function layout(d3h, focused, radius) {
|
|
174
|
+
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));
|
|
175
|
+
const layoutRoot = d3h.partition().size([2 * Math.PI, radius])(hierarchy);
|
|
176
|
+
const segments = [];
|
|
177
|
+
layoutRoot.each((d) => {
|
|
178
|
+
let cursor = d;
|
|
179
|
+
while (cursor && cursor.depth > 1) cursor = cursor.parent;
|
|
180
|
+
const ancestorIdx = cursor?.parent?.children?.indexOf(cursor) ?? 0;
|
|
181
|
+
segments.push({
|
|
182
|
+
node: d.data,
|
|
183
|
+
depth: d.depth,
|
|
184
|
+
x0: d.x0,
|
|
185
|
+
x1: d.x1,
|
|
186
|
+
y0: d.y0,
|
|
187
|
+
y1: d.y1,
|
|
188
|
+
rootSiblingIdx: Math.max(0, ancestorIdx)
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
return segments;
|
|
192
|
+
}
|
|
193
|
+
function findNode(root, id) {
|
|
194
|
+
if (root.id === id) return root;
|
|
195
|
+
if (!root.children) return null;
|
|
196
|
+
for (const c of root.children) {
|
|
197
|
+
const f = findNode(c, id);
|
|
198
|
+
if (f) return f;
|
|
199
|
+
}
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export { Sunburst };
|
|
204
|
+
//# sourceMappingURL=sunburst.js.map
|
|
205
|
+
//# sourceMappingURL=sunburst.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/chart-palette.ts","../src/lib/utils.ts","../src/artifacts/sunburst/sunburst.tsx"],"names":[],"mappings":"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","file":"sunburst.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 * Radial hierarchy by value. Each ring out from the center is a deeper level\n * of the tree; each segment's angular extent is proportional to its summed\n * value. Click any segment to drill into it (the clicked segment becomes the\n * new center); click the center to zoom back out.\n *\n * Heavy peers: requires `d3-hierarchy` and `d3-shape` (~3 KB + ~6 KB gzip).\n *\n * @example\n * <Sunburst\n * root={{\n * id: \"root\", label: \"Total\",\n * children: [\n * { id: \"a\", label: \"A\", value: 60, children: [\n * { id: \"a1\", label: \"A1\", value: 40 },\n * { id: \"a2\", label: \"A2\", value: 20 },\n * ]},\n * { id: \"b\", label: \"B\", value: 30 },\n * ],\n * }}\n * />\n */\nexport type SunburstNode = {\n\tid: string;\n\tlabel: string;\n\tvalue?: number;\n\tchildren?: SunburstNode[];\n};\n\nexport interface SunburstProps extends Omit<React.SVGAttributes<SVGSVGElement>, \"children\"> {\n\t/** Root of the hierarchy. Leaves require a positive `value`; internal nodes are summed. */\n\troot: SunburstNode;\n\t/** Allow a click on a segment to zoom into it as the new center. Default true. */\n\tdrillable?: boolean;\n\t/** Optional content rendered at the SVG center (e.g. total). */\n\tcenterLabel?: React.ReactNode;\n\t/** Pixel size of the rendered SVG (it's square). Default 400. */\n\tsize?: number;\n\t/** Fired when a segment is clicked. Fires before any internal drill. */\n\tonSegmentClick?: (node: SunburstNode) => void;\n}\n\ninterface LaidOutSegment {\n\tnode: SunburstNode;\n\tdepth: number;\n\tx0: number;\n\tx1: number;\n\ty0: number;\n\ty1: number;\n\t/** Index of this segment's depth-1 ancestor among its siblings.\n\t * Used to pick a hue from CHART_PALETTE so all descendants of the\n\t * same top-level branch share a color family. */\n\trootSiblingIdx: number;\n}\n\ntype D3HierarchyMod = typeof import(\"d3-hierarchy\");\ntype D3ShapeMod = typeof import(\"d3-shape\");\n\n/**\n * Sunburst opacity ramp: deeper rings render at lower opacity so they\n * read as subdivisions of their parent without obscuring the parent hue.\n * Hand-tuned: outermost ring lands ~0.45, innermost stays at 0.85.\n *\n * @param depth - Ring depth (1-indexed; 1 is the inner ring).\n * @param maxDepth - The maximum ring depth in the current focused tree.\n * @returns Opacity in [0.45, 0.85].\n */\nfunction depthOpacity(depth: number, maxDepth: number): number {\n\tif (maxDepth <= 1) return 0.85;\n\tconst t = (depth - 1) / Math.max(1, maxDepth - 1);\n\treturn 0.85 - t * 0.4;\n}\n\nfunction Sunburst({\n\troot,\n\tdrillable = true,\n\tcenterLabel,\n\tsize = 400,\n\tonSegmentClick,\n\tclassName,\n\t...rest\n}: SunburstProps) {\n\tconst [d3h, setD3h] = React.useState<D3HierarchyMod | null>(null);\n\tconst [d3s, setD3s] = React.useState<D3ShapeMod | null>(null);\n\tconst [focusId, setFocusId] = React.useState<string>(root.id);\n\n\tReact.useEffect(() => {\n\t\tlet cancelled = false;\n\t\tvoid Promise.all([import(\"d3-hierarchy\"), import(\"d3-shape\")]).then(([h, s]) => {\n\t\t\tif (cancelled) return;\n\t\t\tsetD3h(h);\n\t\t\tsetD3s(s);\n\t\t});\n\t\treturn () => {\n\t\t\tcancelled = true;\n\t\t};\n\t}, []);\n\n\t// Reset focus when root changes\n\tReact.useEffect(() => {\n\t\tsetFocusId(root.id);\n\t}, [root]);\n\n\tif (!d3h || !d3s) {\n\t\treturn (\n\t\t\t<div\n\t\t\t\tdata-hex-sunburst-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: size, height: size }}\n\t\t\t/>\n\t\t);\n\t}\n\n\tconst focused = findNode(root, focusId) ?? root;\n\tconst radius = size / 2;\n\tconst segments = layout(d3h, focused, radius);\n\tconst maxDepth = segments.reduce((m, s) => Math.max(m, s.depth), 1);\n\tconst arc = d3s\n\t\t.arc<LaidOutSegment>()\n\t\t.startAngle((s) => s.x0)\n\t\t.endAngle((s) => s.x1)\n\t\t.innerRadius((s) => s.y0)\n\t\t.outerRadius((s) => s.y1)\n\t\t.padAngle(0.005)\n\t\t.padRadius(radius);\n\n\tconst handleSegmentClick = (segment: LaidOutSegment) => {\n\t\tonSegmentClick?.(segment.node);\n\t\tif (!drillable) return;\n\t\tconst hasChildren = segment.node.children && segment.node.children.length > 0;\n\t\tif (!hasChildren) return;\n\t\tsetFocusId(segment.node.id);\n\t};\n\n\tconst handleCenterClick = () => {\n\t\tif (!drillable || focusId === root.id) return;\n\t\tsetFocusId(root.id);\n\t};\n\n\tconst desc = `Sunburst with ${segments.length} segments, focused on \"${focused.label}\"`;\n\n\treturn (\n\t\t<svg\n\t\t\t{...rest}\n\t\t\tdata-hex-sunburst\n\t\t\tdata-focus-id={focusId}\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>Sunburst</title>\n\t\t\t<desc>{desc}</desc>\n\t\t\t<g data-hex-sunburst-segments>\n\t\t\t\t{segments\n\t\t\t\t\t.filter((s) => s.depth > 0)\n\t\t\t\t\t.map((s) => (\n\t\t\t\t\t\t<path\n\t\t\t\t\t\t\tkey={s.node.id}\n\t\t\t\t\t\t\tdata-hex-sunburst-segment\n\t\t\t\t\t\t\tdata-depth={s.depth}\n\t\t\t\t\t\t\td={arc(s) ?? \"\"}\n\t\t\t\t\t\t\tfill={pickChartHue(s.rootSiblingIdx)}\n\t\t\t\t\t\t\tfillOpacity={depthOpacity(s.depth, maxDepth)}\n\t\t\t\t\t\t\tstroke=\"hsl(var(--background))\"\n\t\t\t\t\t\t\tstrokeWidth={1}\n\t\t\t\t\t\t\tstyle={drillable || onSegmentClick ? { cursor: \"pointer\" } : undefined}\n\t\t\t\t\t\t\tonClick={drillable || onSegmentClick ? () => handleSegmentClick(s) : undefined}\n\t\t\t\t\t\t/>\n\t\t\t\t\t))}\n\t\t\t</g>\n\t\t\t<g data-hex-sunburst-labels aria-hidden=\"true\" pointerEvents=\"none\">\n\t\t\t\t{segments\n\t\t\t\t\t.filter((s) => s.depth > 0 && s.x1 - s.x0 > 0.18)\n\t\t\t\t\t.map((s) => {\n\t\t\t\t\t\tconst angle = (s.x0 + s.x1) / 2;\n\t\t\t\t\t\tconst rMid = (s.y0 + s.y1) / 2;\n\t\t\t\t\t\tconst cx = Math.sin(angle) * rMid;\n\t\t\t\t\t\tconst cy = -Math.cos(angle) * rMid;\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t<text\n\t\t\t\t\t\t\t\tkey={`${s.node.id}-label`}\n\t\t\t\t\t\t\t\tx={cx}\n\t\t\t\t\t\t\t\ty={cy}\n\t\t\t\t\t\t\t\ttextAnchor=\"middle\"\n\t\t\t\t\t\t\t\tdy=\"0.35em\"\n\t\t\t\t\t\t\t\tfontSize={11}\n\t\t\t\t\t\t\t\tfontWeight={500}\n\t\t\t\t\t\t\t\tfill=\"hsl(var(--background))\"\n\t\t\t\t\t\t\t\tstyle={{ paintOrder: \"stroke\", stroke: \"hsl(var(--foreground) / 0.25)\", strokeWidth: 2 }}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{s.node.label}\n\t\t\t\t\t\t\t</text>\n\t\t\t\t\t\t);\n\t\t\t\t\t})}\n\t\t\t</g>\n\t\t\t<g\n\t\t\t\tdata-hex-sunburst-center\n\t\t\t\tstyle={drillable && focusId !== root.id ? { cursor: \"pointer\" } : undefined}\n\t\t\t\tonClick={drillable ? handleCenterClick : undefined}\n\t\t\t>\n\t\t\t\t<circle r={radius / 4} fill=\"hsl(var(--card))\" stroke=\"hsl(var(--border))\" strokeWidth={1} />\n\t\t\t\t{centerLabel ? (\n\t\t\t\t\t<foreignObject x={-radius / 4} y={-radius / 4} width={radius / 2} height={radius / 2}>\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\t\twidth: \"100%\",\n\t\t\t\t\t\t\t\theight: \"100%\",\n\t\t\t\t\t\t\t\tdisplay: \"flex\",\n\t\t\t\t\t\t\t\talignItems: \"center\",\n\t\t\t\t\t\t\t\tjustifyContent: \"center\",\n\t\t\t\t\t\t\t\ttextAlign: \"center\",\n\t\t\t\t\t\t\t\tfontSize: 12,\n\t\t\t\t\t\t\t\tpadding: 4,\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{centerLabel}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</foreignObject>\n\t\t\t\t) : (\n\t\t\t\t\t<text\n\t\t\t\t\t\ttextAnchor=\"middle\"\n\t\t\t\t\t\tdy=\"0.35em\"\n\t\t\t\t\t\tfontSize={12}\n\t\t\t\t\t\tfontWeight={600}\n\t\t\t\t\t\tfill=\"hsl(var(--foreground))\"\n\t\t\t\t\t>\n\t\t\t\t\t\t{focused.label}\n\t\t\t\t\t</text>\n\t\t\t\t)}\n\t\t\t</g>\n\t\t</svg>\n\t);\n}\n\nfunction layout(d3h: D3HierarchyMod, focused: SunburstNode, radius: number): LaidOutSegment[] {\n\tconst hierarchy = d3h\n\t\t.hierarchy<SunburstNode>(focused)\n\t\t.sum((d) => (d.children && d.children.length > 0 ? 0 : d.value ?? 0))\n\t\t.sort((a, b) => (b.value ?? 0) - (a.value ?? 0));\n\t// partition(hierarchy) returns HierarchyRectangularNode<T> with typed\n\t// x0/x1/y0/y1 (angle range × radius range for sunburst).\n\tconst layoutRoot = d3h.partition<SunburstNode>().size([2 * Math.PI, radius])(hierarchy);\n\tconst segments: LaidOutSegment[] = [];\n\tlayoutRoot.each((d) => {\n\t\t// Walk up to the depth-1 ancestor so all descendants of \"Equity\"\n\t\t// share the Equity hue, distinct from \"Fixed Income\".\n\t\tlet cursor: typeof d | null = d;\n\t\twhile (cursor && cursor.depth > 1) cursor = cursor.parent;\n\t\tconst ancestorIdx = cursor?.parent?.children?.indexOf(cursor) ?? 0;\n\t\tsegments.push({\n\t\t\tnode: d.data,\n\t\t\tdepth: d.depth,\n\t\t\tx0: d.x0,\n\t\t\tx1: d.x1,\n\t\t\ty0: d.y0,\n\t\t\ty1: d.y1,\n\t\t\trootSiblingIdx: Math.max(0, ancestorIdx),\n\t\t});\n\t});\n\treturn segments;\n}\n\nfunction findNode(root: SunburstNode, id: string): SunburstNode | null {\n\tif (root.id === id) return root;\n\tif (!root.children) return null;\n\tfor (const c of root.children) {\n\t\tconst f = findNode(c, id);\n\t\tif (f) return f;\n\t}\n\treturn null;\n}\n\nexport { Sunburst };\n"]}
|
package/dist/terminal.js
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { clsx } from 'clsx';
|
|
4
|
+
import { twMerge } from 'tailwind-merge';
|
|
5
|
+
import { jsx } from 'react/jsx-runtime';
|
|
6
|
+
|
|
7
|
+
// src/lib/color.ts
|
|
8
|
+
function parseHslTriplet(triplet) {
|
|
9
|
+
const parts = triplet.trim().split(/\s+/);
|
|
10
|
+
return {
|
|
11
|
+
h: Number.parseFloat(parts[0]) || 0,
|
|
12
|
+
s: Number.parseFloat(parts[1]) || 0,
|
|
13
|
+
l: Number.parseFloat(parts[2]) || 0
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function hslToRgb(h, s, l) {
|
|
17
|
+
const sN = s / 100;
|
|
18
|
+
const lN = l / 100;
|
|
19
|
+
const k = (n) => (n + h / 30) % 12;
|
|
20
|
+
const a = sN * Math.min(lN, 1 - lN);
|
|
21
|
+
const f = (n) => lN - a * Math.max(-1, Math.min(k(n) - 3, 9 - k(n), 1));
|
|
22
|
+
return {
|
|
23
|
+
r: Math.round(255 * f(0)),
|
|
24
|
+
g: Math.round(255 * f(8)),
|
|
25
|
+
b: Math.round(255 * f(4))
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function hslTripletToHex(triplet) {
|
|
29
|
+
const { h, s, l } = parseHslTriplet(triplet);
|
|
30
|
+
const { r, g, b } = hslToRgb(h, s, l);
|
|
31
|
+
const toHex = (n) => n.toString(16).padStart(2, "0");
|
|
32
|
+
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
33
|
+
}
|
|
34
|
+
function cn(...inputs) {
|
|
35
|
+
return twMerge(clsx(inputs));
|
|
36
|
+
}
|
|
37
|
+
var DARK_FALLBACK = {
|
|
38
|
+
background: "#0a0a0a",
|
|
39
|
+
foreground: "#e5e5e5",
|
|
40
|
+
selectionBackground: "#404040"
|
|
41
|
+
};
|
|
42
|
+
var LIGHT_FALLBACK = {
|
|
43
|
+
background: "#fafafa",
|
|
44
|
+
foreground: "#171717",
|
|
45
|
+
selectionBackground: "#d4d4d4"
|
|
46
|
+
};
|
|
47
|
+
function readCssVarAsHex(name) {
|
|
48
|
+
if (typeof document === "undefined") return null;
|
|
49
|
+
const triplet = getComputedStyle(document.documentElement).getPropertyValue(`--${name}`).trim();
|
|
50
|
+
if (!triplet) return null;
|
|
51
|
+
return hslTripletToHex(triplet);
|
|
52
|
+
}
|
|
53
|
+
function Terminal({
|
|
54
|
+
output,
|
|
55
|
+
onInput,
|
|
56
|
+
cols = 80,
|
|
57
|
+
rows = 24,
|
|
58
|
+
theme = "dark",
|
|
59
|
+
cursorBlink = true,
|
|
60
|
+
disableInput = false,
|
|
61
|
+
className,
|
|
62
|
+
...rest
|
|
63
|
+
}) {
|
|
64
|
+
const containerRef = React.useRef(null);
|
|
65
|
+
const termRef = React.useRef(null);
|
|
66
|
+
const writtenRef = React.useRef("");
|
|
67
|
+
const onInputRef = React.useRef(onInput);
|
|
68
|
+
onInputRef.current = onInput;
|
|
69
|
+
const outputRef = React.useRef(output);
|
|
70
|
+
outputRef.current = output;
|
|
71
|
+
React.useEffect(() => {
|
|
72
|
+
if (!containerRef.current) return;
|
|
73
|
+
let disposed = false;
|
|
74
|
+
let inputDispose = null;
|
|
75
|
+
void (async () => {
|
|
76
|
+
const xtermModule = await import('@xterm/xterm');
|
|
77
|
+
if (disposed || !containerRef.current) return;
|
|
78
|
+
const fallback = theme === "dark" ? DARK_FALLBACK : LIGHT_FALLBACK;
|
|
79
|
+
const bgHex = readCssVarAsHex("background") ?? fallback.background;
|
|
80
|
+
const fgHex = readCssVarAsHex("foreground") ?? fallback.foreground;
|
|
81
|
+
const term = new xtermModule.Terminal({
|
|
82
|
+
cols,
|
|
83
|
+
rows,
|
|
84
|
+
cursorBlink,
|
|
85
|
+
disableStdin: disableInput,
|
|
86
|
+
theme: {
|
|
87
|
+
background: bgHex,
|
|
88
|
+
foreground: fgHex,
|
|
89
|
+
cursor: fgHex,
|
|
90
|
+
selectionBackground: fallback.selectionBackground
|
|
91
|
+
},
|
|
92
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
|
|
93
|
+
fontSize: 13
|
|
94
|
+
});
|
|
95
|
+
term.open(containerRef.current);
|
|
96
|
+
termRef.current = term;
|
|
97
|
+
const latest = normalizeOutput(outputRef.current);
|
|
98
|
+
if (latest) {
|
|
99
|
+
term.write(latest);
|
|
100
|
+
writtenRef.current = latest;
|
|
101
|
+
}
|
|
102
|
+
inputDispose = term.onData((data) => {
|
|
103
|
+
onInputRef.current?.(data);
|
|
104
|
+
});
|
|
105
|
+
})();
|
|
106
|
+
return () => {
|
|
107
|
+
disposed = true;
|
|
108
|
+
inputDispose?.dispose();
|
|
109
|
+
termRef.current?.dispose();
|
|
110
|
+
termRef.current = null;
|
|
111
|
+
writtenRef.current = "";
|
|
112
|
+
};
|
|
113
|
+
}, []);
|
|
114
|
+
React.useEffect(() => {
|
|
115
|
+
const term = termRef.current;
|
|
116
|
+
if (!term) return;
|
|
117
|
+
const next = normalizeOutput(output);
|
|
118
|
+
if (next === writtenRef.current) return;
|
|
119
|
+
if (next.startsWith(writtenRef.current)) {
|
|
120
|
+
const delta = next.slice(writtenRef.current.length);
|
|
121
|
+
if (delta) term.write(delta);
|
|
122
|
+
} else {
|
|
123
|
+
term.reset();
|
|
124
|
+
if (next) term.write(next);
|
|
125
|
+
}
|
|
126
|
+
writtenRef.current = next;
|
|
127
|
+
}, [output]);
|
|
128
|
+
const fallbackTriplet = theme === "dark" ? "0 0% 4%" : "0 0% 98%";
|
|
129
|
+
const themeBg = `hsl(var(--background, ${fallbackTriplet}))`;
|
|
130
|
+
return /* @__PURE__ */ jsx(
|
|
131
|
+
"div",
|
|
132
|
+
{
|
|
133
|
+
...rest,
|
|
134
|
+
ref: containerRef,
|
|
135
|
+
"data-hex-terminal": true,
|
|
136
|
+
"data-theme": theme,
|
|
137
|
+
style: { backgroundColor: themeBg, ...rest.style ?? {} },
|
|
138
|
+
className: cn(
|
|
139
|
+
"overflow-hidden rounded-md border p-2",
|
|
140
|
+
"font-mono text-sm leading-tight",
|
|
141
|
+
className
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
function normalizeOutput(value) {
|
|
147
|
+
if (value == null) return "";
|
|
148
|
+
return Array.isArray(value) ? value.join("") : value;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export { Terminal };
|
|
152
|
+
//# sourceMappingURL=terminal.js.map
|
|
153
|
+
//# sourceMappingURL=terminal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/color.ts","../src/lib/utils.ts","../src/ai/terminal/terminal.tsx"],"names":[],"mappings":"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","file":"terminal.js","sourcesContent":["/**\n * Color conversion utilities for the HSL-triplet token format used across\n * `@hex-core/tokens` themes (`H S% L%`, e.g. `\"240 5.9% 10%\"` — no `hsl()`\n * wrapper, no commas).\n *\n * The triplet is the round-trip-safe serialization for Hex UI: tokens flow\n * triplet → CSS `hsl(var(--token))` → rendered color, and the ColorPicker\n * component edits triplets directly. Hex/RGB conversions are display\n * adapters, not the source of truth.\n */\n\n/** Parsed HSL components. `h` is degrees (0–360); `s` and `l` are percentages (0–100). */\nexport interface HslTriplet {\n\th: number;\n\ts: number;\n\tl: number;\n}\n\n/** Parsed RGB components. Each channel is 0–255. */\nexport interface RgbColor {\n\tr: number;\n\tg: number;\n\tb: number;\n}\n\n/**\n * Parse an HSL triplet string into numeric components.\n *\n * Note: malformed input silently coerces to `{0,0,0}` (pure black) rather than\n * returning an error signal. Callers that need to distinguish \"user typed\n * black\" from \"user typed garbage\" should validate the input format first.\n * `hexToHslTriplet` returns `null` for malformed hex; this asymmetry is\n * intentional — triplets feed CSS variables where any non-color value would\n * already break rendering.\n *\n * @param triplet - String in the form `\"<H> <S>% <L>%\"` (e.g. `\"240 5.9% 10%\"`).\n * @returns Numeric components, or `{0,0,0}` if the input is malformed.\n */\nexport function parseHslTriplet(triplet: string): HslTriplet {\n\tconst parts = triplet.trim().split(/\\s+/);\n\treturn {\n\t\th: Number.parseFloat(parts[0]) || 0,\n\t\ts: Number.parseFloat(parts[1]) || 0,\n\t\tl: Number.parseFloat(parts[2]) || 0,\n\t};\n}\n\n/**\n * Format HSL components into an HSL triplet string (the canonical token format).\n * @param hsl - Numeric components.\n * @returns Triplet in the form `\"<H> <S>% <L>%\"`.\n */\nexport function formatHslTriplet({ h, s, l }: HslTriplet): string {\n\t// Tolerant integer check: rgbToHsl can produce values like 5.0000000001 due\n\t// to float arithmetic; format those as \"5\" rather than \"5.0\".\n\tconst round = (n: number) =>\n\t\tMath.abs(n - Math.round(n)) < 1e-6 ? `${Math.round(n)}` : n.toFixed(1);\n\treturn `${Math.round(h)} ${round(s)}% ${round(l)}%`;\n}\n\n/**\n * Convert HSL components to RGB.\n * @param h - Hue (0–360).\n * @param s - Saturation (0–100).\n * @param l - Lightness (0–100).\n * @returns RGB channels (0–255, rounded).\n */\nexport function hslToRgb(h: number, s: number, l: number): RgbColor {\n\tconst sN = s / 100;\n\tconst lN = l / 100;\n\tconst k = (n: number) => (n + h / 30) % 12;\n\tconst a = sN * Math.min(lN, 1 - lN);\n\tconst f = (n: number) => lN - a * Math.max(-1, Math.min(k(n) - 3, 9 - k(n), 1));\n\treturn {\n\t\tr: Math.round(255 * f(0)),\n\t\tg: Math.round(255 * f(8)),\n\t\tb: Math.round(255 * f(4)),\n\t};\n}\n\n/**\n * Convert RGB components to HSL.\n * @param r - Red (0–255).\n * @param g - Green (0–255).\n * @param b - Blue (0–255).\n * @returns HSL components (h: 0–360, s: 0–100, l: 0–100).\n */\nexport function rgbToHsl(r: number, g: number, b: number): HslTriplet {\n\tconst rN = r / 255;\n\tconst gN = g / 255;\n\tconst bN = b / 255;\n\tconst max = Math.max(rN, gN, bN);\n\tconst min = Math.min(rN, gN, bN);\n\tlet h = 0;\n\tlet s = 0;\n\tconst l = (max + min) / 2;\n\tif (max !== min) {\n\t\tconst d = max - min;\n\t\ts = l > 0.5 ? d / (2 - max - min) : d / (max + min);\n\t\tif (max === rN) h = (gN - bN) / d + (gN < bN ? 6 : 0);\n\t\telse if (max === gN) h = (bN - rN) / d + 2;\n\t\telse h = (rN - gN) / d + 4;\n\t\th /= 6;\n\t}\n\treturn { h: h * 360, s: s * 100, l: l * 100 };\n}\n\n/**\n * Convert an HSL triplet to a 6-digit hex string.\n * @param triplet - HSL triplet (e.g. `\"240 5.9% 10%\"`).\n * @returns Lowercase hex string with leading `#` (e.g. `\"#181a1f\"`).\n */\nexport function hslTripletToHex(triplet: string): string {\n\tconst { h, s, l } = parseHslTriplet(triplet);\n\tconst { r, g, b } = hslToRgb(h, s, l);\n\tconst toHex = (n: number) => n.toString(16).padStart(2, \"0\");\n\treturn `#${toHex(r)}${toHex(g)}${toHex(b)}`;\n}\n\n/**\n * Convert a hex string to an HSL triplet.\n * Accepts 3-digit (`#abc`) or 6-digit (`#aabbcc`) hex with optional `#`.\n * @param hex - Hex color string.\n * @returns HSL triplet, or `null` if the input is malformed.\n */\nexport function hexToHslTriplet(hex: string): string | null {\n\tconst clean = hex.trim().replace(/^#/, \"\");\n\tlet normalized: string;\n\tif (/^[0-9a-fA-F]{3}$/.test(clean)) {\n\t\tnormalized = clean\n\t\t\t.split(\"\")\n\t\t\t.map((c) => c + c)\n\t\t\t.join(\"\");\n\t} else if (/^[0-9a-fA-F]{6}$/.test(clean)) {\n\t\tnormalized = clean;\n\t} else {\n\t\treturn null;\n\t}\n\tconst r = Number.parseInt(normalized.slice(0, 2), 16);\n\tconst g = Number.parseInt(normalized.slice(2, 4), 16);\n\tconst b = Number.parseInt(normalized.slice(4, 6), 16);\n\treturn formatHslTriplet(rgbToHsl(r, g, b));\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 { hslTripletToHex } from \"../../lib/color.js\";\nimport { cn } from \"../../lib/utils.js\";\n\n/**\n * Headless terminal display backed by xterm.js. Renders an xterm grid\n * inside a div the consumer styles. No PTY, no shell — the consumer\n * owns the data-flow:\n *\n * - Pass `output` (string or string[]) to write to the display. Each\n * change is diffed against the prior render and only the new tail\n * is `term.write()`-ed, so feeding a streaming buffer doesn't redraw.\n * - Pass `onInput` to receive bytes the user typed. Wire it to a\n * WebSocket / IPC / fetch stream — terminal doesn't care.\n *\n * Heavy peer: requires `@xterm/xterm` (~150 KB gzip). The hex-core CLI's\n * `add` flow prompts before installing.\n *\n * @example\n * <Terminal\n * output={[\"$ ls\\r\\n\", \"package.json src/\\r\\n\", \"$ \"]}\n * onInput={(data) => ws.send(data)}\n * rows={24}\n * cols={80}\n * />\n */\nexport interface TerminalProps extends Omit<React.HTMLAttributes<HTMLDivElement>, \"children\" | \"onInput\"> {\n\t/**\n\t * Bytes to display. String is written verbatim; string[] is joined.\n\t * On change, only the suffix beyond the prior render is emitted, so\n\t * appending to a streaming buffer is O(delta).\n\t */\n\toutput?: string | string[];\n\t/** Receive bytes the user typed (incl. control sequences). */\n\tonInput?: (data: string) => void;\n\t/** Initial cols. xterm allows runtime resize via fit-addon (not bundled). Default 80. */\n\tcols?: number;\n\t/** Initial rows. Default 24. */\n\trows?: number;\n\t/** Theme tokens — defaults to neutral light/dark via CSS vars. */\n\ttheme?: \"dark\" | \"light\";\n\t/** Enable cursor blink. Default true. */\n\tcursorBlink?: boolean;\n\t/** Whether the user can type into the terminal. Default true. */\n\tdisableInput?: boolean;\n}\n\n// Fallback xterm themes used when the consumer hasn't loaded `@hex-core/tokens`\n// or hasn't defined the standard `--background` / `--foreground` CSS vars\n// (e.g. consumer mounted Terminal in isolation). Match the `Terminal`\n// wrapper's inline `themeBg` defaults below — both must agree or there's\n// a visible seam between the xterm canvas and the wrapper.\nconst DARK_FALLBACK = {\n\tbackground: \"#0a0a0a\",\n\tforeground: \"#e5e5e5\",\n\tcursor: \"#e5e5e5\",\n\tselectionBackground: \"#404040\",\n};\nconst LIGHT_FALLBACK = {\n\tbackground: \"#fafafa\",\n\tforeground: \"#171717\",\n\tcursor: \"#171717\",\n\tselectionBackground: \"#d4d4d4\",\n};\n\n/**\n * Read a CSS HSL-triplet variable from `:root` (or the nearest theme\n * scope) and convert it to a 6-digit hex string suitable for xterm's\n * `theme: { background: \"#...\" }` option, which accepts hex/rgb but NOT\n * CSS variables. Returns `null` if the variable is unset, so the caller\n * can fall back to a hand-tuned theme without rendering pure-black.\n *\n * @param name - CSS variable name without the leading `--` (e.g. `\"background\"`).\n * @returns A `#xxxxxx` hex color string, or `null` if the var isn't defined.\n */\nfunction readCssVarAsHex(name: string): string | null {\n\tif (typeof document === \"undefined\") return null;\n\tconst triplet = getComputedStyle(document.documentElement)\n\t\t.getPropertyValue(`--${name}`)\n\t\t.trim();\n\tif (!triplet) return null;\n\treturn hslTripletToHex(triplet);\n}\n\n/**\n * Renders an xterm.js terminal display.\n * @param props - Terminal output + input handler + display options\n * @returns A div containing the xterm grid\n */\nfunction Terminal({\n\toutput,\n\tonInput,\n\tcols = 80,\n\trows = 24,\n\ttheme = \"dark\",\n\tcursorBlink = true,\n\tdisableInput = false,\n\tclassName,\n\t...rest\n}: TerminalProps) {\n\tconst containerRef = React.useRef<HTMLDivElement | null>(null);\n\tconst termRef = React.useRef<import(\"@xterm/xterm\").Terminal | null>(null);\n\tconst writtenRef = React.useRef<string>(\"\");\n\tconst onInputRef = React.useRef(onInput);\n\tonInputRef.current = onInput;\n\t// Latest-output mirror so the dynamic-import callback can read the value\n\t// at the moment the engine actually mounts (not the value at first render\n\t// — those can differ when the parent's `output` prop is set lazily).\n\tconst outputRef = React.useRef(output);\n\toutputRef.current = output;\n\n\t// Initialize xterm once on mount. Dynamic import keeps the engine out\n\t// of consumers' bundles unless they actually mount the component.\n\tReact.useEffect(() => {\n\t\tif (!containerRef.current) return;\n\n\t\tlet disposed = false;\n\t\tlet inputDispose: { dispose: () => void } | null = null;\n\n\t\tvoid (async () => {\n\t\t\tconst xtermModule = await import(\"@xterm/xterm\");\n\t\t\tif (disposed || !containerRef.current) return;\n\n\t\t\t// xterm needs hex colors (it can't accept CSS vars), so read\n\t\t\t// the consumer's `--background` / `--foreground` triplets at\n\t\t\t// mount time and convert. A consumer who themes those tokens\n\t\t\t// gets a terminal that follows the page; a consumer mounting\n\t\t\t// Terminal in isolation falls back to the hand-tuned defaults.\n\t\t\tconst fallback = theme === \"dark\" ? DARK_FALLBACK : LIGHT_FALLBACK;\n\t\t\tconst bgHex = readCssVarAsHex(\"background\") ?? fallback.background;\n\t\t\tconst fgHex = readCssVarAsHex(\"foreground\") ?? fallback.foreground;\n\n\t\t\tconst term = new xtermModule.Terminal({\n\t\t\t\tcols,\n\t\t\t\trows,\n\t\t\t\tcursorBlink,\n\t\t\t\tdisableStdin: disableInput,\n\t\t\t\ttheme: {\n\t\t\t\t\tbackground: bgHex,\n\t\t\t\t\tforeground: fgHex,\n\t\t\t\t\tcursor: fgHex,\n\t\t\t\t\tselectionBackground: fallback.selectionBackground,\n\t\t\t\t},\n\t\t\t\tfontFamily: \"ui-monospace, SFMono-Regular, Menlo, monospace\",\n\t\t\t\tfontSize: 13,\n\t\t\t});\n\t\t\tterm.open(containerRef.current);\n\t\t\ttermRef.current = term;\n\n\t\t\t// Read the LATEST output via the ref — between mount and\n\t\t\t// import-resolve, the parent may have updated the prop. Without\n\t\t\t// this, fast prop changes get clobbered.\n\t\t\tconst latest = normalizeOutput(outputRef.current);\n\t\t\tif (latest) {\n\t\t\t\tterm.write(latest);\n\t\t\t\twrittenRef.current = latest;\n\t\t\t}\n\n\t\t\tinputDispose = term.onData((data) => {\n\t\t\t\tonInputRef.current?.(data);\n\t\t\t});\n\t\t})();\n\n\t\treturn () => {\n\t\t\tdisposed = true;\n\t\t\tinputDispose?.dispose();\n\t\t\ttermRef.current?.dispose();\n\t\t\ttermRef.current = null;\n\t\t\twrittenRef.current = \"\";\n\t\t};\n\t\t// cols/rows/theme/cursorBlink/disableInput are mount-time options.\n\t\t// Changing them mid-session would force a full re-init that'd lose\n\t\t// scrollback — out of scope for v1.\n\t}, []);\n\n\t// Diff `output` against what's already on screen and write only the new tail.\n\tReact.useEffect(() => {\n\t\tconst term = termRef.current;\n\t\tif (!term) return;\n\t\tconst next = normalizeOutput(output);\n\t\tif (next === writtenRef.current) return;\n\t\tif (next.startsWith(writtenRef.current)) {\n\t\t\tconst delta = next.slice(writtenRef.current.length);\n\t\t\tif (delta) term.write(delta);\n\t\t} else {\n\t\t\t// Non-suffix change (e.g. consumer cleared the buffer or replaced it\n\t\t\t// with unrelated content) — reset the screen and write fresh.\n\t\t\tterm.reset();\n\t\t\tif (next) term.write(next);\n\t\t}\n\t\twrittenRef.current = next;\n\t}, [output]);\n\n\t// Inline background matches the xterm theme bg. xterm renders its grid\n\t// into a canvas (or DOM rows without their own background-color), so\n\t// a11y tools walk up the DOM looking for a background and would\n\t// otherwise hit the docs-page bg, producing a contrast false positive\n\t// against the xterm fg. The CSS var fallback is an HSL triplet — `hsl()`\n\t// rejects hex literals as a fallback, so the triplet is what makes this\n\t// work when a consumer mounts Terminal without loading `@hex-core/tokens`.\n\tconst fallbackTriplet = theme === \"dark\" ? \"0 0% 4%\" : \"0 0% 98%\";\n\tconst themeBg = `hsl(var(--background, ${fallbackTriplet}))`;\n\treturn (\n\t\t<div\n\t\t\t{...rest}\n\t\t\tref={containerRef}\n\t\t\tdata-hex-terminal\n\t\t\tdata-theme={theme}\n\t\t\tstyle={{ backgroundColor: themeBg, ...(rest.style ?? {}) }}\n\t\t\tclassName={cn(\n\t\t\t\t\"overflow-hidden rounded-md border p-2\",\n\t\t\t\t\"font-mono text-sm leading-tight\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t/>\n\t);\n}\n\nfunction normalizeOutput(value: string | string[] | undefined): string {\n\tif (value == null) return \"\";\n\treturn Array.isArray(value) ? value.join(\"\") : value;\n}\n\nexport { Terminal };\n"]}
|