@hex-core/components 1.7.0 → 1.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_tsup-dts-rollup.d.ts +1566 -5
- 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/color-picker.js.map +1 -1
- 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/data-table.js.map +1 -1
- package/dist/deck.d.ts +3 -0
- package/dist/deck.js +231 -0
- package/dist/deck.js.map +1 -0
- package/dist/dendrogram.d.ts +3 -0
- package/dist/dendrogram.js +162 -0
- package/dist/dendrogram.js.map +1 -0
- package/dist/diagram.d.ts +2 -0
- package/dist/diagram.js +70 -0
- package/dist/diagram.js.map +1 -0
- package/dist/flashcard.d.ts +2 -0
- package/dist/flashcard.js +107 -0
- package/dist/flashcard.js.map +1 -0
- package/dist/flowchart.d.ts +4 -0
- package/dist/flowchart.js +275 -0
- package/dist/flowchart.js.map +1 -0
- package/dist/funnel.d.ts +3 -0
- package/dist/funnel.js +157 -0
- package/dist/funnel.js.map +1 -0
- package/dist/gantt.d.ts +3 -0
- package/dist/gantt.js +279 -0
- package/dist/gantt.js.map +1 -0
- package/dist/image-occlusion.d.ts +3 -0
- package/dist/image-occlusion.js +106 -0
- package/dist/image-occlusion.js.map +1 -0
- package/dist/index.d.ts +84 -0
- package/dist/index.js +3946 -2
- package/dist/index.js.map +1 -1
- package/dist/matrix.d.ts +3 -0
- package/dist/matrix.js +155 -0
- package/dist/matrix.js.map +1 -0
- package/dist/mind-map.d.ts +3 -0
- package/dist/mind-map.js +167 -0
- package/dist/mind-map.js.map +1 -0
- package/dist/org-chart.d.ts +3 -0
- package/dist/org-chart.js +215 -0
- package/dist/org-chart.js.map +1 -0
- package/dist/pyramid.d.ts +3 -0
- package/dist/pyramid.js +150 -0
- package/dist/pyramid.js.map +1 -0
- package/dist/quiz.d.ts +3 -0
- package/dist/quiz.js +128 -0
- package/dist/quiz.js.map +1 -0
- package/dist/sankey.d.ts +4 -0
- package/dist/sankey.js +190 -0
- package/dist/sankey.js.map +1 -0
- package/dist/schemas.d.ts +23 -0
- package/dist/schemas.js +2210 -3
- 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/sonner.js.map +1 -1
- package/dist/spaced-repetition.d.ts +3 -0
- package/dist/spaced-repetition.js +73 -0
- package/dist/spaced-repetition.js.map +1 -0
- package/dist/sunburst.d.ts +3 -0
- package/dist/sunburst.js +205 -0
- package/dist/sunburst.js.map +1 -0
- package/dist/terminal.d.ts +2 -0
- package/dist/terminal.js +153 -0
- package/dist/terminal.js.map +1 -0
- package/dist/textarea.js.map +1 -1
- 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 +49 -5
package/dist/arc.d.ts
ADDED
package/dist/arc.js
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
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 Arc({
|
|
11
|
+
nodes,
|
|
12
|
+
edges,
|
|
13
|
+
width = 720,
|
|
14
|
+
height = 360,
|
|
15
|
+
nodeRadius = 5,
|
|
16
|
+
onEdgeHover,
|
|
17
|
+
onNodeClick,
|
|
18
|
+
className,
|
|
19
|
+
...rest
|
|
20
|
+
}) {
|
|
21
|
+
const laidOut = React.useMemo(() => layout(nodes, edges, width, height), [nodes, edges, width, height]);
|
|
22
|
+
const desc = `Arc diagram with ${nodes.length} node${nodes.length === 1 ? "" : "s"} and ${edges.length} edge${edges.length === 1 ? "" : "s"}`;
|
|
23
|
+
return /* @__PURE__ */ jsxs(
|
|
24
|
+
"svg",
|
|
25
|
+
{
|
|
26
|
+
...rest,
|
|
27
|
+
"data-hex-arc": true,
|
|
28
|
+
role: "img",
|
|
29
|
+
width,
|
|
30
|
+
height,
|
|
31
|
+
viewBox: `0 0 ${width} ${height}`,
|
|
32
|
+
className: cn("block", className),
|
|
33
|
+
children: [
|
|
34
|
+
/* @__PURE__ */ jsx("title", { children: "Arc diagram" }),
|
|
35
|
+
/* @__PURE__ */ jsx("desc", { children: desc }),
|
|
36
|
+
/* @__PURE__ */ jsx("g", { "data-hex-arc-edges": true, fill: "none", children: laidOut.edges.map((e, i) => {
|
|
37
|
+
const interactive = Boolean(onEdgeHover);
|
|
38
|
+
const fireHover = (edge) => onEdgeHover?.(edge);
|
|
39
|
+
return /* @__PURE__ */ jsx(
|
|
40
|
+
"path",
|
|
41
|
+
{
|
|
42
|
+
"data-hex-arc-edge": true,
|
|
43
|
+
d: e.d,
|
|
44
|
+
stroke: "hsl(var(--primary))",
|
|
45
|
+
strokeOpacity: 0.5,
|
|
46
|
+
strokeWidth: Math.max(1, e.width),
|
|
47
|
+
role: interactive ? "button" : void 0,
|
|
48
|
+
tabIndex: interactive ? 0 : void 0,
|
|
49
|
+
"aria-label": interactive ? `Edge between ${e.edge.source} and ${e.edge.target}${e.edge.value != null ? `, value ${e.edge.value}` : ""}` : void 0,
|
|
50
|
+
style: {
|
|
51
|
+
cursor: interactive ? "pointer" : void 0,
|
|
52
|
+
transition: "stroke-opacity 120ms ease"
|
|
53
|
+
},
|
|
54
|
+
onMouseEnter: interactive ? () => fireHover(e.edge) : void 0,
|
|
55
|
+
onMouseLeave: interactive ? () => fireHover(null) : void 0,
|
|
56
|
+
onFocus: interactive ? () => fireHover(e.edge) : void 0,
|
|
57
|
+
onBlur: interactive ? () => fireHover(null) : void 0
|
|
58
|
+
},
|
|
59
|
+
`${e.edge.source}-${e.edge.target}-${i}`
|
|
60
|
+
);
|
|
61
|
+
}) }),
|
|
62
|
+
/* @__PURE__ */ jsx("g", { "data-hex-arc-nodes": true, children: laidOut.nodes.map((n) => {
|
|
63
|
+
const interactive = Boolean(onNodeClick);
|
|
64
|
+
const handleActivate = () => onNodeClick?.(n.node);
|
|
65
|
+
return /* @__PURE__ */ jsxs(
|
|
66
|
+
"g",
|
|
67
|
+
{
|
|
68
|
+
"data-hex-arc-node": true,
|
|
69
|
+
"data-depth": n.depth,
|
|
70
|
+
role: interactive ? "button" : void 0,
|
|
71
|
+
tabIndex: interactive ? 0 : void 0,
|
|
72
|
+
"aria-label": interactive ? n.node.label : void 0,
|
|
73
|
+
style: interactive ? { cursor: "pointer" } : void 0,
|
|
74
|
+
onClick: interactive ? handleActivate : void 0,
|
|
75
|
+
onKeyDown: interactive ? (e) => activateOnKey(e, handleActivate) : void 0,
|
|
76
|
+
children: [
|
|
77
|
+
/* @__PURE__ */ jsx(
|
|
78
|
+
"circle",
|
|
79
|
+
{
|
|
80
|
+
cx: n.x,
|
|
81
|
+
cy: n.y,
|
|
82
|
+
r: nodeRadius,
|
|
83
|
+
fill: "hsl(var(--primary))",
|
|
84
|
+
stroke: "hsl(var(--background))",
|
|
85
|
+
strokeWidth: 2
|
|
86
|
+
}
|
|
87
|
+
),
|
|
88
|
+
/* @__PURE__ */ jsx(
|
|
89
|
+
"text",
|
|
90
|
+
{
|
|
91
|
+
x: n.x,
|
|
92
|
+
y: n.y + nodeRadius + 14,
|
|
93
|
+
textAnchor: "middle",
|
|
94
|
+
fontSize: 10,
|
|
95
|
+
fill: "hsl(var(--foreground))",
|
|
96
|
+
style: { pointerEvents: "none" },
|
|
97
|
+
children: n.node.label
|
|
98
|
+
}
|
|
99
|
+
)
|
|
100
|
+
]
|
|
101
|
+
},
|
|
102
|
+
n.node.id
|
|
103
|
+
);
|
|
104
|
+
}) })
|
|
105
|
+
]
|
|
106
|
+
}
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
function layout(nodes, edges, width, height) {
|
|
110
|
+
if (nodes.length === 0) return { nodes: [], edges: [] };
|
|
111
|
+
const margin = 32;
|
|
112
|
+
const baselineY = height - 32;
|
|
113
|
+
const usable = width - margin * 2;
|
|
114
|
+
const step = nodes.length > 1 ? usable / (nodes.length - 1) : 0;
|
|
115
|
+
const positions = /* @__PURE__ */ new Map();
|
|
116
|
+
const laidOutNodes = nodes.map((node, i) => {
|
|
117
|
+
const x = nodes.length === 1 ? width / 2 : margin + step * i;
|
|
118
|
+
positions.set(node.id, { x, y: baselineY, index: i });
|
|
119
|
+
return { node, x, y: baselineY, depth: i };
|
|
120
|
+
});
|
|
121
|
+
const maxValue = edges.reduce((m, e) => Math.max(m, e.value ?? 1), 1);
|
|
122
|
+
const laidOutEdges = edges.map((edge) => {
|
|
123
|
+
const sp = positions.get(edge.source);
|
|
124
|
+
const tp = positions.get(edge.target);
|
|
125
|
+
if (!sp || !tp) return null;
|
|
126
|
+
const [a, b] = sp.x < tp.x ? [sp, tp] : [tp, sp];
|
|
127
|
+
const span = (b.x - a.x) / 2;
|
|
128
|
+
const ctrlY = baselineY - span;
|
|
129
|
+
const d = `M${a.x},${baselineY} C${a.x},${ctrlY} ${b.x},${ctrlY} ${b.x},${baselineY}`;
|
|
130
|
+
return {
|
|
131
|
+
edge,
|
|
132
|
+
d,
|
|
133
|
+
width: 1 + (edge.value ?? 1) / maxValue * 3
|
|
134
|
+
};
|
|
135
|
+
}).filter((e) => e !== null);
|
|
136
|
+
return { nodes: laidOutNodes, edges: laidOutEdges };
|
|
137
|
+
}
|
|
138
|
+
function activateOnKey(e, fn) {
|
|
139
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
140
|
+
e.preventDefault();
|
|
141
|
+
fn();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export { Arc };
|
|
146
|
+
//# sourceMappingURL=arc.js.map
|
|
147
|
+
//# sourceMappingURL=arc.js.map
|
package/dist/arc.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/utils.ts","../src/artifacts/arc/arc.tsx"],"names":[],"mappings":";;;;;AAQO,SAAS,MAAM,MAAA,EAAsB;AAC3C,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC5B;AC4DA,SAAS,GAAA,CAAI;AAAA,EACZ,KAAA;AAAA,EACA,KAAA;AAAA,EACA,KAAA,GAAQ,GAAA;AAAA,EACR,MAAA,GAAS,GAAA;AAAA,EACT,UAAA,GAAa,CAAA;AAAA,EACb,WAAA;AAAA,EACA,WAAA;AAAA,EACA,SAAA;AAAA,EACA,GAAG;AACJ,CAAA,EAAa;AACZ,EAAA,MAAM,OAAA,GAAgB,KAAA,CAAA,OAAA,CAAQ,MAAM,MAAA,CAAO,OAAO,KAAA,EAAO,KAAA,EAAO,MAAM,CAAA,EAAG,CAAC,KAAA,EAAO,KAAA,EAAO,KAAA,EAAO,MAAM,CAAC,CAAA;AACtG,EAAA,MAAM,OAAO,CAAA,iBAAA,EAAoB,KAAA,CAAM,MAAM,CAAA,KAAA,EAAQ,KAAA,CAAM,WAAW,CAAA,GAAI,EAAA,GAAK,GAAG,CAAA,KAAA,EAAQ,MAAM,MAAM,CAAA,KAAA,EAAQ,MAAM,MAAA,KAAW,CAAA,GAAI,KAAK,GAAG,CAAA,CAAA;AAE3I,EAAA,uBACC,IAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,GAAG,IAAA;AAAA,MACJ,cAAA,EAAY,IAAA;AAAA,MACZ,IAAA,EAAK,KAAA;AAAA,MACL,KAAA;AAAA,MACA,MAAA;AAAA,MACA,OAAA,EAAS,CAAA,IAAA,EAAO,KAAK,CAAA,CAAA,EAAI,MAAM,CAAA,CAAA;AAAA,MAC/B,SAAA,EAAW,EAAA,CAAG,OAAA,EAAS,SAAS,CAAA;AAAA,MAEhC,QAAA,EAAA;AAAA,wBAAA,GAAA,CAAC,WAAM,QAAA,EAAA,aAAA,EAAW,CAAA;AAAA,wBAClB,GAAA,CAAC,UAAM,QAAA,EAAA,IAAA,EAAK,CAAA;AAAA,wBACZ,GAAA,CAAC,GAAA,EAAA,EAAE,oBAAA,EAAkB,IAAA,EAAC,IAAA,EAAK,MAAA,EACzB,QAAA,EAAA,OAAA,CAAQ,KAAA,CAAM,GAAA,CAAI,CAAC,CAAA,EAAG,CAAA,KAAM;AAC5B,UAAA,MAAM,WAAA,GAAc,QAAQ,WAAW,CAAA;AACvC,UAAA,MAAM,SAAA,GAAY,CAAC,IAAA,KAAyB,WAAA,GAAc,IAAI,CAAA;AAC9D,UAAA,uBACC,GAAA;AAAA,YAAC,MAAA;AAAA,YAAA;AAAA,cAEA,mBAAA,EAAiB,IAAA;AAAA,cACjB,GAAG,CAAA,CAAE,CAAA;AAAA,cACL,MAAA,EAAO,qBAAA;AAAA,cACP,aAAA,EAAe,GAAA;AAAA,cACf,WAAA,EAAa,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,EAAE,KAAK,CAAA;AAAA,cAChC,IAAA,EAAM,cAAc,QAAA,GAAW,MAAA;AAAA,cAC/B,QAAA,EAAU,cAAc,CAAA,GAAI,MAAA;AAAA,cAC5B,YAAA,EACC,cACG,CAAA,aAAA,EAAgB,CAAA,CAAE,KAAK,MAAM,CAAA,KAAA,EAAQ,EAAE,IAAA,CAAK,MAAM,GAAG,CAAA,CAAE,IAAA,CAAK,SAAS,IAAA,GAAO,CAAA,QAAA,EAAW,EAAE,IAAA,CAAK,KAAK,CAAA,CAAA,GAAK,EAAE,CAAA,CAAA,GAC1G,MAAA;AAAA,cAEJ,KAAA,EAAO;AAAA,gBACN,MAAA,EAAQ,cAAc,SAAA,GAAY,MAAA;AAAA,gBAClC,UAAA,EAAY;AAAA,eACb;AAAA,cACA,cAAc,WAAA,GAAc,MAAM,SAAA,CAAU,CAAA,CAAE,IAAI,CAAA,GAAI,MAAA;AAAA,cACtD,YAAA,EAAc,WAAA,GAAc,MAAM,SAAA,CAAU,IAAI,CAAA,GAAI,MAAA;AAAA,cACpD,SAAS,WAAA,GAAc,MAAM,SAAA,CAAU,CAAA,CAAE,IAAI,CAAA,GAAI,MAAA;AAAA,cACjD,MAAA,EAAQ,WAAA,GAAc,MAAM,SAAA,CAAU,IAAI,CAAA,GAAI;AAAA,aAAA;AAAA,YApBzC,CAAA,EAAG,EAAE,IAAA,CAAK,MAAM,IAAI,CAAA,CAAE,IAAA,CAAK,MAAM,CAAA,CAAA,EAAI,CAAC,CAAA;AAAA,WAqB5C;AAAA,QAEF,CAAC,CAAA,EACF,CAAA;AAAA,wBACA,GAAA,CAAC,OAAE,oBAAA,EAAkB,IAAA,EACnB,kBAAQ,KAAA,CAAM,GAAA,CAAI,CAAC,CAAA,KAAM;AACzB,UAAA,MAAM,WAAA,GAAc,QAAQ,WAAW,CAAA;AACvC,UAAA,MAAM,cAAA,GAAiB,MAAM,WAAA,GAAc,CAAA,CAAE,IAAI,CAAA;AACjD,UAAA,uBACC,IAAA;AAAA,YAAC,GAAA;AAAA,YAAA;AAAA,cAEA,mBAAA,EAAiB,IAAA;AAAA,cACjB,cAAY,CAAA,CAAE,KAAA;AAAA,cACd,IAAA,EAAM,cAAc,QAAA,GAAW,MAAA;AAAA,cAC/B,QAAA,EAAU,cAAc,CAAA,GAAI,MAAA;AAAA,cAC5B,YAAA,EAAY,WAAA,GAAc,CAAA,CAAE,IAAA,CAAK,KAAA,GAAQ,MAAA;AAAA,cACzC,KAAA,EAAO,WAAA,GAAc,EAAE,MAAA,EAAQ,WAAU,GAAI,MAAA;AAAA,cAC7C,OAAA,EAAS,cAAc,cAAA,GAAiB,MAAA;AAAA,cACxC,WAAW,WAAA,GAAc,CAAC,MAAM,aAAA,CAAc,CAAA,EAAG,cAAc,CAAA,GAAI,MAAA;AAAA,cAEnE,QAAA,EAAA;AAAA,gCAAA,GAAA;AAAA,kBAAC,QAAA;AAAA,kBAAA;AAAA,oBACA,IAAI,CAAA,CAAE,CAAA;AAAA,oBACN,IAAI,CAAA,CAAE,CAAA;AAAA,oBACN,CAAA,EAAG,UAAA;AAAA,oBACH,IAAA,EAAK,qBAAA;AAAA,oBACL,MAAA,EAAO,wBAAA;AAAA,oBACP,WAAA,EAAa;AAAA;AAAA,iBACd;AAAA,gCACA,GAAA;AAAA,kBAAC,MAAA;AAAA,kBAAA;AAAA,oBACA,GAAG,CAAA,CAAE,CAAA;AAAA,oBACL,CAAA,EAAG,CAAA,CAAE,CAAA,GAAI,UAAA,GAAa,EAAA;AAAA,oBACtB,UAAA,EAAW,QAAA;AAAA,oBACX,QAAA,EAAU,EAAA;AAAA,oBACV,IAAA,EAAK,wBAAA;AAAA,oBACL,KAAA,EAAO,EAAE,aAAA,EAAe,MAAA,EAAO;AAAA,oBAE9B,YAAE,IAAA,CAAK;AAAA;AAAA;AACT;AAAA,aAAA;AAAA,YA3BK,EAAE,IAAA,CAAK;AAAA,WA4Bb;AAAA,QAEF,CAAC,CAAA,EACF;AAAA;AAAA;AAAA,GACD;AAEF;AAEA,SAAS,MAAA,CACR,KAAA,EACA,KAAA,EACA,KAAA,EACA,MAAA,EACiD;AACjD,EAAA,IAAI,KAAA,CAAM,MAAA,KAAW,CAAA,EAAG,OAAO,EAAE,OAAO,EAAC,EAAG,KAAA,EAAO,EAAC,EAAE;AAEtD,EAAA,MAAM,MAAA,GAAS,EAAA;AACf,EAAA,MAAM,YAAY,MAAA,GAAS,EAAA;AAC3B,EAAA,MAAM,MAAA,GAAS,QAAQ,MAAA,GAAS,CAAA;AAChC,EAAA,MAAM,OAAO,KAAA,CAAM,MAAA,GAAS,IAAI,MAAA,IAAU,KAAA,CAAM,SAAS,CAAA,CAAA,GAAK,CAAA;AAE9D,EAAA,MAAM,SAAA,uBAAgB,GAAA,EAAqD;AAC3E,EAAA,MAAM,YAAA,GAA8B,KAAA,CAAM,GAAA,CAAI,CAAC,MAAM,CAAA,KAAM;AAG1D,IAAA,MAAM,IAAI,KAAA,CAAM,MAAA,KAAW,IAAI,KAAA,GAAQ,CAAA,GAAI,SAAS,IAAA,GAAO,CAAA;AAC3D,IAAA,SAAA,CAAU,GAAA,CAAI,KAAK,EAAA,EAAI,EAAE,GAAG,CAAA,EAAG,SAAA,EAAW,KAAA,EAAO,CAAA,EAAG,CAAA;AACpD,IAAA,OAAO,EAAE,IAAA,EAAM,CAAA,EAAG,CAAA,EAAG,SAAA,EAAW,OAAO,CAAA,EAAE;AAAA,EAC1C,CAAC,CAAA;AAED,EAAA,MAAM,QAAA,GAAW,KAAA,CAAM,MAAA,CAAO,CAAC,CAAA,EAAG,CAAA,KAAM,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,CAAA,CAAE,KAAA,IAAS,CAAC,GAAG,CAAC,CAAA;AAEpE,EAAA,MAAM,YAAA,GAA8B,KAAA,CAClC,GAAA,CAAI,CAAC,IAAA,KAAS;AACd,IAAA,MAAM,EAAA,GAAK,SAAA,CAAU,GAAA,CAAI,IAAA,CAAK,MAAM,CAAA;AACpC,IAAA,MAAM,EAAA,GAAK,SAAA,CAAU,GAAA,CAAI,IAAA,CAAK,MAAM,CAAA;AACpC,IAAA,IAAI,CAAC,EAAA,IAAM,CAAC,EAAA,EAAI,OAAO,IAAA;AACvB,IAAA,MAAM,CAAC,CAAA,EAAG,CAAC,CAAA,GAAI,GAAG,CAAA,GAAI,EAAA,CAAG,CAAA,GAAI,CAAC,EAAA,EAAI,EAAE,CAAA,GAAI,CAAC,IAAI,EAAE,CAAA;AAC/C,IAAA,MAAM,IAAA,GAAA,CAAQ,CAAA,CAAE,CAAA,GAAI,CAAA,CAAE,CAAA,IAAK,CAAA;AAG3B,IAAA,MAAM,QAAQ,SAAA,GAAY,IAAA;AAC1B,IAAA,MAAM,CAAA,GAAI,IAAI,CAAA,CAAE,CAAC,IAAI,SAAS,CAAA,EAAA,EAAK,EAAE,CAAC,CAAA,CAAA,EAAI,KAAK,CAAA,CAAA,EAAI,CAAA,CAAE,CAAC,CAAA,CAAA,EAAI,KAAK,IAAI,CAAA,CAAE,CAAC,IAAI,SAAS,CAAA,CAAA;AACnF,IAAA,OAAO;AAAA,MACN,IAAA;AAAA,MACA,CAAA;AAAA,MACA,KAAA,EAAO,CAAA,GAAA,CAAM,IAAA,CAAK,KAAA,IAAS,KAAK,QAAA,GAAY;AAAA,KAC7C;AAAA,EACD,CAAC,CAAA,CACA,MAAA,CAAO,CAAC,CAAA,KAAwB,MAAM,IAAI,CAAA;AAE5C,EAAA,OAAO,EAAE,KAAA,EAAO,YAAA,EAAc,KAAA,EAAO,YAAA,EAAa;AACnD;AAEA,SAAS,aAAA,CAAc,GAAwB,EAAA,EAAsB;AACpE,EAAA,IAAI,CAAA,CAAE,GAAA,KAAQ,OAAA,IAAW,CAAA,CAAE,QAAQ,GAAA,EAAK;AACvC,IAAA,CAAA,CAAE,cAAA,EAAe;AACjB,IAAA,EAAA,EAAG;AAAA,EACJ;AACD","file":"arc.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 * Arc diagram. Nodes lie on a horizontal baseline; relationships are\n * drawn as semicircle arcs above the line. Pure SVG; no heavy peer\n * dependency. Excellent for sequence-aware relational data:\n * co-occurrence in a story, train-stop transfer connections,\n * citation chains where node order is meaningful.\n *\n * Distinct from Chord: Chord wraps nodes around a ring (no inherent\n * order); Arc keeps them on a line (order matters).\n *\n * @example\n * <Arc\n * nodes={[\n * { id: \"alice\", label: \"Alice\" },\n * { id: \"bob\", label: \"Bob\" },\n * { id: \"carol\", label: \"Carol\" },\n * ]}\n * edges={[\n * { source: \"alice\", target: \"bob\", value: 3 },\n * { source: \"alice\", target: \"carol\", value: 1 },\n * ]}\n * />\n */\nexport type ArcNode = {\n\tid: string;\n\tlabel: string;\n\tvalue?: number;\n};\n\nexport type ArcEdge = {\n\tsource: string;\n\ttarget: string;\n\tvalue?: number;\n};\n\nexport interface ArcProps extends Omit<React.SVGAttributes<SVGSVGElement>, \"children\"> {\n\t/** Nodes in display order along the baseline. */\n\tnodes: ArcNode[];\n\t/** Edges between nodes. Edges whose source or target id is missing are skipped. */\n\tedges: ArcEdge[];\n\t/** Pixel width of the rendered SVG. Default 720. */\n\twidth?: number;\n\t/** Pixel height of the rendered SVG. Default 360. */\n\theight?: number;\n\t/** Pixel radius of each node circle. Default 5. */\n\tnodeRadius?: number;\n\t/** Fired when an edge is hovered (or hover ends, with `null`). */\n\tonEdgeHover?: (edge: ArcEdge | null) => void;\n\t/** Fired when a node is clicked. */\n\tonNodeClick?: (node: ArcNode) => void;\n}\n\ninterface LaidOutNode {\n\tnode: ArcNode;\n\tx: number;\n\ty: number;\n\tdepth: number;\n}\n\ninterface LaidOutEdge {\n\tedge: ArcEdge;\n\td: string;\n\twidth: number;\n}\n\nfunction Arc({\n\tnodes,\n\tedges,\n\twidth = 720,\n\theight = 360,\n\tnodeRadius = 5,\n\tonEdgeHover,\n\tonNodeClick,\n\tclassName,\n\t...rest\n}: ArcProps) {\n\tconst laidOut = React.useMemo(() => layout(nodes, edges, width, height), [nodes, edges, width, height]);\n\tconst desc = `Arc diagram with ${nodes.length} node${nodes.length === 1 ? \"\" : \"s\"} and ${edges.length} edge${edges.length === 1 ? \"\" : \"s\"}`;\n\n\treturn (\n\t\t<svg\n\t\t\t{...rest}\n\t\t\tdata-hex-arc\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>Arc diagram</title>\n\t\t\t<desc>{desc}</desc>\n\t\t\t<g data-hex-arc-edges fill=\"none\">\n\t\t\t\t{laidOut.edges.map((e, i) => {\n\t\t\t\t\tconst interactive = Boolean(onEdgeHover);\n\t\t\t\t\tconst fireHover = (edge: ArcEdge | null) => onEdgeHover?.(edge);\n\t\t\t\t\treturn (\n\t\t\t\t\t\t<path\n\t\t\t\t\t\t\tkey={`${e.edge.source}-${e.edge.target}-${i}`}\n\t\t\t\t\t\t\tdata-hex-arc-edge\n\t\t\t\t\t\t\td={e.d}\n\t\t\t\t\t\t\tstroke=\"hsl(var(--primary))\"\n\t\t\t\t\t\t\tstrokeOpacity={0.5}\n\t\t\t\t\t\t\tstrokeWidth={Math.max(1, e.width)}\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? `Edge between ${e.edge.source} and ${e.edge.target}${e.edge.value != null ? `, value ${e.edge.value}` : \"\"}`\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: \"stroke-opacity 120ms ease\",\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tonMouseEnter={interactive ? () => fireHover(e.edge) : undefined}\n\t\t\t\t\t\t\tonMouseLeave={interactive ? () => fireHover(null) : undefined}\n\t\t\t\t\t\t\tonFocus={interactive ? () => fireHover(e.edge) : 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-arc-nodes>\n\t\t\t\t{laidOut.nodes.map((n) => {\n\t\t\t\t\tconst interactive = Boolean(onNodeClick);\n\t\t\t\t\tconst handleActivate = () => onNodeClick?.(n.node);\n\t\t\t\t\treturn (\n\t\t\t\t\t\t<g\n\t\t\t\t\t\t\tkey={n.node.id}\n\t\t\t\t\t\t\tdata-hex-arc-node\n\t\t\t\t\t\t\tdata-depth={n.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 ? n.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<circle\n\t\t\t\t\t\t\t\tcx={n.x}\n\t\t\t\t\t\t\t\tcy={n.y}\n\t\t\t\t\t\t\t\tr={nodeRadius}\n\t\t\t\t\t\t\t\tfill=\"hsl(var(--primary))\"\n\t\t\t\t\t\t\t\tstroke=\"hsl(var(--background))\"\n\t\t\t\t\t\t\t\tstrokeWidth={2}\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={n.x}\n\t\t\t\t\t\t\t\ty={n.y + nodeRadius + 14}\n\t\t\t\t\t\t\t\ttextAnchor=\"middle\"\n\t\t\t\t\t\t\t\tfontSize={10}\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{n.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\tnodes: ArcNode[],\n\tedges: ArcEdge[],\n\twidth: number,\n\theight: number,\n): { nodes: LaidOutNode[]; edges: LaidOutEdge[] } {\n\tif (nodes.length === 0) return { nodes: [], edges: [] };\n\n\tconst margin = 32;\n\tconst baselineY = height - 32;\n\tconst usable = width - margin * 2;\n\tconst step = nodes.length > 1 ? usable / (nodes.length - 1) : 0;\n\n\tconst positions = new Map<string, { x: number; y: number; index: number }>();\n\tconst laidOutNodes: LaidOutNode[] = nodes.map((node, i) => {\n\t\t// Center the lone node when the input has only one — `step * 0` would\n\t\t// otherwise leave it pinned at the left margin.\n\t\tconst x = nodes.length === 1 ? width / 2 : margin + step * i;\n\t\tpositions.set(node.id, { x, y: baselineY, index: i });\n\t\treturn { node, x, y: baselineY, depth: i };\n\t});\n\n\tconst maxValue = edges.reduce((m, e) => Math.max(m, e.value ?? 1), 1);\n\n\tconst laidOutEdges: LaidOutEdge[] = edges\n\t\t.map((edge) => {\n\t\t\tconst sp = positions.get(edge.source);\n\t\t\tconst tp = positions.get(edge.target);\n\t\t\tif (!sp || !tp) return null;\n\t\t\tconst [a, b] = sp.x < tp.x ? [sp, tp] : [tp, sp];\n\t\t\tconst span = (b.x - a.x) / 2;\n\t\t\t// Cubic bezier with two control points to approximate a clean\n\t\t\t// semicircle without trigonometry. Height scales with span.\n\t\t\tconst ctrlY = baselineY - span;\n\t\t\tconst d = `M${a.x},${baselineY} C${a.x},${ctrlY} ${b.x},${ctrlY} ${b.x},${baselineY}`;\n\t\t\treturn {\n\t\t\t\tedge,\n\t\t\t\td,\n\t\t\t\twidth: 1 + ((edge.value ?? 1) / maxValue) * 3,\n\t\t\t};\n\t\t})\n\t\t.filter((e): e is LaidOutEdge => e !== null);\n\n\treturn { nodes: laidOutNodes, edges: laidOutEdges };\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 { Arc };\n"]}
|
|
@@ -0,0 +1,119 @@
|
|
|
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 AudioPlayer({ src, autoPlay = false, onEnded, onError, className, ...rest }) {
|
|
11
|
+
const containerRef = React.useRef(null);
|
|
12
|
+
const wsRef = React.useRef(null);
|
|
13
|
+
const onEndedRef = React.useRef(onEnded);
|
|
14
|
+
const onErrorRef = React.useRef(onError);
|
|
15
|
+
onEndedRef.current = onEnded;
|
|
16
|
+
onErrorRef.current = onError;
|
|
17
|
+
const [isPlaying, setIsPlaying] = React.useState(false);
|
|
18
|
+
const [duration, setDuration] = React.useState(0);
|
|
19
|
+
const [currentTime, setCurrentTime] = React.useState(0);
|
|
20
|
+
const [isReady, setIsReady] = React.useState(false);
|
|
21
|
+
React.useEffect(() => {
|
|
22
|
+
if (!containerRef.current) return;
|
|
23
|
+
let disposed = false;
|
|
24
|
+
const objectUrl = src instanceof Blob ? URL.createObjectURL(src) : null;
|
|
25
|
+
void (async () => {
|
|
26
|
+
const { default: WaveSurfer } = await import('wavesurfer.js');
|
|
27
|
+
if (disposed || !containerRef.current) return;
|
|
28
|
+
const ws = WaveSurfer.create({
|
|
29
|
+
container: containerRef.current,
|
|
30
|
+
waveColor: "#a3a3a3",
|
|
31
|
+
progressColor: "#171717",
|
|
32
|
+
cursorColor: "transparent",
|
|
33
|
+
height: 32,
|
|
34
|
+
barWidth: 2,
|
|
35
|
+
barGap: 1,
|
|
36
|
+
barRadius: 1,
|
|
37
|
+
normalize: true
|
|
38
|
+
});
|
|
39
|
+
ws.on("ready", () => {
|
|
40
|
+
setIsReady(true);
|
|
41
|
+
setDuration(ws.getDuration());
|
|
42
|
+
if (autoPlay) void ws.play();
|
|
43
|
+
});
|
|
44
|
+
ws.on("play", () => setIsPlaying(true));
|
|
45
|
+
ws.on("pause", () => setIsPlaying(false));
|
|
46
|
+
ws.on("finish", () => {
|
|
47
|
+
setIsPlaying(false);
|
|
48
|
+
onEndedRef.current?.();
|
|
49
|
+
});
|
|
50
|
+
ws.on("timeupdate", (t) => setCurrentTime(t));
|
|
51
|
+
ws.on("error", (err) => {
|
|
52
|
+
onErrorRef.current?.(err.message);
|
|
53
|
+
});
|
|
54
|
+
void ws.load(objectUrl ?? src);
|
|
55
|
+
wsRef.current = ws;
|
|
56
|
+
})();
|
|
57
|
+
return () => {
|
|
58
|
+
disposed = true;
|
|
59
|
+
wsRef.current?.destroy();
|
|
60
|
+
wsRef.current = null;
|
|
61
|
+
if (objectUrl) URL.revokeObjectURL(objectUrl);
|
|
62
|
+
};
|
|
63
|
+
}, [src]);
|
|
64
|
+
function togglePlay() {
|
|
65
|
+
const ws = wsRef.current;
|
|
66
|
+
if (!ws || !isReady) return;
|
|
67
|
+
void ws.playPause();
|
|
68
|
+
}
|
|
69
|
+
return /* @__PURE__ */ jsxs(
|
|
70
|
+
"div",
|
|
71
|
+
{
|
|
72
|
+
...rest,
|
|
73
|
+
"data-hex-audio-player": true,
|
|
74
|
+
className: cn("flex items-center gap-3 rounded-md border bg-background p-2", className),
|
|
75
|
+
children: [
|
|
76
|
+
/* @__PURE__ */ jsx(
|
|
77
|
+
"button",
|
|
78
|
+
{
|
|
79
|
+
type: "button",
|
|
80
|
+
onClick: togglePlay,
|
|
81
|
+
disabled: !isReady,
|
|
82
|
+
"aria-label": isPlaying ? "Pause" : "Play",
|
|
83
|
+
className: cn(
|
|
84
|
+
"inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-md",
|
|
85
|
+
"bg-foreground text-background transition-opacity",
|
|
86
|
+
"hover:opacity-80 disabled:cursor-not-allowed disabled:opacity-50"
|
|
87
|
+
),
|
|
88
|
+
children: isPlaying ? /* @__PURE__ */ jsx(PauseIcon, {}) : /* @__PURE__ */ jsx(PlayIcon, {})
|
|
89
|
+
}
|
|
90
|
+
),
|
|
91
|
+
/* @__PURE__ */ jsx("div", { ref: containerRef, className: "flex-1 min-w-0" }),
|
|
92
|
+
/* @__PURE__ */ jsxs("span", { className: "shrink-0 text-xs tabular-nums text-muted-foreground", children: [
|
|
93
|
+
formatTime(currentTime),
|
|
94
|
+
" / ",
|
|
95
|
+
formatTime(duration)
|
|
96
|
+
] })
|
|
97
|
+
]
|
|
98
|
+
}
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
function formatTime(seconds) {
|
|
102
|
+
if (!Number.isFinite(seconds) || seconds < 0) return "0:00";
|
|
103
|
+
const m = Math.floor(seconds / 60);
|
|
104
|
+
const s = Math.floor(seconds % 60);
|
|
105
|
+
return `${m}:${s.toString().padStart(2, "0")}`;
|
|
106
|
+
}
|
|
107
|
+
function PlayIcon() {
|
|
108
|
+
return /* @__PURE__ */ jsx("svg", { "aria-hidden": true, viewBox: "0 0 16 16", width: "12", height: "12", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M5 3.5v9l8-4.5z" }) });
|
|
109
|
+
}
|
|
110
|
+
function PauseIcon() {
|
|
111
|
+
return /* @__PURE__ */ jsxs("svg", { "aria-hidden": true, viewBox: "0 0 16 16", width: "12", height: "12", fill: "currentColor", children: [
|
|
112
|
+
/* @__PURE__ */ jsx("rect", { x: "4", y: "3", width: "3", height: "10", rx: "0.5" }),
|
|
113
|
+
/* @__PURE__ */ jsx("rect", { x: "9", y: "3", width: "3", height: "10", rx: "0.5" })
|
|
114
|
+
] });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export { AudioPlayer };
|
|
118
|
+
//# sourceMappingURL=audio-player.js.map
|
|
119
|
+
//# sourceMappingURL=audio-player.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/utils.ts","../src/ai/audio-player/audio-player.tsx"],"names":[],"mappings":"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","file":"audio-player.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 * Audio playback control backed by wavesurfer.js. Renders a play/pause\n * button + linear progress bar + duration. Pair with `<AudioWaveform>`\n * for the visual waveform display (separate component, same engine).\n *\n * Headless on data: pass `src` (URL or Blob); the engine streams + decodes.\n *\n * Heavy peer: requires `wavesurfer.js` (~50 KB gzip, shared with\n * AudioWaveform). The hex-core CLI's `add` flow prompts before installing.\n *\n * @example\n * <AudioPlayer src=\"/podcast.mp3\" />\n * <AudioPlayer src={voicemailBlob} autoPlay onEnded={markRead} />\n */\nexport interface AudioPlayerProps extends Omit<React.HTMLAttributes<HTMLDivElement>, \"children\" | \"onError\"> {\n\t/** Audio source — URL string or Blob. Re-loads when changed. */\n\tsrc: string | Blob;\n\t/** Auto-play once loaded. Default false. */\n\tautoPlay?: boolean;\n\t/** Called when playback finishes. */\n\tonEnded?: () => void;\n\t/** Called on engine error (network, decode, etc.). */\n\tonError?: (message: string) => void;\n}\n\n/**\n * Renders an audio player UI driven by wavesurfer.js.\n * @param props - Audio source + playback callbacks\n * @returns A div containing play/pause control + progress bar\n */\nfunction AudioPlayer({ src, autoPlay = false, onEnded, onError, className, ...rest }: AudioPlayerProps) {\n\tconst containerRef = React.useRef<HTMLDivElement | null>(null);\n\tconst wsRef = React.useRef<import(\"wavesurfer.js\").default | null>(null);\n\tconst onEndedRef = React.useRef(onEnded);\n\tconst onErrorRef = React.useRef(onError);\n\tonEndedRef.current = onEnded;\n\tonErrorRef.current = onError;\n\n\tconst [isPlaying, setIsPlaying] = React.useState(false);\n\tconst [duration, setDuration] = React.useState(0);\n\tconst [currentTime, setCurrentTime] = React.useState(0);\n\tconst [isReady, setIsReady] = React.useState(false);\n\n\tReact.useEffect(() => {\n\t\tif (!containerRef.current) return;\n\t\tlet disposed = false;\n\t\t// Track Blob ObjectURLs so we can revoke on teardown — without this,\n\t\t// each src change leaks one URL + retains the underlying Blob until\n\t\t// page unload (a real leak for swap-heavy UIs like voicemail lists).\n\t\tconst objectUrl = src instanceof Blob ? URL.createObjectURL(src) : null;\n\n\t\tvoid (async () => {\n\t\t\tconst { default: WaveSurfer } = await import(\"wavesurfer.js\");\n\t\t\tif (disposed || !containerRef.current) return;\n\n\t\t\tconst ws = WaveSurfer.create({\n\t\t\t\tcontainer: containerRef.current,\n\t\t\t\twaveColor: \"#a3a3a3\",\n\t\t\t\tprogressColor: \"#171717\",\n\t\t\t\tcursorColor: \"transparent\",\n\t\t\t\theight: 32,\n\t\t\t\tbarWidth: 2,\n\t\t\t\tbarGap: 1,\n\t\t\t\tbarRadius: 1,\n\t\t\t\tnormalize: true,\n\t\t\t});\n\n\t\t\tws.on(\"ready\", () => {\n\t\t\t\tsetIsReady(true);\n\t\t\t\tsetDuration(ws.getDuration());\n\t\t\t\tif (autoPlay) void ws.play();\n\t\t\t});\n\t\t\tws.on(\"play\", () => setIsPlaying(true));\n\t\t\tws.on(\"pause\", () => setIsPlaying(false));\n\t\t\tws.on(\"finish\", () => {\n\t\t\t\tsetIsPlaying(false);\n\t\t\t\tonEndedRef.current?.();\n\t\t\t});\n\t\t\tws.on(\"timeupdate\", (t) => setCurrentTime(t));\n\t\t\tws.on(\"error\", (err: Error) => {\n\t\t\t\tonErrorRef.current?.(err.message);\n\t\t\t});\n\n\t\t\tvoid ws.load(objectUrl ?? (src as string));\n\t\t\twsRef.current = ws;\n\t\t})();\n\n\t\treturn () => {\n\t\t\tdisposed = true;\n\t\t\twsRef.current?.destroy();\n\t\t\twsRef.current = null;\n\t\t\tif (objectUrl) URL.revokeObjectURL(objectUrl);\n\t\t};\n\t\t// autoPlay is mount-only; src changes trigger a separate effect.\n\t}, [src]);\n\n\tfunction togglePlay() {\n\t\tconst ws = wsRef.current;\n\t\tif (!ws || !isReady) return;\n\t\tvoid ws.playPause();\n\t}\n\n\treturn (\n\t\t<div\n\t\t\t{...rest}\n\t\t\tdata-hex-audio-player\n\t\t\tclassName={cn(\"flex items-center gap-3 rounded-md border bg-background p-2\", className)}\n\t\t>\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tonClick={togglePlay}\n\t\t\t\tdisabled={!isReady}\n\t\t\t\taria-label={isPlaying ? \"Pause\" : \"Play\"}\n\t\t\t\tclassName={cn(\n\t\t\t\t\t\"inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-md\",\n\t\t\t\t\t\"bg-foreground text-background transition-opacity\",\n\t\t\t\t\t\"hover:opacity-80 disabled:cursor-not-allowed disabled:opacity-50\",\n\t\t\t\t)}\n\t\t\t>\n\t\t\t\t{isPlaying ? <PauseIcon /> : <PlayIcon />}\n\t\t\t</button>\n\t\t\t<div ref={containerRef} className=\"flex-1 min-w-0\" />\n\t\t\t<span className=\"shrink-0 text-xs tabular-nums text-muted-foreground\">\n\t\t\t\t{formatTime(currentTime)} / {formatTime(duration)}\n\t\t\t</span>\n\t\t</div>\n\t);\n}\n\nfunction formatTime(seconds: number): string {\n\tif (!Number.isFinite(seconds) || seconds < 0) return \"0:00\";\n\tconst m = Math.floor(seconds / 60);\n\tconst s = Math.floor(seconds % 60);\n\treturn `${m}:${s.toString().padStart(2, \"0\")}`;\n}\n\nfunction PlayIcon() {\n\treturn (\n\t\t<svg aria-hidden viewBox=\"0 0 16 16\" width=\"12\" height=\"12\" fill=\"currentColor\">\n\t\t\t<path d=\"M5 3.5v9l8-4.5z\" />\n\t\t</svg>\n\t);\n}\n\nfunction PauseIcon() {\n\treturn (\n\t\t<svg aria-hidden viewBox=\"0 0 16 16\" width=\"12\" height=\"12\" fill=\"currentColor\">\n\t\t\t<rect x=\"4\" y=\"3\" width=\"3\" height=\"10\" rx=\"0.5\" />\n\t\t\t<rect x=\"9\" y=\"3\" width=\"3\" height=\"10\" rx=\"0.5\" />\n\t\t</svg>\n\t);\n}\n\nexport { AudioPlayer };\n"]}
|
|
@@ -0,0 +1,72 @@
|
|
|
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
|
+
function cn(...inputs) {
|
|
8
|
+
return twMerge(clsx(inputs));
|
|
9
|
+
}
|
|
10
|
+
function AudioWaveform({
|
|
11
|
+
src,
|
|
12
|
+
height = 48,
|
|
13
|
+
waveColor = "#a3a3a3",
|
|
14
|
+
progressColor,
|
|
15
|
+
onReady,
|
|
16
|
+
onError,
|
|
17
|
+
className,
|
|
18
|
+
...rest
|
|
19
|
+
}) {
|
|
20
|
+
const containerRef = React.useRef(null);
|
|
21
|
+
const onReadyRef = React.useRef(onReady);
|
|
22
|
+
const onErrorRef = React.useRef(onError);
|
|
23
|
+
onReadyRef.current = onReady;
|
|
24
|
+
onErrorRef.current = onError;
|
|
25
|
+
React.useEffect(() => {
|
|
26
|
+
if (!containerRef.current) return;
|
|
27
|
+
let disposed = false;
|
|
28
|
+
let ws = null;
|
|
29
|
+
const objectUrl = src instanceof Blob ? URL.createObjectURL(src) : null;
|
|
30
|
+
void (async () => {
|
|
31
|
+
const { default: WaveSurfer } = await import('wavesurfer.js');
|
|
32
|
+
if (disposed || !containerRef.current) return;
|
|
33
|
+
ws = WaveSurfer.create({
|
|
34
|
+
container: containerRef.current,
|
|
35
|
+
waveColor,
|
|
36
|
+
progressColor: progressColor ?? waveColor,
|
|
37
|
+
cursorColor: "transparent",
|
|
38
|
+
height,
|
|
39
|
+
barWidth: 2,
|
|
40
|
+
barGap: 1,
|
|
41
|
+
barRadius: 1,
|
|
42
|
+
normalize: true,
|
|
43
|
+
interact: false
|
|
44
|
+
});
|
|
45
|
+
ws.on("ready", () => {
|
|
46
|
+
if (ws) onReadyRef.current?.(ws.getDuration());
|
|
47
|
+
});
|
|
48
|
+
ws.on("error", (err) => {
|
|
49
|
+
onErrorRef.current?.(err.message);
|
|
50
|
+
});
|
|
51
|
+
void ws.load(objectUrl ?? src);
|
|
52
|
+
})();
|
|
53
|
+
return () => {
|
|
54
|
+
disposed = true;
|
|
55
|
+
ws?.destroy();
|
|
56
|
+
if (objectUrl) URL.revokeObjectURL(objectUrl);
|
|
57
|
+
};
|
|
58
|
+
}, [src, height, waveColor, progressColor]);
|
|
59
|
+
return /* @__PURE__ */ jsx(
|
|
60
|
+
"div",
|
|
61
|
+
{
|
|
62
|
+
...rest,
|
|
63
|
+
ref: containerRef,
|
|
64
|
+
"data-hex-audio-waveform": true,
|
|
65
|
+
className: cn("w-full", className)
|
|
66
|
+
}
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export { AudioWaveform };
|
|
71
|
+
//# sourceMappingURL=audio-waveform.js.map
|
|
72
|
+
//# sourceMappingURL=audio-waveform.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/utils.ts","../src/ai/audio-waveform/audio-waveform.tsx"],"names":[],"mappings":";;;;;AAQO,SAAS,MAAM,MAAA,EAAsB;AAC3C,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC5B;AC6BA,SAAS,aAAA,CAAc;AAAA,EACtB,GAAA;AAAA,EACA,MAAA,GAAS,EAAA;AAAA,EACT,SAAA,GAAY,SAAA;AAAA,EACZ,aAAA;AAAA,EACA,OAAA;AAAA,EACA,OAAA;AAAA,EACA,SAAA;AAAA,EACA,GAAG;AACJ,CAAA,EAAuB;AACtB,EAAA,MAAM,YAAA,GAAqB,aAA8B,IAAI,CAAA;AAC7D,EAAA,MAAM,UAAA,GAAmB,aAAO,OAAO,CAAA;AACvC,EAAA,MAAM,UAAA,GAAmB,aAAO,OAAO,CAAA;AACvC,EAAA,UAAA,CAAW,OAAA,GAAU,OAAA;AACrB,EAAA,UAAA,CAAW,OAAA,GAAU,OAAA;AAErB,EAAM,gBAAU,MAAM;AACrB,IAAA,IAAI,CAAC,aAAa,OAAA,EAAS;AAC3B,IAAA,IAAI,QAAA,GAAW,KAAA;AACf,IAAA,IAAI,EAAA,GAA6C,IAAA;AAEjD,IAAA,MAAM,YAAY,GAAA,YAAe,IAAA,GAAO,GAAA,CAAI,eAAA,CAAgB,GAAG,CAAA,GAAI,IAAA;AAEnE,IAAA,KAAA,CAAM,YAAY;AACjB,MAAA,MAAM,EAAE,OAAA,EAAS,UAAA,EAAW,GAAI,MAAM,OAAO,eAAe,CAAA;AAC5D,MAAA,IAAI,QAAA,IAAY,CAAC,YAAA,CAAa,OAAA,EAAS;AAEvC,MAAA,EAAA,GAAK,WAAW,MAAA,CAAO;AAAA,QACtB,WAAW,YAAA,CAAa,OAAA;AAAA,QACxB,SAAA;AAAA,QACA,eAAe,aAAA,IAAiB,SAAA;AAAA,QAChC,WAAA,EAAa,aAAA;AAAA,QACb,MAAA;AAAA,QACA,QAAA,EAAU,CAAA;AAAA,QACV,MAAA,EAAQ,CAAA;AAAA,QACR,SAAA,EAAW,CAAA;AAAA,QACX,SAAA,EAAW,IAAA;AAAA,QACX,QAAA,EAAU;AAAA,OACV,CAAA;AAED,MAAA,EAAA,CAAG,EAAA,CAAG,SAAS,MAAM;AACpB,QAAA,IAAI,EAAA,EAAI,UAAA,CAAW,OAAA,GAAU,EAAA,CAAG,aAAa,CAAA;AAAA,MAC9C,CAAC,CAAA;AACD,MAAA,EAAA,CAAG,EAAA,CAAG,OAAA,EAAS,CAAC,GAAA,KAAe;AAC9B,QAAA,UAAA,CAAW,OAAA,GAAU,IAAI,OAAO,CAAA;AAAA,MACjC,CAAC,CAAA;AAED,MAAA,KAAK,EAAA,CAAG,IAAA,CAAK,SAAA,IAAc,GAAc,CAAA;AAAA,IAC1C,CAAA,GAAG;AAEH,IAAA,OAAO,MAAM;AACZ,MAAA,QAAA,GAAW,IAAA;AACX,MAAA,EAAA,EAAI,OAAA,EAAQ;AACZ,MAAA,IAAI,SAAA,EAAW,GAAA,CAAI,eAAA,CAAgB,SAAS,CAAA;AAAA,IAC7C,CAAA;AAAA,EACD,GAAG,CAAC,GAAA,EAAK,MAAA,EAAQ,SAAA,EAAW,aAAa,CAAC,CAAA;AAE1C,EAAA,uBACC,GAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,GAAG,IAAA;AAAA,MACJ,GAAA,EAAK,YAAA;AAAA,MACL,yBAAA,EAAuB,IAAA;AAAA,MACvB,SAAA,EAAW,EAAA,CAAG,QAAA,EAAU,SAAS;AAAA;AAAA,GAClC;AAEF","file":"audio-waveform.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 * Standalone audio waveform display backed by wavesurfer.js. Renders the\n * waveform of a source (URL or Blob) without playback controls — for\n * voice-message previews, audio thumbnails, recording indicators, anywhere\n * the visual is the point.\n *\n * Heavy peer: requires `wavesurfer.js` (~50 KB gzip, shared with\n * AudioPlayer). The hex-core CLI's `add` flow prompts before installing.\n *\n * For interactive playback + waveform together, use `<AudioPlayer>`.\n *\n * @example\n * <AudioWaveform src=\"/voice-message.mp3\" height={48} />\n */\nexport interface AudioWaveformProps extends Omit<React.HTMLAttributes<HTMLDivElement>, \"children\" | \"onError\"> {\n\t/** Audio source — URL string or Blob. */\n\tsrc: string | Blob;\n\t/** Pixel height of the waveform. Default 48. */\n\theight?: number;\n\t/** Bar color for unplayed regions. CSS color string. */\n\twaveColor?: string;\n\t/** Bar color for played regions. Default same as `waveColor`. */\n\tprogressColor?: string;\n\t/** Called when wavesurfer finishes decoding the source. */\n\tonReady?: (durationSeconds: number) => void;\n\t/** Called on engine error (network, decode). */\n\tonError?: (message: string) => void;\n}\n\n/**\n * Renders an audio waveform without playback controls.\n * @param props - Audio source + visual options + lifecycle callbacks\n * @returns A div containing the wavesurfer-rendered waveform\n */\nfunction AudioWaveform({\n\tsrc,\n\theight = 48,\n\twaveColor = \"#a3a3a3\",\n\tprogressColor,\n\tonReady,\n\tonError,\n\tclassName,\n\t...rest\n}: AudioWaveformProps) {\n\tconst containerRef = React.useRef<HTMLDivElement | null>(null);\n\tconst onReadyRef = React.useRef(onReady);\n\tconst onErrorRef = React.useRef(onError);\n\tonReadyRef.current = onReady;\n\tonErrorRef.current = onError;\n\n\tReact.useEffect(() => {\n\t\tif (!containerRef.current) return;\n\t\tlet disposed = false;\n\t\tlet ws: import(\"wavesurfer.js\").default | null = null;\n\t\t// Track Blob ObjectURLs so we can revoke on teardown.\n\t\tconst objectUrl = src instanceof Blob ? URL.createObjectURL(src) : null;\n\n\t\tvoid (async () => {\n\t\t\tconst { default: WaveSurfer } = await import(\"wavesurfer.js\");\n\t\t\tif (disposed || !containerRef.current) return;\n\n\t\t\tws = WaveSurfer.create({\n\t\t\t\tcontainer: containerRef.current,\n\t\t\t\twaveColor,\n\t\t\t\tprogressColor: progressColor ?? waveColor,\n\t\t\t\tcursorColor: \"transparent\",\n\t\t\t\theight,\n\t\t\t\tbarWidth: 2,\n\t\t\t\tbarGap: 1,\n\t\t\t\tbarRadius: 1,\n\t\t\t\tnormalize: true,\n\t\t\t\tinteract: false,\n\t\t\t});\n\n\t\t\tws.on(\"ready\", () => {\n\t\t\t\tif (ws) onReadyRef.current?.(ws.getDuration());\n\t\t\t});\n\t\t\tws.on(\"error\", (err: Error) => {\n\t\t\t\tonErrorRef.current?.(err.message);\n\t\t\t});\n\n\t\t\tvoid ws.load(objectUrl ?? (src as string));\n\t\t})();\n\n\t\treturn () => {\n\t\t\tdisposed = true;\n\t\t\tws?.destroy();\n\t\t\tif (objectUrl) URL.revokeObjectURL(objectUrl);\n\t\t};\n\t}, [src, height, waveColor, progressColor]);\n\n\treturn (\n\t\t<div\n\t\t\t{...rest}\n\t\t\tref={containerRef}\n\t\t\tdata-hex-audio-waveform\n\t\t\tclassName={cn(\"w-full\", className)}\n\t\t/>\n\t);\n}\n\nexport { AudioWaveform };\n"]}
|
package/dist/canvas.d.ts
ADDED
package/dist/canvas.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
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 Canvas({
|
|
11
|
+
nodes,
|
|
12
|
+
edges,
|
|
13
|
+
onNodesChange,
|
|
14
|
+
onEdgesChange,
|
|
15
|
+
onConnect,
|
|
16
|
+
hideControls = false,
|
|
17
|
+
hideBackground = false,
|
|
18
|
+
fitView = true,
|
|
19
|
+
children,
|
|
20
|
+
className,
|
|
21
|
+
...rest
|
|
22
|
+
}) {
|
|
23
|
+
const [rf, setRf] = React.useState(null);
|
|
24
|
+
React.useEffect(() => {
|
|
25
|
+
let cancelled = false;
|
|
26
|
+
void import('reactflow').then((mod) => {
|
|
27
|
+
if (!cancelled) setRf(mod);
|
|
28
|
+
});
|
|
29
|
+
return () => {
|
|
30
|
+
cancelled = true;
|
|
31
|
+
};
|
|
32
|
+
}, []);
|
|
33
|
+
if (!rf) {
|
|
34
|
+
return /* @__PURE__ */ jsx(
|
|
35
|
+
"div",
|
|
36
|
+
{
|
|
37
|
+
...rest,
|
|
38
|
+
"data-hex-canvas-loading": true,
|
|
39
|
+
className: cn("h-full w-full bg-muted/20", className),
|
|
40
|
+
"aria-busy": "true"
|
|
41
|
+
}
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
const { ReactFlow, Background, Controls } = rf;
|
|
45
|
+
return /* @__PURE__ */ jsx(
|
|
46
|
+
"div",
|
|
47
|
+
{
|
|
48
|
+
...rest,
|
|
49
|
+
"data-hex-canvas": true,
|
|
50
|
+
className: cn("h-full w-full", className),
|
|
51
|
+
children: /* @__PURE__ */ jsxs(
|
|
52
|
+
ReactFlow,
|
|
53
|
+
{
|
|
54
|
+
nodes,
|
|
55
|
+
edges,
|
|
56
|
+
onNodesChange,
|
|
57
|
+
onEdgesChange,
|
|
58
|
+
onConnect,
|
|
59
|
+
fitView,
|
|
60
|
+
children: [
|
|
61
|
+
!hideBackground && /* @__PURE__ */ jsx(Background, {}),
|
|
62
|
+
!hideControls && /* @__PURE__ */ jsx(Controls, {}),
|
|
63
|
+
children
|
|
64
|
+
]
|
|
65
|
+
}
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export { Canvas };
|
|
72
|
+
//# sourceMappingURL=canvas.js.map
|
|
73
|
+
//# sourceMappingURL=canvas.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/utils.ts","../src/ai/canvas/canvas.tsx"],"names":[],"mappings":";;;;;AAQO,SAAS,MAAM,MAAA,EAAsB;AAC3C,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC5B;AC0CA,SAAS,MAAA,CAAO;AAAA,EACf,KAAA;AAAA,EACA,KAAA;AAAA,EACA,aAAA;AAAA,EACA,aAAA;AAAA,EACA,SAAA;AAAA,EACA,YAAA,GAAe,KAAA;AAAA,EACf,cAAA,GAAiB,KAAA;AAAA,EACjB,OAAA,GAAU,IAAA;AAAA,EACV,QAAA;AAAA,EACA,SAAA;AAAA,EACA,GAAG;AACJ,CAAA,EAAgB;AAGf,EAAA,MAAM,CAAC,EAAA,EAAI,KAAK,CAAA,GAAU,eAA4C,IAAI,CAAA;AAE1E,EAAM,gBAAU,MAAM;AACrB,IAAA,IAAI,SAAA,GAAY,KAAA;AAChB,IAAA,KAAK,OAAO,WAAW,CAAA,CAAE,IAAA,CAAK,CAAC,GAAA,KAAQ;AACtC,MAAA,IAAI,CAAC,SAAA,EAAW,KAAA,CAAM,GAAG,CAAA;AAAA,IAC1B,CAAC,CAAA;AACD,IAAA,OAAO,MAAM;AACZ,MAAA,SAAA,GAAY,IAAA;AAAA,IACb,CAAA;AAAA,EACD,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,IAAI,CAAC,EAAA,EAAI;AACR,IAAA,uBACC,GAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,GAAG,IAAA;AAAA,QACJ,yBAAA,EAAuB,IAAA;AAAA,QACvB,SAAA,EAAW,EAAA,CAAG,2BAAA,EAA6B,SAAS,CAAA;AAAA,QACpD,WAAA,EAAU;AAAA;AAAA,KACX;AAAA,EAEF;AAEA,EAAA,MAAM,EAAE,SAAA,EAAW,UAAA,EAAY,QAAA,EAAS,GAAI,EAAA;AAE5C,EAAA,uBACC,GAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,GAAG,IAAA;AAAA,MACJ,iBAAA,EAAe,IAAA;AAAA,MACf,SAAA,EAAW,EAAA,CAAG,eAAA,EAAiB,SAAS,CAAA;AAAA,MAExC,QAAA,kBAAA,IAAA;AAAA,QAAC,SAAA;AAAA,QAAA;AAAA,UACA,KAAA;AAAA,UACA,KAAA;AAAA,UACA,aAAA;AAAA,UACA,aAAA;AAAA,UACA,SAAA;AAAA,UACA,OAAA;AAAA,UAEC,QAAA,EAAA;AAAA,YAAA,CAAC,cAAA,wBAAmB,UAAA,EAAA,EAAW,CAAA;AAAA,YAC/B,CAAC,YAAA,oBAAgB,GAAA,CAAC,QAAA,EAAA,EAAS,CAAA;AAAA,YAC3B;AAAA;AAAA;AAAA;AACF;AAAA,GACD;AAEF","file":"canvas.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 * Headless node-graph canvas backed by reactflow. Renders an interactive\n * graph of agent steps, workflow nodes, RAG document refs — anywhere your\n * AI app needs to visualize relationships.\n *\n * Controlled: pass `nodes` and `edges`; receive change events. Reactflow's\n * default Background + Controls are rendered automatically; pass `children`\n * to add Panels, MiniMap, or custom overlays.\n *\n * Heavy peer: requires `reactflow` (~80 KB gzip). The hex-core CLI's `add`\n * flow prompts before installing.\n *\n * @example\n * import \"reactflow/dist/style.css\"; // once in your app entry\n * <Canvas\n * nodes={[{ id: \"1\", position: { x: 0, y: 0 }, data: { label: \"Agent\" } }]}\n * edges={[]}\n * onNodesChange={onNodesChange}\n * onEdgesChange={onEdgesChange}\n * />\n */\nexport interface CanvasProps extends Omit<React.HTMLAttributes<HTMLDivElement>, \"children\" | \"onError\"> {\n\t/** Node objects passed to ReactFlow. See reactflow docs for shape. */\n\tnodes: import(\"reactflow\").Node[];\n\t/** Edge objects passed to ReactFlow. */\n\tedges: import(\"reactflow\").Edge[];\n\t/** Forwarded to ReactFlow.onNodesChange. */\n\tonNodesChange?: import(\"reactflow\").OnNodesChange;\n\t/** Forwarded to ReactFlow.onEdgesChange. */\n\tonEdgesChange?: import(\"reactflow\").OnEdgesChange;\n\t/** Forwarded to ReactFlow.onConnect (fires when user wires two nodes). */\n\tonConnect?: import(\"reactflow\").OnConnect;\n\t/** Hide the bottom-left zoom/pan/fit controls. Default false (visible). */\n\thideControls?: boolean;\n\t/** Hide the dotted background. Default false (visible). */\n\thideBackground?: boolean;\n\t/** Auto-fit the view to all nodes on first render. Default true. */\n\tfitView?: boolean;\n\t/** Slot for MiniMap, Panel, or custom overlays rendered inside ReactFlow. */\n\tchildren?: React.ReactNode;\n}\n\n/**\n * Renders a ReactFlow graph with sensible defaults.\n * @param props - Controlled nodes/edges + interaction handlers\n * @returns A div containing the ReactFlow canvas\n */\nfunction Canvas({\n\tnodes,\n\tedges,\n\tonNodesChange,\n\tonEdgesChange,\n\tonConnect,\n\thideControls = false,\n\thideBackground = false,\n\tfitView = true,\n\tchildren,\n\tclassName,\n\t...rest\n}: CanvasProps) {\n\t// Lazy-loaded reactflow modules. Held in state so a re-render kicks in\n\t// once the dynamic import resolves; until then we render a placeholder.\n\tconst [rf, setRf] = React.useState<typeof import(\"reactflow\") | null>(null);\n\n\tReact.useEffect(() => {\n\t\tlet cancelled = false;\n\t\tvoid import(\"reactflow\").then((mod) => {\n\t\t\tif (!cancelled) setRf(mod);\n\t\t});\n\t\treturn () => {\n\t\t\tcancelled = true;\n\t\t};\n\t}, []);\n\n\tif (!rf) {\n\t\treturn (\n\t\t\t<div\n\t\t\t\t{...rest}\n\t\t\t\tdata-hex-canvas-loading\n\t\t\t\tclassName={cn(\"h-full w-full bg-muted/20\", className)}\n\t\t\t\taria-busy=\"true\"\n\t\t\t/>\n\t\t);\n\t}\n\n\tconst { ReactFlow, Background, Controls } = rf;\n\n\treturn (\n\t\t<div\n\t\t\t{...rest}\n\t\t\tdata-hex-canvas\n\t\t\tclassName={cn(\"h-full w-full\", className)}\n\t\t>\n\t\t\t<ReactFlow\n\t\t\t\tnodes={nodes}\n\t\t\t\tedges={edges}\n\t\t\t\tonNodesChange={onNodesChange}\n\t\t\t\tonEdgesChange={onEdgesChange}\n\t\t\t\tonConnect={onConnect}\n\t\t\t\tfitView={fitView}\n\t\t\t>\n\t\t\t\t{!hideBackground && <Background />}\n\t\t\t\t{!hideControls && <Controls />}\n\t\t\t\t{children}\n\t\t\t</ReactFlow>\n\t\t</div>\n\t);\n}\n\nexport { Canvas };\n"]}
|
package/dist/chord.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { ChordNode_alias_1 as ChordNode } from './_tsup-dts-rollup.js';
|
|
2
|
+
export { ChordHoverPayload_alias_1 as ChordHoverPayload } from './_tsup-dts-rollup.js';
|
|
3
|
+
export { ChordProps_alias_1 as ChordProps } from './_tsup-dts-rollup.js';
|
|
4
|
+
export { Chord_alias_1 as Chord } from './_tsup-dts-rollup.js';
|