@hex-core/components 1.6.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_tsup-dts-rollup.d.ts +1597 -0
- package/dist/arc.d.ts +4 -0
- package/dist/arc.js +147 -0
- package/dist/arc.js.map +1 -0
- package/dist/audio-player.d.ts +2 -0
- package/dist/audio-player.js +119 -0
- package/dist/audio-player.js.map +1 -0
- package/dist/audio-waveform.d.ts +2 -0
- package/dist/audio-waveform.js +72 -0
- package/dist/audio-waveform.js.map +1 -0
- package/dist/canvas.d.ts +2 -0
- package/dist/canvas.js +73 -0
- package/dist/canvas.js.map +1 -0
- package/dist/chord.d.ts +4 -0
- package/dist/chord.js +230 -0
- package/dist/chord.js.map +1 -0
- package/dist/cloze.d.ts +3 -0
- package/dist/cloze.js +98 -0
- package/dist/cloze.js.map +1 -0
- package/dist/compare-table.d.ts +4 -0
- package/dist/compare-table.js +109 -0
- package/dist/compare-table.js.map +1 -0
- package/dist/deck.d.ts +3 -0
- package/dist/deck.js +231 -0
- package/dist/deck.js.map +1 -0
- package/dist/dendrogram.d.ts +3 -0
- package/dist/dendrogram.js +162 -0
- package/dist/dendrogram.js.map +1 -0
- package/dist/diagram.d.ts +2 -0
- package/dist/diagram.js +70 -0
- package/dist/diagram.js.map +1 -0
- package/dist/flashcard.d.ts +2 -0
- package/dist/flashcard.js +107 -0
- package/dist/flashcard.js.map +1 -0
- package/dist/flowchart.d.ts +4 -0
- package/dist/flowchart.js +275 -0
- package/dist/flowchart.js.map +1 -0
- package/dist/funnel.d.ts +3 -0
- package/dist/funnel.js +157 -0
- package/dist/funnel.js.map +1 -0
- package/dist/gantt.d.ts +3 -0
- package/dist/gantt.js +279 -0
- package/dist/gantt.js.map +1 -0
- package/dist/image-occlusion.d.ts +3 -0
- package/dist/image-occlusion.js +106 -0
- package/dist/image-occlusion.js.map +1 -0
- package/dist/index.d.ts +86 -0
- package/dist/index.js +4085 -2
- package/dist/index.js.map +1 -1
- package/dist/matrix.d.ts +3 -0
- package/dist/matrix.js +155 -0
- package/dist/matrix.js.map +1 -0
- package/dist/mind-map.d.ts +3 -0
- package/dist/mind-map.js +167 -0
- package/dist/mind-map.js.map +1 -0
- package/dist/org-chart.d.ts +3 -0
- package/dist/org-chart.js +215 -0
- package/dist/org-chart.js.map +1 -0
- package/dist/pyramid.d.ts +3 -0
- package/dist/pyramid.js +150 -0
- package/dist/pyramid.js.map +1 -0
- package/dist/quiz.d.ts +3 -0
- package/dist/quiz.js +128 -0
- package/dist/quiz.js.map +1 -0
- package/dist/sankey.d.ts +4 -0
- package/dist/sankey.js +190 -0
- package/dist/sankey.js.map +1 -0
- package/dist/schemas.d.ts +23 -0
- package/dist/schemas.js +2211 -4
- package/dist/schemas.js.map +1 -1
- package/dist/sequence.d.ts +4 -0
- package/dist/sequence.js +229 -0
- package/dist/sequence.js.map +1 -0
- package/dist/spaced-repetition.d.ts +3 -0
- package/dist/spaced-repetition.js +73 -0
- package/dist/spaced-repetition.js.map +1 -0
- package/dist/speech-recognition.d.ts +2 -0
- package/dist/speech-recognition.js +152 -0
- package/dist/speech-recognition.js.map +1 -0
- package/dist/sunburst.d.ts +3 -0
- package/dist/sunburst.js +205 -0
- package/dist/sunburst.js.map +1 -0
- package/dist/terminal.d.ts +2 -0
- package/dist/terminal.js +153 -0
- package/dist/terminal.js.map +1 -0
- package/dist/time-axis.d.ts +3 -0
- package/dist/time-axis.js +233 -0
- package/dist/time-axis.js.map +1 -0
- package/dist/tool-call.js +6 -1
- package/dist/tool-call.js.map +1 -1
- package/dist/tree-map.d.ts +3 -0
- package/dist/tree-map.js +171 -0
- package/dist/tree-map.js.map +1 -0
- package/dist/venn.d.ts +3 -0
- package/dist/venn.js +196 -0
- package/dist/venn.js.map +1 -0
- package/package.json +47 -3
|
@@ -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"]}
|
|
@@ -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"]}
|
|
@@ -0,0 +1,152 @@
|
|
|
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 getSpeechRecognitionCtor() {
|
|
11
|
+
if (typeof window === "undefined") return null;
|
|
12
|
+
const w = window;
|
|
13
|
+
return w.SpeechRecognition ?? w.webkitSpeechRecognition ?? null;
|
|
14
|
+
}
|
|
15
|
+
function SpeechRecognition({
|
|
16
|
+
isListening,
|
|
17
|
+
onListeningChange,
|
|
18
|
+
onTranscript,
|
|
19
|
+
onError,
|
|
20
|
+
lang = "en-US",
|
|
21
|
+
continuous = true,
|
|
22
|
+
interimResults = true,
|
|
23
|
+
startLabel = "Start dictation",
|
|
24
|
+
stopLabel = "Stop dictation",
|
|
25
|
+
notSupportedLabel = "Speech recognition not supported in this browser",
|
|
26
|
+
disabled,
|
|
27
|
+
className,
|
|
28
|
+
...rest
|
|
29
|
+
}) {
|
|
30
|
+
const recognitionRef = React.useRef(null);
|
|
31
|
+
const [isSupported, setIsSupported] = React.useState(true);
|
|
32
|
+
const onTranscriptRef = React.useRef(onTranscript);
|
|
33
|
+
const onListeningChangeRef = React.useRef(onListeningChange);
|
|
34
|
+
const onErrorRef = React.useRef(onError);
|
|
35
|
+
onTranscriptRef.current = onTranscript;
|
|
36
|
+
onListeningChangeRef.current = onListeningChange;
|
|
37
|
+
onErrorRef.current = onError;
|
|
38
|
+
const mountedRef = React.useRef(true);
|
|
39
|
+
React.useEffect(
|
|
40
|
+
() => () => {
|
|
41
|
+
mountedRef.current = false;
|
|
42
|
+
},
|
|
43
|
+
[]
|
|
44
|
+
);
|
|
45
|
+
const rebuildingRef = React.useRef(false);
|
|
46
|
+
const isListeningRef = React.useRef(isListening);
|
|
47
|
+
isListeningRef.current = isListening;
|
|
48
|
+
React.useEffect(() => {
|
|
49
|
+
const Ctor = getSpeechRecognitionCtor();
|
|
50
|
+
setIsSupported(Ctor !== null);
|
|
51
|
+
}, []);
|
|
52
|
+
React.useEffect(() => {
|
|
53
|
+
if (!isListening) return;
|
|
54
|
+
const Ctor = getSpeechRecognitionCtor();
|
|
55
|
+
if (!Ctor) return;
|
|
56
|
+
const instance = new Ctor();
|
|
57
|
+
instance.continuous = continuous;
|
|
58
|
+
instance.interimResults = interimResults;
|
|
59
|
+
instance.lang = lang;
|
|
60
|
+
instance.onresult = (event) => {
|
|
61
|
+
if (!mountedRef.current) return;
|
|
62
|
+
for (let i = event.resultIndex; i < event.results.length; i++) {
|
|
63
|
+
const result = event.results[i];
|
|
64
|
+
const transcript = result[0]?.transcript ?? "";
|
|
65
|
+
if (transcript) onTranscriptRef.current(transcript, result.isFinal);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
instance.onerror = (event) => {
|
|
69
|
+
if (!mountedRef.current) return;
|
|
70
|
+
onErrorRef.current?.(event.error, event.message);
|
|
71
|
+
if (event.error !== "aborted") onListeningChangeRef.current(false);
|
|
72
|
+
};
|
|
73
|
+
instance.onend = () => {
|
|
74
|
+
if (!mountedRef.current) return;
|
|
75
|
+
if (rebuildingRef.current) return;
|
|
76
|
+
onListeningChangeRef.current(false);
|
|
77
|
+
};
|
|
78
|
+
recognitionRef.current = instance;
|
|
79
|
+
try {
|
|
80
|
+
instance.start();
|
|
81
|
+
} catch (err) {
|
|
82
|
+
onErrorRef.current?.("start-failed", err instanceof Error ? err.message : String(err));
|
|
83
|
+
onListeningChangeRef.current(false);
|
|
84
|
+
}
|
|
85
|
+
return () => {
|
|
86
|
+
rebuildingRef.current = isListeningRef.current;
|
|
87
|
+
instance.onresult = null;
|
|
88
|
+
instance.onerror = null;
|
|
89
|
+
instance.onend = null;
|
|
90
|
+
try {
|
|
91
|
+
instance.abort();
|
|
92
|
+
} catch {
|
|
93
|
+
}
|
|
94
|
+
recognitionRef.current = null;
|
|
95
|
+
queueMicrotask(() => {
|
|
96
|
+
rebuildingRef.current = false;
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
}, [isListening, continuous, interimResults, lang]);
|
|
100
|
+
const tooltip = !isSupported ? notSupportedLabel : isListening ? stopLabel : startLabel;
|
|
101
|
+
const accessibleName = isSupported ? startLabel : notSupportedLabel;
|
|
102
|
+
const isDisabled = disabled || !isSupported;
|
|
103
|
+
return /* @__PURE__ */ jsx(
|
|
104
|
+
"button",
|
|
105
|
+
{
|
|
106
|
+
type: "button",
|
|
107
|
+
...rest,
|
|
108
|
+
disabled: isDisabled,
|
|
109
|
+
"aria-label": accessibleName,
|
|
110
|
+
"aria-pressed": isListening,
|
|
111
|
+
title: tooltip,
|
|
112
|
+
onClick: (event) => {
|
|
113
|
+
rest.onClick?.(event);
|
|
114
|
+
if (event.defaultPrevented || isDisabled) return;
|
|
115
|
+
onListeningChange(!isListening);
|
|
116
|
+
},
|
|
117
|
+
className: cn(
|
|
118
|
+
"inline-flex h-9 w-9 items-center justify-center rounded-md border bg-background",
|
|
119
|
+
"text-foreground transition-colors duration-[var(--duration-normal,200ms)] ease-out",
|
|
120
|
+
"hover:bg-accent hover:text-accent-foreground",
|
|
121
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
122
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
123
|
+
isListening && "animate-pulse border-destructive text-destructive",
|
|
124
|
+
className
|
|
125
|
+
),
|
|
126
|
+
children: /* @__PURE__ */ jsxs(
|
|
127
|
+
"svg",
|
|
128
|
+
{
|
|
129
|
+
"aria-hidden": true,
|
|
130
|
+
viewBox: "0 0 16 16",
|
|
131
|
+
width: "14",
|
|
132
|
+
height: "14",
|
|
133
|
+
fill: "none",
|
|
134
|
+
stroke: "currentColor",
|
|
135
|
+
strokeWidth: "1.5",
|
|
136
|
+
strokeLinecap: "round",
|
|
137
|
+
strokeLinejoin: "round",
|
|
138
|
+
children: [
|
|
139
|
+
/* @__PURE__ */ jsx("rect", { x: "6", y: "2", width: "4", height: "8", rx: "2" }),
|
|
140
|
+
/* @__PURE__ */ jsx("path", { d: "M3.5 7.5a4.5 4.5 0 0 0 9 0" }),
|
|
141
|
+
/* @__PURE__ */ jsx("path", { d: "M8 12v2" }),
|
|
142
|
+
/* @__PURE__ */ jsx("path", { d: "M5.5 14h5" })
|
|
143
|
+
]
|
|
144
|
+
}
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export { SpeechRecognition };
|
|
151
|
+
//# sourceMappingURL=speech-recognition.js.map
|
|
152
|
+
//# sourceMappingURL=speech-recognition.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/utils.ts","../src/ai/speech-recognition/speech-recognition.tsx"],"names":[],"mappings":";;;;;AAQO,SAAS,MAAM,MAAA,EAAsB;AAC3C,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC5B;ACqDA,SAAS,wBAAA,GAAgE;AACxE,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,OAAO,IAAA;AAC1C,EAAA,MAAM,CAAA,GAAI,MAAA;AAIV,EAAA,OAAO,CAAA,CAAE,iBAAA,IAAqB,CAAA,CAAE,uBAAA,IAA2B,IAAA;AAC5D;AA+BA,SAAS,iBAAA,CAAkB;AAAA,EAC1B,WAAA;AAAA,EACA,iBAAA;AAAA,EACA,YAAA;AAAA,EACA,OAAA;AAAA,EACA,IAAA,GAAO,OAAA;AAAA,EACP,UAAA,GAAa,IAAA;AAAA,EACb,cAAA,GAAiB,IAAA;AAAA,EACjB,UAAA,GAAa,iBAAA;AAAA,EACb,SAAA,GAAY,gBAAA;AAAA,EACZ,iBAAA,GAAoB,kDAAA;AAAA,EACpB,QAAA;AAAA,EACA,SAAA;AAAA,EACA,GAAG;AACJ,CAAA,EAA2B;AAC1B,EAAA,MAAM,cAAA,GAAuB,aAAyC,IAAI,CAAA;AAC1E,EAAA,MAAM,CAAC,WAAA,EAAa,cAAc,CAAA,GAAU,eAAS,IAAI,CAAA;AAMzD,EAAA,MAAM,eAAA,GAAwB,aAAO,YAAY,CAAA;AACjD,EAAA,MAAM,oBAAA,GAA6B,aAAO,iBAAiB,CAAA;AAC3D,EAAA,MAAM,UAAA,GAAmB,aAAO,OAAO,CAAA;AACvC,EAAA,eAAA,CAAgB,OAAA,GAAU,YAAA;AAC1B,EAAA,oBAAA,CAAqB,OAAA,GAAU,iBAAA;AAC/B,EAAA,UAAA,CAAW,OAAA,GAAU,OAAA;AAKrB,EAAA,MAAM,UAAA,GAAmB,aAAO,IAAI,CAAA;AACpC,EAAM,KAAA,CAAA,SAAA;AAAA,IACL,MAAM,MAAM;AACX,MAAA,UAAA,CAAW,OAAA,GAAU,KAAA;AAAA,IACtB,CAAA;AAAA,IACA;AAAC,GACF;AAMA,EAAA,MAAM,aAAA,GAAsB,aAAO,KAAK,CAAA;AAIxC,EAAA,MAAM,cAAA,GAAuB,aAAO,WAAW,CAAA;AAC/C,EAAA,cAAA,CAAe,OAAA,GAAU,WAAA;AAGzB,EAAM,gBAAU,MAAM;AACrB,IAAA,MAAM,OAAO,wBAAA,EAAyB;AACtC,IAAA,cAAA,CAAe,SAAS,IAAI,CAAA;AAAA,EAC7B,CAAA,EAAG,EAAE,CAAA;AAKL,EAAM,gBAAU,MAAM;AACrB,IAAA,IAAI,CAAC,WAAA,EAAa;AAElB,IAAA,MAAM,OAAO,wBAAA,EAAyB;AACtC,IAAA,IAAI,CAAC,IAAA,EAAM;AAEX,IAAA,MAAM,QAAA,GAAW,IAAI,IAAA,EAAK;AAC1B,IAAA,QAAA,CAAS,UAAA,GAAa,UAAA;AACtB,IAAA,QAAA,CAAS,cAAA,GAAiB,cAAA;AAC1B,IAAA,QAAA,CAAS,IAAA,GAAO,IAAA;AAEhB,IAAA,QAAA,CAAS,QAAA,GAAW,CAAC,KAAA,KAAU;AAC9B,MAAA,IAAI,CAAC,WAAW,OAAA,EAAS;AACzB,MAAA,KAAA,IAAS,IAAI,KAAA,CAAM,WAAA,EAAa,IAAI,KAAA,CAAM,OAAA,CAAQ,QAAQ,CAAA,EAAA,EAAK;AAC9D,QAAA,MAAM,MAAA,GAAS,KAAA,CAAM,OAAA,CAAQ,CAAC,CAAA;AAC9B,QAAA,MAAM,UAAA,GAAa,MAAA,CAAO,CAAC,CAAA,EAAG,UAAA,IAAc,EAAA;AAC5C,QAAA,IAAI,UAAA,EAAY,eAAA,CAAgB,OAAA,CAAQ,UAAA,EAAY,OAAO,OAAO,CAAA;AAAA,MACnE;AAAA,IACD,CAAA;AACA,IAAA,QAAA,CAAS,OAAA,GAAU,CAAC,KAAA,KAAU;AAC7B,MAAA,IAAI,CAAC,WAAW,OAAA,EAAS;AACzB,MAAA,UAAA,CAAW,OAAA,GAAU,KAAA,CAAM,KAAA,EAAO,KAAA,CAAM,OAAO,CAAA;AAE/C,MAAA,IAAI,KAAA,CAAM,KAAA,KAAU,SAAA,EAAW,oBAAA,CAAqB,QAAQ,KAAK,CAAA;AAAA,IAClE,CAAA;AACA,IAAA,QAAA,CAAS,QAAQ,MAAM;AACtB,MAAA,IAAI,CAAC,WAAW,OAAA,EAAS;AAGzB,MAAA,IAAI,cAAc,OAAA,EAAS;AAC3B,MAAA,oBAAA,CAAqB,QAAQ,KAAK,CAAA;AAAA,IACnC,CAAA;AAEA,IAAA,cAAA,CAAe,OAAA,GAAU,QAAA;AACzB,IAAA,IAAI;AACH,MAAA,QAAA,CAAS,KAAA,EAAM;AAAA,IAChB,SAAS,GAAA,EAAK;AAGb,MAAA,UAAA,CAAW,OAAA,GAAU,gBAAgB,GAAA,YAAe,KAAA,GAAQ,IAAI,OAAA,GAAU,MAAA,CAAO,GAAG,CAAC,CAAA;AACrF,MAAA,oBAAA,CAAqB,QAAQ,KAAK,CAAA;AAAA,IACnC;AAEA,IAAA,OAAO,MAAM;AAKZ,MAAA,aAAA,CAAc,UAAU,cAAA,CAAe,OAAA;AACvC,MAAA,QAAA,CAAS,QAAA,GAAW,IAAA;AACpB,MAAA,QAAA,CAAS,OAAA,GAAU,IAAA;AACnB,MAAA,QAAA,CAAS,KAAA,GAAQ,IAAA;AACjB,MAAA,IAAI;AACH,QAAA,QAAA,CAAS,KAAA,EAAM;AAAA,MAChB,CAAA,CAAA,MAAQ;AAAA,MAER;AACA,MAAA,cAAA,CAAe,OAAA,GAAU,IAAA;AAEzB,MAAA,cAAA,CAAe,MAAM;AACpB,QAAA,aAAA,CAAc,OAAA,GAAU,KAAA;AAAA,MACzB,CAAC,CAAA;AAAA,IACF,CAAA;AAAA,EACD,GAAG,CAAC,WAAA,EAAa,UAAA,EAAY,cAAA,EAAgB,IAAI,CAAC,CAAA;AAElD,EAAA,MAAM,OAAA,GAAU,CAAC,WAAA,GACd,iBAAA,GACA,cACC,SAAA,GACA,UAAA;AAIJ,EAAA,MAAM,cAAA,GAAiB,cAAc,UAAA,GAAa,iBAAA;AAClD,EAAA,MAAM,UAAA,GAAa,YAAY,CAAC,WAAA;AAEhC,EAAA,uBACC,GAAA;AAAA,IAAC,QAAA;AAAA,IAAA;AAAA,MACA,IAAA,EAAK,QAAA;AAAA,MACJ,GAAG,IAAA;AAAA,MACJ,QAAA,EAAU,UAAA;AAAA,MACV,YAAA,EAAY,cAAA;AAAA,MACZ,cAAA,EAAc,WAAA;AAAA,MACd,KAAA,EAAO,OAAA;AAAA,MACP,OAAA,EAAS,CAAC,KAAA,KAAU;AACnB,QAAA,IAAA,CAAK,UAAU,KAAK,CAAA;AACpB,QAAA,IAAI,KAAA,CAAM,oBAAoB,UAAA,EAAY;AAC1C,QAAA,iBAAA,CAAkB,CAAC,WAAW,CAAA;AAAA,MAC/B,CAAA;AAAA,MACA,SAAA,EAAW,EAAA;AAAA,QACV,iFAAA;AAAA,QACA,oFAAA;AAAA,QACA,8CAAA;AAAA,QACA,qGAAA;AAAA,QACA,iDAAA;AAAA,QACA,WAAA,IAAe,mDAAA;AAAA,QACf;AAAA,OACD;AAAA,MAEA,QAAA,kBAAA,IAAA;AAAA,QAAC,KAAA;AAAA,QAAA;AAAA,UACA,aAAA,EAAW,IAAA;AAAA,UACX,OAAA,EAAQ,WAAA;AAAA,UACR,KAAA,EAAM,IAAA;AAAA,UACN,MAAA,EAAO,IAAA;AAAA,UACP,IAAA,EAAK,MAAA;AAAA,UACL,MAAA,EAAO,cAAA;AAAA,UACP,WAAA,EAAY,KAAA;AAAA,UACZ,aAAA,EAAc,OAAA;AAAA,UACd,cAAA,EAAe,OAAA;AAAA,UAEf,QAAA,EAAA;AAAA,4BAAA,GAAA,CAAC,MAAA,EAAA,EAAK,CAAA,EAAE,GAAA,EAAI,CAAA,EAAE,GAAA,EAAI,OAAM,GAAA,EAAI,MAAA,EAAO,GAAA,EAAI,EAAA,EAAG,GAAA,EAAI,CAAA;AAAA,4BAC9C,GAAA,CAAC,MAAA,EAAA,EAAK,CAAA,EAAE,4BAAA,EAA6B,CAAA;AAAA,4BACrC,GAAA,CAAC,MAAA,EAAA,EAAK,CAAA,EAAE,SAAA,EAAU,CAAA;AAAA,4BAClB,GAAA,CAAC,MAAA,EAAA,EAAK,CAAA,EAAE,WAAA,EAAY;AAAA;AAAA;AAAA;AACrB;AAAA,GACD;AAEF","file":"speech-recognition.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 * Browser SpeechRecognition wrapper. Renders a mic toggle button that\n * starts/stops the Web Speech API and emits transcript chunks.\n *\n * Headless on data: `isListening` + `onListeningChange` are required so\n * the consumer keeps state where it fits (a `useChat` hook, redux,\n * local state). `onTranscript` fires per result with `isFinal` so the\n * consumer can append finalized phrases and replace interim ones.\n *\n * Falls back to a disabled button labeled `notSupportedLabel` when the\n * browser lacks `SpeechRecognition` (Firefox as of 2026, older Safari).\n *\n * @example\n * const [listening, setListening] = useState(false);\n * const [text, setText] = useState(\"\");\n * <SpeechRecognition\n * isListening={listening}\n * onListeningChange={setListening}\n * onTranscript={(chunk, isFinal) => {\n * if (isFinal) setText((t) => t + chunk);\n * }}\n * />\n */\ntype SpeechRecognitionConstructor = new () => SpeechRecognitionInstance;\n\ninterface SpeechRecognitionResultLike {\n\treadonly isFinal: boolean;\n\treadonly length: number;\n\treadonly [index: number]: { readonly transcript: string };\n}\n\ninterface SpeechRecognitionResultListLike {\n\treadonly length: number;\n\treadonly [index: number]: SpeechRecognitionResultLike;\n}\n\ninterface SpeechRecognitionEventLike {\n\treadonly resultIndex: number;\n\treadonly results: SpeechRecognitionResultListLike;\n}\n\ninterface SpeechRecognitionErrorEventLike {\n\treadonly error: string;\n\treadonly message?: string;\n}\n\ninterface SpeechRecognitionInstance {\n\tcontinuous: boolean;\n\tinterimResults: boolean;\n\tlang: string;\n\tonresult: ((event: SpeechRecognitionEventLike) => void) | null;\n\tonerror: ((event: SpeechRecognitionErrorEventLike) => void) | null;\n\tonend: (() => void) | null;\n\tstart(): void;\n\tstop(): void;\n\tabort(): void;\n}\n\nfunction getSpeechRecognitionCtor(): SpeechRecognitionConstructor | null {\n\tif (typeof window === \"undefined\") return null;\n\tconst w = window as unknown as {\n\t\tSpeechRecognition?: SpeechRecognitionConstructor;\n\t\twebkitSpeechRecognition?: SpeechRecognitionConstructor;\n\t};\n\treturn w.SpeechRecognition ?? w.webkitSpeechRecognition ?? null;\n}\n\nexport interface SpeechRecognitionProps\n\textends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, \"onError\"> {\n\t/** Controlled listening state. */\n\tisListening: boolean;\n\t/** Called when listening starts/stops (user toggle or browser auto-end). */\n\tonListeningChange: (listening: boolean) => void;\n\t/** Called per transcript chunk. `isFinal` indicates a finalized phrase. */\n\tonTranscript: (text: string, isFinal: boolean) => void;\n\t/** Called on browser error (e.g. \"not-allowed\", \"no-speech\", \"network\"). */\n\tonError?: (error: string, message?: string) => void;\n\t/** BCP-47 language tag. Default `\"en-US\"`. */\n\tlang?: string;\n\t/** Keep listening across pauses. Default `true`. */\n\tcontinuous?: boolean;\n\t/** Emit interim (in-progress) results. Default `true`. */\n\tinterimResults?: boolean;\n\t/** Accessible name when idle. Default `\"Start dictation\"`. */\n\tstartLabel?: string;\n\t/** Accessible name when listening. Default `\"Stop dictation\"`. */\n\tstopLabel?: string;\n\t/** Accessible name + tooltip when the browser lacks the API. */\n\tnotSupportedLabel?: string;\n}\n\n/**\n * Renders a mic toggle button wired to the Web Speech API.\n * @param props - controlled listening state + transcript callback\n * @returns A button element that toggles speech recognition\n */\nfunction SpeechRecognition({\n\tisListening,\n\tonListeningChange,\n\tonTranscript,\n\tonError,\n\tlang = \"en-US\",\n\tcontinuous = true,\n\tinterimResults = true,\n\tstartLabel = \"Start dictation\",\n\tstopLabel = \"Stop dictation\",\n\tnotSupportedLabel = \"Speech recognition not supported in this browser\",\n\tdisabled,\n\tclassName,\n\t...rest\n}: SpeechRecognitionProps) {\n\tconst recognitionRef = React.useRef<SpeechRecognitionInstance | null>(null);\n\tconst [isSupported, setIsSupported] = React.useState(true);\n\n\t// \"Latest ref\" pattern, assigned synchronously in render so a Web Speech\n\t// callback firing between commit and a useEffect can never see stale\n\t// closures. React permits ref mutation during render when the assignment\n\t// is purely a latest-value mirror.\n\tconst onTranscriptRef = React.useRef(onTranscript);\n\tconst onListeningChangeRef = React.useRef(onListeningChange);\n\tconst onErrorRef = React.useRef(onError);\n\tonTranscriptRef.current = onTranscript;\n\tonListeningChangeRef.current = onListeningChange;\n\tonErrorRef.current = onError;\n\n\t// Mounted guard: the engine fires onend asynchronously, sometimes after\n\t// the React tree has been torn down. Without this, a stale handler can\n\t// invoke setState on an unmounted parent.\n\tconst mountedRef = React.useRef(true);\n\tReact.useEffect(\n\t\t() => () => {\n\t\t\tmountedRef.current = false;\n\t\t},\n\t\t[],\n\t);\n\n\t// Set when the lifecycle effect's cleanup runs because of a prop change\n\t// (lang/continuous/interimResults), not user-initiated stop. The about-\n\t// to-fire onend should NOT bubble back as `onListeningChange(false)` in\n\t// that case — the new effect run is about to re-create the engine.\n\tconst rebuildingRef = React.useRef(false);\n\t// Latest `isListening` mirror so cleanup can read the NEW value (closure\n\t// captures OLD). NEW=true means this is a prop-change rebuild; NEW=false\n\t// means the user stopped.\n\tconst isListeningRef = React.useRef(isListening);\n\tisListeningRef.current = isListening;\n\n\t// SSR: ctor lookup must run after mount.\n\tReact.useEffect(() => {\n\t\tconst Ctor = getSpeechRecognitionCtor();\n\t\tsetIsSupported(Ctor !== null);\n\t}, []);\n\n\t// Toggle the engine on `isListening` change. Recreate per session so a\n\t// stuck session can't leak — Chrome's recognition is single-use after\n\t// onend in some failure modes.\n\tReact.useEffect(() => {\n\t\tif (!isListening) return;\n\n\t\tconst Ctor = getSpeechRecognitionCtor();\n\t\tif (!Ctor) return;\n\n\t\tconst instance = new Ctor();\n\t\tinstance.continuous = continuous;\n\t\tinstance.interimResults = interimResults;\n\t\tinstance.lang = lang;\n\n\t\tinstance.onresult = (event) => {\n\t\t\tif (!mountedRef.current) return;\n\t\t\tfor (let i = event.resultIndex; i < event.results.length; i++) {\n\t\t\t\tconst result = event.results[i];\n\t\t\t\tconst transcript = result[0]?.transcript ?? \"\";\n\t\t\t\tif (transcript) onTranscriptRef.current(transcript, result.isFinal);\n\t\t\t}\n\t\t};\n\t\tinstance.onerror = (event) => {\n\t\t\tif (!mountedRef.current) return;\n\t\t\tonErrorRef.current?.(event.error, event.message);\n\t\t\t// \"aborted\" is a normal stop signal — don't toggle off twice.\n\t\t\tif (event.error !== \"aborted\") onListeningChangeRef.current(false);\n\t\t};\n\t\tinstance.onend = () => {\n\t\t\tif (!mountedRef.current) return;\n\t\t\t// Skip the off-toggle when cleanup is from a prop-change rebuild;\n\t\t\t// the next effect run will re-start with the new options.\n\t\t\tif (rebuildingRef.current) return;\n\t\t\tonListeningChangeRef.current(false);\n\t\t};\n\n\t\trecognitionRef.current = instance;\n\t\ttry {\n\t\t\tinstance.start();\n\t\t} catch (err) {\n\t\t\t// Chrome throws if start() is called twice; surface as an error\n\t\t\t// rather than letting it crash the React tree.\n\t\t\tonErrorRef.current?.(\"start-failed\", err instanceof Error ? err.message : String(err));\n\t\t\tonListeningChangeRef.current(false);\n\t\t}\n\n\t\treturn () => {\n\t\t\t// Mark this teardown as a rebuild iff isListening is STILL true\n\t\t\t// in the latest render (only lang/continuous/interimResults\n\t\t\t// changed). On a real user-stop the NEW isListening is false and\n\t\t\t// any synchronous onend-from-abort should toggle parent state.\n\t\t\trebuildingRef.current = isListeningRef.current;\n\t\t\tinstance.onresult = null;\n\t\t\tinstance.onerror = null;\n\t\t\tinstance.onend = null;\n\t\t\ttry {\n\t\t\t\tinstance.abort();\n\t\t\t} catch {\n\t\t\t\t// abort() throws if the engine never started; safe to ignore.\n\t\t\t}\n\t\t\trecognitionRef.current = null;\n\t\t\t// Reset on next microtask so a follow-up effect run sees a clean slate.\n\t\t\tqueueMicrotask(() => {\n\t\t\t\trebuildingRef.current = false;\n\t\t\t});\n\t\t};\n\t}, [isListening, continuous, interimResults, lang]);\n\n\tconst tooltip = !isSupported\n\t\t? notSupportedLabel\n\t\t: isListening\n\t\t\t? stopLabel\n\t\t\t: startLabel;\n\t// aria-label is stable when supported so screen readers don't re-announce\n\t// the entire button on each toggle. State is conveyed via aria-pressed.\n\t// Only the unsupported case swaps the accessible name.\n\tconst accessibleName = isSupported ? startLabel : notSupportedLabel;\n\tconst isDisabled = disabled || !isSupported;\n\n\treturn (\n\t\t<button\n\t\t\ttype=\"button\"\n\t\t\t{...rest}\n\t\t\tdisabled={isDisabled}\n\t\t\taria-label={accessibleName}\n\t\t\taria-pressed={isListening}\n\t\t\ttitle={tooltip}\n\t\t\tonClick={(event) => {\n\t\t\t\trest.onClick?.(event);\n\t\t\t\tif (event.defaultPrevented || isDisabled) return;\n\t\t\t\tonListeningChange(!isListening);\n\t\t\t}}\n\t\t\tclassName={cn(\n\t\t\t\t\"inline-flex h-9 w-9 items-center justify-center rounded-md border bg-background\",\n\t\t\t\t\"text-foreground transition-colors duration-[var(--duration-normal,200ms)] ease-out\",\n\t\t\t\t\"hover:bg-accent hover:text-accent-foreground\",\n\t\t\t\t\"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\",\n\t\t\t\t\"disabled:cursor-not-allowed disabled:opacity-50\",\n\t\t\t\tisListening && \"animate-pulse border-destructive text-destructive\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t>\n\t\t\t<svg\n\t\t\t\taria-hidden\n\t\t\t\tviewBox=\"0 0 16 16\"\n\t\t\t\twidth=\"14\"\n\t\t\t\theight=\"14\"\n\t\t\t\tfill=\"none\"\n\t\t\t\tstroke=\"currentColor\"\n\t\t\t\tstrokeWidth=\"1.5\"\n\t\t\t\tstrokeLinecap=\"round\"\n\t\t\t\tstrokeLinejoin=\"round\"\n\t\t\t>\n\t\t\t\t<rect x=\"6\" y=\"2\" width=\"4\" height=\"8\" rx=\"2\" />\n\t\t\t\t<path d=\"M3.5 7.5a4.5 4.5 0 0 0 9 0\" />\n\t\t\t\t<path d=\"M8 12v2\" />\n\t\t\t\t<path d=\"M5.5 14h5\" />\n\t\t\t</svg>\n\t\t</button>\n\t);\n}\n\nexport { SpeechRecognition };\n"]}
|