@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
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { SequenceActor_alias_1 as SequenceActor } from './_tsup-dts-rollup.js';
|
|
2
|
+
export { SequenceMessage_alias_1 as SequenceMessage } from './_tsup-dts-rollup.js';
|
|
3
|
+
export { SequenceProps_alias_1 as SequenceProps } from './_tsup-dts-rollup.js';
|
|
4
|
+
export { Sequence_alias_1 as Sequence } from './_tsup-dts-rollup.js';
|
package/dist/sequence.js
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { clsx } from 'clsx';
|
|
4
|
+
import { twMerge } from 'tailwind-merge';
|
|
5
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
6
|
+
|
|
7
|
+
function cn(...inputs) {
|
|
8
|
+
return twMerge(clsx(inputs));
|
|
9
|
+
}
|
|
10
|
+
var HEADER_PADDING = 16;
|
|
11
|
+
var MESSAGE_TOP_PADDING = 24;
|
|
12
|
+
function Sequence({
|
|
13
|
+
actors,
|
|
14
|
+
messages,
|
|
15
|
+
width = 720,
|
|
16
|
+
headerHeight = 40,
|
|
17
|
+
messageGap = 36,
|
|
18
|
+
onActorClick,
|
|
19
|
+
onMessageClick,
|
|
20
|
+
className,
|
|
21
|
+
...rest
|
|
22
|
+
}) {
|
|
23
|
+
const laidOut = React.useMemo(
|
|
24
|
+
() => layout(actors, messages, width, headerHeight, messageGap),
|
|
25
|
+
[actors, messages, width, headerHeight, messageGap]
|
|
26
|
+
);
|
|
27
|
+
const desc = `Sequence diagram with ${actors.length} actor${actors.length === 1 ? "" : "s"} and ${messages.length} message${messages.length === 1 ? "" : "s"}`;
|
|
28
|
+
const arrowId = React.useId().replace(/:/g, "-");
|
|
29
|
+
const SELF_CALL_DROP = Math.min(14, Math.max(6, messageGap * 0.4));
|
|
30
|
+
const SELF_CALL_OUT = 28;
|
|
31
|
+
return /* @__PURE__ */ jsxs(
|
|
32
|
+
"svg",
|
|
33
|
+
{
|
|
34
|
+
...rest,
|
|
35
|
+
"data-hex-sequence": true,
|
|
36
|
+
role: "img",
|
|
37
|
+
width,
|
|
38
|
+
height: laidOut.totalHeight,
|
|
39
|
+
viewBox: `0 0 ${width} ${laidOut.totalHeight}`,
|
|
40
|
+
className: cn("block", className),
|
|
41
|
+
children: [
|
|
42
|
+
/* @__PURE__ */ jsx("title", { children: "Sequence diagram" }),
|
|
43
|
+
/* @__PURE__ */ jsx("desc", { children: desc }),
|
|
44
|
+
/* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx(
|
|
45
|
+
"marker",
|
|
46
|
+
{
|
|
47
|
+
id: `hex-sequence-arrow-${arrowId}`,
|
|
48
|
+
viewBox: "0 0 10 10",
|
|
49
|
+
refX: "10",
|
|
50
|
+
refY: "5",
|
|
51
|
+
markerWidth: "6",
|
|
52
|
+
markerHeight: "6",
|
|
53
|
+
orient: "auto-start-reverse",
|
|
54
|
+
children: /* @__PURE__ */ jsx("path", { d: "M 0 0 L 10 5 L 0 10 z", fill: "hsl(var(--foreground))" })
|
|
55
|
+
}
|
|
56
|
+
) }),
|
|
57
|
+
/* @__PURE__ */ jsx("g", { "data-hex-sequence-lifelines": true, children: laidOut.actors.map((a) => /* @__PURE__ */ jsx(
|
|
58
|
+
"line",
|
|
59
|
+
{
|
|
60
|
+
"data-hex-sequence-lifeline": true,
|
|
61
|
+
x1: a.x,
|
|
62
|
+
x2: a.x,
|
|
63
|
+
y1: headerHeight,
|
|
64
|
+
y2: laidOut.totalHeight - 8,
|
|
65
|
+
stroke: "hsl(var(--muted-foreground))",
|
|
66
|
+
strokeOpacity: 0.7,
|
|
67
|
+
strokeDasharray: "3 4",
|
|
68
|
+
strokeWidth: 1
|
|
69
|
+
},
|
|
70
|
+
`lifeline-${a.actor.id}`
|
|
71
|
+
)) }),
|
|
72
|
+
/* @__PURE__ */ jsx("g", { "data-hex-sequence-actors": true, children: laidOut.actors.map((a) => {
|
|
73
|
+
const interactive = Boolean(onActorClick);
|
|
74
|
+
const handleActivate = () => onActorClick?.(a.actor);
|
|
75
|
+
return /* @__PURE__ */ jsxs(
|
|
76
|
+
"g",
|
|
77
|
+
{
|
|
78
|
+
"data-hex-sequence-actor": true,
|
|
79
|
+
"data-depth": a.depth,
|
|
80
|
+
role: interactive ? "button" : void 0,
|
|
81
|
+
tabIndex: interactive ? 0 : void 0,
|
|
82
|
+
"aria-label": interactive ? a.actor.label : void 0,
|
|
83
|
+
style: interactive ? { cursor: "pointer" } : void 0,
|
|
84
|
+
onClick: interactive ? handleActivate : void 0,
|
|
85
|
+
onKeyDown: interactive ? (e) => activateOnKey(e, handleActivate) : void 0,
|
|
86
|
+
children: [
|
|
87
|
+
/* @__PURE__ */ jsx(
|
|
88
|
+
"rect",
|
|
89
|
+
{
|
|
90
|
+
x: a.x - 64,
|
|
91
|
+
y: 4,
|
|
92
|
+
width: 128,
|
|
93
|
+
height: headerHeight - 8,
|
|
94
|
+
rx: 4,
|
|
95
|
+
ry: 4,
|
|
96
|
+
fill: "hsl(var(--card))",
|
|
97
|
+
stroke: "hsl(var(--border))",
|
|
98
|
+
strokeWidth: 1
|
|
99
|
+
}
|
|
100
|
+
),
|
|
101
|
+
/* @__PURE__ */ jsx(
|
|
102
|
+
"text",
|
|
103
|
+
{
|
|
104
|
+
x: a.x,
|
|
105
|
+
y: headerHeight / 2,
|
|
106
|
+
dy: "0.35em",
|
|
107
|
+
textAnchor: "middle",
|
|
108
|
+
fontSize: 12,
|
|
109
|
+
fontWeight: 600,
|
|
110
|
+
fill: "hsl(var(--foreground))",
|
|
111
|
+
style: { pointerEvents: "none" },
|
|
112
|
+
children: a.actor.label
|
|
113
|
+
}
|
|
114
|
+
)
|
|
115
|
+
]
|
|
116
|
+
},
|
|
117
|
+
a.actor.id
|
|
118
|
+
);
|
|
119
|
+
}) }),
|
|
120
|
+
/* @__PURE__ */ jsx("g", { "data-hex-sequence-messages": true, children: laidOut.messages.map((m, i) => {
|
|
121
|
+
const interactive = Boolean(onMessageClick);
|
|
122
|
+
const handleActivate = () => onMessageClick?.(m.message);
|
|
123
|
+
const stroke = "hsl(var(--foreground))";
|
|
124
|
+
const dashed = m.message.type === "return" ? "4 3" : void 0;
|
|
125
|
+
const strokeWidth = m.message.type === "async" ? 1 : 1.25;
|
|
126
|
+
return /* @__PURE__ */ jsxs(
|
|
127
|
+
"g",
|
|
128
|
+
{
|
|
129
|
+
"data-hex-sequence-message": true,
|
|
130
|
+
"data-depth": m.depth,
|
|
131
|
+
"data-type": m.message.type ?? "sync",
|
|
132
|
+
role: interactive ? "button" : void 0,
|
|
133
|
+
tabIndex: interactive ? 0 : void 0,
|
|
134
|
+
"aria-label": interactive ? `Message ${i + 1}: from ${m.from.actor.label} to ${m.to.actor.label}${m.message.label ? `, "${m.message.label}"` : ""}` : void 0,
|
|
135
|
+
style: interactive ? { cursor: "pointer" } : void 0,
|
|
136
|
+
onClick: interactive ? handleActivate : void 0,
|
|
137
|
+
onKeyDown: interactive ? (e) => activateOnKey(e, handleActivate) : void 0,
|
|
138
|
+
children: [
|
|
139
|
+
m.isSelfCall ? (
|
|
140
|
+
// Loopback: out + down + back. Drop scales with messageGap.
|
|
141
|
+
/* @__PURE__ */ jsx(
|
|
142
|
+
"path",
|
|
143
|
+
{
|
|
144
|
+
d: `M${m.from.x},${m.y} L${m.from.x + SELF_CALL_OUT},${m.y} L${m.from.x + SELF_CALL_OUT},${m.y + SELF_CALL_DROP} L${m.from.x + 4},${m.y + SELF_CALL_DROP}`,
|
|
145
|
+
fill: "none",
|
|
146
|
+
stroke,
|
|
147
|
+
strokeWidth,
|
|
148
|
+
strokeDasharray: dashed,
|
|
149
|
+
markerEnd: `url(#hex-sequence-arrow-${arrowId})`
|
|
150
|
+
}
|
|
151
|
+
)
|
|
152
|
+
) : /* @__PURE__ */ jsx(
|
|
153
|
+
"line",
|
|
154
|
+
{
|
|
155
|
+
x1: m.from.x,
|
|
156
|
+
x2: m.to.x,
|
|
157
|
+
y1: m.y,
|
|
158
|
+
y2: m.y,
|
|
159
|
+
stroke,
|
|
160
|
+
strokeWidth,
|
|
161
|
+
strokeDasharray: dashed,
|
|
162
|
+
markerEnd: `url(#hex-sequence-arrow-${arrowId})`
|
|
163
|
+
}
|
|
164
|
+
),
|
|
165
|
+
m.message.label ? /* @__PURE__ */ jsx(
|
|
166
|
+
"text",
|
|
167
|
+
{
|
|
168
|
+
x: m.isSelfCall ? m.from.x + SELF_CALL_OUT + 4 : (m.from.x + m.to.x) / 2,
|
|
169
|
+
y: m.y - 4,
|
|
170
|
+
textAnchor: m.isSelfCall ? "start" : "middle",
|
|
171
|
+
fontSize: 11,
|
|
172
|
+
fill: "hsl(var(--foreground))",
|
|
173
|
+
style: {
|
|
174
|
+
paintOrder: "stroke",
|
|
175
|
+
pointerEvents: "none"
|
|
176
|
+
},
|
|
177
|
+
stroke: "hsl(var(--background))",
|
|
178
|
+
strokeWidth: 3,
|
|
179
|
+
strokeLinejoin: "round",
|
|
180
|
+
children: m.message.label
|
|
181
|
+
}
|
|
182
|
+
) : null
|
|
183
|
+
]
|
|
184
|
+
},
|
|
185
|
+
`${m.message.from}-${m.message.to}-${i}`
|
|
186
|
+
);
|
|
187
|
+
}) })
|
|
188
|
+
]
|
|
189
|
+
}
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
function layout(actors, messages, width, headerHeight, messageGap) {
|
|
193
|
+
if (actors.length === 0) {
|
|
194
|
+
return { actors: [], messages: [], totalHeight: headerHeight + 16 };
|
|
195
|
+
}
|
|
196
|
+
const usableWidth = width - HEADER_PADDING * 2;
|
|
197
|
+
const colStep = actors.length > 1 ? usableWidth / (actors.length - 1) : 0;
|
|
198
|
+
const actorPositions = actors.map((a, i) => ({
|
|
199
|
+
actor: a,
|
|
200
|
+
x: HEADER_PADDING + colStep * i,
|
|
201
|
+
depth: i
|
|
202
|
+
}));
|
|
203
|
+
const byId = new Map(actorPositions.map((a) => [a.actor.id, a]));
|
|
204
|
+
const laidOutMessages = messages.map((message, i) => {
|
|
205
|
+
const from = byId.get(message.from);
|
|
206
|
+
const to = byId.get(message.to);
|
|
207
|
+
if (!from || !to) return null;
|
|
208
|
+
return {
|
|
209
|
+
message,
|
|
210
|
+
from,
|
|
211
|
+
to,
|
|
212
|
+
y: headerHeight + MESSAGE_TOP_PADDING + i * messageGap,
|
|
213
|
+
depth: i,
|
|
214
|
+
isSelfCall: message.from === message.to
|
|
215
|
+
};
|
|
216
|
+
}).filter((m) => m !== null);
|
|
217
|
+
const totalHeight = headerHeight + MESSAGE_TOP_PADDING + Math.max(0, laidOutMessages.length - 1) * messageGap + 32;
|
|
218
|
+
return { actors: actorPositions, messages: laidOutMessages, totalHeight };
|
|
219
|
+
}
|
|
220
|
+
function activateOnKey(e, fn) {
|
|
221
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
222
|
+
e.preventDefault();
|
|
223
|
+
fn();
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export { Sequence };
|
|
228
|
+
//# sourceMappingURL=sequence.js.map
|
|
229
|
+
//# sourceMappingURL=sequence.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/utils.ts","../src/artifacts/sequence/sequence.tsx"],"names":[],"mappings":"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","file":"sequence.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 * UML-style sequence diagram. Actors render as columns with vertical\n * lifelines; messages render as horizontal arrows between actors,\n * stacked top-to-bottom in declaration order. Pure SVG; no heavy peer\n * dependency.\n *\n * Distinct from Flowchart (DAG of steps) and TimeAxis (events on a\n * time axis): Sequence encodes WHO talks to WHO and IN WHAT ORDER.\n *\n * @example\n * <Sequence\n * actors={[\n * { id: \"user\", label: \"User\" },\n * { id: \"api\", label: \"API\" },\n * { id: \"db\", label: \"DB\" },\n * ]}\n * messages={[\n * { from: \"user\", to: \"api\", label: \"POST /signup\" },\n * { from: \"api\", to: \"db\", label: \"INSERT user\" },\n * { from: \"db\", to: \"api\", label: \"ok\", type: \"return\" },\n * { from: \"api\", to: \"user\", label: \"201 Created\", type: \"return\" },\n * ]}\n * />\n */\nexport type SequenceActor = {\n\tid: string;\n\tlabel: string;\n};\n\nexport type SequenceMessage = {\n\tfrom: string;\n\tto: string;\n\tlabel?: string;\n\t/** \"sync\" (default) draws a solid arrow; \"async\" draws a thinner half-head; \"return\" is dashed. */\n\ttype?: \"sync\" | \"async\" | \"return\";\n};\n\nexport interface SequenceProps extends Omit<React.SVGAttributes<SVGSVGElement>, \"children\"> {\n\t/** Actors in display order (columns left-to-right). */\n\tactors: SequenceActor[];\n\t/** Messages in chronological order (top-to-bottom). */\n\tmessages: SequenceMessage[];\n\t/** Pixel width of the rendered SVG. Default 720. */\n\twidth?: number;\n\t/** Pixel height of each actor header. Default 40. */\n\theaderHeight?: number;\n\t/** Pixel vertical gap between consecutive messages. Default 36. */\n\tmessageGap?: number;\n\t/** Fired when an actor header is clicked. */\n\tonActorClick?: (actor: SequenceActor) => void;\n\t/** Fired when a message arrow is clicked. */\n\tonMessageClick?: (message: SequenceMessage) => void;\n}\n\ninterface ActorPos {\n\tactor: SequenceActor;\n\tx: number;\n\tdepth: number;\n}\n\ninterface LaidOutMessage {\n\tmessage: SequenceMessage;\n\tfrom: ActorPos;\n\tto: ActorPos;\n\ty: number;\n\tdepth: number;\n\tisSelfCall: boolean;\n}\n\nconst HEADER_PADDING = 16;\nconst MESSAGE_TOP_PADDING = 24;\n\nfunction Sequence({\n\tactors,\n\tmessages,\n\twidth = 720,\n\theaderHeight = 40,\n\tmessageGap = 36,\n\tonActorClick,\n\tonMessageClick,\n\tclassName,\n\t...rest\n}: SequenceProps) {\n\tconst laidOut = React.useMemo(\n\t\t() => layout(actors, messages, width, headerHeight, messageGap),\n\t\t[actors, messages, width, headerHeight, messageGap],\n\t);\n\n\tconst desc = `Sequence diagram with ${actors.length} actor${actors.length === 1 ? \"\" : \"s\"} and ${messages.length} message${messages.length === 1 ? \"\" : \"s\"}`;\n\t// Per-instance to avoid <defs> id collision when two <Sequence> mount on\n\t// the same page; React.useId() is unique per component instance.\n\tconst arrowId = React.useId().replace(/:/g, \"-\");\n\t// Loopback height scales with messageGap so the self-call doesn't\n\t// overflow into the next message at small messageGap values.\n\tconst SELF_CALL_DROP = Math.min(14, Math.max(6, messageGap * 0.4));\n\tconst SELF_CALL_OUT = 28;\n\n\treturn (\n\t\t<svg\n\t\t\t{...rest}\n\t\t\tdata-hex-sequence\n\t\t\trole=\"img\"\n\t\t\twidth={width}\n\t\t\theight={laidOut.totalHeight}\n\t\t\tviewBox={`0 0 ${width} ${laidOut.totalHeight}`}\n\t\t\tclassName={cn(\"block\", className)}\n\t\t>\n\t\t\t<title>Sequence diagram</title>\n\t\t\t<desc>{desc}</desc>\n\t\t\t<defs>\n\t\t\t\t<marker\n\t\t\t\t\tid={`hex-sequence-arrow-${arrowId}`}\n\t\t\t\t\tviewBox=\"0 0 10 10\"\n\t\t\t\t\trefX=\"10\"\n\t\t\t\t\trefY=\"5\"\n\t\t\t\t\tmarkerWidth=\"6\"\n\t\t\t\t\tmarkerHeight=\"6\"\n\t\t\t\t\torient=\"auto-start-reverse\"\n\t\t\t\t>\n\t\t\t\t\t<path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"hsl(var(--foreground))\" />\n\t\t\t\t</marker>\n\t\t\t</defs>\n\t\t\t{/* Lifelines */}\n\t\t\t<g data-hex-sequence-lifelines>\n\t\t\t\t{laidOut.actors.map((a) => (\n\t\t\t\t\t<line\n\t\t\t\t\t\tkey={`lifeline-${a.actor.id}`}\n\t\t\t\t\t\tdata-hex-sequence-lifeline\n\t\t\t\t\t\tx1={a.x}\n\t\t\t\t\t\tx2={a.x}\n\t\t\t\t\t\ty1={headerHeight}\n\t\t\t\t\t\ty2={laidOut.totalHeight - 8}\n\t\t\t\t\t\tstroke=\"hsl(var(--muted-foreground))\"\n\t\t\t\t\t\tstrokeOpacity={0.7}\n\t\t\t\t\t\tstrokeDasharray=\"3 4\"\n\t\t\t\t\t\tstrokeWidth={1}\n\t\t\t\t\t/>\n\t\t\t\t))}\n\t\t\t</g>\n\t\t\t{/* Actor headers */}\n\t\t\t<g data-hex-sequence-actors>\n\t\t\t\t{laidOut.actors.map((a) => {\n\t\t\t\t\tconst interactive = Boolean(onActorClick);\n\t\t\t\t\tconst handleActivate = () => onActorClick?.(a.actor);\n\t\t\t\t\treturn (\n\t\t\t\t\t\t<g\n\t\t\t\t\t\t\tkey={a.actor.id}\n\t\t\t\t\t\t\tdata-hex-sequence-actor\n\t\t\t\t\t\t\tdata-depth={a.depth}\n\t\t\t\t\t\t\trole={interactive ? \"button\" : undefined}\n\t\t\t\t\t\t\ttabIndex={interactive ? 0 : undefined}\n\t\t\t\t\t\t\taria-label={interactive ? a.actor.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<rect\n\t\t\t\t\t\t\t\tx={a.x - 64}\n\t\t\t\t\t\t\t\ty={4}\n\t\t\t\t\t\t\t\twidth={128}\n\t\t\t\t\t\t\t\theight={headerHeight - 8}\n\t\t\t\t\t\t\t\trx={4}\n\t\t\t\t\t\t\t\try={4}\n\t\t\t\t\t\t\t\tfill=\"hsl(var(--card))\"\n\t\t\t\t\t\t\t\tstroke=\"hsl(var(--border))\"\n\t\t\t\t\t\t\t\tstrokeWidth={1}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t<text\n\t\t\t\t\t\t\t\tx={a.x}\n\t\t\t\t\t\t\t\ty={headerHeight / 2}\n\t\t\t\t\t\t\t\tdy=\"0.35em\"\n\t\t\t\t\t\t\t\ttextAnchor=\"middle\"\n\t\t\t\t\t\t\t\tfontSize={12}\n\t\t\t\t\t\t\t\tfontWeight={600}\n\t\t\t\t\t\t\t\tfill=\"hsl(var(--foreground))\"\n\t\t\t\t\t\t\t\tstyle={{ pointerEvents: \"none\" }}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{a.actor.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\t{/* Messages */}\n\t\t\t<g data-hex-sequence-messages>\n\t\t\t\t{laidOut.messages.map((m, i) => {\n\t\t\t\t\tconst interactive = Boolean(onMessageClick);\n\t\t\t\t\tconst handleActivate = () => onMessageClick?.(m.message);\n\t\t\t\t\tconst stroke = \"hsl(var(--foreground))\";\n\t\t\t\t\tconst dashed = m.message.type === \"return\" ? \"4 3\" : undefined;\n\t\t\t\t\tconst strokeWidth = m.message.type === \"async\" ? 1 : 1.25;\n\t\t\t\t\treturn (\n\t\t\t\t\t\t<g\n\t\t\t\t\t\t\tkey={`${m.message.from}-${m.message.to}-${i}`}\n\t\t\t\t\t\t\tdata-hex-sequence-message\n\t\t\t\t\t\t\tdata-depth={m.depth}\n\t\t\t\t\t\t\tdata-type={m.message.type ?? \"sync\"}\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? `Message ${i + 1}: from ${m.from.actor.label} to ${m.to.actor.label}${m.message.label ? `, \"${m.message.label}\"` : \"\"}`\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={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{m.isSelfCall ? (\n\t\t\t\t\t\t\t\t// Loopback: out + down + back. Drop scales with messageGap.\n\t\t\t\t\t\t\t\t<path\n\t\t\t\t\t\t\t\t\td={`M${m.from.x},${m.y} L${m.from.x + SELF_CALL_OUT},${m.y} L${m.from.x + SELF_CALL_OUT},${m.y + SELF_CALL_DROP} L${m.from.x + 4},${m.y + SELF_CALL_DROP}`}\n\t\t\t\t\t\t\t\t\tfill=\"none\"\n\t\t\t\t\t\t\t\t\tstroke={stroke}\n\t\t\t\t\t\t\t\t\tstrokeWidth={strokeWidth}\n\t\t\t\t\t\t\t\t\tstrokeDasharray={dashed}\n\t\t\t\t\t\t\t\t\tmarkerEnd={`url(#hex-sequence-arrow-${arrowId})`}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t<line\n\t\t\t\t\t\t\t\t\tx1={m.from.x}\n\t\t\t\t\t\t\t\t\tx2={m.to.x}\n\t\t\t\t\t\t\t\t\ty1={m.y}\n\t\t\t\t\t\t\t\t\ty2={m.y}\n\t\t\t\t\t\t\t\t\tstroke={stroke}\n\t\t\t\t\t\t\t\t\tstrokeWidth={strokeWidth}\n\t\t\t\t\t\t\t\t\tstrokeDasharray={dashed}\n\t\t\t\t\t\t\t\t\tmarkerEnd={`url(#hex-sequence-arrow-${arrowId})`}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t{m.message.label ? (\n\t\t\t\t\t\t\t\t<text\n\t\t\t\t\t\t\t\t\t// Self-calls anchor the label right of the loopback's apex\n\t\t\t\t\t\t\t\t\t// (start-anchored, just past the outbound segment) so it\n\t\t\t\t\t\t\t\t\t// doesn't sit above the actor's own lifeline. Non-self\n\t\t\t\t\t\t\t\t\t// messages center the label between the two actors.\n\t\t\t\t\t\t\t\t\tx={m.isSelfCall ? m.from.x + SELF_CALL_OUT + 4 : (m.from.x + m.to.x) / 2}\n\t\t\t\t\t\t\t\t\ty={m.y - 4}\n\t\t\t\t\t\t\t\t\ttextAnchor={m.isSelfCall ? \"start\" : \"middle\"}\n\t\t\t\t\t\t\t\t\tfontSize={11}\n\t\t\t\t\t\t\t\t\tfill=\"hsl(var(--foreground))\"\n\t\t\t\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\t\t\t\tpaintOrder: \"stroke\",\n\t\t\t\t\t\t\t\t\t\tpointerEvents: \"none\",\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\tstroke=\"hsl(var(--background))\"\n\t\t\t\t\t\t\t\t\tstrokeWidth={3}\n\t\t\t\t\t\t\t\t\tstrokeLinejoin=\"round\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t{m.message.label}\n\t\t\t\t\t\t\t\t</text>\n\t\t\t\t\t\t\t) : null}\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\tactors: SequenceActor[],\n\tmessages: SequenceMessage[],\n\twidth: number,\n\theaderHeight: number,\n\tmessageGap: number,\n): {\n\tactors: ActorPos[];\n\tmessages: LaidOutMessage[];\n\ttotalHeight: number;\n} {\n\tif (actors.length === 0) {\n\t\treturn { actors: [], messages: [], totalHeight: headerHeight + 16 };\n\t}\n\n\tconst usableWidth = width - HEADER_PADDING * 2;\n\tconst colStep = actors.length > 1 ? usableWidth / (actors.length - 1) : 0;\n\tconst actorPositions: ActorPos[] = actors.map((a, i) => ({\n\t\tactor: a,\n\t\tx: HEADER_PADDING + colStep * i,\n\t\tdepth: i,\n\t}));\n\tconst byId = new Map(actorPositions.map((a) => [a.actor.id, a]));\n\n\tconst laidOutMessages: LaidOutMessage[] = messages\n\t\t.map((message, i) => {\n\t\t\tconst from = byId.get(message.from);\n\t\t\tconst to = byId.get(message.to);\n\t\t\tif (!from || !to) return null;\n\t\t\treturn {\n\t\t\t\tmessage,\n\t\t\t\tfrom,\n\t\t\t\tto,\n\t\t\t\ty: headerHeight + MESSAGE_TOP_PADDING + i * messageGap,\n\t\t\t\tdepth: i,\n\t\t\t\tisSelfCall: message.from === message.to,\n\t\t\t};\n\t\t})\n\t\t.filter((m): m is LaidOutMessage => m !== null);\n\n\tconst totalHeight =\n\t\theaderHeight +\n\t\tMESSAGE_TOP_PADDING +\n\t\tMath.max(0, laidOutMessages.length - 1) * messageGap +\n\t\t32;\n\n\treturn { actors: actorPositions, messages: laidOutMessages, totalHeight };\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 { Sequence };\n"]}
|
package/dist/sonner.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/sonner/sonner.tsx"],"names":["SonnerToaster"],"mappings":";;;;AAYA,SAAS,OAAA,CAAQ,EAAE,GAAG,KAAA,EAAM,EAAiB;AAC5C,EAAA,uBACC,GAAA;AAAA,IAACA,SAAA;AAAA,IAAA;AAAA,MACA,KAAA,EAAM,QAAA;AAAA,MACN,SAAA,EAAU,eAAA;AAAA,MACV,YAAA,EAAc;AAAA,QACb,UAAA,EAAY;AAAA,UACX,KAAA,EACC,uIAAA;AAAA,UACD,WAAA,EAAa,sCAAA;AAAA,UACb,YAAA,EACC,kEAAA;AAAA,UACD,YAAA,EACC;AAAA;AACF,OACD;AAAA,MACC,GAAG;AAAA;AAAA,GACL;AAEF","file":"sonner.js","sourcesContent":["\"use client\";\n\nimport { Toaster as SonnerToaster, toast } from \"sonner\";\n\ntype ToasterProps = React.ComponentProps<typeof SonnerToaster>;\n\n/**\n * The global toast container. Render once in your app root.\n * Re-export of Sonner's Toaster styled to use Hex
|
|
1
|
+
{"version":3,"sources":["../src/components/sonner/sonner.tsx"],"names":["SonnerToaster"],"mappings":";;;;AAYA,SAAS,OAAA,CAAQ,EAAE,GAAG,KAAA,EAAM,EAAiB;AAC5C,EAAA,uBACC,GAAA;AAAA,IAACA,SAAA;AAAA,IAAA;AAAA,MACA,KAAA,EAAM,QAAA;AAAA,MACN,SAAA,EAAU,eAAA;AAAA,MACV,YAAA,EAAc;AAAA,QACb,UAAA,EAAY;AAAA,UACX,KAAA,EACC,uIAAA;AAAA,UACD,WAAA,EAAa,sCAAA;AAAA,UACb,YAAA,EACC,kEAAA;AAAA,UACD,YAAA,EACC;AAAA;AACF,OACD;AAAA,MACC,GAAG;AAAA;AAAA,GACL;AAEF","file":"sonner.js","sourcesContent":["\"use client\";\n\nimport { Toaster as SonnerToaster, toast } from \"sonner\";\n\ntype ToasterProps = React.ComponentProps<typeof SonnerToaster>;\n\n/**\n * The global toast container. Render once in your app root.\n * Re-export of Sonner's Toaster styled to use Hex Core theme tokens.\n * @param props - Sonner Toaster props (position, richColors, etc.)\n * @returns A styled portal container for toast notifications\n */\nfunction Toaster({ ...props }: ToasterProps) {\n\treturn (\n\t\t<SonnerToaster\n\t\t\ttheme=\"system\"\n\t\t\tclassName=\"toaster group\"\n\t\t\ttoastOptions={{\n\t\t\t\tclassNames: {\n\t\t\t\t\ttoast:\n\t\t\t\t\t\t\"group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg\",\n\t\t\t\t\tdescription: \"group-[.toast]:text-muted-foreground\",\n\t\t\t\t\tactionButton:\n\t\t\t\t\t\t\"group-[.toast]:bg-primary group-[.toast]:text-primary-foreground\",\n\t\t\t\t\tcancelButton:\n\t\t\t\t\t\t\"group-[.toast]:bg-muted group-[.toast]:text-muted-foreground\",\n\t\t\t\t},\n\t\t\t}}\n\t\t\t{...props}\n\t\t/>\n\t);\n}\n\nexport { Toaster, toast };\n"]}
|
|
@@ -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 } from 'react/jsx-runtime';
|
|
6
|
+
|
|
7
|
+
function cn(...inputs) {
|
|
8
|
+
return twMerge(clsx(inputs));
|
|
9
|
+
}
|
|
10
|
+
var RATINGS = ["again", "hard", "good", "easy"];
|
|
11
|
+
var DEFAULT_LABELS = {
|
|
12
|
+
again: "Again",
|
|
13
|
+
hard: "Hard",
|
|
14
|
+
good: "Good",
|
|
15
|
+
easy: "Easy"
|
|
16
|
+
};
|
|
17
|
+
var RATING_HINT = {
|
|
18
|
+
again: "Couldn't recall \u2014 show this card again soon.",
|
|
19
|
+
hard: "Recalled with effort \u2014 review sooner than usual.",
|
|
20
|
+
good: "Recalled correctly \u2014 keep the standard interval.",
|
|
21
|
+
easy: "Recalled instantly \u2014 push the next review further out."
|
|
22
|
+
};
|
|
23
|
+
var RATING_CLASSES = {
|
|
24
|
+
again: "border-destructive/40 bg-destructive/10 text-destructive hover:bg-destructive/15 focus-visible:ring-destructive/40",
|
|
25
|
+
hard: "border-secondary bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
26
|
+
good: "border-primary/40 bg-primary/10 text-foreground hover:bg-primary/15 focus-visible:ring-primary/40",
|
|
27
|
+
easy: "border-accent bg-accent/10 text-accent-foreground hover:bg-accent/20 focus-visible:ring-accent/40"
|
|
28
|
+
};
|
|
29
|
+
function SpacedRepetition({
|
|
30
|
+
cardId,
|
|
31
|
+
onRate,
|
|
32
|
+
labels,
|
|
33
|
+
className,
|
|
34
|
+
...rest
|
|
35
|
+
}) {
|
|
36
|
+
const onRateRef = React.useRef(onRate);
|
|
37
|
+
onRateRef.current = onRate;
|
|
38
|
+
return /* @__PURE__ */ jsx(
|
|
39
|
+
"div",
|
|
40
|
+
{
|
|
41
|
+
...rest,
|
|
42
|
+
"data-hex-spaced-repetition": true,
|
|
43
|
+
"data-card-id": cardId,
|
|
44
|
+
role: "group",
|
|
45
|
+
"aria-label": "Confidence rating",
|
|
46
|
+
className: cn("inline-flex flex-wrap items-center gap-2", className),
|
|
47
|
+
children: RATINGS.map((rating) => {
|
|
48
|
+
const label = labels?.[rating] ?? DEFAULT_LABELS[rating];
|
|
49
|
+
return /* @__PURE__ */ jsx(
|
|
50
|
+
"button",
|
|
51
|
+
{
|
|
52
|
+
type: "button",
|
|
53
|
+
"data-hex-spaced-repetition-button": true,
|
|
54
|
+
"data-rating": rating,
|
|
55
|
+
"aria-label": `${label}: ${RATING_HINT[rating]}`,
|
|
56
|
+
onClick: () => onRateRef.current(rating, cardId),
|
|
57
|
+
className: cn(
|
|
58
|
+
"inline-flex h-9 items-center justify-center rounded-md border px-3 text-sm font-medium transition-colors",
|
|
59
|
+
"focus:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
60
|
+
RATING_CLASSES[rating]
|
|
61
|
+
),
|
|
62
|
+
children: label
|
|
63
|
+
},
|
|
64
|
+
rating
|
|
65
|
+
);
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export { SpacedRepetition };
|
|
72
|
+
//# sourceMappingURL=spaced-repetition.js.map
|
|
73
|
+
//# sourceMappingURL=spaced-repetition.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/utils.ts","../src/artifacts/spaced-repetition/spaced-repetition.tsx"],"names":[],"mappings":";;;;;AAQO,SAAS,MAAM,MAAA,EAAsB;AAC3C,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC5B;ACyBA,IAAM,OAAA,GAAuB,CAAC,OAAA,EAAS,MAAA,EAAQ,QAAQ,MAAM,CAAA;AAE7D,IAAM,cAAA,GAA4C;AAAA,EACjD,KAAA,EAAO,OAAA;AAAA,EACP,IAAA,EAAM,MAAA;AAAA,EACN,IAAA,EAAM,MAAA;AAAA,EACN,IAAA,EAAM;AACP,CAAA;AAEA,IAAM,WAAA,GAAyC;AAAA,EAC9C,KAAA,EAAO,mDAAA;AAAA,EACP,IAAA,EAAM,uDAAA;AAAA,EACN,IAAA,EAAM,uDAAA;AAAA,EACN,IAAA,EAAM;AACP,CAAA;AAKA,IAAM,cAAA,GAA4C;AAAA,EACjD,KAAA,EACC,oHAAA;AAAA,EACD,IAAA,EACC,+EAAA;AAAA,EACD,IAAA,EACC,mGAAA;AAAA,EACD,IAAA,EACC;AACF,CAAA;AAEA,SAAS,gBAAA,CAAiB;AAAA,EACzB,MAAA;AAAA,EACA,MAAA;AAAA,EACA,MAAA;AAAA,EACA,SAAA;AAAA,EACA,GAAG;AACJ,CAAA,EAA0B;AACzB,EAAA,MAAM,SAAA,GAAkB,aAAO,MAAM,CAAA;AACrC,EAAA,SAAA,CAAU,OAAA,GAAU,MAAA;AAEpB,EAAA,uBACC,GAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,GAAG,IAAA;AAAA,MACJ,4BAAA,EAA0B,IAAA;AAAA,MAC1B,cAAA,EAAc,MAAA;AAAA,MACd,IAAA,EAAK,OAAA;AAAA,MACL,YAAA,EAAW,mBAAA;AAAA,MACX,SAAA,EAAW,EAAA,CAAG,0CAAA,EAA4C,SAAS,CAAA;AAAA,MAElE,QAAA,EAAA,OAAA,CAAQ,GAAA,CAAI,CAAC,MAAA,KAAW;AACxB,QAAA,MAAM,KAAA,GAAQ,MAAA,GAAS,MAAM,CAAA,IAAK,eAAe,MAAM,CAAA;AACvD,QAAA,uBACC,GAAA;AAAA,UAAC,QAAA;AAAA,UAAA;AAAA,YAEA,IAAA,EAAK,QAAA;AAAA,YACL,mCAAA,EAAiC,IAAA;AAAA,YACjC,aAAA,EAAa,MAAA;AAAA,YACb,cAAY,CAAA,EAAG,KAAK,CAAA,EAAA,EAAK,WAAA,CAAY,MAAM,CAAC,CAAA,CAAA;AAAA,YAC5C,OAAA,EAAS,MAAM,SAAA,CAAU,OAAA,CAAQ,QAAQ,MAAM,CAAA;AAAA,YAC/C,SAAA,EAAW,EAAA;AAAA,cACV,0GAAA;AAAA,cACA,iEAAA;AAAA,cACA,eAAe,MAAM;AAAA,aACtB;AAAA,YAEC,QAAA,EAAA;AAAA,WAAA;AAAA,UAZI;AAAA,SAaN;AAAA,MAEF,CAAC;AAAA;AAAA,GACF;AAEF","file":"spaced-repetition.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 * Anki-style confidence rating row. Four buttons (Again / Hard / Good /\n * Easy) emit a rating; the consumer applies SM-2 / FSRS / hand-rolled\n * scheduling to decide when to surface the card again. Headless on\n * scheduling — this primitive doesn't compute intervals, it just\n * captures the user's signal.\n *\n * @example\n * <SpacedRepetition\n * cardId={card.id}\n * onRate={(rating, id) => scheduler.update(id, rating)}\n * />\n *\n * <SpacedRepetition\n * cardId={card.id}\n * onRate={onRate}\n * labels={{ again: \"Forgot\", hard: \"Tough\", good: \"Got it\", easy: \"Easy\" }}\n * />\n */\nexport type SrsRating = \"again\" | \"hard\" | \"good\" | \"easy\";\n\nexport interface SpacedRepetitionProps extends Omit<React.HTMLAttributes<HTMLDivElement>, \"children\" | \"onRate\"> {\n\t/** Identifier of the card being rated; passed back to onRate. */\n\tcardId: string;\n\t/** Called with (rating, cardId) when the learner picks a button. */\n\tonRate: (rating: SrsRating, cardId: string) => void;\n\t/** Override the default button labels. Defaults: \"Again\" / \"Hard\" / \"Good\" / \"Easy\". */\n\tlabels?: Partial<Record<SrsRating, string>>;\n}\n\nconst RATINGS: SrsRating[] = [\"again\", \"hard\", \"good\", \"easy\"];\n\nconst DEFAULT_LABELS: Record<SrsRating, string> = {\n\tagain: \"Again\",\n\thard: \"Hard\",\n\tgood: \"Good\",\n\teasy: \"Easy\",\n};\n\nconst RATING_HINT: Record<SrsRating, string> = {\n\tagain: \"Couldn't recall — show this card again soon.\",\n\thard: \"Recalled with effort — review sooner than usual.\",\n\tgood: \"Recalled correctly — keep the standard interval.\",\n\teasy: \"Recalled instantly — push the next review further out.\",\n};\n\n// Visual gradient: again (most concerning) → easy (most confident).\n// `easy` carries a faint accent border so the four buttons read as a\n// progression rather than three styled + one plain.\nconst RATING_CLASSES: Record<SrsRating, string> = {\n\tagain:\n\t\t\"border-destructive/40 bg-destructive/10 text-destructive hover:bg-destructive/15 focus-visible:ring-destructive/40\",\n\thard:\n\t\t\"border-secondary bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n\tgood:\n\t\t\"border-primary/40 bg-primary/10 text-foreground hover:bg-primary/15 focus-visible:ring-primary/40\",\n\teasy:\n\t\t\"border-accent bg-accent/10 text-accent-foreground hover:bg-accent/20 focus-visible:ring-accent/40\",\n};\n\nfunction SpacedRepetition({\n\tcardId,\n\tonRate,\n\tlabels,\n\tclassName,\n\t...rest\n}: SpacedRepetitionProps) {\n\tconst onRateRef = React.useRef(onRate);\n\tonRateRef.current = onRate;\n\n\treturn (\n\t\t<div\n\t\t\t{...rest}\n\t\t\tdata-hex-spaced-repetition\n\t\t\tdata-card-id={cardId}\n\t\t\trole=\"group\"\n\t\t\taria-label=\"Confidence rating\"\n\t\t\tclassName={cn(\"inline-flex flex-wrap items-center gap-2\", className)}\n\t\t>\n\t\t\t{RATINGS.map((rating) => {\n\t\t\t\tconst label = labels?.[rating] ?? DEFAULT_LABELS[rating];\n\t\t\t\treturn (\n\t\t\t\t\t<button\n\t\t\t\t\t\tkey={rating}\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tdata-hex-spaced-repetition-button\n\t\t\t\t\t\tdata-rating={rating}\n\t\t\t\t\t\taria-label={`${label}: ${RATING_HINT[rating]}`}\n\t\t\t\t\t\tonClick={() => onRateRef.current(rating, cardId)}\n\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\"inline-flex h-9 items-center justify-center rounded-md border px-3 text-sm font-medium transition-colors\",\n\t\t\t\t\t\t\t\"focus:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n\t\t\t\t\t\t\tRATING_CLASSES[rating],\n\t\t\t\t\t\t)}\n\t\t\t\t\t>\n\t\t\t\t\t\t{label}\n\t\t\t\t\t</button>\n\t\t\t\t);\n\t\t\t})}\n\t\t</div>\n\t);\n}\n\nexport { SpacedRepetition };\n"]}
|
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"]}
|