@glyphjs/components 0.1.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/index.js ADDED
@@ -0,0 +1,4855 @@
1
+ import { accordionSchema, architectureSchema, calloutSchema, cardSchema, chartSchema, codediffSchema, comparisonSchema, equationSchema, filetreeSchema, flowchartSchema, graphSchema, infographicSchema, kpiSchema, mindmapSchema, quizSchema, relationSchema, sequenceSchema, stepsSchema, tableSchema, tabsSchema, timelineSchema } from '@glyphjs/schemas';
2
+ import { jsxs, jsx } from 'react/jsx-runtime';
3
+ import { useRef, useState, useEffect, useCallback, useMemo } from 'react';
4
+ import * as d32 from 'd3';
5
+ import { scaleTime, scaleOrdinal } from 'd3';
6
+ import dagre from 'dagre';
7
+ import katex from 'katex';
8
+
9
+ // src/callout/index.ts
10
+ var CALLOUT_ICONS = {
11
+ info: "\u2139\uFE0F",
12
+ warning: "\u26A0\uFE0F",
13
+ error: "\u274C",
14
+ tip: "\u{1F4A1}"
15
+ };
16
+ var CALLOUT_LABELS = {
17
+ info: "Information",
18
+ warning: "Warning",
19
+ error: "Error",
20
+ tip: "Tip"
21
+ };
22
+ function Callout({ data }) {
23
+ const { type, title, content } = data;
24
+ const containerStyle3 = {
25
+ backgroundColor: `var(--glyph-callout-${type}-bg)`,
26
+ borderLeft: `4px solid var(--glyph-callout-${type}-border)`,
27
+ borderRadius: "var(--glyph-radius-md, 0.1875rem)",
28
+ padding: "var(--glyph-spacing-md, 1rem)",
29
+ margin: "var(--glyph-spacing-sm, 0.5rem) 0",
30
+ fontFamily: "var(--glyph-font-body, system-ui, sans-serif)",
31
+ color: "var(--glyph-text, #1a2035)",
32
+ display: "flex",
33
+ gap: "var(--glyph-spacing-sm, 0.5rem)",
34
+ alignItems: "flex-start"
35
+ };
36
+ const iconStyle = {
37
+ flexShrink: 0,
38
+ fontSize: "1.25em",
39
+ lineHeight: 1
40
+ };
41
+ const bodyStyle2 = {
42
+ flex: 1,
43
+ minWidth: 0
44
+ };
45
+ const titleStyle2 = {
46
+ fontWeight: 700,
47
+ marginBottom: "var(--glyph-spacing-xs, 0.25rem)"
48
+ };
49
+ return /* @__PURE__ */ jsxs("div", { role: "note", "aria-label": CALLOUT_LABELS[type], style: containerStyle3, children: [
50
+ /* @__PURE__ */ jsx("span", { style: iconStyle, "aria-hidden": "true", children: CALLOUT_ICONS[type] }),
51
+ /* @__PURE__ */ jsxs("div", { style: bodyStyle2, children: [
52
+ title && /* @__PURE__ */ jsx("div", { style: titleStyle2, children: title }),
53
+ /* @__PURE__ */ jsx("div", { children: content })
54
+ ] })
55
+ ] });
56
+ }
57
+
58
+ // src/callout/index.ts
59
+ var calloutDefinition = {
60
+ type: "ui:callout",
61
+ schema: calloutSchema,
62
+ render: Callout
63
+ };
64
+ var DEFAULT_WIDTH = 600;
65
+ var DEFAULT_HEIGHT = 400;
66
+ var MARGIN = { top: 20, right: 30, bottom: 50, left: 60 };
67
+ var COLOR_SCHEME = [
68
+ "#00d4aa",
69
+ // cyan-green
70
+ "#b44dff",
71
+ // purple
72
+ "#22c55e",
73
+ // green
74
+ "#e040fb",
75
+ // magenta
76
+ "#00e5ff",
77
+ // teal
78
+ "#84cc16",
79
+ // lime
80
+ "#f472b6",
81
+ // rose
82
+ "#fb923c",
83
+ // orange
84
+ "#818cf8",
85
+ // indigo
86
+ "#38bdf8"
87
+ // sky
88
+ ];
89
+ function getNumericValue(d, key) {
90
+ const v = d[key];
91
+ return typeof v === "number" ? v : Number(v);
92
+ }
93
+ function getAllNumericValues(series, key) {
94
+ return series.flatMap(
95
+ (s) => s.data.map((d) => getNumericValue(d, key))
96
+ );
97
+ }
98
+ function renderAxes(g, xScale, yScale, xAxisConfig, yAxisConfig, innerWidth, innerHeight) {
99
+ const xAxisG = g.append("g").attr("class", "x-axis").attr("transform", `translate(0,${String(innerHeight)})`);
100
+ if ("bandwidth" in xScale) {
101
+ xAxisG.call(
102
+ d32.axisBottom(xScale)
103
+ );
104
+ } else {
105
+ xAxisG.call(
106
+ d32.axisBottom(xScale)
107
+ );
108
+ }
109
+ xAxisG.selectAll("text, line, path").attr("fill", "var(--glyph-text, #1a2035)").attr("stroke", "var(--glyph-grid, #1a2035)");
110
+ if (xAxisConfig?.label) {
111
+ g.append("text").attr("class", "x-label").attr("x", innerWidth / 2).attr("y", innerHeight + MARGIN.bottom - 6).attr("text-anchor", "middle").attr("fill", "var(--glyph-text, #1a2035)").attr("font-size", "12px").text(xAxisConfig.label);
112
+ }
113
+ const yAxisG = g.append("g").attr("class", "y-axis");
114
+ yAxisG.call(
115
+ d32.axisLeft(yScale)
116
+ );
117
+ yAxisG.selectAll("text, line, path").attr("fill", "var(--glyph-text, #1a2035)").attr("stroke", "var(--glyph-grid, #1a2035)");
118
+ if (yAxisConfig?.label) {
119
+ g.append("text").attr("class", "y-label").attr("transform", "rotate(-90)").attr("x", -innerHeight / 2).attr("y", -MARGIN.left + 14).attr("text-anchor", "middle").attr("fill", "var(--glyph-text, #1a2035)").attr("font-size", "12px").text(yAxisConfig.label);
120
+ }
121
+ }
122
+ function renderGridLines(g, yScale, innerWidth) {
123
+ g.append("g").attr("class", "grid").selectAll("line").data(yScale.ticks()).join("line").attr("x1", 0).attr("x2", innerWidth).attr("y1", (d) => yScale(d)).attr("y2", (d) => yScale(d)).attr("stroke", "var(--glyph-grid, #1a2035)").attr("stroke-opacity", 0.5).attr("stroke-dasharray", "2,2");
124
+ }
125
+ function renderLineSeries(g, seriesData, xScalePoint, yScale, yKey, xKey, color3, index, seriesName, showTooltip, hideTooltip) {
126
+ const line6 = d32.line().x((d) => xScalePoint(d)).y((d) => yScale(getNumericValue(d, yKey)));
127
+ g.append("path").datum(seriesData).attr("fill", "none").attr("stroke", color3).attr("stroke-width", 2).attr("d", line6);
128
+ g.selectAll(`.dot-${String(index)}`).data(seriesData).join("circle").attr("class", `dot-${String(index)}`).attr("cx", (d) => xScalePoint(d)).attr("cy", (d) => yScale(getNumericValue(d, yKey))).attr("r", 3.5).attr("fill", color3).attr("cursor", "pointer").on("mouseenter", function(event, d) {
129
+ showTooltip(event, `${seriesName}: ${String(d[xKey] ?? "")}, ${String(d[yKey] ?? "")}`);
130
+ }).on("mouseleave", () => hideTooltip());
131
+ }
132
+ function renderAreaSeries(g, seriesData, xScalePoint, yScale, yKey, xKey, innerHeight, color3, index, seriesName, showTooltip, hideTooltip) {
133
+ const area2 = d32.area().x((d) => xScalePoint(d)).y0(innerHeight).y1((d) => yScale(getNumericValue(d, yKey)));
134
+ g.append("path").datum(seriesData).attr("fill", color3).attr("fill-opacity", 0.3).attr("stroke", color3).attr("stroke-width", 1.5).attr("d", area2);
135
+ g.selectAll(`.dot-${String(index)}`).data(seriesData).join("circle").attr("class", `dot-${String(index)}`).attr("cx", (d) => xScalePoint(d)).attr("cy", (d) => yScale(getNumericValue(d, yKey))).attr("r", 3).attr("fill", color3).attr("cursor", "pointer").on("mouseenter", function(event, d) {
136
+ showTooltip(event, `${seriesName}: ${String(d[xKey] ?? "")}, ${String(d[yKey] ?? "")}`);
137
+ }).on("mouseleave", () => hideTooltip());
138
+ }
139
+ function renderBarSeries(g, seriesData, xScale, yScale, yKey, xKey, color3, index, seriesCount, innerHeight, seriesName, showTooltip, hideTooltip) {
140
+ if (!("bandwidth" in xScale)) return;
141
+ const band = xScale;
142
+ const barWidth = band.bandwidth() / seriesCount;
143
+ g.selectAll(`.bar-${String(index)}`).data(seriesData).join("rect").attr("class", `bar-${String(index)}`).attr("x", (d) => (band(String(d[xKey] ?? "")) ?? 0) + barWidth * index).attr("y", (d) => yScale(getNumericValue(d, yKey))).attr("width", barWidth - 1).attr("height", (d) => innerHeight - yScale(getNumericValue(d, yKey))).attr("fill", color3).attr("cursor", "pointer").on("mouseenter", function(event, d) {
144
+ showTooltip(event, `${seriesName}: ${String(d[xKey] ?? "")}, ${String(d[yKey] ?? "")}`);
145
+ }).on("mouseleave", () => hideTooltip());
146
+ }
147
+ function renderOHLCSeries(g, seriesData, xScale, xScalePoint, yScale, innerWidth, seriesName, showTooltip, hideTooltip) {
148
+ const candleWidth = "bandwidth" in xScale ? xScale.bandwidth() * 0.6 : Math.max(4, innerWidth / (seriesData.length * 2));
149
+ seriesData.forEach((d) => {
150
+ const open = getNumericValue(d, "open");
151
+ const close = getNumericValue(d, "close");
152
+ const high = getNumericValue(d, "high");
153
+ const low = getNumericValue(d, "low");
154
+ const cx = xScalePoint(d);
155
+ const isBullish = close >= open;
156
+ const candleColor = isBullish ? "var(--glyph-chart-bullish, #22c55e)" : "var(--glyph-chart-bearish, #f87171)";
157
+ g.append("line").attr("x1", cx).attr("x2", cx).attr("y1", yScale(high)).attr("y2", yScale(low)).attr("stroke", candleColor).attr("stroke-width", 1);
158
+ const bodyTop = yScale(Math.max(open, close));
159
+ const bodyBottom = yScale(Math.min(open, close));
160
+ const bodyHeight = Math.max(1, bodyBottom - bodyTop);
161
+ g.append("rect").attr("x", cx - candleWidth / 2).attr("y", bodyTop).attr("width", candleWidth).attr("height", bodyHeight).attr("fill", candleColor).attr("stroke", candleColor).attr("cursor", "pointer").on("mouseenter", (event) => {
162
+ showTooltip(
163
+ event,
164
+ `${seriesName}: O=${String(open)} H=${String(high)} L=${String(low)} C=${String(close)}`
165
+ );
166
+ }).on("mouseleave", () => hideTooltip());
167
+ });
168
+ }
169
+ function renderLegend(sel, series, marginLeft, marginTop, fontSize = "12px") {
170
+ const legendG = sel.append("g").attr("transform", `translate(${String(marginLeft + 8)},${String(marginTop)})`);
171
+ series.forEach((s, i) => {
172
+ const color3 = COLOR_SCHEME[i % COLOR_SCHEME.length] ?? "var(--glyph-text, #1a2035)";
173
+ const row = legendG.append("g").attr("transform", `translate(0,${String(i * 20)})`);
174
+ row.append("rect").attr("width", 14).attr("height", 14).attr("fill", color3).attr("rx", 2);
175
+ row.append("text").attr("x", 20).attr("y", 11).attr("fill", "var(--glyph-text, #1a2035)").attr("font-size", fontSize).text(s.name);
176
+ });
177
+ }
178
+ function ChartAccessibleTable({
179
+ type,
180
+ series,
181
+ xKey,
182
+ yKey,
183
+ xLabel,
184
+ yLabel
185
+ }) {
186
+ return /* @__PURE__ */ jsxs(
187
+ "table",
188
+ {
189
+ style: {
190
+ position: "absolute",
191
+ width: "1px",
192
+ height: "1px",
193
+ padding: 0,
194
+ margin: "-1px",
195
+ overflow: "hidden",
196
+ clip: "rect(0,0,0,0)",
197
+ whiteSpace: "nowrap",
198
+ border: 0
199
+ },
200
+ children: [
201
+ /* @__PURE__ */ jsxs("caption", { children: [
202
+ type,
203
+ " chart data"
204
+ ] }),
205
+ series.map((s, si) => /* @__PURE__ */ jsxs("tbody", { children: [
206
+ /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("th", { colSpan: 2, children: s.name }) }),
207
+ /* @__PURE__ */ jsxs("tr", { children: [
208
+ /* @__PURE__ */ jsx("th", { children: xLabel ?? xKey }),
209
+ /* @__PURE__ */ jsx("th", { children: yLabel ?? yKey })
210
+ ] }),
211
+ s.data.map((d, di) => /* @__PURE__ */ jsxs("tr", { children: [
212
+ /* @__PURE__ */ jsx("td", { children: String(d[xKey] ?? "") }),
213
+ /* @__PURE__ */ jsx("td", { children: type === "ohlc" ? `O=${String(d["open"] ?? "")} H=${String(d["high"] ?? "")} L=${String(d["low"] ?? "")} C=${String(d["close"] ?? "")}` : String(d[yKey] ?? "") })
214
+ ] }, di))
215
+ ] }, si))
216
+ ]
217
+ }
218
+ );
219
+ }
220
+ function computeScales(width, height, type, series, xKey, yKey, margin) {
221
+ const innerWidth = width - margin.left - margin.right;
222
+ const innerHeight = height - margin.top - margin.bottom;
223
+ const firstRecord = series[0]?.data[0];
224
+ const xIsNumeric = firstRecord != null && typeof firstRecord[xKey] === "number";
225
+ let xScale;
226
+ let xScalePoint;
227
+ if (type === "bar" || !xIsNumeric) {
228
+ const allLabels = series.flatMap(
229
+ (s) => s.data.map((d) => String(d[xKey] ?? ""))
230
+ );
231
+ const uniqueLabels = [...new Set(allLabels)];
232
+ const band = d32.scaleBand().domain(uniqueLabels).range([0, innerWidth]).padding(0.2);
233
+ xScale = band;
234
+ xScalePoint = (d) => (band(String(d[xKey] ?? "")) ?? 0) + band.bandwidth() / 2;
235
+ } else {
236
+ const allX = getAllNumericValues(series, xKey);
237
+ const linear = d32.scaleLinear().domain(d32.extent(allX)).nice().range([0, innerWidth]);
238
+ xScale = linear;
239
+ xScalePoint = (d) => linear(getNumericValue(d, xKey));
240
+ }
241
+ let yMin;
242
+ let yMax;
243
+ if (type === "ohlc") {
244
+ const lows = getAllNumericValues(series, "low");
245
+ const highs = getAllNumericValues(series, "high");
246
+ yMin = d32.min(lows) ?? 0;
247
+ yMax = d32.max(highs) ?? 0;
248
+ } else {
249
+ const allY = getAllNumericValues(series, yKey);
250
+ yMin = d32.min(allY) ?? 0;
251
+ yMax = d32.max(allY) ?? 0;
252
+ }
253
+ const yScale = d32.scaleLinear().domain([yMin, yMax]).nice().range([innerHeight, 0]);
254
+ return { xScale, xScalePoint, yScale, innerWidth, innerHeight };
255
+ }
256
+ function Chart({
257
+ data,
258
+ container: containerCtx
259
+ }) {
260
+ const containerRef = useRef(null);
261
+ const svgRef = useRef(null);
262
+ const tooltipRef = useRef(null);
263
+ const [width, setWidth] = useState(DEFAULT_WIDTH);
264
+ const { type, series, xAxis, yAxis, legend } = data;
265
+ const xKey = xAxis?.key ?? "x";
266
+ const yKey = yAxis?.key ?? "y";
267
+ const height = DEFAULT_HEIGHT;
268
+ const isCompact = containerCtx.tier === "compact";
269
+ const margin = isCompact ? {
270
+ top: Math.round(MARGIN.top * 0.7),
271
+ right: Math.round(MARGIN.right * 0.7),
272
+ bottom: Math.round(MARGIN.bottom * 0.7),
273
+ left: Math.round(MARGIN.left * 0.7)
274
+ } : MARGIN;
275
+ useEffect(() => {
276
+ const container = containerRef.current;
277
+ if (!container) return;
278
+ const observer = new ResizeObserver((entries) => {
279
+ for (const entry of entries) {
280
+ const cr = entry.contentRect;
281
+ if (cr.width > 0) {
282
+ setWidth(cr.width);
283
+ }
284
+ }
285
+ });
286
+ observer.observe(container);
287
+ return () => observer.disconnect();
288
+ }, []);
289
+ const showTooltip = useCallback((event, text) => {
290
+ const tip = tooltipRef.current;
291
+ if (!tip) return;
292
+ tip.textContent = text;
293
+ tip.style.display = "block";
294
+ tip.style.left = `${String(event.offsetX + 12)}px`;
295
+ tip.style.top = `${String(event.offsetY - 12)}px`;
296
+ }, []);
297
+ const hideTooltip = useCallback(() => {
298
+ const tip = tooltipRef.current;
299
+ if (!tip) return;
300
+ tip.style.display = "none";
301
+ }, []);
302
+ const scales = useMemo(
303
+ () => computeScales(width, height, type, series, xKey, yKey, margin),
304
+ [width, height, type, series, xKey, yKey, margin]
305
+ );
306
+ useEffect(() => {
307
+ const svg = svgRef.current;
308
+ if (!svg || series.length === 0) return;
309
+ const sel = d32.select(svg);
310
+ sel.selectAll("*").remove();
311
+ const { xScale, xScalePoint, yScale, innerWidth, innerHeight } = scales;
312
+ const g = sel.append("g").attr("transform", `translate(${String(margin.left)},${String(margin.top)})`);
313
+ renderAxes(g, xScale, yScale, xAxis, yAxis, innerWidth, innerHeight);
314
+ renderGridLines(g, yScale, innerWidth);
315
+ series.forEach((s, i) => {
316
+ const color3 = COLOR_SCHEME[i % COLOR_SCHEME.length] ?? "#333";
317
+ switch (type) {
318
+ case "line":
319
+ renderLineSeries(
320
+ g,
321
+ s.data,
322
+ xScalePoint,
323
+ yScale,
324
+ yKey,
325
+ xKey,
326
+ color3,
327
+ i,
328
+ s.name,
329
+ showTooltip,
330
+ hideTooltip
331
+ );
332
+ break;
333
+ case "area":
334
+ renderAreaSeries(
335
+ g,
336
+ s.data,
337
+ xScalePoint,
338
+ yScale,
339
+ yKey,
340
+ xKey,
341
+ innerHeight,
342
+ color3,
343
+ i,
344
+ s.name,
345
+ showTooltip,
346
+ hideTooltip
347
+ );
348
+ break;
349
+ case "bar":
350
+ renderBarSeries(
351
+ g,
352
+ s.data,
353
+ xScale,
354
+ yScale,
355
+ yKey,
356
+ xKey,
357
+ color3,
358
+ i,
359
+ series.length,
360
+ innerHeight,
361
+ s.name,
362
+ showTooltip,
363
+ hideTooltip
364
+ );
365
+ break;
366
+ case "ohlc":
367
+ renderOHLCSeries(
368
+ g,
369
+ s.data,
370
+ xScale,
371
+ xScalePoint,
372
+ yScale,
373
+ innerWidth,
374
+ s.name,
375
+ showTooltip,
376
+ hideTooltip
377
+ );
378
+ break;
379
+ }
380
+ });
381
+ if (legend) {
382
+ renderLegend(sel, series, margin.left, margin.top, isCompact ? "10px" : void 0);
383
+ }
384
+ }, [
385
+ scales,
386
+ type,
387
+ series,
388
+ xKey,
389
+ yKey,
390
+ xAxis,
391
+ yAxis,
392
+ legend,
393
+ margin,
394
+ isCompact,
395
+ showTooltip,
396
+ hideTooltip
397
+ ]);
398
+ const ariaLabel = `${type} chart with ${String(series.length)} series: ${series.map((s) => s.name).join(", ")}`;
399
+ return /* @__PURE__ */ jsxs(
400
+ "div",
401
+ {
402
+ ref: containerRef,
403
+ style: {
404
+ position: "relative",
405
+ width: "100%",
406
+ fontFamily: "var(--glyph-font-body, system-ui, sans-serif)"
407
+ },
408
+ children: [
409
+ /* @__PURE__ */ jsx(
410
+ "svg",
411
+ {
412
+ ref: svgRef,
413
+ role: "img",
414
+ "aria-label": ariaLabel,
415
+ width,
416
+ height,
417
+ viewBox: `0 0 ${String(width)} ${String(height)}`,
418
+ style: { display: "block", maxWidth: "100%", height: "auto" }
419
+ }
420
+ ),
421
+ /* @__PURE__ */ jsx(
422
+ "div",
423
+ {
424
+ ref: tooltipRef,
425
+ role: "tooltip",
426
+ "aria-live": "polite",
427
+ style: {
428
+ display: "none",
429
+ position: "absolute",
430
+ pointerEvents: "none",
431
+ backgroundColor: "var(--glyph-tooltip-bg, rgba(0,0,0,0.8))",
432
+ color: "var(--glyph-tooltip-text, #d4dae3)",
433
+ padding: "4px 8px",
434
+ borderRadius: "3px",
435
+ fontSize: "12px",
436
+ whiteSpace: "nowrap",
437
+ zIndex: 10
438
+ }
439
+ }
440
+ ),
441
+ /* @__PURE__ */ jsx(
442
+ ChartAccessibleTable,
443
+ {
444
+ type,
445
+ series,
446
+ xKey,
447
+ yKey,
448
+ xLabel: xAxis?.label,
449
+ yLabel: yAxis?.label
450
+ }
451
+ )
452
+ ]
453
+ }
454
+ );
455
+ }
456
+
457
+ // src/chart/index.ts
458
+ var chartDefinition = {
459
+ type: "ui:chart",
460
+ schema: chartSchema,
461
+ render: Chart
462
+ };
463
+ var STATUS_LABELS = {
464
+ pending: "Pending",
465
+ active: "Active",
466
+ completed: "Completed"
467
+ };
468
+ function Steps({ data }) {
469
+ const { steps } = data;
470
+ const listStyle = {
471
+ listStyle: "none",
472
+ padding: 0,
473
+ margin: "var(--glyph-spacing-sm, 0.5rem) 0",
474
+ fontFamily: "var(--glyph-font-body, system-ui, sans-serif)",
475
+ color: "var(--glyph-text, #1a2035)"
476
+ };
477
+ return /* @__PURE__ */ jsx("ol", { role: "list", style: listStyle, children: steps.map((step, index) => {
478
+ const status = step.status ?? "pending";
479
+ const isLast = index === steps.length - 1;
480
+ return /* @__PURE__ */ jsxs(
481
+ "li",
482
+ {
483
+ "aria-label": `Step ${String(index + 1)}: ${step.title} \u2014 ${STATUS_LABELS[status]}`,
484
+ ...status === "active" ? { "aria-current": "step" } : {},
485
+ style: itemStyle(isLast),
486
+ children: [
487
+ !isLast && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", style: connectorStyle(status) }),
488
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", style: indicatorStyle(status), children: status === "completed" ? "\u2713" : "" }),
489
+ /* @__PURE__ */ jsxs("div", { style: bodyStyle, children: [
490
+ /* @__PURE__ */ jsx("div", { style: titleStyle(status), children: step.title }),
491
+ /* @__PURE__ */ jsx("div", { style: contentStyle(status), children: step.content })
492
+ ] })
493
+ ]
494
+ },
495
+ index
496
+ );
497
+ }) });
498
+ }
499
+ var INDICATOR_SIZE = "1.5rem";
500
+ var CONNECTOR_WIDTH = "2px";
501
+ function colorForStatus(status) {
502
+ switch (status) {
503
+ case "pending":
504
+ return "var(--glyph-steps-pending-color, #7a8599)";
505
+ case "active":
506
+ return "var(--glyph-steps-active-color, #00d4aa)";
507
+ case "completed":
508
+ return "var(--glyph-steps-completed-color, #22c55e)";
509
+ }
510
+ }
511
+ function itemStyle(isLast) {
512
+ return {
513
+ position: "relative",
514
+ display: "flex",
515
+ gap: "var(--glyph-spacing-sm, 0.75rem)",
516
+ alignItems: "flex-start",
517
+ paddingBottom: isLast ? 0 : "var(--glyph-spacing-md, 1.25rem)"
518
+ };
519
+ }
520
+ function connectorStyle(status) {
521
+ return {
522
+ position: "absolute",
523
+ left: `calc(${INDICATOR_SIZE} / 2 - ${CONNECTOR_WIDTH} / 2)`,
524
+ top: INDICATOR_SIZE,
525
+ bottom: 0,
526
+ width: CONNECTOR_WIDTH,
527
+ backgroundColor: status === "completed" ? "var(--glyph-steps-completed-color, #22c55e)" : "var(--glyph-steps-connector-color, #d0d8e4)"
528
+ };
529
+ }
530
+ function indicatorStyle(status) {
531
+ const color3 = colorForStatus(status);
532
+ return {
533
+ flexShrink: 0,
534
+ width: INDICATOR_SIZE,
535
+ height: INDICATOR_SIZE,
536
+ borderRadius: "50%",
537
+ display: "flex",
538
+ alignItems: "center",
539
+ justifyContent: "center",
540
+ fontSize: "0.85rem",
541
+ fontWeight: 700,
542
+ lineHeight: 1,
543
+ color: status === "completed" ? "#fff" : color3,
544
+ backgroundColor: status === "completed" ? color3 : "transparent",
545
+ border: status === "completed" ? "none" : `${CONNECTOR_WIDTH} solid ${color3}`,
546
+ boxSizing: "border-box"
547
+ };
548
+ }
549
+ var bodyStyle = {
550
+ flex: 1,
551
+ minWidth: 0,
552
+ paddingTop: "0.1rem"
553
+ };
554
+ function titleStyle(status) {
555
+ return {
556
+ fontWeight: 600,
557
+ color: status === "pending" ? "var(--glyph-steps-pending-color, #7a8599)" : "var(--glyph-text, #1a2035)"
558
+ };
559
+ }
560
+ function contentStyle(status) {
561
+ return {
562
+ marginTop: "var(--glyph-spacing-xs, 0.25rem)",
563
+ fontSize: "0.9em",
564
+ color: status === "pending" ? "var(--glyph-steps-pending-color, #7a8599)" : "var(--glyph-text-muted, #7a8599)"
565
+ };
566
+ }
567
+
568
+ // src/steps/index.ts
569
+ var stepsDefinition = {
570
+ type: "ui:steps",
571
+ schema: stepsSchema,
572
+ render: Steps
573
+ };
574
+ function nextDirection(current) {
575
+ if (current === "none") return "ascending";
576
+ if (current === "ascending") return "descending";
577
+ return "none";
578
+ }
579
+ function computeAggregation(rows, column, fn) {
580
+ if (fn === "count") {
581
+ return String(rows.length);
582
+ }
583
+ const values = rows.map((r) => r[column]).filter((v) => typeof v === "number" && !Number.isNaN(v));
584
+ if (values.length === 0) return "";
585
+ switch (fn) {
586
+ case "sum":
587
+ return String(values.reduce((a, b) => a + b, 0));
588
+ case "avg":
589
+ return String(values.reduce((a, b) => a + b, 0) / values.length);
590
+ case "min":
591
+ return String(Math.min(...values));
592
+ case "max":
593
+ return String(Math.max(...values));
594
+ }
595
+ }
596
+ function sortIndicator(direction) {
597
+ if (direction === "ascending") return " \u25B2";
598
+ if (direction === "descending") return " \u25BC";
599
+ return "";
600
+ }
601
+ function TableAggregationFooter({
602
+ columns,
603
+ aggMap,
604
+ rows
605
+ }) {
606
+ return /* @__PURE__ */ jsx("tfoot", { children: /* @__PURE__ */ jsx("tr", { children: columns.map((col) => {
607
+ const agg = aggMap.get(col.key);
608
+ return /* @__PURE__ */ jsx(
609
+ "td",
610
+ {
611
+ style: {
612
+ padding: "var(--glyph-table-cell-padding, 8px 12px)",
613
+ borderTop: "2px solid var(--glyph-table-border, #d0d8e4)",
614
+ fontWeight: "bold",
615
+ background: "var(--glyph-table-footer-bg, #e8ecf3)",
616
+ color: "var(--glyph-table-footer-color, inherit)"
617
+ },
618
+ children: agg ? computeAggregation(rows, col.key, agg.fn) : ""
619
+ },
620
+ col.key
621
+ );
622
+ }) }) });
623
+ }
624
+ function Table({ data, container }) {
625
+ const { columns, rows, aggregation } = data;
626
+ const [sort, setSort] = useState({ column: "", direction: "none" });
627
+ const [filters, setFilters] = useState({});
628
+ const filteredRows = useMemo(() => {
629
+ return rows.filter((row) => {
630
+ return columns.every((col) => {
631
+ if (!col.filterable) return true;
632
+ const filterValue = filters[col.key];
633
+ if (!filterValue) return true;
634
+ const cellValue = String(row[col.key] ?? "").toLowerCase();
635
+ return cellValue.includes(filterValue.toLowerCase());
636
+ });
637
+ });
638
+ }, [rows, columns, filters]);
639
+ const sortedRows = useMemo(() => {
640
+ if (sort.direction === "none" || !sort.column) return filteredRows;
641
+ return [...filteredRows].sort((a, b) => {
642
+ const aVal = a[sort.column];
643
+ const bVal = b[sort.column];
644
+ if (aVal == null && bVal == null) return 0;
645
+ if (aVal == null) return 1;
646
+ if (bVal == null) return -1;
647
+ let cmp = 0;
648
+ if (typeof aVal === "number" && typeof bVal === "number") {
649
+ cmp = aVal - bVal;
650
+ } else {
651
+ cmp = String(aVal).localeCompare(String(bVal));
652
+ }
653
+ return sort.direction === "ascending" ? cmp : -cmp;
654
+ });
655
+ }, [filteredRows, sort]);
656
+ const handleSort = (columnKey) => {
657
+ setSort((prev) => {
658
+ if (prev.column === columnKey) {
659
+ return { column: columnKey, direction: nextDirection(prev.direction) };
660
+ }
661
+ return { column: columnKey, direction: "ascending" };
662
+ });
663
+ };
664
+ const handleHeaderKeyDown = (e, columnKey) => {
665
+ if (e.key === "Enter" || e.key === " ") {
666
+ e.preventDefault();
667
+ handleSort(columnKey);
668
+ }
669
+ };
670
+ const handleFilterChange = (columnKey, value) => {
671
+ setFilters((prev) => ({ ...prev, [columnKey]: value }));
672
+ };
673
+ const hasFilters = columns.some((c) => c.filterable);
674
+ const aggMap = useMemo(() => {
675
+ if (!aggregation) return null;
676
+ const map = /* @__PURE__ */ new Map();
677
+ for (const agg of aggregation) {
678
+ map.set(agg.column, { fn: agg.function });
679
+ }
680
+ return map;
681
+ }, [aggregation]);
682
+ const isCompact = container.tier === "compact";
683
+ const tableEl = /* @__PURE__ */ jsxs(
684
+ "table",
685
+ {
686
+ role: "grid",
687
+ style: {
688
+ width: "100%",
689
+ borderCollapse: "collapse",
690
+ border: "1px solid var(--glyph-table-border, #d0d8e4)",
691
+ fontFamily: "var(--glyph-font-body, inherit)",
692
+ fontSize: isCompact ? "0.8125rem" : "var(--glyph-table-font-size, 0.9rem)"
693
+ },
694
+ children: [
695
+ /* @__PURE__ */ jsxs("thead", { children: [
696
+ /* @__PURE__ */ jsx("tr", { children: columns.map((col) => {
697
+ const isSorted = sort.column === col.key;
698
+ const direction = isSorted ? sort.direction : "none";
699
+ return /* @__PURE__ */ jsxs(
700
+ "th",
701
+ {
702
+ scope: "col",
703
+ "aria-sort": col.sortable ? direction : void 0,
704
+ tabIndex: col.sortable ? 0 : void 0,
705
+ role: col.sortable ? "columnheader" : void 0,
706
+ onClick: col.sortable ? () => handleSort(col.key) : void 0,
707
+ onKeyDown: col.sortable ? (e) => handleHeaderKeyDown(e, col.key) : void 0,
708
+ style: {
709
+ padding: "var(--glyph-table-cell-padding, 8px 12px)",
710
+ textAlign: "left",
711
+ borderBottom: "2px solid var(--glyph-table-border, #d0d8e4)",
712
+ background: "var(--glyph-table-header-bg, #e8ecf3)",
713
+ color: "var(--glyph-table-header-color, inherit)",
714
+ cursor: col.sortable ? "pointer" : "default",
715
+ userSelect: col.sortable ? "none" : void 0,
716
+ whiteSpace: "nowrap"
717
+ },
718
+ children: [
719
+ col.label,
720
+ col.sortable ? sortIndicator(direction) : ""
721
+ ]
722
+ },
723
+ col.key
724
+ );
725
+ }) }),
726
+ hasFilters && /* @__PURE__ */ jsx("tr", { children: columns.map((col) => /* @__PURE__ */ jsx(
727
+ "th",
728
+ {
729
+ scope: "col",
730
+ style: { padding: "4px 8px", fontWeight: "normal" },
731
+ children: col.filterable ? /* @__PURE__ */ jsx(
732
+ "input",
733
+ {
734
+ type: "text",
735
+ "aria-label": `Filter ${col.label}`,
736
+ placeholder: `Filter ${col.label}...`,
737
+ value: filters[col.key] ?? "",
738
+ onChange: (e) => handleFilterChange(col.key, e.target.value),
739
+ style: {
740
+ width: "100%",
741
+ padding: "4px 6px",
742
+ border: "1px solid var(--glyph-table-border, #d0d8e4)",
743
+ borderRadius: "3px",
744
+ fontSize: "inherit",
745
+ boxSizing: "border-box",
746
+ background: "var(--glyph-surface, #e8ecf3)",
747
+ color: "var(--glyph-text, inherit)"
748
+ }
749
+ }
750
+ ) : /* @__PURE__ */ jsx(
751
+ "span",
752
+ {
753
+ style: {
754
+ position: "absolute",
755
+ width: "1px",
756
+ height: "1px",
757
+ overflow: "hidden",
758
+ clip: "rect(0,0,0,0)",
759
+ whiteSpace: "nowrap"
760
+ },
761
+ children: `No filter for ${col.label}`
762
+ }
763
+ )
764
+ },
765
+ `filter-${col.key}`
766
+ )) })
767
+ ] }),
768
+ /* @__PURE__ */ jsx("tbody", { children: sortedRows.map((row, rowIdx) => /* @__PURE__ */ jsx(
769
+ "tr",
770
+ {
771
+ style: {
772
+ background: rowIdx % 2 === 0 ? "var(--glyph-table-row-bg, transparent)" : "var(--glyph-table-row-alt-bg, #f4f6fa)"
773
+ },
774
+ children: columns.map((col) => /* @__PURE__ */ jsx(
775
+ "td",
776
+ {
777
+ style: {
778
+ padding: "var(--glyph-table-cell-padding, 8px 12px)",
779
+ borderBottom: "1px solid var(--glyph-table-border, #d0d8e4)",
780
+ color: "var(--glyph-table-cell-color, inherit)"
781
+ },
782
+ children: String(row[col.key] ?? "")
783
+ },
784
+ col.key
785
+ ))
786
+ },
787
+ rowIdx
788
+ )) }),
789
+ aggMap && aggMap.size > 0 && /* @__PURE__ */ jsx(TableAggregationFooter, { columns, aggMap, rows: sortedRows })
790
+ ]
791
+ }
792
+ );
793
+ if (isCompact) {
794
+ return /* @__PURE__ */ jsx("div", { style: { overflowX: "auto" }, children: tableEl });
795
+ }
796
+ return tableEl;
797
+ }
798
+
799
+ // src/table/index.ts
800
+ var tableDefinition = {
801
+ type: "ui:table",
802
+ schema: tableSchema,
803
+ render: Table
804
+ };
805
+ function Tabs({ data, block }) {
806
+ const [activeIndex, setActiveIndex] = useState(0);
807
+ const tabRefs = useRef([]);
808
+ const tabs = data.tabs;
809
+ const baseId = `glyph-tabs-${block.id}`;
810
+ const focusTab = useCallback(
811
+ (index) => {
812
+ const clampedIndex = Math.max(0, Math.min(index, tabs.length - 1));
813
+ setActiveIndex(clampedIndex);
814
+ tabRefs.current[clampedIndex]?.focus();
815
+ },
816
+ [tabs.length]
817
+ );
818
+ const handleKeyDown = useCallback(
819
+ (e) => {
820
+ switch (e.key) {
821
+ case "ArrowRight": {
822
+ e.preventDefault();
823
+ const next = (activeIndex + 1) % tabs.length;
824
+ focusTab(next);
825
+ break;
826
+ }
827
+ case "ArrowLeft": {
828
+ e.preventDefault();
829
+ const prev = (activeIndex - 1 + tabs.length) % tabs.length;
830
+ focusTab(prev);
831
+ break;
832
+ }
833
+ case "Home": {
834
+ e.preventDefault();
835
+ focusTab(0);
836
+ break;
837
+ }
838
+ case "End": {
839
+ e.preventDefault();
840
+ focusTab(tabs.length - 1);
841
+ break;
842
+ }
843
+ }
844
+ },
845
+ [activeIndex, focusTab, tabs.length]
846
+ );
847
+ return /* @__PURE__ */ jsxs(
848
+ "div",
849
+ {
850
+ style: {
851
+ fontFamily: 'var(--glyph-font-body, "Inter", system-ui, sans-serif)',
852
+ border: "1px solid var(--glyph-border, #d0d8e4)",
853
+ borderRadius: "var(--glyph-radius-lg, 0.25rem)",
854
+ overflow: "hidden"
855
+ },
856
+ children: [
857
+ /* @__PURE__ */ jsx(
858
+ "div",
859
+ {
860
+ role: "tablist",
861
+ "aria-label": "Tabs",
862
+ style: {
863
+ display: "flex",
864
+ borderBottom: "1px solid var(--glyph-border, #d0d8e4)",
865
+ backgroundColor: "var(--glyph-surface, #e8ecf3)",
866
+ margin: 0,
867
+ padding: 0
868
+ },
869
+ children: tabs.map((tab, index) => {
870
+ const isActive = index === activeIndex;
871
+ const tabId = `${baseId}-tab-${String(index)}`;
872
+ const panelId = `${baseId}-panel-${String(index)}`;
873
+ return /* @__PURE__ */ jsx(
874
+ "button",
875
+ {
876
+ id: tabId,
877
+ ref: (el) => {
878
+ tabRefs.current[index] = el;
879
+ },
880
+ role: "tab",
881
+ "aria-selected": isActive,
882
+ "aria-controls": panelId,
883
+ tabIndex: isActive ? 0 : -1,
884
+ onClick: () => setActiveIndex(index),
885
+ onKeyDown: handleKeyDown,
886
+ style: {
887
+ padding: "10px 18px",
888
+ border: "none",
889
+ borderBottom: isActive ? "2px solid var(--glyph-accent, #00d4aa)" : "2px solid transparent",
890
+ background: isActive ? "var(--glyph-surface-raised, #f4f6fa)" : "transparent",
891
+ color: isActive ? "var(--glyph-heading, #0a0e1a)" : "var(--glyph-text-muted, #7a8599)",
892
+ cursor: "pointer",
893
+ fontFamily: "inherit",
894
+ fontSize: "0.9rem",
895
+ fontWeight: isActive ? 600 : 400,
896
+ transition: "color 0.15s, border-color 0.15s, background 0.15s",
897
+ outline: "revert",
898
+ outlineOffset: "2px"
899
+ },
900
+ children: tab.label
901
+ },
902
+ tabId
903
+ );
904
+ })
905
+ }
906
+ ),
907
+ tabs.map((tab, index) => {
908
+ const isActive = index === activeIndex;
909
+ const tabId = `${baseId}-tab-${String(index)}`;
910
+ const panelId = `${baseId}-panel-${String(index)}`;
911
+ return /* @__PURE__ */ jsx(
912
+ "div",
913
+ {
914
+ id: panelId,
915
+ role: "tabpanel",
916
+ "aria-labelledby": tabId,
917
+ "aria-live": "polite",
918
+ hidden: !isActive,
919
+ tabIndex: 0,
920
+ style: {
921
+ padding: "16px",
922
+ backgroundColor: "var(--glyph-surface-raised, #f4f6fa)",
923
+ color: "var(--glyph-heading, #0a0e1a)",
924
+ lineHeight: 1.6
925
+ },
926
+ children: tab.content
927
+ },
928
+ panelId
929
+ );
930
+ })
931
+ ]
932
+ }
933
+ );
934
+ }
935
+
936
+ // src/tabs/index.ts
937
+ var tabsDefinition = {
938
+ type: "ui:tabs",
939
+ schema: tabsSchema,
940
+ render: Tabs
941
+ };
942
+ var MARKER_RADIUS = 8;
943
+ var LINE_THICKNESS = 2;
944
+ var EVENT_SPACING_MIN = 80;
945
+ var TYPE_PALETTE = [
946
+ "var(--glyph-timeline-color-1, #00d4aa)",
947
+ "var(--glyph-timeline-color-2, #b44dff)",
948
+ "var(--glyph-timeline-color-3, #22c55e)",
949
+ "var(--glyph-timeline-color-4, #e040fb)",
950
+ "var(--glyph-timeline-color-5, #00e5ff)",
951
+ "var(--glyph-timeline-color-6, #84cc16)",
952
+ "var(--glyph-timeline-color-7, #f472b6)",
953
+ "var(--glyph-timeline-color-8, #fb923c)"
954
+ ];
955
+ function parseDate(raw) {
956
+ const d = new Date(raw);
957
+ if (!isNaN(d.getTime())) return d;
958
+ const ym = raw.match(/^(\d{4})-(\d{1,2})$/);
959
+ if (ym && ym[1] && ym[2]) return new Date(+ym[1], +ym[2] - 1, 1);
960
+ const q1 = raw.match(/^Q([1-4])\s+(\d{4})$/i);
961
+ if (q1 && q1[1] && q1[2]) return new Date(+q1[2], (+q1[1] - 1) * 3, 1);
962
+ const q2 = raw.match(/^(\d{4})-Q([1-4])$/i);
963
+ if (q2 && q2[1] && q2[2]) return new Date(+q2[1], (+q2[2] - 1) * 3, 1);
964
+ return /* @__PURE__ */ new Date(0);
965
+ }
966
+ function formatDate(raw) {
967
+ const d = parseDate(raw);
968
+ if (d.getTime() === 0) return raw;
969
+ return d.toLocaleDateString(void 0, {
970
+ year: "numeric",
971
+ month: "short",
972
+ day: "numeric"
973
+ });
974
+ }
975
+ function isoDate(raw) {
976
+ return parseDate(raw).toISOString().slice(0, 10);
977
+ }
978
+ function Timeline({ data }) {
979
+ const { events, orientation = "vertical" } = data;
980
+ const containerRef = useRef(null);
981
+ const isVertical = orientation === "vertical";
982
+ const sorted = [...events].map((e) => ({ ...e, _parsed: parseDate(e.date) })).sort((a, b) => a._parsed.getTime() - b._parsed.getTime());
983
+ const dates = sorted.map((e) => e._parsed);
984
+ const minDate = dates[0] ?? /* @__PURE__ */ new Date();
985
+ const maxDate = dates[dates.length - 1] ?? /* @__PURE__ */ new Date();
986
+ const totalLength = Math.max(sorted.length * EVENT_SPACING_MIN, 400);
987
+ const timeScale = scaleTime().domain([minDate, maxDate]).range([MARKER_RADIUS + 20, totalLength - MARKER_RADIUS - 20]);
988
+ const typeValues = [...new Set(events.map((e) => e.type ?? "_default"))];
989
+ const colorScale = scaleOrdinal().domain(typeValues).range(TYPE_PALETTE);
990
+ const positioned = sorted.map((e, i) => ({
991
+ event: e,
992
+ parsed: e._parsed,
993
+ position: dates.length === 1 ? totalLength / 2 : timeScale(e._parsed),
994
+ side: isVertical ? i % 2 === 0 ? "left" : "right" : i % 2 === 0 ? "top" : "bottom"
995
+ }));
996
+ const containerStyle3 = {
997
+ position: "relative",
998
+ fontFamily: "var(--glyph-font-body, system-ui, sans-serif)",
999
+ color: "var(--glyph-text, #1a2035)",
1000
+ ...isVertical ? { width: "100%", minHeight: totalLength } : { minHeight: 300, minWidth: totalLength }
1001
+ };
1002
+ const lineStyle = isVertical ? {
1003
+ position: "absolute",
1004
+ left: "50%",
1005
+ top: 0,
1006
+ bottom: 0,
1007
+ width: LINE_THICKNESS,
1008
+ backgroundColor: "var(--glyph-timeline-line, #d0d8e4)",
1009
+ transform: "translateX(-50%)"
1010
+ } : {
1011
+ position: "absolute",
1012
+ top: "50%",
1013
+ left: 0,
1014
+ right: 0,
1015
+ height: LINE_THICKNESS,
1016
+ backgroundColor: "var(--glyph-timeline-line, #d0d8e4)",
1017
+ transform: "translateY(-50%)"
1018
+ };
1019
+ const inner = /* @__PURE__ */ jsxs(
1020
+ "div",
1021
+ {
1022
+ ref: containerRef,
1023
+ style: containerStyle3,
1024
+ role: "img",
1025
+ "aria-label": `Timeline with ${events.length} events`,
1026
+ children: [
1027
+ /* @__PURE__ */ jsx("div", { style: lineStyle, "aria-hidden": "true" }),
1028
+ positioned.map((pe, idx) => {
1029
+ const color3 = colorScale(pe.event.type ?? "_default");
1030
+ return /* @__PURE__ */ jsxs("div", { style: eventContainerStyle(pe, isVertical), "aria-hidden": "true", children: [
1031
+ /* @__PURE__ */ jsx("div", { style: connectorStyle2(pe, isVertical), "aria-hidden": "true" }),
1032
+ /* @__PURE__ */ jsx(
1033
+ "div",
1034
+ {
1035
+ style: {
1036
+ width: MARKER_RADIUS * 2,
1037
+ height: MARKER_RADIUS * 2,
1038
+ borderRadius: "50%",
1039
+ backgroundColor: color3,
1040
+ border: "2px solid var(--glyph-timeline-marker-border, var(--glyph-bg, #f4f6fa))",
1041
+ boxShadow: "0 0 0 2px var(--glyph-border, #d0d8e4)",
1042
+ flexShrink: 0,
1043
+ zIndex: 1
1044
+ }
1045
+ }
1046
+ ),
1047
+ /* @__PURE__ */ jsxs("div", { style: labelStyle(pe, isVertical), children: [
1048
+ /* @__PURE__ */ jsx(
1049
+ "div",
1050
+ {
1051
+ style: {
1052
+ fontSize: "var(--glyph-timeline-date-size, 0.75rem)",
1053
+ color: "var(--glyph-timeline-date-color, #7a8599)",
1054
+ fontWeight: 600
1055
+ },
1056
+ children: formatDate(pe.event.date)
1057
+ }
1058
+ ),
1059
+ /* @__PURE__ */ jsx(
1060
+ "div",
1061
+ {
1062
+ style: {
1063
+ fontSize: "var(--glyph-timeline-title-size, 0.9rem)",
1064
+ fontWeight: 700,
1065
+ marginTop: 2
1066
+ },
1067
+ children: pe.event.title
1068
+ }
1069
+ ),
1070
+ pe.event.description && /* @__PURE__ */ jsx(
1071
+ "div",
1072
+ {
1073
+ style: {
1074
+ fontSize: "var(--glyph-timeline-desc-size, 0.8rem)",
1075
+ color: "var(--glyph-timeline-desc-color, #7a8599)",
1076
+ marginTop: 2
1077
+ },
1078
+ children: pe.event.description
1079
+ }
1080
+ )
1081
+ ] })
1082
+ ] }, idx);
1083
+ }),
1084
+ /* @__PURE__ */ jsx(
1085
+ "ol",
1086
+ {
1087
+ style: {
1088
+ position: "absolute",
1089
+ width: 1,
1090
+ height: 1,
1091
+ overflow: "hidden",
1092
+ clip: "rect(0 0 0 0)",
1093
+ clipPath: "inset(50%)",
1094
+ whiteSpace: "nowrap"
1095
+ },
1096
+ children: sorted.map((e, idx) => /* @__PURE__ */ jsxs("li", { children: [
1097
+ /* @__PURE__ */ jsx("time", { dateTime: isoDate(e.date), children: formatDate(e.date) }),
1098
+ " \u2014 ",
1099
+ /* @__PURE__ */ jsx("strong", { children: e.title }),
1100
+ e.description ? `: ${e.description}` : ""
1101
+ ] }, idx))
1102
+ }
1103
+ )
1104
+ ]
1105
+ }
1106
+ );
1107
+ if (!isVertical) {
1108
+ return /* @__PURE__ */ jsx("div", { style: { overflowX: "auto", width: "100%" }, children: inner });
1109
+ }
1110
+ return inner;
1111
+ }
1112
+ function eventContainerStyle(pe, isVertical) {
1113
+ if (isVertical) {
1114
+ const isLeft = pe.side === "left";
1115
+ return {
1116
+ position: "absolute",
1117
+ top: pe.position,
1118
+ left: isLeft ? 0 : "50%",
1119
+ right: isLeft ? "50%" : 0,
1120
+ display: "flex",
1121
+ flexDirection: isLeft ? "row-reverse" : "row",
1122
+ alignItems: "center",
1123
+ gap: 8,
1124
+ transform: "translateY(-50%)"
1125
+ };
1126
+ }
1127
+ const isTop = pe.side === "top";
1128
+ return {
1129
+ position: "absolute",
1130
+ left: pe.position,
1131
+ top: isTop ? 0 : "50%",
1132
+ bottom: isTop ? "50%" : 0,
1133
+ display: "flex",
1134
+ flexDirection: isTop ? "column-reverse" : "column",
1135
+ alignItems: "center",
1136
+ gap: 8,
1137
+ transform: "translateX(-50%)"
1138
+ };
1139
+ }
1140
+ function connectorStyle2(_pe, isVertical) {
1141
+ if (isVertical) {
1142
+ return {
1143
+ flex: "0 0 20px",
1144
+ height: LINE_THICKNESS,
1145
+ backgroundColor: "var(--glyph-timeline-line, #d0d8e4)"
1146
+ };
1147
+ }
1148
+ return {
1149
+ flex: "0 0 20px",
1150
+ width: LINE_THICKNESS,
1151
+ backgroundColor: "var(--glyph-timeline-line, #d0d8e4)"
1152
+ };
1153
+ }
1154
+ function labelStyle(pe, isVertical) {
1155
+ if (isVertical) {
1156
+ return {
1157
+ textAlign: pe.side === "left" ? "right" : "left",
1158
+ maxWidth: 200
1159
+ };
1160
+ }
1161
+ return {
1162
+ textAlign: "center",
1163
+ maxWidth: 160
1164
+ };
1165
+ }
1166
+
1167
+ // src/timeline/index.ts
1168
+ var timelineDefinition = {
1169
+ type: "ui:timeline",
1170
+ schema: timelineSchema,
1171
+ render: Timeline
1172
+ };
1173
+ var RANKDIR_MAP = {
1174
+ "top-down": "TB",
1175
+ "left-right": "LR",
1176
+ "bottom-up": "BT",
1177
+ radial: "TB"
1178
+ };
1179
+ var DEFAULT_NODE_WIDTH = 160;
1180
+ var DEFAULT_NODE_HEIGHT = 40;
1181
+ var NODE_SEP = 50;
1182
+ var RANK_SEP = 70;
1183
+ var EDGE_SEP = 10;
1184
+ var LAYOUT_PADDING = 40;
1185
+ function computeDagreLayout(nodes, edges, direction = "top-down") {
1186
+ const g = new dagre.graphlib.Graph();
1187
+ g.setGraph({
1188
+ rankdir: RANKDIR_MAP[direction] ?? "TB",
1189
+ nodesep: NODE_SEP,
1190
+ ranksep: RANK_SEP,
1191
+ edgesep: EDGE_SEP
1192
+ });
1193
+ g.setDefaultEdgeLabel(() => ({}));
1194
+ for (const node of nodes) {
1195
+ g.setNode(node.id, {
1196
+ label: node.label,
1197
+ width: DEFAULT_NODE_WIDTH,
1198
+ height: DEFAULT_NODE_HEIGHT
1199
+ });
1200
+ }
1201
+ for (const edge of edges) {
1202
+ g.setEdge(edge.from, edge.to);
1203
+ }
1204
+ dagre.layout(g);
1205
+ const positionedNodes = nodes.map((node) => {
1206
+ const dagreNode = g.node(node.id);
1207
+ return {
1208
+ ...node,
1209
+ x: dagreNode.x,
1210
+ y: dagreNode.y,
1211
+ width: dagreNode.width,
1212
+ height: dagreNode.height
1213
+ };
1214
+ });
1215
+ const positionedEdges = edges.map((edge) => {
1216
+ const dagreEdge = g.edge(edge.from, edge.to);
1217
+ return {
1218
+ ...edge,
1219
+ points: dagreEdge.points
1220
+ };
1221
+ });
1222
+ let maxX = 0;
1223
+ let maxY = 0;
1224
+ for (const n of positionedNodes) {
1225
+ const right = n.x + n.width / 2;
1226
+ const bottom = n.y + n.height / 2;
1227
+ if (right > maxX) maxX = right;
1228
+ if (bottom > maxY) maxY = bottom;
1229
+ }
1230
+ return {
1231
+ nodes: positionedNodes,
1232
+ edges: positionedEdges,
1233
+ width: maxX + LAYOUT_PADDING,
1234
+ height: maxY + LAYOUT_PADDING
1235
+ };
1236
+ }
1237
+ function computeForceLayout(nodes, edges) {
1238
+ const simNodes = nodes.map((n) => ({
1239
+ ...n,
1240
+ x: void 0,
1241
+ y: void 0
1242
+ }));
1243
+ const simLinks = edges.map((e) => ({
1244
+ source: e.from,
1245
+ target: e.to
1246
+ }));
1247
+ const simulation = d32.forceSimulation(simNodes).force(
1248
+ "link",
1249
+ d32.forceLink(simLinks).id((d) => d.id).distance(120)
1250
+ ).force("charge", d32.forceManyBody().strength(-300)).force("center", d32.forceCenter(400, 300)).force("collision", d32.forceCollide().radius(DEFAULT_NODE_WIDTH / 2 + 10)).stop();
1251
+ const tickCount = 300;
1252
+ for (let i = 0; i < tickCount; i++) {
1253
+ simulation.tick();
1254
+ }
1255
+ let minX = Infinity;
1256
+ let minY = Infinity;
1257
+ for (const sn of simNodes) {
1258
+ if ((sn.x ?? 0) < minX) minX = sn.x ?? 0;
1259
+ if ((sn.y ?? 0) < minY) minY = sn.y ?? 0;
1260
+ }
1261
+ const offsetX = -minX + LAYOUT_PADDING + DEFAULT_NODE_WIDTH / 2;
1262
+ const offsetY = -minY + LAYOUT_PADDING + DEFAULT_NODE_HEIGHT / 2;
1263
+ const nodeMap = /* @__PURE__ */ new Map();
1264
+ const positionedNodes = simNodes.map((sn) => {
1265
+ const pn = {
1266
+ id: sn.id,
1267
+ label: sn.label,
1268
+ type: sn.type,
1269
+ style: sn.style,
1270
+ group: sn.group,
1271
+ x: (sn.x ?? 0) + offsetX,
1272
+ y: (sn.y ?? 0) + offsetY,
1273
+ width: DEFAULT_NODE_WIDTH,
1274
+ height: DEFAULT_NODE_HEIGHT
1275
+ };
1276
+ nodeMap.set(sn.id, pn);
1277
+ return pn;
1278
+ });
1279
+ const positionedEdges = edges.map((edge) => {
1280
+ const source = nodeMap.get(edge.from);
1281
+ const target = nodeMap.get(edge.to);
1282
+ return {
1283
+ ...edge,
1284
+ points: [
1285
+ { x: source?.x ?? 0, y: source?.y ?? 0 },
1286
+ { x: target?.x ?? 0, y: target?.y ?? 0 }
1287
+ ]
1288
+ };
1289
+ });
1290
+ let maxX = 0;
1291
+ let maxY = 0;
1292
+ for (const n of positionedNodes) {
1293
+ const right = n.x + n.width / 2;
1294
+ const bottom = n.y + n.height / 2;
1295
+ if (right > maxX) maxX = right;
1296
+ if (bottom > maxY) maxY = bottom;
1297
+ }
1298
+ return {
1299
+ nodes: positionedNodes,
1300
+ edges: positionedEdges,
1301
+ width: maxX + LAYOUT_PADDING,
1302
+ height: maxY + LAYOUT_PADDING
1303
+ };
1304
+ }
1305
+ var GROUP_PALETTE = [
1306
+ "#00d4aa",
1307
+ // cyan-green
1308
+ "#b44dff",
1309
+ // purple
1310
+ "#22c55e",
1311
+ // green
1312
+ "#e040fb",
1313
+ // magenta
1314
+ "#00e5ff",
1315
+ // teal
1316
+ "#84cc16",
1317
+ // lime
1318
+ "#f472b6",
1319
+ // rose
1320
+ "#fb923c",
1321
+ // orange
1322
+ "#818cf8",
1323
+ // indigo
1324
+ "#38bdf8"
1325
+ // sky
1326
+ ];
1327
+ var DEFAULT_NODE_COLOR = "#00d4aa";
1328
+ function getGroupColor(group, groupIndex) {
1329
+ if (!group) return GROUP_PALETTE[0];
1330
+ let idx = groupIndex.get(group);
1331
+ if (idx === void 0) {
1332
+ idx = groupIndex.size;
1333
+ groupIndex.set(group, idx);
1334
+ }
1335
+ return GROUP_PALETTE[idx % GROUP_PALETTE.length] ?? DEFAULT_NODE_COLOR;
1336
+ }
1337
+ var TYPE_LAYOUT_DEFAULTS = {
1338
+ dag: "top-down",
1339
+ flowchart: "top-down",
1340
+ mindmap: "left-right",
1341
+ force: "force"
1342
+ };
1343
+ function resolveLayout(data) {
1344
+ if (data.layout) return data.layout;
1345
+ return TYPE_LAYOUT_DEFAULTS[data.type] ?? "top-down";
1346
+ }
1347
+ function getThemeVar(container, varName, fallback) {
1348
+ return getComputedStyle(container).getPropertyValue(varName).trim() || fallback;
1349
+ }
1350
+ var ARROW_MARKER_ID = "glyph-graph-arrowhead";
1351
+ function renderGraph(svgElement, layout, groupIndex, outgoingRefs, onNavigate) {
1352
+ const svg = d32.select(svgElement);
1353
+ svg.selectAll("*").remove();
1354
+ const width = Math.max(layout.width, 200);
1355
+ const height = Math.max(layout.height, 200);
1356
+ svg.attr("viewBox", `0 0 ${width} ${height}`);
1357
+ const defs = svg.append("defs");
1358
+ defs.append("marker").attr("id", ARROW_MARKER_ID).attr("viewBox", "0 0 10 10").attr("refX", 10).attr("refY", 5).attr("markerWidth", 8).attr("markerHeight", 8).attr("orient", "auto-start-reverse").append("path").attr("d", "M 0 0 L 10 5 L 0 10 Z").attr("fill", "var(--glyph-edge-color, #6b7a94)");
1359
+ const container = svgElement.parentElement ?? svgElement;
1360
+ const nodeRadius = getThemeVar(container, "--glyph-node-radius", "3");
1361
+ const nodeStrokeWidth = getThemeVar(container, "--glyph-node-stroke-width", "1.5");
1362
+ const nodeFillOpacity = getThemeVar(container, "--glyph-node-fill-opacity", "0.85");
1363
+ const root = svg.append("g").attr("class", "glyph-graph-root");
1364
+ const zoomBehavior = d32.zoom().scaleExtent([0.1, 4]).on("zoom", (event) => {
1365
+ root.attr("transform", event.transform.toString());
1366
+ });
1367
+ svg.call(zoomBehavior);
1368
+ const navigableNodes = /* @__PURE__ */ new Set();
1369
+ const refByAnchor = /* @__PURE__ */ new Map();
1370
+ for (const ref of outgoingRefs) {
1371
+ if (ref.sourceAnchor) {
1372
+ navigableNodes.add(ref.sourceAnchor);
1373
+ refByAnchor.set(ref.sourceAnchor, ref);
1374
+ }
1375
+ }
1376
+ const lineGen = d32.line().x((d) => d.x).y((d) => d.y).curve(d32.curveBasis);
1377
+ const edgeGroup = root.append("g").attr("class", "glyph-graph-edges");
1378
+ for (const edge of layout.edges) {
1379
+ const edgeG = edgeGroup.append("g").attr("class", "glyph-graph-edge");
1380
+ edgeG.append("path").attr("d", lineGen(edge.points) ?? "").attr("fill", "none").attr("stroke", edge.style?.["stroke"] ?? "var(--glyph-edge-color, #6b7a94)").attr("stroke-width", edge.style?.["stroke-width"] ?? nodeStrokeWidth).attr("marker-end", `url(#${ARROW_MARKER_ID})`).attr("stroke-dasharray", edge.type === "dashed" ? "5,5" : null);
1381
+ if (edge.label) {
1382
+ const mid = edge.points[Math.floor(edge.points.length / 2)];
1383
+ if (mid) {
1384
+ edgeG.append("text").attr("x", mid.x).attr("y", mid.y - 8).attr("text-anchor", "middle").attr("font-size", "11px").attr("fill", "var(--glyph-edge-color, #6b7a94)").text(edge.label);
1385
+ }
1386
+ }
1387
+ }
1388
+ const nodeGroup = root.append("g").attr("class", "glyph-graph-nodes");
1389
+ for (const node of layout.nodes) {
1390
+ const nodeG = nodeGroup.append("g").attr("class", "glyph-graph-node");
1391
+ const color3 = getGroupColor(node.group, groupIndex);
1392
+ const isNavigable = navigableNodes.has(node.id);
1393
+ const nodeX = node.x - node.width / 2;
1394
+ const nodeY = node.y - node.height / 2;
1395
+ const defaultStroke = d32.color(color3)?.darker(0.5)?.toString() ?? "var(--glyph-edge-color, #6b7a94)";
1396
+ if (node.type === "circle") {
1397
+ nodeG.append("circle").attr("cx", node.x).attr("cy", node.y).attr("r", Math.min(node.width, node.height) / 2).attr("fill", node.style?.["fill"] ?? color3).attr("stroke", node.style?.["stroke"] ?? defaultStroke).attr("stroke-width", node.style?.["stroke-width"] ?? nodeStrokeWidth).attr("opacity", nodeFillOpacity);
1398
+ } else {
1399
+ nodeG.append("rect").attr("x", nodeX).attr("y", nodeY).attr("width", node.width).attr("height", node.height).attr("rx", nodeRadius).attr("ry", nodeRadius).attr("fill", node.style?.["fill"] ?? color3).attr("stroke", node.style?.["stroke"] ?? defaultStroke).attr("stroke-width", node.style?.["stroke-width"] ?? nodeStrokeWidth).attr("opacity", nodeFillOpacity);
1400
+ }
1401
+ nodeG.append("text").attr("x", node.x).attr("y", node.y).attr("dy", "0.35em").attr("text-anchor", "middle").attr("font-size", "13px").attr("font-family", "Inter, system-ui, sans-serif").attr("fill", "var(--glyph-node-label-color, #fff)").attr("pointer-events", "none").text(node.label);
1402
+ if (isNavigable) {
1403
+ nodeG.attr("cursor", "pointer");
1404
+ nodeG.on("click", () => {
1405
+ const ref = refByAnchor.get(node.id);
1406
+ if (ref) onNavigate(ref);
1407
+ });
1408
+ }
1409
+ }
1410
+ }
1411
+ function Graph({
1412
+ data,
1413
+ outgoingRefs,
1414
+ onNavigate,
1415
+ container
1416
+ }) {
1417
+ const svgRef = useRef(null);
1418
+ const groupIndex = useRef(/* @__PURE__ */ new Map());
1419
+ const layoutResult = useMemo(() => {
1420
+ const direction = resolveLayout(data);
1421
+ if (direction === "force") {
1422
+ return computeForceLayout(data.nodes, data.edges);
1423
+ }
1424
+ return computeDagreLayout(data.nodes, data.edges, direction);
1425
+ }, [data]);
1426
+ useEffect(() => {
1427
+ if (!svgRef.current) return;
1428
+ renderGraph(svgRef.current, layoutResult, groupIndex.current, outgoingRefs, onNavigate);
1429
+ }, [layoutResult, outgoingRefs, onNavigate]);
1430
+ const ariaLabel = `${data.type} graph with ${data.nodes.length} nodes and ${data.edges.length} edges`;
1431
+ return /* @__PURE__ */ jsxs("div", { className: "glyph-graph-container", children: [
1432
+ /* @__PURE__ */ jsx(
1433
+ "svg",
1434
+ {
1435
+ ref: svgRef,
1436
+ role: "img",
1437
+ "aria-label": ariaLabel,
1438
+ width: "100%",
1439
+ height: "100%",
1440
+ style: {
1441
+ minHeight: container.tier === "compact" ? 200 : 300,
1442
+ maxHeight: container.tier === "compact" ? 500 : 700,
1443
+ display: "block"
1444
+ }
1445
+ }
1446
+ ),
1447
+ /* @__PURE__ */ jsxs("table", { className: "sr-only", "aria-label": "Graph data", style: SR_ONLY_STYLE, children: [
1448
+ /* @__PURE__ */ jsx("caption", { children: "Graph nodes and connections" }),
1449
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
1450
+ /* @__PURE__ */ jsx("th", { scope: "col", children: "Node" }),
1451
+ /* @__PURE__ */ jsx("th", { scope: "col", children: "Group" }),
1452
+ /* @__PURE__ */ jsx("th", { scope: "col", children: "Connections" })
1453
+ ] }) }),
1454
+ /* @__PURE__ */ jsx("tbody", { children: data.nodes.map((node) => {
1455
+ const connections = data.edges.filter((e) => e.from === node.id || e.to === node.id).map((e) => {
1456
+ const target = e.from === node.id ? e.to : e.from;
1457
+ const dir = e.from === node.id ? "->" : "<-";
1458
+ return `${dir} ${target}${e.label ? ` (${e.label})` : ""}`;
1459
+ }).join(", ");
1460
+ return /* @__PURE__ */ jsxs("tr", { children: [
1461
+ /* @__PURE__ */ jsx("td", { children: node.label }),
1462
+ /* @__PURE__ */ jsx("td", { children: node.group ?? "" }),
1463
+ /* @__PURE__ */ jsx("td", { children: connections })
1464
+ ] }, node.id);
1465
+ }) })
1466
+ ] })
1467
+ ] });
1468
+ }
1469
+ var SR_ONLY_STYLE = {
1470
+ position: "absolute",
1471
+ width: "1px",
1472
+ height: "1px",
1473
+ padding: 0,
1474
+ margin: "-1px",
1475
+ overflow: "hidden",
1476
+ clip: "rect(0, 0, 0, 0)",
1477
+ whiteSpace: "nowrap",
1478
+ border: 0
1479
+ };
1480
+
1481
+ // src/graph/index.ts
1482
+ var graphDefinition = {
1483
+ type: "ui:graph",
1484
+ schema: graphSchema,
1485
+ render: Graph
1486
+ };
1487
+ var ENTITY_MIN_WIDTH = 180;
1488
+ var ENTITY_HEADER_HEIGHT = 32;
1489
+ var ENTITY_ATTR_HEIGHT = 22;
1490
+ var ENTITY_PADDING = 12;
1491
+ var NODE_SEP2 = 60;
1492
+ var RANK_SEP2 = 80;
1493
+ var EDGE_SEP2 = 10;
1494
+ var LAYOUT_PADDING2 = 40;
1495
+ var CHAR_WIDTH = 7.5;
1496
+ function computeEntitySize(entity) {
1497
+ const attrs = entity.attributes ?? [];
1498
+ const height = ENTITY_HEADER_HEIGHT + attrs.length * ENTITY_ATTR_HEIGHT + ENTITY_PADDING;
1499
+ let maxTextWidth = entity.label.length * (CHAR_WIDTH + 1);
1500
+ for (const attr of attrs) {
1501
+ const attrText = `${attr.name}: ${attr.type}`;
1502
+ maxTextWidth = Math.max(maxTextWidth, attrText.length * CHAR_WIDTH);
1503
+ }
1504
+ const width = Math.max(ENTITY_MIN_WIDTH, maxTextWidth + ENTITY_PADDING * 2 + 16);
1505
+ return { width, height };
1506
+ }
1507
+ function computeRelationLayout(data) {
1508
+ const direction = data.layout ?? "top-down";
1509
+ const rankdir = direction === "left-right" ? "LR" : "TB";
1510
+ const g = new dagre.graphlib.Graph();
1511
+ g.setGraph({
1512
+ rankdir,
1513
+ nodesep: NODE_SEP2,
1514
+ ranksep: RANK_SEP2,
1515
+ edgesep: EDGE_SEP2
1516
+ });
1517
+ g.setDefaultEdgeLabel(() => ({}));
1518
+ const sizeMap = /* @__PURE__ */ new Map();
1519
+ for (const entity of data.entities) {
1520
+ const size = computeEntitySize(entity);
1521
+ sizeMap.set(entity.id, size);
1522
+ g.setNode(entity.id, {
1523
+ label: entity.label,
1524
+ width: size.width,
1525
+ height: size.height
1526
+ });
1527
+ }
1528
+ for (const rel of data.relationships) {
1529
+ g.setEdge(rel.from, rel.to);
1530
+ }
1531
+ dagre.layout(g);
1532
+ const positionedEntities = data.entities.map((entity) => {
1533
+ const dagreNode = g.node(entity.id);
1534
+ const size = sizeMap.get(entity.id) ?? {
1535
+ width: ENTITY_MIN_WIDTH,
1536
+ height: ENTITY_HEADER_HEIGHT
1537
+ };
1538
+ return {
1539
+ ...entity,
1540
+ x: dagreNode.x,
1541
+ y: dagreNode.y,
1542
+ width: size.width,
1543
+ height: size.height
1544
+ };
1545
+ });
1546
+ const positionedRelationships = data.relationships.map((rel) => {
1547
+ const dagreEdge = g.edge(rel.from, rel.to);
1548
+ return {
1549
+ ...rel,
1550
+ points: dagreEdge.points
1551
+ };
1552
+ });
1553
+ let maxX = 0;
1554
+ let maxY = 0;
1555
+ for (const e of positionedEntities) {
1556
+ const right = e.x + e.width / 2;
1557
+ const bottom = e.y + e.height / 2;
1558
+ if (right > maxX) maxX = right;
1559
+ if (bottom > maxY) maxY = bottom;
1560
+ }
1561
+ return {
1562
+ entities: positionedEntities,
1563
+ relationships: positionedRelationships,
1564
+ width: maxX + LAYOUT_PADDING2,
1565
+ height: maxY + LAYOUT_PADDING2
1566
+ };
1567
+ }
1568
+ function parseCardinality(cardinality) {
1569
+ const parts = cardinality.split(":");
1570
+ return { fromSymbol: parts[0] ?? "1", toSymbol: parts[1] ?? "1" };
1571
+ }
1572
+ function drawCrowsFoot(g, x, y, angle, symbol) {
1573
+ const len = 12;
1574
+ const spread = Math.PI / 6;
1575
+ if (symbol === "N" || symbol === "M") {
1576
+ const cx = x + Math.cos(angle) * len;
1577
+ const cy = y + Math.sin(angle) * len;
1578
+ g.append("line").attr("x1", x).attr("y1", y).attr("x2", cx).attr("y2", cy).attr("stroke", "var(--glyph-relation-line, #6b7a94)").attr("stroke-width", "var(--glyph-node-stroke-width, 1.5)");
1579
+ const lx = x + Math.cos(angle + spread) * len;
1580
+ const ly = y + Math.sin(angle + spread) * len;
1581
+ g.append("line").attr("x1", x).attr("y1", y).attr("x2", lx).attr("y2", ly).attr("stroke", "var(--glyph-relation-line, #6b7a94)").attr("stroke-width", "var(--glyph-node-stroke-width, 1.5)");
1582
+ const rx = x + Math.cos(angle - spread) * len;
1583
+ const ry = y + Math.sin(angle - spread) * len;
1584
+ g.append("line").attr("x1", x).attr("y1", y).attr("x2", rx).attr("y2", ry).attr("stroke", "var(--glyph-relation-line, #6b7a94)").attr("stroke-width", "var(--glyph-node-stroke-width, 1.5)");
1585
+ } else {
1586
+ const perpAngle = angle + Math.PI / 2;
1587
+ const halfLen = 8;
1588
+ const tx = x + Math.cos(angle) * 6;
1589
+ const ty = y + Math.sin(angle) * 6;
1590
+ g.append("line").attr("x1", tx - Math.cos(perpAngle) * halfLen).attr("y1", ty - Math.sin(perpAngle) * halfLen).attr("x2", tx + Math.cos(perpAngle) * halfLen).attr("y2", ty + Math.sin(perpAngle) * halfLen).attr("stroke", "var(--glyph-relation-line, #6b7a94)").attr("stroke-width", "var(--glyph-node-stroke-width, 1.5)");
1591
+ }
1592
+ }
1593
+ function renderRelation(svgElement, layout) {
1594
+ const svg = d32.select(svgElement);
1595
+ svg.selectAll("*").remove();
1596
+ const width = Math.max(layout.width, 200);
1597
+ const height = Math.max(layout.height, 200);
1598
+ svg.attr("viewBox", `0 0 ${width} ${height}`);
1599
+ const root = svg.append("g").attr("class", "glyph-relation-root");
1600
+ const zoomBehavior = d32.zoom().scaleExtent([0.1, 4]).on("zoom", (event) => {
1601
+ root.attr("transform", event.transform.toString());
1602
+ });
1603
+ svg.call(zoomBehavior);
1604
+ const entityMap = /* @__PURE__ */ new Map();
1605
+ for (const entity of layout.entities) {
1606
+ entityMap.set(entity.id, entity);
1607
+ }
1608
+ const lineGen = d32.line().x((d) => d.x).y((d) => d.y).curve(d32.curveBasis);
1609
+ const edgeGroup = root.append("g").attr("class", "glyph-relation-edges");
1610
+ for (const rel of layout.relationships) {
1611
+ const edgeG = edgeGroup.append("g").attr("class", "glyph-relation-edge");
1612
+ edgeG.append("path").attr("d", lineGen(rel.points) ?? "").attr("fill", "none").attr("stroke", "var(--glyph-relation-line, #6b7a94)").attr("stroke-width", "var(--glyph-node-stroke-width, 1.5)");
1613
+ const { fromSymbol, toSymbol } = parseCardinality(rel.cardinality);
1614
+ const p0 = rel.points[0];
1615
+ const p1 = rel.points[1];
1616
+ if (p0 && p1) {
1617
+ const angleFrom = Math.atan2(p1.y - p0.y, p1.x - p0.x);
1618
+ drawCrowsFoot(edgeG, p0.x, p0.y, angleFrom, fromSymbol);
1619
+ }
1620
+ const pLast = rel.points[rel.points.length - 1];
1621
+ const pPrev = rel.points[rel.points.length - 2];
1622
+ if (pLast && pPrev) {
1623
+ const angleTo = Math.atan2(pPrev.y - pLast.y, pPrev.x - pLast.x);
1624
+ drawCrowsFoot(edgeG, pLast.x, pLast.y, angleTo, toSymbol);
1625
+ }
1626
+ if (rel.label) {
1627
+ const mid = rel.points[Math.floor(rel.points.length / 2)];
1628
+ if (mid) {
1629
+ edgeG.append("text").attr("x", mid.x).attr("y", mid.y - 10).attr("text-anchor", "middle").attr("font-size", "11px").attr("font-family", "Inter, system-ui, sans-serif").attr("fill", "var(--glyph-relation-label, #6b7a94)").text(rel.label);
1630
+ }
1631
+ }
1632
+ const pFirst = rel.points[0];
1633
+ const pSecond = rel.points[1];
1634
+ if (pFirst && pSecond) {
1635
+ const fromLabelX = pFirst.x + (pSecond.x - pFirst.x) * 0.15;
1636
+ const fromLabelY = pFirst.y + (pSecond.y - pFirst.y) * 0.15 - 10;
1637
+ edgeG.append("text").attr("x", fromLabelX).attr("y", fromLabelY).attr("text-anchor", "middle").attr("font-size", "10px").attr("font-family", "Inter, system-ui, sans-serif").attr("fill", "var(--glyph-relation-cardinality, #6b7a94)").text(fromSymbol);
1638
+ }
1639
+ const pEnd = rel.points[rel.points.length - 1];
1640
+ const pBeforeEnd = rel.points[rel.points.length - 2];
1641
+ if (pEnd && pBeforeEnd) {
1642
+ const toLabelX = pEnd.x + (pBeforeEnd.x - pEnd.x) * 0.15;
1643
+ const toLabelY = pEnd.y + (pBeforeEnd.y - pEnd.y) * 0.15 - 10;
1644
+ edgeG.append("text").attr("x", toLabelX).attr("y", toLabelY).attr("text-anchor", "middle").attr("font-size", "10px").attr("font-family", "Inter, system-ui, sans-serif").attr("fill", "var(--glyph-relation-cardinality, #6b7a94)").text(toSymbol);
1645
+ }
1646
+ }
1647
+ const entityGroup = root.append("g").attr("class", "glyph-relation-entities");
1648
+ for (const entity of layout.entities) {
1649
+ const entityG = entityGroup.append("g").attr("class", "glyph-relation-entity");
1650
+ const x = entity.x - entity.width / 2;
1651
+ const y = entity.y - entity.height / 2;
1652
+ const attrs = entity.attributes ?? [];
1653
+ entityG.append("rect").attr("x", x).attr("y", y).attr("width", entity.width).attr("height", entity.height).attr("rx", 4).attr("ry", 4).attr("fill", "var(--glyph-relation-entity-bg, #f4f6fa)").attr("stroke", "var(--glyph-relation-entity-border, #a8b5c8)").attr("stroke-width", "var(--glyph-node-stroke-width, 1.5)");
1654
+ const headerHeight = ENTITY_HEADER_HEIGHT;
1655
+ entityG.append("rect").attr("x", x).attr("y", y).attr("width", entity.width).attr("height", headerHeight).attr("rx", 4).attr("ry", 4).attr("fill", "var(--glyph-relation-header-bg, #00d4aa)");
1656
+ entityG.append("rect").attr("x", x).attr("y", y + headerHeight - 4).attr("width", entity.width).attr("height", 4).attr("fill", "var(--glyph-relation-header-bg, #00d4aa)");
1657
+ entityG.append("text").attr("x", entity.x).attr("y", y + headerHeight / 2).attr("dy", "0.35em").attr("text-anchor", "middle").attr("font-size", "13px").attr("font-weight", "bold").attr("font-family", "Inter, system-ui, sans-serif").attr("fill", "var(--glyph-relation-header-text, #fff)").text(entity.label);
1658
+ if (attrs.length > 0) {
1659
+ entityG.append("line").attr("x1", x).attr("y1", y + headerHeight).attr("x2", x + entity.width).attr("y2", y + headerHeight).attr("stroke", "var(--glyph-relation-entity-border, #a8b5c8)").attr("stroke-width", 1);
1660
+ }
1661
+ for (let i = 0; i < attrs.length; i++) {
1662
+ const attr = attrs[i];
1663
+ if (!attr) continue;
1664
+ const attrY = y + headerHeight + i * ENTITY_ATTR_HEIGHT + ENTITY_ATTR_HEIGHT / 2 + 4;
1665
+ const textEl = entityG.append("text").attr("x", x + ENTITY_PADDING).attr("y", attrY).attr("dy", "0.35em").attr("font-size", "12px").attr("font-family", "system-ui, -apple-system, monospace").attr("fill", "var(--glyph-relation-attr-text, #1a2035)");
1666
+ const nameSpan = textEl.append("tspan").text(attr.name);
1667
+ if (attr.primaryKey) {
1668
+ nameSpan.attr("font-weight", "bold").attr("text-decoration", "underline");
1669
+ }
1670
+ textEl.append("tspan").attr("fill", "var(--glyph-relation-attr-type, #6b7a94)").text(`: ${attr.type}`);
1671
+ }
1672
+ }
1673
+ }
1674
+ function Relation({ data }) {
1675
+ const svgRef = useRef(null);
1676
+ const layoutResult = useMemo(() => {
1677
+ return computeRelationLayout(data);
1678
+ }, [data]);
1679
+ useEffect(() => {
1680
+ if (!svgRef.current) return;
1681
+ renderRelation(svgRef.current, layoutResult);
1682
+ }, [layoutResult]);
1683
+ const ariaLabel = `Entity-relationship diagram with ${data.entities.length} entities and ${data.relationships.length} relationships`;
1684
+ return /* @__PURE__ */ jsxs("div", { className: "glyph-relation-container", children: [
1685
+ /* @__PURE__ */ jsx(
1686
+ "svg",
1687
+ {
1688
+ ref: svgRef,
1689
+ role: "img",
1690
+ "aria-label": ariaLabel,
1691
+ width: "100%",
1692
+ height: "100%",
1693
+ style: { minHeight: 300, maxHeight: 700, display: "block" }
1694
+ }
1695
+ ),
1696
+ /* @__PURE__ */ jsxs("table", { className: "sr-only", "aria-label": "Entity-relationship data", style: SR_ONLY_STYLE2, children: [
1697
+ /* @__PURE__ */ jsx("caption", { children: "Entities and relationships" }),
1698
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
1699
+ /* @__PURE__ */ jsx("th", { scope: "col", children: "Entity" }),
1700
+ /* @__PURE__ */ jsx("th", { scope: "col", children: "Attributes" }),
1701
+ /* @__PURE__ */ jsx("th", { scope: "col", children: "Relationships" })
1702
+ ] }) }),
1703
+ /* @__PURE__ */ jsx("tbody", { children: data.entities.map((entity) => {
1704
+ const attrs = (entity.attributes ?? []).map((a) => `${a.name}: ${a.type}${a.primaryKey ? " (PK)" : ""}`).join(", ");
1705
+ const rels = data.relationships.filter((r) => r.from === entity.id || r.to === entity.id).map((r) => {
1706
+ const target = r.from === entity.id ? r.to : r.from;
1707
+ const dir = r.from === entity.id ? "->" : "<-";
1708
+ return `${dir} ${target} [${r.cardinality}]${r.label ? ` (${r.label})` : ""}`;
1709
+ }).join(", ");
1710
+ return /* @__PURE__ */ jsxs("tr", { children: [
1711
+ /* @__PURE__ */ jsx("td", { children: entity.label }),
1712
+ /* @__PURE__ */ jsx("td", { children: attrs }),
1713
+ /* @__PURE__ */ jsx("td", { children: rels })
1714
+ ] }, entity.id);
1715
+ }) })
1716
+ ] })
1717
+ ] });
1718
+ }
1719
+ var SR_ONLY_STYLE2 = {
1720
+ position: "absolute",
1721
+ width: "1px",
1722
+ height: "1px",
1723
+ padding: 0,
1724
+ margin: "-1px",
1725
+ overflow: "hidden",
1726
+ clip: "rect(0, 0, 0, 0)",
1727
+ whiteSpace: "nowrap",
1728
+ border: 0
1729
+ };
1730
+
1731
+ // src/relation/index.ts
1732
+ var relationDefinition = {
1733
+ type: "ui:relation",
1734
+ schema: relationSchema,
1735
+ render: Relation
1736
+ };
1737
+ var TREND_SYMBOLS = {
1738
+ up: "\u25B2",
1739
+ down: "\u25BC",
1740
+ flat: "\u2014"
1741
+ };
1742
+ function resolveSentiment(metric) {
1743
+ if (metric.sentiment) return metric.sentiment;
1744
+ if (metric.trend === "up") return "positive";
1745
+ if (metric.trend === "down") return "negative";
1746
+ return "neutral";
1747
+ }
1748
+ function buildAriaLabel(metric) {
1749
+ let label = `${metric.label}: ${metric.value}`;
1750
+ if (metric.unit) label += ` ${metric.unit}`;
1751
+ if (metric.delta && metric.trend) {
1752
+ label += `, ${metric.trend} ${metric.delta}`;
1753
+ } else if (metric.delta) {
1754
+ label += `, ${metric.delta}`;
1755
+ }
1756
+ return label;
1757
+ }
1758
+ function Kpi({ data, block, container }) {
1759
+ const { title, metrics, columns } = data;
1760
+ const baseId = `glyph-kpi-${block.id}`;
1761
+ const authorCols = columns ?? Math.min(metrics.length, 4);
1762
+ let colCount;
1763
+ switch (container.tier) {
1764
+ case "compact":
1765
+ colCount = Math.min(metrics.length, 2);
1766
+ break;
1767
+ case "standard":
1768
+ colCount = Math.min(authorCols, 3);
1769
+ break;
1770
+ default:
1771
+ colCount = authorCols;
1772
+ }
1773
+ const containerStyle3 = {
1774
+ fontFamily: "var(--glyph-font-body, system-ui, sans-serif)",
1775
+ color: "var(--glyph-text, #1a2035)"
1776
+ };
1777
+ const gapCount = colCount - 1;
1778
+ const gridStyle = {
1779
+ display: "grid",
1780
+ gridTemplateColumns: `repeat(auto-fill, minmax(max(120px, calc((100% - ${String(gapCount)}rem) / ${String(colCount)})), 1fr))`,
1781
+ gap: "var(--glyph-spacing-md, 1rem)"
1782
+ };
1783
+ const cardStyle = {
1784
+ background: "var(--glyph-surface-raised, #f4f6fa)",
1785
+ border: "1px solid var(--glyph-border, #d0d8e4)",
1786
+ borderRadius: "var(--glyph-radius-md, 0.5rem)",
1787
+ padding: "var(--glyph-spacing-md, 1rem)"
1788
+ };
1789
+ const labelStyle3 = {
1790
+ fontSize: "0.8125rem",
1791
+ color: "var(--glyph-text-muted, #6b7a94)",
1792
+ marginBottom: "var(--glyph-spacing-xs, 0.25rem)"
1793
+ };
1794
+ const valueStyle = {
1795
+ fontSize: "1.75rem",
1796
+ fontWeight: 700,
1797
+ color: "var(--glyph-heading, #0a0e1a)",
1798
+ lineHeight: 1.2
1799
+ };
1800
+ return /* @__PURE__ */ jsxs("div", { id: baseId, role: "region", "aria-label": title ?? "Key metrics", style: containerStyle3, children: [
1801
+ title && /* @__PURE__ */ jsx(
1802
+ "div",
1803
+ {
1804
+ style: {
1805
+ fontWeight: 700,
1806
+ fontSize: "1.125rem",
1807
+ marginBottom: "var(--glyph-spacing-sm, 0.5rem)",
1808
+ color: "var(--glyph-heading, #0a0e1a)"
1809
+ },
1810
+ children: title
1811
+ }
1812
+ ),
1813
+ /* @__PURE__ */ jsx("div", { style: gridStyle, children: metrics.map((metric, i) => {
1814
+ const sentiment = resolveSentiment(metric);
1815
+ const deltaStyle = {
1816
+ fontSize: "0.875rem",
1817
+ marginTop: "var(--glyph-spacing-xs, 0.25rem)",
1818
+ color: `var(--glyph-kpi-${sentiment}, inherit)`
1819
+ };
1820
+ return /* @__PURE__ */ jsxs("div", { role: "group", "aria-label": buildAriaLabel(metric), style: cardStyle, children: [
1821
+ /* @__PURE__ */ jsx("div", { style: labelStyle3, children: metric.label }),
1822
+ /* @__PURE__ */ jsxs("div", { style: valueStyle, children: [
1823
+ metric.value,
1824
+ metric.unit && /* @__PURE__ */ jsx("span", { style: { fontSize: "0.875rem", fontWeight: 400, marginLeft: "0.25rem" }, children: metric.unit })
1825
+ ] }),
1826
+ (metric.delta || metric.trend) && /* @__PURE__ */ jsxs("div", { style: deltaStyle, children: [
1827
+ metric.trend && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", style: { marginRight: "0.25rem" }, children: TREND_SYMBOLS[metric.trend] }),
1828
+ metric.delta
1829
+ ] })
1830
+ ] }, i);
1831
+ }) })
1832
+ ] });
1833
+ }
1834
+
1835
+ // src/kpi/index.ts
1836
+ var kpiDefinition = {
1837
+ type: "ui:kpi",
1838
+ schema: kpiSchema,
1839
+ render: Kpi
1840
+ };
1841
+ function Accordion({ data, block }) {
1842
+ const { title, sections, defaultOpen = [], multiple = true } = data;
1843
+ const baseId = `glyph-accordion-${block.id}`;
1844
+ const containerRef = useRef(null);
1845
+ const handleToggle = useCallback(
1846
+ (e) => {
1847
+ if (multiple) return;
1848
+ const target = e.currentTarget;
1849
+ if (!target.open || !containerRef.current) return;
1850
+ const allDetails = containerRef.current.querySelectorAll("details");
1851
+ for (const details of allDetails) {
1852
+ if (details !== target && details.open) {
1853
+ details.open = false;
1854
+ }
1855
+ }
1856
+ },
1857
+ [multiple]
1858
+ );
1859
+ const containerStyle3 = {
1860
+ fontFamily: "var(--glyph-font-body, system-ui, sans-serif)",
1861
+ color: "var(--glyph-text, #1a2035)",
1862
+ border: "1px solid var(--glyph-border, #d0d8e4)",
1863
+ borderRadius: "var(--glyph-radius-md, 0.5rem)",
1864
+ overflow: "hidden"
1865
+ };
1866
+ const sectionStyle = (isLast) => ({
1867
+ borderBottom: isLast ? "none" : "1px solid var(--glyph-border, #d0d8e4)"
1868
+ });
1869
+ const summaryStyle = {
1870
+ padding: "var(--glyph-spacing-md, 1rem)",
1871
+ cursor: "pointer",
1872
+ fontWeight: 600,
1873
+ fontSize: "0.9375rem",
1874
+ background: "var(--glyph-surface, #e8ecf3)",
1875
+ listStyle: "none",
1876
+ display: "flex",
1877
+ alignItems: "center",
1878
+ gap: "0.5rem",
1879
+ userSelect: "none"
1880
+ };
1881
+ const contentStyle2 = {
1882
+ padding: "var(--glyph-spacing-md, 1rem)",
1883
+ fontSize: "0.875rem",
1884
+ lineHeight: 1.6
1885
+ };
1886
+ return /* @__PURE__ */ jsxs(
1887
+ "div",
1888
+ {
1889
+ id: baseId,
1890
+ ref: containerRef,
1891
+ role: "region",
1892
+ "aria-label": title ?? "Accordion",
1893
+ style: containerStyle3,
1894
+ children: [
1895
+ title && /* @__PURE__ */ jsx(
1896
+ "div",
1897
+ {
1898
+ style: {
1899
+ fontWeight: 700,
1900
+ fontSize: "1.125rem",
1901
+ padding: "var(--glyph-spacing-md, 1rem)",
1902
+ borderBottom: "1px solid var(--glyph-border, #d0d8e4)",
1903
+ color: "var(--glyph-heading, #0a0e1a)"
1904
+ },
1905
+ children: title
1906
+ }
1907
+ ),
1908
+ sections.map((section, i) => /* @__PURE__ */ jsxs(
1909
+ "details",
1910
+ {
1911
+ open: defaultOpen.includes(i),
1912
+ onToggle: handleToggle,
1913
+ style: sectionStyle(i === sections.length - 1),
1914
+ children: [
1915
+ /* @__PURE__ */ jsxs("summary", { style: summaryStyle, children: [
1916
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", style: { fontSize: "0.75rem", width: "1rem", flexShrink: 0 }, children: "\u25B8" }),
1917
+ section.title
1918
+ ] }),
1919
+ /* @__PURE__ */ jsx("div", { style: contentStyle2, children: section.content })
1920
+ ]
1921
+ },
1922
+ i
1923
+ ))
1924
+ ]
1925
+ }
1926
+ );
1927
+ }
1928
+
1929
+ // src/accordion/index.ts
1930
+ var accordionDefinition = {
1931
+ type: "ui:accordion",
1932
+ schema: accordionSchema,
1933
+ render: Accordion
1934
+ };
1935
+ var YES_VALUES = /* @__PURE__ */ new Set(["yes", "true", "full"]);
1936
+ var NO_VALUES = /* @__PURE__ */ new Set(["no", "false", "none"]);
1937
+ function classifyValue(value) {
1938
+ const lower = value.toLowerCase().trim();
1939
+ if (YES_VALUES.has(lower)) return "yes";
1940
+ if (NO_VALUES.has(lower)) return "no";
1941
+ if (lower === "partial") return "partial";
1942
+ return "text";
1943
+ }
1944
+ function renderValue(value) {
1945
+ const kind = classifyValue(value);
1946
+ switch (kind) {
1947
+ case "yes":
1948
+ return /* @__PURE__ */ jsx(
1949
+ "span",
1950
+ {
1951
+ "aria-label": "Supported",
1952
+ style: { color: "var(--glyph-comparison-yes, #16a34a)", fontSize: "1.25rem" },
1953
+ children: "\u2713"
1954
+ }
1955
+ );
1956
+ case "no":
1957
+ return /* @__PURE__ */ jsx(
1958
+ "span",
1959
+ {
1960
+ "aria-label": "Not supported",
1961
+ style: { color: "var(--glyph-comparison-no, #dc2626)", fontSize: "1.25rem" },
1962
+ children: "\u2717"
1963
+ }
1964
+ );
1965
+ case "partial":
1966
+ return /* @__PURE__ */ jsx(
1967
+ "span",
1968
+ {
1969
+ "aria-label": "Partially supported",
1970
+ style: { color: "var(--glyph-comparison-partial, #d97706)", fontSize: "1.25rem" },
1971
+ children: "\u25D0"
1972
+ }
1973
+ );
1974
+ default:
1975
+ return /* @__PURE__ */ jsx("span", { children: value });
1976
+ }
1977
+ }
1978
+ function Comparison({
1979
+ data,
1980
+ block,
1981
+ container
1982
+ }) {
1983
+ const { title, options, features } = data;
1984
+ const baseId = `glyph-comparison-${block.id}`;
1985
+ const isCompact = container.tier === "compact";
1986
+ const cellPadding = isCompact ? "var(--glyph-spacing-xs, 0.25rem) var(--glyph-spacing-sm, 0.5rem)" : "var(--glyph-spacing-sm, 0.5rem) var(--glyph-spacing-md, 1rem)";
1987
+ const containerStyle3 = {
1988
+ fontFamily: "var(--glyph-font-body, system-ui, sans-serif)",
1989
+ color: "var(--glyph-text, #1a2035)"
1990
+ };
1991
+ const tableStyle = {
1992
+ width: "100%",
1993
+ borderCollapse: "collapse",
1994
+ border: "1px solid var(--glyph-table-border, #d0d8e4)",
1995
+ borderRadius: "var(--glyph-radius-md, 0.5rem)",
1996
+ overflow: "hidden",
1997
+ fontSize: isCompact ? "0.8125rem" : "0.875rem"
1998
+ };
1999
+ const thStyle = {
2000
+ padding: cellPadding,
2001
+ textAlign: "center",
2002
+ fontWeight: 600,
2003
+ background: "var(--glyph-table-header-bg, #e8ecf3)",
2004
+ borderBottom: "2px solid var(--glyph-table-border, #d0d8e4)",
2005
+ color: "var(--glyph-heading, #0a0e1a)"
2006
+ };
2007
+ const featureThStyle = {
2008
+ ...thStyle,
2009
+ textAlign: "left"
2010
+ };
2011
+ const rowThStyle = {
2012
+ padding: cellPadding,
2013
+ textAlign: "left",
2014
+ fontWeight: 600,
2015
+ borderBottom: "1px solid var(--glyph-table-border, #d0d8e4)",
2016
+ fontSize: "0.8125rem"
2017
+ };
2018
+ const cellStyle = (rowIndex) => ({
2019
+ padding: cellPadding,
2020
+ textAlign: "center",
2021
+ borderBottom: "1px solid var(--glyph-table-border, #d0d8e4)",
2022
+ background: rowIndex % 2 === 1 ? "var(--glyph-table-row-alt-bg, transparent)" : "transparent"
2023
+ });
2024
+ return /* @__PURE__ */ jsxs("div", { id: baseId, role: "region", "aria-label": title ?? "Comparison", style: containerStyle3, children: [
2025
+ title && /* @__PURE__ */ jsx(
2026
+ "div",
2027
+ {
2028
+ style: {
2029
+ fontWeight: 700,
2030
+ fontSize: "1.125rem",
2031
+ marginBottom: "var(--glyph-spacing-sm, 0.5rem)",
2032
+ color: "var(--glyph-heading, #0a0e1a)"
2033
+ },
2034
+ children: title
2035
+ }
2036
+ ),
2037
+ /* @__PURE__ */ jsx("div", { style: { overflowX: "auto" }, children: /* @__PURE__ */ jsxs("table", { role: "grid", style: tableStyle, children: [
2038
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
2039
+ /* @__PURE__ */ jsx("th", { style: featureThStyle, scope: "col", children: "Feature" }),
2040
+ options.map((option, i) => /* @__PURE__ */ jsxs("th", { style: thStyle, scope: "col", children: [
2041
+ /* @__PURE__ */ jsx("div", { children: option.name }),
2042
+ option.description && /* @__PURE__ */ jsx(
2043
+ "div",
2044
+ {
2045
+ style: {
2046
+ fontWeight: 400,
2047
+ fontSize: "0.75rem",
2048
+ color: "var(--glyph-text-muted, #6b7a94)"
2049
+ },
2050
+ children: option.description
2051
+ }
2052
+ )
2053
+ ] }, i))
2054
+ ] }) }),
2055
+ /* @__PURE__ */ jsx("tbody", { children: features.map((feature, rowIndex) => /* @__PURE__ */ jsxs("tr", { children: [
2056
+ /* @__PURE__ */ jsx(
2057
+ "th",
2058
+ {
2059
+ scope: "row",
2060
+ style: {
2061
+ ...rowThStyle,
2062
+ background: rowIndex % 2 === 1 ? "var(--glyph-table-row-alt-bg, transparent)" : "transparent"
2063
+ },
2064
+ children: feature.name
2065
+ }
2066
+ ),
2067
+ options.map((_, colIndex) => {
2068
+ const value = feature.values[colIndex] ?? "";
2069
+ return /* @__PURE__ */ jsx("td", { style: cellStyle(rowIndex), children: value ? renderValue(value) : null }, colIndex);
2070
+ })
2071
+ ] }, rowIndex)) })
2072
+ ] }) })
2073
+ ] });
2074
+ }
2075
+
2076
+ // src/comparison/index.ts
2077
+ var comparisonDefinition = {
2078
+ type: "ui:comparison",
2079
+ schema: comparisonSchema,
2080
+ render: Comparison
2081
+ };
2082
+
2083
+ // src/codediff/diff.ts
2084
+ function dpGet(dp, i, j) {
2085
+ const row = dp[i];
2086
+ if (!row) return 0;
2087
+ return row[j] ?? 0;
2088
+ }
2089
+ function lineAt(arr, i) {
2090
+ return arr[i] ?? "";
2091
+ }
2092
+ function computeDiff(before, after) {
2093
+ const a = before.split("\n");
2094
+ const b = after.split("\n");
2095
+ const n = a.length;
2096
+ const m = b.length;
2097
+ const dp = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0));
2098
+ for (let i2 = 1; i2 <= n; i2++) {
2099
+ for (let j2 = 1; j2 <= m; j2++) {
2100
+ const row = dp[i2];
2101
+ if (!row) continue;
2102
+ if (lineAt(a, i2 - 1) === lineAt(b, j2 - 1)) {
2103
+ row[j2] = dpGet(dp, i2 - 1, j2 - 1) + 1;
2104
+ } else {
2105
+ row[j2] = Math.max(dpGet(dp, i2 - 1, j2), dpGet(dp, i2, j2 - 1));
2106
+ }
2107
+ }
2108
+ }
2109
+ const edits = [];
2110
+ let i = n;
2111
+ let j = m;
2112
+ while (i > 0 || j > 0) {
2113
+ if (i > 0 && j > 0 && lineAt(a, i - 1) === lineAt(b, j - 1)) {
2114
+ edits.unshift({ kind: "eq", text: lineAt(a, i - 1), oldLineNo: i, newLineNo: j });
2115
+ i--;
2116
+ j--;
2117
+ } else if (j > 0 && (i === 0 || dpGet(dp, i, j - 1) >= dpGet(dp, i - 1, j))) {
2118
+ edits.unshift({ kind: "add", text: lineAt(b, j - 1), newLineNo: j });
2119
+ j--;
2120
+ } else {
2121
+ edits.unshift({ kind: "del", text: lineAt(a, i - 1), oldLineNo: i });
2122
+ i--;
2123
+ }
2124
+ }
2125
+ return edits;
2126
+ }
2127
+ function summarizeDiff(lines) {
2128
+ const added = lines.filter((l) => l.kind === "add").length;
2129
+ const removed = lines.filter((l) => l.kind === "del").length;
2130
+ return `Code diff: ${String(added)} line${added !== 1 ? "s" : ""} added, ${String(removed)} line${removed !== 1 ? "s" : ""} removed`;
2131
+ }
2132
+ var GUTTER_MARKERS = {
2133
+ add: "+",
2134
+ del: "-",
2135
+ eq: " "
2136
+ };
2137
+ var ARIA_LABELS = {
2138
+ add: "added",
2139
+ del: "removed",
2140
+ eq: "unchanged"
2141
+ };
2142
+ function CodeDiff({ data, block }) {
2143
+ const { before, after, beforeLabel, afterLabel } = data;
2144
+ const baseId = `glyph-codediff-${block.id}`;
2145
+ const diffLines = useMemo(() => computeDiff(before, after), [before, after]);
2146
+ const summary = useMemo(() => summarizeDiff(diffLines), [diffLines]);
2147
+ const containerStyle3 = {
2148
+ fontFamily: 'var(--glyph-font-mono, ui-monospace, "Cascadia Code", "Fira Code", monospace)',
2149
+ fontSize: "0.8125rem",
2150
+ lineHeight: 1.5,
2151
+ color: "var(--glyph-text, #1a2035)",
2152
+ border: "1px solid var(--glyph-border, #d0d8e4)",
2153
+ borderRadius: "var(--glyph-radius-md, 0.5rem)",
2154
+ overflow: "hidden"
2155
+ };
2156
+ const labelBarStyle = {
2157
+ display: "flex",
2158
+ gap: "1rem",
2159
+ padding: "var(--glyph-spacing-xs, 0.25rem) var(--glyph-spacing-md, 1rem)",
2160
+ background: "var(--glyph-codediff-gutter-bg, var(--glyph-surface, #e8ecf3))",
2161
+ borderBottom: "1px solid var(--glyph-border, #d0d8e4)",
2162
+ fontSize: "0.75rem",
2163
+ fontWeight: 600,
2164
+ color: "var(--glyph-text-muted, #6b7a94)"
2165
+ };
2166
+ const tableStyle = {
2167
+ width: "100%",
2168
+ borderCollapse: "collapse",
2169
+ tableLayout: "fixed"
2170
+ };
2171
+ const gutterStyle = {
2172
+ width: "1.5rem",
2173
+ textAlign: "center",
2174
+ userSelect: "none",
2175
+ color: "var(--glyph-text-muted, #6b7a94)",
2176
+ background: "var(--glyph-codediff-gutter-bg, var(--glyph-surface, #e8ecf3))",
2177
+ borderRight: "1px solid var(--glyph-border, #d0d8e4)",
2178
+ verticalAlign: "top",
2179
+ padding: "0 0.25rem"
2180
+ };
2181
+ const lineNoStyle = {
2182
+ width: "2.5rem",
2183
+ textAlign: "right",
2184
+ userSelect: "none",
2185
+ color: "var(--glyph-text-muted, #6b7a94)",
2186
+ paddingRight: "0.5rem",
2187
+ verticalAlign: "top"
2188
+ };
2189
+ const codeStyle = {
2190
+ paddingLeft: "0.5rem",
2191
+ whiteSpace: "pre",
2192
+ overflowX: "auto",
2193
+ verticalAlign: "top"
2194
+ };
2195
+ function rowBg(kind) {
2196
+ if (kind === "add") return "var(--glyph-codediff-add-bg, rgba(22, 163, 106, 0.1))";
2197
+ if (kind === "del") return "var(--glyph-codediff-del-bg, rgba(220, 38, 38, 0.1))";
2198
+ return void 0;
2199
+ }
2200
+ function rowColor(kind) {
2201
+ if (kind === "add") return "var(--glyph-codediff-add-color, inherit)";
2202
+ if (kind === "del") return "var(--glyph-codediff-del-color, inherit)";
2203
+ return void 0;
2204
+ }
2205
+ return /* @__PURE__ */ jsxs("div", { id: baseId, role: "region", "aria-label": summary, style: containerStyle3, children: [
2206
+ (beforeLabel || afterLabel) && /* @__PURE__ */ jsxs("div", { style: labelBarStyle, children: [
2207
+ beforeLabel && /* @__PURE__ */ jsx("span", { children: beforeLabel }),
2208
+ beforeLabel && afterLabel && /* @__PURE__ */ jsx("span", { children: "\u2192" }),
2209
+ afterLabel && /* @__PURE__ */ jsx("span", { children: afterLabel })
2210
+ ] }),
2211
+ /* @__PURE__ */ jsx("div", { style: { overflowX: "auto" }, children: /* @__PURE__ */ jsx("table", { role: "grid", style: tableStyle, children: /* @__PURE__ */ jsx("tbody", { children: diffLines.map((line6, i) => /* @__PURE__ */ jsxs(
2212
+ "tr",
2213
+ {
2214
+ "aria-label": ARIA_LABELS[line6.kind],
2215
+ style: { background: rowBg(line6.kind), color: rowColor(line6.kind) },
2216
+ children: [
2217
+ /* @__PURE__ */ jsx("td", { style: gutterStyle, children: GUTTER_MARKERS[line6.kind] }),
2218
+ /* @__PURE__ */ jsx("td", { style: lineNoStyle, children: line6.oldLineNo ?? "" }),
2219
+ /* @__PURE__ */ jsx("td", { style: lineNoStyle, children: line6.newLineNo ?? "" }),
2220
+ /* @__PURE__ */ jsx("td", { style: codeStyle, children: line6.text })
2221
+ ]
2222
+ },
2223
+ i
2224
+ )) }) }) })
2225
+ ] });
2226
+ }
2227
+
2228
+ // src/codediff/index.ts
2229
+ var codeDiffDefinition = {
2230
+ type: "ui:codediff",
2231
+ schema: codediffSchema,
2232
+ render: CodeDiff
2233
+ };
2234
+ var NODE_WIDTH = 160;
2235
+ var NODE_HEIGHT = 40;
2236
+ var DECISION_WIDTH = 170;
2237
+ var DECISION_HEIGHT = 70;
2238
+ var NODE_SEP3 = 50;
2239
+ var RANK_SEP3 = 70;
2240
+ var LAYOUT_PADDING3 = 40;
2241
+ function computeLayout(nodes, edges, direction) {
2242
+ const g = new dagre.graphlib.Graph();
2243
+ g.setGraph({
2244
+ rankdir: direction === "left-right" ? "LR" : "TB",
2245
+ nodesep: NODE_SEP3,
2246
+ ranksep: RANK_SEP3,
2247
+ edgesep: 10
2248
+ });
2249
+ g.setDefaultEdgeLabel(() => ({}));
2250
+ for (const node of nodes) {
2251
+ const isDecision = node.type === "decision";
2252
+ g.setNode(node.id, {
2253
+ label: node.label,
2254
+ width: isDecision ? DECISION_WIDTH : NODE_WIDTH,
2255
+ height: isDecision ? DECISION_HEIGHT : NODE_HEIGHT
2256
+ });
2257
+ }
2258
+ for (const edge of edges) {
2259
+ g.setEdge(edge.from, edge.to);
2260
+ }
2261
+ dagre.layout(g);
2262
+ const positionedNodes = nodes.map((node) => {
2263
+ const d = g.node(node.id);
2264
+ return { ...node, x: d.x, y: d.y, width: d.width, height: d.height };
2265
+ });
2266
+ const positionedEdges = edges.map((edge) => {
2267
+ const d = g.edge(edge.from, edge.to);
2268
+ return { ...edge, points: d.points };
2269
+ });
2270
+ let maxX = 0;
2271
+ let maxY = 0;
2272
+ for (const n of positionedNodes) {
2273
+ maxX = Math.max(maxX, n.x + n.width / 2);
2274
+ maxY = Math.max(maxY, n.y + n.height / 2);
2275
+ }
2276
+ return {
2277
+ nodes: positionedNodes,
2278
+ edges: positionedEdges,
2279
+ width: maxX + LAYOUT_PADDING3,
2280
+ height: maxY + LAYOUT_PADDING3
2281
+ };
2282
+ }
2283
+ function getThemeVar2(container, varName, fallback) {
2284
+ return getComputedStyle(container).getPropertyValue(varName).trim() || fallback;
2285
+ }
2286
+ var ARROW_MARKER_ID2 = "glyph-flowchart-arrowhead";
2287
+ function renderNodeShape(nodeG, node, fillOpacity, strokeWidth) {
2288
+ const cx = node.x;
2289
+ const cy = node.y;
2290
+ const w = node.width;
2291
+ const h = node.height;
2292
+ switch (node.type) {
2293
+ case "start":
2294
+ case "end": {
2295
+ nodeG.append("rect").attr("x", cx - w / 2).attr("y", cy - h / 2).attr("width", w).attr("height", h).attr("rx", h / 2).attr("ry", h / 2).attr("fill", "var(--glyph-accent, #00d4aa)").attr("stroke", "var(--glyph-accent-hover, #33e0be)").attr("stroke-width", strokeWidth).attr("opacity", fillOpacity);
2296
+ break;
2297
+ }
2298
+ case "decision": {
2299
+ const top = `${cx},${cy - h / 2}`;
2300
+ const right = `${cx + w / 2},${cy}`;
2301
+ const bottom = `${cx},${cy + h / 2}`;
2302
+ const left = `${cx - w / 2},${cy}`;
2303
+ nodeG.append("polygon").attr("points", `${top} ${right} ${bottom} ${left}`).attr("fill", "var(--glyph-surface-raised, #162038)").attr("stroke", "var(--glyph-accent, #00d4aa)").attr("stroke-width", strokeWidth).attr("opacity", fillOpacity);
2304
+ break;
2305
+ }
2306
+ case "process":
2307
+ default: {
2308
+ nodeG.append("rect").attr("x", cx - w / 2).attr("y", cy - h / 2).attr("width", w).attr("height", h).attr("rx", 3).attr("ry", 3).attr("fill", "var(--glyph-surface-raised, #162038)").attr("stroke", "var(--glyph-border-strong, #2a3550)").attr("stroke-width", strokeWidth).attr("opacity", fillOpacity);
2309
+ break;
2310
+ }
2311
+ }
2312
+ }
2313
+ function renderFlowchart(svgElement, layout) {
2314
+ const svg = d32.select(svgElement);
2315
+ svg.selectAll("*").remove();
2316
+ const width = Math.max(layout.width, 200);
2317
+ const height = Math.max(layout.height, 200);
2318
+ svg.attr("viewBox", `0 0 ${width} ${height}`);
2319
+ const defs = svg.append("defs");
2320
+ defs.append("marker").attr("id", ARROW_MARKER_ID2).attr("viewBox", "0 0 10 10").attr("refX", 10).attr("refY", 5).attr("markerWidth", 8).attr("markerHeight", 8).attr("orient", "auto-start-reverse").append("path").attr("d", "M 0 0 L 10 5 L 0 10 Z").attr("fill", "var(--glyph-edge-color, #6b7a94)");
2321
+ const container = svgElement.parentElement ?? svgElement;
2322
+ const nodeStrokeWidth = getThemeVar2(container, "--glyph-node-stroke-width", "1.5");
2323
+ const nodeFillOpacity = getThemeVar2(container, "--glyph-node-fill-opacity", "0.85");
2324
+ const root = svg.append("g").attr("class", "glyph-flowchart-root");
2325
+ const zoomBehavior = d32.zoom().scaleExtent([0.1, 4]).on("zoom", (event) => {
2326
+ root.attr("transform", event.transform.toString());
2327
+ });
2328
+ svg.call(zoomBehavior);
2329
+ const lineGen = d32.line().x((d) => d.x).y((d) => d.y).curve(d32.curveBasis);
2330
+ const edgeGroup = root.append("g").attr("class", "glyph-flowchart-edges");
2331
+ for (const edge of layout.edges) {
2332
+ const edgeG = edgeGroup.append("g").attr("class", "glyph-flowchart-edge");
2333
+ edgeG.append("path").attr("d", lineGen(edge.points) ?? "").attr("fill", "none").attr("stroke", "var(--glyph-edge-color, #6b7a94)").attr("stroke-width", nodeStrokeWidth).attr("marker-end", `url(#${ARROW_MARKER_ID2})`);
2334
+ if (edge.label) {
2335
+ const mid = edge.points[Math.floor(edge.points.length / 2)];
2336
+ if (mid) {
2337
+ edgeG.append("text").attr("x", mid.x).attr("y", mid.y - 8).attr("text-anchor", "middle").attr("font-size", "11px").attr("fill", "var(--glyph-text-muted, #6b7a94)").text(edge.label);
2338
+ }
2339
+ }
2340
+ }
2341
+ const nodeGroup = root.append("g").attr("class", "glyph-flowchart-nodes");
2342
+ for (const node of layout.nodes) {
2343
+ const nodeG = nodeGroup.append("g").attr("class", "glyph-flowchart-node");
2344
+ renderNodeShape(nodeG, node, nodeFillOpacity, nodeStrokeWidth);
2345
+ nodeG.append("text").attr("x", node.x).attr("y", node.y).attr("dy", "0.35em").attr("text-anchor", "middle").attr("font-size", "13px").attr("font-family", "Inter, system-ui, sans-serif").attr("fill", "var(--glyph-node-label-color, #fff)").attr("pointer-events", "none").text(node.label);
2346
+ }
2347
+ }
2348
+ function Flowchart({ data, container }) {
2349
+ const svgRef = useRef(null);
2350
+ const layoutResult = useMemo(() => computeLayout(data.nodes, data.edges, data.direction), [data]);
2351
+ useEffect(() => {
2352
+ if (!svgRef.current) return;
2353
+ renderFlowchart(svgRef.current, layoutResult);
2354
+ }, [layoutResult]);
2355
+ const nodeCount = data.nodes.length;
2356
+ const edgeCount = data.edges.length;
2357
+ const ariaLabel = data.title ? `${data.title}: flowchart with ${nodeCount} nodes and ${edgeCount} edges` : `Flowchart with ${nodeCount} nodes and ${edgeCount} edges`;
2358
+ return /* @__PURE__ */ jsxs("div", { className: "glyph-flowchart-container", children: [
2359
+ data.title && /* @__PURE__ */ jsx(
2360
+ "div",
2361
+ {
2362
+ style: {
2363
+ fontFamily: "var(--glyph-font-heading, Inter, system-ui, sans-serif)",
2364
+ fontWeight: 600,
2365
+ fontSize: "1rem",
2366
+ color: "var(--glyph-heading, #edf0f5)",
2367
+ marginBottom: "0.5rem"
2368
+ },
2369
+ children: data.title
2370
+ }
2371
+ ),
2372
+ /* @__PURE__ */ jsx(
2373
+ "svg",
2374
+ {
2375
+ ref: svgRef,
2376
+ role: "img",
2377
+ "aria-label": ariaLabel,
2378
+ width: "100%",
2379
+ height: "100%",
2380
+ style: {
2381
+ minHeight: container.tier === "compact" ? 200 : 300,
2382
+ maxHeight: container.tier === "compact" ? 500 : 700,
2383
+ display: "block"
2384
+ }
2385
+ }
2386
+ ),
2387
+ /* @__PURE__ */ jsxs("table", { className: "sr-only", "aria-label": "Flowchart data", style: SR_ONLY_STYLE3, children: [
2388
+ /* @__PURE__ */ jsx("caption", { children: "Flowchart nodes and connections" }),
2389
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
2390
+ /* @__PURE__ */ jsx("th", { scope: "col", children: "Node" }),
2391
+ /* @__PURE__ */ jsx("th", { scope: "col", children: "Type" }),
2392
+ /* @__PURE__ */ jsx("th", { scope: "col", children: "Connections" })
2393
+ ] }) }),
2394
+ /* @__PURE__ */ jsx("tbody", { children: data.nodes.map((node) => {
2395
+ const connections = data.edges.filter((e) => e.from === node.id || e.to === node.id).map((e) => {
2396
+ const target = e.from === node.id ? e.to : e.from;
2397
+ const dir = e.from === node.id ? "->" : "<-";
2398
+ return `${dir} ${target}${e.label ? ` (${e.label})` : ""}`;
2399
+ }).join(", ");
2400
+ return /* @__PURE__ */ jsxs("tr", { children: [
2401
+ /* @__PURE__ */ jsx("td", { children: node.label }),
2402
+ /* @__PURE__ */ jsx("td", { children: node.type }),
2403
+ /* @__PURE__ */ jsx("td", { children: connections })
2404
+ ] }, node.id);
2405
+ }) })
2406
+ ] })
2407
+ ] });
2408
+ }
2409
+ var SR_ONLY_STYLE3 = {
2410
+ position: "absolute",
2411
+ width: "1px",
2412
+ height: "1px",
2413
+ padding: 0,
2414
+ margin: "-1px",
2415
+ overflow: "hidden",
2416
+ clip: "rect(0, 0, 0, 0)",
2417
+ whiteSpace: "nowrap",
2418
+ border: 0
2419
+ };
2420
+
2421
+ // src/flowchart/index.ts
2422
+ var flowchartDefinition = {
2423
+ type: "ui:flowchart",
2424
+ schema: flowchartSchema,
2425
+ render: Flowchart
2426
+ };
2427
+ function getFileIcon(name) {
2428
+ const ext = name.split(".").pop()?.toLowerCase() ?? "";
2429
+ switch (ext) {
2430
+ case "ts":
2431
+ case "tsx":
2432
+ return "\u{1F1F9}";
2433
+ // 🇹 — TS indicator
2434
+ case "js":
2435
+ case "jsx":
2436
+ return "\u{1F1EF}";
2437
+ // 🇯 — JS indicator
2438
+ case "json":
2439
+ return "\u{1F4CB}";
2440
+ // 📋
2441
+ case "md":
2442
+ case "mdx":
2443
+ return "\u{1F4DD}";
2444
+ // 📝
2445
+ case "css":
2446
+ case "scss":
2447
+ case "less":
2448
+ return "\u{1F3A8}";
2449
+ // 🎨
2450
+ case "html":
2451
+ return "\u{1F310}";
2452
+ // 🌐
2453
+ case "svg":
2454
+ case "png":
2455
+ case "jpg":
2456
+ case "jpeg":
2457
+ case "gif":
2458
+ return "\u{1F5BC}";
2459
+ // 🖼
2460
+ case "yml":
2461
+ case "yaml":
2462
+ return "\u2699";
2463
+ // ⚙
2464
+ default:
2465
+ return "\u{1F4C4}";
2466
+ }
2467
+ }
2468
+ function TreeItem({
2469
+ node,
2470
+ level,
2471
+ defaultExpanded,
2472
+ flatIndex,
2473
+ focusedIndex,
2474
+ setSize,
2475
+ posInSet,
2476
+ onFocusChange,
2477
+ flatItems
2478
+ }) {
2479
+ const [expanded, setExpanded] = useState(defaultExpanded);
2480
+ const itemRef = useRef(null);
2481
+ const isDir = Array.isArray(node.children) && node.children.length > 0;
2482
+ const isFocused = flatIndex === focusedIndex;
2483
+ const handleToggle = useCallback(() => {
2484
+ if (isDir) setExpanded((prev) => !prev);
2485
+ }, [isDir]);
2486
+ const handleKeyDown = useCallback(
2487
+ (e) => {
2488
+ let handled = true;
2489
+ switch (e.key) {
2490
+ case "ArrowDown": {
2491
+ const next = flatIndex + 1;
2492
+ if (next < flatItems.length) onFocusChange(next);
2493
+ break;
2494
+ }
2495
+ case "ArrowUp": {
2496
+ const prev = flatIndex - 1;
2497
+ if (prev >= 0) onFocusChange(prev);
2498
+ break;
2499
+ }
2500
+ case "ArrowRight":
2501
+ if (isDir && !expanded) {
2502
+ setExpanded(true);
2503
+ } else if (isDir && expanded) {
2504
+ const next = flatIndex + 1;
2505
+ if (next < flatItems.length) onFocusChange(next);
2506
+ }
2507
+ break;
2508
+ case "ArrowLeft":
2509
+ if (isDir && expanded) {
2510
+ setExpanded(false);
2511
+ } else if (level > 0) {
2512
+ for (let idx = flatIndex - 1; idx >= 0; idx--) {
2513
+ const item = flatItems[idx];
2514
+ if (item && item.level < level && item.isDir) {
2515
+ onFocusChange(idx);
2516
+ break;
2517
+ }
2518
+ }
2519
+ }
2520
+ break;
2521
+ case "Enter":
2522
+ case " ":
2523
+ handleToggle();
2524
+ break;
2525
+ case "Home":
2526
+ onFocusChange(0);
2527
+ break;
2528
+ case "End":
2529
+ onFocusChange(flatItems.length - 1);
2530
+ break;
2531
+ default:
2532
+ handled = false;
2533
+ }
2534
+ if (handled) {
2535
+ e.preventDefault();
2536
+ e.stopPropagation();
2537
+ }
2538
+ },
2539
+ [flatIndex, flatItems, isDir, expanded, level, onFocusChange, handleToggle]
2540
+ );
2541
+ const icon = isDir ? expanded ? "\u{1F4C2}" : "\u{1F4C1}" : getFileIcon(node.name);
2542
+ const ariaLabel = node.annotation ? `${node.name}, ${node.annotation}` : node.name;
2543
+ return /* @__PURE__ */ jsxs(
2544
+ "li",
2545
+ {
2546
+ ref: itemRef,
2547
+ role: "treeitem",
2548
+ "aria-expanded": isDir ? expanded : void 0,
2549
+ "aria-label": ariaLabel,
2550
+ "aria-level": level + 1,
2551
+ "aria-setsize": setSize,
2552
+ "aria-posinset": posInSet,
2553
+ tabIndex: isFocused ? 0 : -1,
2554
+ onKeyDown: handleKeyDown,
2555
+ onClick: (e) => {
2556
+ e.stopPropagation();
2557
+ onFocusChange(flatIndex);
2558
+ handleToggle();
2559
+ },
2560
+ style: {
2561
+ listStyle: "none",
2562
+ cursor: isDir ? "pointer" : "default",
2563
+ padding: "2px 0",
2564
+ outline: "none",
2565
+ borderRadius: "var(--glyph-radius-sm, 0.375rem)",
2566
+ background: isFocused ? "var(--glyph-accent-subtle, rgba(0,212,170,0.1))" : "transparent"
2567
+ },
2568
+ "data-flat-index": flatIndex,
2569
+ children: [
2570
+ /* @__PURE__ */ jsxs(
2571
+ "div",
2572
+ {
2573
+ style: {
2574
+ display: "flex",
2575
+ alignItems: "center",
2576
+ gap: "0.25rem",
2577
+ paddingLeft: `${level * 1.25}rem`,
2578
+ fontFamily: "var(--glyph-font-mono, monospace)",
2579
+ fontSize: "0.875rem",
2580
+ lineHeight: "1.6",
2581
+ color: "var(--glyph-text, #d4dae3)",
2582
+ userSelect: "none"
2583
+ },
2584
+ children: [
2585
+ /* @__PURE__ */ jsx("span", { style: { width: "1.25rem", textAlign: "center", flexShrink: 0 }, children: icon }),
2586
+ /* @__PURE__ */ jsx("span", { style: { fontWeight: isDir ? 600 : 400 }, children: node.name }),
2587
+ node.annotation && /* @__PURE__ */ jsx(
2588
+ "span",
2589
+ {
2590
+ style: {
2591
+ fontSize: "0.75rem",
2592
+ color: "var(--glyph-text-muted, #6b7a94)",
2593
+ background: "var(--glyph-surface, #0f1526)",
2594
+ padding: "0 0.375rem",
2595
+ borderRadius: "var(--glyph-radius-sm, 0.375rem)",
2596
+ marginLeft: "0.25rem"
2597
+ },
2598
+ children: node.annotation
2599
+ }
2600
+ )
2601
+ ]
2602
+ }
2603
+ ),
2604
+ isDir && expanded && node.children && /* @__PURE__ */ jsx("ul", { role: "group", style: { margin: 0, padding: 0 }, children: node.children.map((child, childIdx) => {
2605
+ const childFlatIndex = getChildFlatIndex(flatItems, flatIndex, childIdx, node.children);
2606
+ return /* @__PURE__ */ jsx(
2607
+ TreeItem,
2608
+ {
2609
+ node: child,
2610
+ level: level + 1,
2611
+ defaultExpanded,
2612
+ flatIndex: childFlatIndex,
2613
+ focusedIndex,
2614
+ setSize: node.children?.length ?? 0,
2615
+ posInSet: childIdx + 1,
2616
+ onFocusChange,
2617
+ flatItems
2618
+ },
2619
+ child.name
2620
+ );
2621
+ }) })
2622
+ ]
2623
+ }
2624
+ );
2625
+ }
2626
+ function flattenTree(nodes, level) {
2627
+ const result = [];
2628
+ for (const node of nodes) {
2629
+ const isDir = Array.isArray(node.children) && node.children.length > 0;
2630
+ result.push({ node, level, isDir });
2631
+ if (isDir && node.children) {
2632
+ result.push(...flattenTree(node.children, level + 1));
2633
+ }
2634
+ }
2635
+ return result;
2636
+ }
2637
+ function getChildFlatIndex(flatItems, parentFlatIndex, childIdx, children) {
2638
+ if (!children) return parentFlatIndex + 1;
2639
+ let count = 0;
2640
+ for (let i = parentFlatIndex + 1; i < flatItems.length; i++) {
2641
+ const item = flatItems[i];
2642
+ if (!item) continue;
2643
+ if (item.node === children[count]) {
2644
+ if (count === childIdx) return i;
2645
+ count++;
2646
+ }
2647
+ }
2648
+ return parentFlatIndex + childIdx + 1;
2649
+ }
2650
+ function FileTree({ data }) {
2651
+ const [focusedIndex, setFocusedIndex] = useState(0);
2652
+ const containerRef = useRef(null);
2653
+ const flatItems = flattenTree(data.tree, data.root ? 1 : 0);
2654
+ const handleFocusChange = useCallback((index) => {
2655
+ setFocusedIndex(index);
2656
+ const container = containerRef.current;
2657
+ if (!container) return;
2658
+ const el = container.querySelector(`[data-flat-index="${String(index)}"]`);
2659
+ el?.focus();
2660
+ }, []);
2661
+ return /* @__PURE__ */ jsx(
2662
+ "div",
2663
+ {
2664
+ ref: containerRef,
2665
+ className: "glyph-filetree-container",
2666
+ style: {
2667
+ border: "1px solid var(--glyph-border, #1a2035)",
2668
+ borderRadius: "var(--glyph-radius-md, 0.5rem)",
2669
+ padding: "0.75rem",
2670
+ background: "var(--glyph-surface, #0f1526)"
2671
+ },
2672
+ children: /* @__PURE__ */ jsxs("ul", { role: "tree", "aria-label": data.root ?? "File tree", style: { margin: 0, padding: 0 }, children: [
2673
+ data.root && /* @__PURE__ */ jsxs(
2674
+ "li",
2675
+ {
2676
+ role: "treeitem",
2677
+ "aria-expanded": true,
2678
+ "aria-level": 1,
2679
+ "aria-setsize": 1,
2680
+ "aria-posinset": 1,
2681
+ style: {
2682
+ listStyle: "none",
2683
+ padding: "2px 0"
2684
+ },
2685
+ children: [
2686
+ /* @__PURE__ */ jsxs(
2687
+ "div",
2688
+ {
2689
+ style: {
2690
+ display: "flex",
2691
+ alignItems: "center",
2692
+ gap: "0.25rem",
2693
+ fontFamily: "var(--glyph-font-mono, monospace)",
2694
+ fontSize: "0.875rem",
2695
+ fontWeight: 700,
2696
+ color: "var(--glyph-heading, #edf0f5)"
2697
+ },
2698
+ children: [
2699
+ /* @__PURE__ */ jsx("span", { style: { width: "1.25rem", textAlign: "center" }, children: "\u{1F4C2}" }),
2700
+ data.root
2701
+ ]
2702
+ }
2703
+ ),
2704
+ /* @__PURE__ */ jsx("ul", { role: "group", style: { margin: 0, padding: 0 }, children: data.tree.map((node, idx) => {
2705
+ const flatIndex = getFlatIndexForTopLevel(flatItems, idx, data.tree);
2706
+ return /* @__PURE__ */ jsx(
2707
+ TreeItem,
2708
+ {
2709
+ node,
2710
+ level: 1,
2711
+ defaultExpanded: data.defaultExpanded,
2712
+ flatIndex,
2713
+ focusedIndex,
2714
+ setSize: data.tree.length,
2715
+ posInSet: idx + 1,
2716
+ onFocusChange: handleFocusChange,
2717
+ flatItems
2718
+ },
2719
+ node.name
2720
+ );
2721
+ }) })
2722
+ ]
2723
+ }
2724
+ ),
2725
+ !data.root && data.tree.map((node, idx) => {
2726
+ const flatIndex = getFlatIndexForTopLevel(flatItems, idx, data.tree);
2727
+ return /* @__PURE__ */ jsx(
2728
+ TreeItem,
2729
+ {
2730
+ node,
2731
+ level: 0,
2732
+ defaultExpanded: data.defaultExpanded,
2733
+ flatIndex,
2734
+ focusedIndex,
2735
+ setSize: data.tree.length,
2736
+ posInSet: idx + 1,
2737
+ onFocusChange: handleFocusChange,
2738
+ flatItems
2739
+ },
2740
+ node.name
2741
+ );
2742
+ })
2743
+ ] })
2744
+ }
2745
+ );
2746
+ }
2747
+ function getFlatIndexForTopLevel(flatItems, idx, tree) {
2748
+ let count = 0;
2749
+ for (let i = 0; i < flatItems.length; i++) {
2750
+ const item = flatItems[i];
2751
+ if (item && item.node === tree[count]) {
2752
+ if (count === idx) return i;
2753
+ count++;
2754
+ }
2755
+ }
2756
+ return idx;
2757
+ }
2758
+
2759
+ // src/filetree/index.ts
2760
+ var fileTreeDefinition = {
2761
+ type: "ui:filetree",
2762
+ schema: filetreeSchema,
2763
+ render: FileTree
2764
+ };
2765
+ var ACTOR_WIDTH = 120;
2766
+ var ACTOR_HEIGHT = 40;
2767
+ var ACTOR_GAP = 160;
2768
+ var TOP_MARGIN = 20;
2769
+ var MSG_SPACING = 50;
2770
+ var SELF_ARC_WIDTH = 30;
2771
+ var SELF_ARC_HEIGHT = 30;
2772
+ var BOTTOM_PADDING = 40;
2773
+ var SIDE_PADDING = 40;
2774
+ var ARROW_SIZE = 8;
2775
+ function renderActorBox(actor, cx, y, keyPrefix, width = ACTOR_WIDTH, height = ACTOR_HEIGHT, fontSize = "13px") {
2776
+ return /* @__PURE__ */ jsxs("g", { children: [
2777
+ /* @__PURE__ */ jsx(
2778
+ "rect",
2779
+ {
2780
+ x: cx - width / 2,
2781
+ y,
2782
+ width,
2783
+ height,
2784
+ rx: 4,
2785
+ ry: 4,
2786
+ fill: "var(--glyph-surface-raised, #162038)",
2787
+ stroke: "var(--glyph-border-strong, #2a3550)",
2788
+ strokeWidth: 1.5
2789
+ }
2790
+ ),
2791
+ /* @__PURE__ */ jsx(
2792
+ "text",
2793
+ {
2794
+ x: cx,
2795
+ y: y + height / 2,
2796
+ dy: "0.35em",
2797
+ textAnchor: "middle",
2798
+ fontSize,
2799
+ fontFamily: "Inter, system-ui, sans-serif",
2800
+ fontWeight: 600,
2801
+ fill: "var(--glyph-text, #d4dae3)",
2802
+ children: actor.label
2803
+ }
2804
+ )
2805
+ ] }, `${keyPrefix}-${actor.id}`);
2806
+ }
2807
+ function renderSelfMessage(x, y, label, idx, fontSize = "12px") {
2808
+ return /* @__PURE__ */ jsxs("g", { children: [
2809
+ /* @__PURE__ */ jsx(
2810
+ "path",
2811
+ {
2812
+ d: `M ${x} ${y} L ${x + SELF_ARC_WIDTH} ${y} L ${x + SELF_ARC_WIDTH} ${y + SELF_ARC_HEIGHT} L ${x} ${y + SELF_ARC_HEIGHT}`,
2813
+ fill: "none",
2814
+ stroke: "var(--glyph-text, #d4dae3)",
2815
+ strokeWidth: 1.5,
2816
+ markerEnd: "url(#seq-arrow-solid)"
2817
+ }
2818
+ ),
2819
+ /* @__PURE__ */ jsx(
2820
+ "text",
2821
+ {
2822
+ x: x + SELF_ARC_WIDTH + 6,
2823
+ y: y + SELF_ARC_HEIGHT / 2,
2824
+ dy: "0.35em",
2825
+ fontSize,
2826
+ fontFamily: "Inter, system-ui, sans-serif",
2827
+ fill: "var(--glyph-text, #d4dae3)",
2828
+ children: label
2829
+ }
2830
+ )
2831
+ ] }, `msg-${idx}`);
2832
+ }
2833
+ function renderStandardMessage(fromX, toX, y, label, isDashed, idx, fontSize = "12px") {
2834
+ const markerId = isDashed ? "seq-arrow-dashed" : "seq-arrow-solid";
2835
+ const midX = (fromX + toX) / 2;
2836
+ return /* @__PURE__ */ jsxs("g", { children: [
2837
+ /* @__PURE__ */ jsx(
2838
+ "line",
2839
+ {
2840
+ x1: fromX,
2841
+ y1: y,
2842
+ x2: toX,
2843
+ y2: y,
2844
+ stroke: isDashed ? "var(--glyph-text-muted, #6b7a94)" : "var(--glyph-text, #d4dae3)",
2845
+ strokeWidth: 1.5,
2846
+ strokeDasharray: isDashed ? "6,4" : void 0,
2847
+ markerEnd: `url(#${markerId})`
2848
+ }
2849
+ ),
2850
+ /* @__PURE__ */ jsx(
2851
+ "text",
2852
+ {
2853
+ x: midX,
2854
+ y: y - 8,
2855
+ textAnchor: "middle",
2856
+ fontSize,
2857
+ fontFamily: "Inter, system-ui, sans-serif",
2858
+ fill: "var(--glyph-text, #d4dae3)",
2859
+ children: label
2860
+ }
2861
+ )
2862
+ ] }, `msg-${idx}`);
2863
+ }
2864
+ function Sequence({ data, container }) {
2865
+ const actorCount = data.actors.length;
2866
+ const messageCount = data.messages.length;
2867
+ const isCompact = container.tier === "compact";
2868
+ const effectiveActorGap = isCompact ? Math.round(ACTOR_GAP * 0.6) : ACTOR_GAP;
2869
+ const effectiveActorWidth = isCompact ? Math.round(ACTOR_WIDTH * 0.7) : ACTOR_WIDTH;
2870
+ const effectiveActorHeight = isCompact ? Math.round(ACTOR_HEIGHT * 0.85) : ACTOR_HEIGHT;
2871
+ const actorFontSize = isCompact ? "11px" : "13px";
2872
+ const msgFontSize = isCompact ? "10px" : "12px";
2873
+ const actorX = /* @__PURE__ */ new Map();
2874
+ for (let i = 0; i < actorCount; i++) {
2875
+ const actor = data.actors[i];
2876
+ if (actor) {
2877
+ actorX.set(actor.id, SIDE_PADDING + i * effectiveActorGap + effectiveActorWidth / 2);
2878
+ }
2879
+ }
2880
+ const svgWidth = SIDE_PADDING * 2 + (actorCount - 1) * effectiveActorGap + effectiveActorWidth;
2881
+ const actorBoxY = TOP_MARGIN;
2882
+ const lifelineStartY = actorBoxY + effectiveActorHeight;
2883
+ const firstMsgY = lifelineStartY + MSG_SPACING;
2884
+ const lastMsgY = firstMsgY + (messageCount - 1) * MSG_SPACING;
2885
+ const svgHeight = lastMsgY + BOTTOM_PADDING + effectiveActorHeight;
2886
+ const ariaLabel = data.title ? `${data.title}: sequence diagram with ${actorCount} actors and ${messageCount} messages` : `Sequence diagram with ${actorCount} actors and ${messageCount} messages`;
2887
+ return /* @__PURE__ */ jsxs("div", { className: "glyph-sequence-container", children: [
2888
+ data.title && /* @__PURE__ */ jsx(
2889
+ "div",
2890
+ {
2891
+ style: {
2892
+ fontFamily: "var(--glyph-font-heading, Inter, system-ui, sans-serif)",
2893
+ fontWeight: 600,
2894
+ fontSize: "1rem",
2895
+ color: "var(--glyph-heading, #edf0f5)",
2896
+ marginBottom: "0.5rem"
2897
+ },
2898
+ children: data.title
2899
+ }
2900
+ ),
2901
+ /* @__PURE__ */ jsxs(
2902
+ "svg",
2903
+ {
2904
+ role: "img",
2905
+ "aria-label": ariaLabel,
2906
+ viewBox: `0 0 ${svgWidth} ${svgHeight}`,
2907
+ width: "100%",
2908
+ style: { minHeight: 200, maxHeight: 800, display: "block" },
2909
+ children: [
2910
+ /* @__PURE__ */ jsxs("defs", { children: [
2911
+ /* @__PURE__ */ jsx(
2912
+ "marker",
2913
+ {
2914
+ id: "seq-arrow-solid",
2915
+ viewBox: "0 0 10 10",
2916
+ refX: 10,
2917
+ refY: 5,
2918
+ markerWidth: ARROW_SIZE,
2919
+ markerHeight: ARROW_SIZE,
2920
+ orient: "auto-start-reverse",
2921
+ children: /* @__PURE__ */ jsx("path", { d: "M 0 0 L 10 5 L 0 10 Z", fill: "var(--glyph-text, #d4dae3)" })
2922
+ }
2923
+ ),
2924
+ /* @__PURE__ */ jsx(
2925
+ "marker",
2926
+ {
2927
+ id: "seq-arrow-dashed",
2928
+ viewBox: "0 0 10 10",
2929
+ refX: 10,
2930
+ refY: 5,
2931
+ markerWidth: ARROW_SIZE,
2932
+ markerHeight: ARROW_SIZE,
2933
+ orient: "auto-start-reverse",
2934
+ children: /* @__PURE__ */ jsx("path", { d: "M 0 0 L 10 5 L 0 10 Z", fill: "var(--glyph-text-muted, #6b7a94)" })
2935
+ }
2936
+ )
2937
+ ] }),
2938
+ data.actors.map((actor) => {
2939
+ const cx = actorX.get(actor.id) ?? 0;
2940
+ return renderActorBox(
2941
+ actor,
2942
+ cx,
2943
+ actorBoxY,
2944
+ "actor-top",
2945
+ effectiveActorWidth,
2946
+ effectiveActorHeight,
2947
+ actorFontSize
2948
+ );
2949
+ }),
2950
+ data.actors.map((actor) => {
2951
+ const cx = actorX.get(actor.id) ?? 0;
2952
+ return /* @__PURE__ */ jsx(
2953
+ "line",
2954
+ {
2955
+ x1: cx,
2956
+ y1: lifelineStartY,
2957
+ x2: cx,
2958
+ y2: lastMsgY + BOTTOM_PADDING / 2,
2959
+ stroke: "var(--glyph-border, #1a2035)",
2960
+ strokeWidth: 1,
2961
+ strokeDasharray: "6,4"
2962
+ },
2963
+ `lifeline-${actor.id}`
2964
+ );
2965
+ }),
2966
+ data.actors.map((actor) => {
2967
+ const cx = actorX.get(actor.id) ?? 0;
2968
+ const bottomY = lastMsgY + BOTTOM_PADDING / 2;
2969
+ return renderActorBox(
2970
+ actor,
2971
+ cx,
2972
+ bottomY,
2973
+ "actor-bottom",
2974
+ effectiveActorWidth,
2975
+ effectiveActorHeight,
2976
+ actorFontSize
2977
+ );
2978
+ }),
2979
+ data.messages.map((msg, idx) => {
2980
+ const y = firstMsgY + idx * MSG_SPACING;
2981
+ const fromX = actorX.get(msg.from) ?? 0;
2982
+ const toX = actorX.get(msg.to) ?? 0;
2983
+ if (msg.type === "self") {
2984
+ return renderSelfMessage(fromX, y, msg.label, idx, msgFontSize);
2985
+ }
2986
+ return renderStandardMessage(
2987
+ fromX,
2988
+ toX,
2989
+ y,
2990
+ msg.label,
2991
+ msg.type === "reply",
2992
+ idx,
2993
+ msgFontSize
2994
+ );
2995
+ })
2996
+ ]
2997
+ }
2998
+ ),
2999
+ /* @__PURE__ */ jsxs("table", { className: "sr-only", "aria-label": "Sequence data", style: SR_ONLY_STYLE4, children: [
3000
+ /* @__PURE__ */ jsx("caption", { children: "Sequence messages in order" }),
3001
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
3002
+ /* @__PURE__ */ jsx("th", { scope: "col", children: "#" }),
3003
+ /* @__PURE__ */ jsx("th", { scope: "col", children: "From" }),
3004
+ /* @__PURE__ */ jsx("th", { scope: "col", children: "To" }),
3005
+ /* @__PURE__ */ jsx("th", { scope: "col", children: "Message" }),
3006
+ /* @__PURE__ */ jsx("th", { scope: "col", children: "Type" })
3007
+ ] }) }),
3008
+ /* @__PURE__ */ jsx("tbody", { children: data.messages.map((msg, idx) => {
3009
+ const fromActor = data.actors.find((a) => a.id === msg.from);
3010
+ const toActor = data.actors.find((a) => a.id === msg.to);
3011
+ return /* @__PURE__ */ jsxs("tr", { children: [
3012
+ /* @__PURE__ */ jsx("td", { children: idx + 1 }),
3013
+ /* @__PURE__ */ jsx("td", { children: fromActor?.label ?? msg.from }),
3014
+ /* @__PURE__ */ jsx("td", { children: toActor?.label ?? msg.to }),
3015
+ /* @__PURE__ */ jsx("td", { children: msg.label }),
3016
+ /* @__PURE__ */ jsx("td", { children: msg.type })
3017
+ ] }, idx);
3018
+ }) })
3019
+ ] })
3020
+ ] });
3021
+ }
3022
+ var SR_ONLY_STYLE4 = {
3023
+ position: "absolute",
3024
+ width: "1px",
3025
+ height: "1px",
3026
+ padding: 0,
3027
+ margin: "-1px",
3028
+ overflow: "hidden",
3029
+ clip: "rect(0, 0, 0, 0)",
3030
+ whiteSpace: "nowrap",
3031
+ border: 0
3032
+ };
3033
+
3034
+ // src/sequence/index.ts
3035
+ var sequenceDefinition = {
3036
+ type: "ui:sequence",
3037
+ schema: sequenceSchema,
3038
+ render: Sequence
3039
+ };
3040
+
3041
+ // src/architecture/layout.ts
3042
+ var NODE_WIDTH2 = 120;
3043
+ var NODE_HEIGHT_WITH_ICON = 60;
3044
+ var NODE_HEIGHT_NO_ICON = 40;
3045
+ var ZONE_PADDING_TOP = 30;
3046
+ var ZONE_PADDING_SIDES = 15;
3047
+ var ZONE_PADDING_BOTTOM = 15;
3048
+ var LAYOUT_PADDING4 = 20;
3049
+ var DIRECTION_MAP = {
3050
+ "top-down": "DOWN",
3051
+ "left-right": "RIGHT",
3052
+ "bottom-up": "UP"
3053
+ };
3054
+ function buildElkNode(node) {
3055
+ if (node.type === "zone" && node.children?.length) {
3056
+ return {
3057
+ id: node.id,
3058
+ labels: [{ text: node.label }],
3059
+ children: node.children.map(buildElkNode),
3060
+ layoutOptions: {
3061
+ "elk.padding": `[top=${ZONE_PADDING_TOP},left=${ZONE_PADDING_SIDES},bottom=${ZONE_PADDING_BOTTOM},right=${ZONE_PADDING_SIDES}]`
3062
+ }
3063
+ };
3064
+ }
3065
+ return {
3066
+ id: node.id,
3067
+ width: NODE_WIDTH2,
3068
+ height: node.icon ? NODE_HEIGHT_WITH_ICON : NODE_HEIGHT_NO_ICON,
3069
+ labels: [{ text: node.label }]
3070
+ };
3071
+ }
3072
+ function buildElkGraph(data) {
3073
+ const direction = DIRECTION_MAP[data.layout ?? "top-down"] ?? "DOWN";
3074
+ return {
3075
+ id: "root",
3076
+ children: data.children.map(buildElkNode),
3077
+ edges: data.edges.map((e, i) => ({
3078
+ id: `e${i}`,
3079
+ sources: [e.from],
3080
+ targets: [e.to],
3081
+ labels: e.label ? [{ text: e.label }] : []
3082
+ })),
3083
+ layoutOptions: {
3084
+ "elk.algorithm": "layered",
3085
+ "elk.hierarchyHandling": "INCLUDE_CHILDREN",
3086
+ "elk.edgeRouting": "ORTHOGONAL",
3087
+ "elk.direction": direction,
3088
+ "elk.spacing.nodeNode": "20",
3089
+ "elk.layered.spacing.nodeNodeBetweenLayers": "30",
3090
+ "elk.spacing.edgeNode": "10",
3091
+ "elk.spacing.edgeEdge": "8"
3092
+ }
3093
+ };
3094
+ }
3095
+ function collectNodes(elkNode, nodeMap, offsetX, offsetY, nodes, zones, depth, zoneId) {
3096
+ const absX = offsetX + (elkNode.x ?? 0);
3097
+ const absY = offsetY + (elkNode.y ?? 0);
3098
+ const original = nodeMap.get(elkNode.id);
3099
+ if (original?.type === "zone" && elkNode.children?.length) {
3100
+ zones.push({
3101
+ id: elkNode.id,
3102
+ label: original.label,
3103
+ x: absX,
3104
+ y: absY,
3105
+ width: elkNode.width,
3106
+ height: elkNode.height,
3107
+ depth
3108
+ });
3109
+ for (const child of elkNode.children) {
3110
+ collectNodes(child, nodeMap, absX, absY, nodes, zones, depth + 1, elkNode.id);
3111
+ }
3112
+ } else {
3113
+ nodes.push({
3114
+ id: elkNode.id,
3115
+ label: original?.label ?? elkNode.id,
3116
+ icon: original?.icon,
3117
+ x: absX,
3118
+ y: absY,
3119
+ width: elkNode.width,
3120
+ height: elkNode.height,
3121
+ zoneId
3122
+ });
3123
+ }
3124
+ }
3125
+ function flattenNodeMap(children, map) {
3126
+ for (const node of children) {
3127
+ map.set(node.id, node);
3128
+ if (node.children) {
3129
+ flattenNodeMap(node.children, map);
3130
+ }
3131
+ }
3132
+ }
3133
+ function buildAncestorMap(children, ancestors, map) {
3134
+ for (const node of children) {
3135
+ map.set(node.id, [...ancestors]);
3136
+ if (node.type === "zone" && node.children) {
3137
+ buildAncestorMap(node.children, [...ancestors, node.id], map);
3138
+ }
3139
+ }
3140
+ }
3141
+ function findLCA(ancestorsA, ancestorsB) {
3142
+ let lca;
3143
+ const len = Math.min(ancestorsA.length, ancestorsB.length);
3144
+ for (let i = 0; i < len; i++) {
3145
+ if (ancestorsA[i] === ancestorsB[i]) {
3146
+ lca = ancestorsA[i];
3147
+ } else {
3148
+ break;
3149
+ }
3150
+ }
3151
+ return lca;
3152
+ }
3153
+ function extractEdges(elkEdges, dataEdges, ancestorMap, zoneAbsOffsets) {
3154
+ if (!elkEdges) return [];
3155
+ return elkEdges.map((elkEdge, i) => {
3156
+ const dataEdge = dataEdges[i];
3157
+ if (!dataEdge) {
3158
+ return { from: "", to: "", points: [] };
3159
+ }
3160
+ const srcAncestors = ancestorMap.get(dataEdge.from) ?? [];
3161
+ const tgtAncestors = ancestorMap.get(dataEdge.to) ?? [];
3162
+ const lca = findLCA(srcAncestors, tgtAncestors);
3163
+ const offset = lca ? zoneAbsOffsets.get(lca) ?? { x: 0, y: 0 } : { x: 0, y: 0 };
3164
+ const points = [];
3165
+ if (elkEdge.sections) {
3166
+ for (const section of elkEdge.sections) {
3167
+ points.push({ x: section.startPoint.x + offset.x, y: section.startPoint.y + offset.y });
3168
+ if (section.bendPoints) {
3169
+ for (const bp of section.bendPoints) {
3170
+ points.push({ x: bp.x + offset.x, y: bp.y + offset.y });
3171
+ }
3172
+ }
3173
+ points.push({ x: section.endPoint.x + offset.x, y: section.endPoint.y + offset.y });
3174
+ }
3175
+ }
3176
+ return {
3177
+ from: dataEdge.from,
3178
+ to: dataEdge.to,
3179
+ label: dataEdge.label,
3180
+ type: dataEdge.type,
3181
+ style: dataEdge.style,
3182
+ points
3183
+ };
3184
+ });
3185
+ }
3186
+ async function computeArchitectureLayout(data) {
3187
+ const ELK = await import('elkjs/lib/elk.bundled.js');
3188
+ const elk = new ELK.default();
3189
+ const elkGraph = buildElkGraph(data);
3190
+ const result = await elk.layout(
3191
+ elkGraph
3192
+ );
3193
+ const nodeMap = /* @__PURE__ */ new Map();
3194
+ flattenNodeMap(data.children, nodeMap);
3195
+ const nodes = [];
3196
+ const zones = [];
3197
+ if (result.children) {
3198
+ for (const child of result.children) {
3199
+ collectNodes(child, nodeMap, 0, 0, nodes, zones, 0);
3200
+ }
3201
+ }
3202
+ const zoneAbsOffsets = /* @__PURE__ */ new Map();
3203
+ for (const z of zones) {
3204
+ zoneAbsOffsets.set(z.id, { x: z.x, y: z.y });
3205
+ }
3206
+ const ancestorMap = /* @__PURE__ */ new Map();
3207
+ buildAncestorMap(data.children, [], ancestorMap);
3208
+ const edges = extractEdges(result.edges, data.edges, ancestorMap, zoneAbsOffsets);
3209
+ let maxX = 0;
3210
+ let maxY = 0;
3211
+ for (const n of nodes) {
3212
+ const right = n.x + n.width;
3213
+ const bottom = n.y + n.height;
3214
+ if (right > maxX) maxX = right;
3215
+ if (bottom > maxY) maxY = bottom;
3216
+ }
3217
+ for (const z of zones) {
3218
+ const right = z.x + z.width;
3219
+ const bottom = z.y + z.height;
3220
+ if (right > maxX) maxX = right;
3221
+ if (bottom > maxY) maxY = bottom;
3222
+ }
3223
+ for (const e of edges) {
3224
+ for (const p of e.points) {
3225
+ if (p.x > maxX) maxX = p.x;
3226
+ if (p.y > maxY) maxY = p.y;
3227
+ }
3228
+ }
3229
+ return {
3230
+ nodes,
3231
+ zones,
3232
+ edges,
3233
+ width: maxX + LAYOUT_PADDING4,
3234
+ height: maxY + LAYOUT_PADDING4
3235
+ };
3236
+ }
3237
+
3238
+ // src/architecture/icons.ts
3239
+ var ICON_PATHS = {
3240
+ // Rack server
3241
+ server: "M4 4h16v4H4zm0 6h16v4H4zm0 6h16v4H4zM6 6h1v0.5H6zm0 6h1v0.5H6zm0 6h1v0.5H6z",
3242
+ // Cylinder
3243
+ database: "M12 2C7.58 2 4 3.34 4 5v14c0 1.66 3.58 3 8 3s8-1.34 8-3V5c0-1.66-3.58-3-8-3zm0 2c3.87 0 6 1.12 6 1s-2.13 1-6 1-6-0.12-6-1 2.13-1 6-1zM6 7.26C7.53 7.83 9.64 8 12 8s4.47-0.17 6-0.74V10c0 0.88-2.13 2-6 2s-6-1.12-6-2V7.26zm0 5C7.53 12.83 9.64 13 12 13s4.47-0.17 6-0.74V15c0 0.88-2.13 2-6 2s-6-1.12-6-2v-2.74zM12 20c-3.87 0-6-1.12-6-2v-2.74C7.53 15.83 9.64 16 12 16s4.47-0.17 6-0.74V18c0 0.88-2.13 2-6 2z",
3244
+ // Horizontal arrows/bars
3245
+ queue: "M2 7h14l-4-3.5M2 12h18l-4-3.5M2 17h14l-4-3.5",
3246
+ // Lightning bolt
3247
+ cache: "M13 2L3 14h7l-2 8 10-12h-7z",
3248
+ // Branching arrows
3249
+ loadbalancer: "M12 2v6m0 0l-6 6m6-6l6 6m-6-6v0M6 14v4h4m4 0h4v-4M12 8a2 2 0 100-4 2 2 0 000 4z",
3250
+ // Lambda symbol
3251
+ function: "M7 4l5 16M12 4l5 16M5 10h14M5 14h14",
3252
+ // Disk/bucket
3253
+ storage: "M4 6a8 3 0 0116 0v12a8 3 0 01-16 0V6zm0 0a8 3 0 0016 0",
3254
+ // Gateway arch
3255
+ gateway: "M3 21V8a9 9 0 0118 0v13M7 21v-8a5 5 0 0110 0v8M11 3v2m-4 1l1 1m8-1l-1 1",
3256
+ // Person silhouette
3257
+ user: "M12 4a4 4 0 110 8 4 4 0 010-8zm0 10c-4.42 0-8 1.79-8 4v2h16v-2c0-2.21-3.58-4-8-4z",
3258
+ // Cloud shape
3259
+ cloud: "M19.35 10.04A7.49 7.49 0 0012 4C9.11 4 6.6 5.64 5.35 8.04A5.994 5.994 0 000 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96z",
3260
+ // Box with lid
3261
+ container: "M2 8l10-5 10 5v10l-10 5-10-5V8zm10-5v20M2 8l10 5 10-5",
3262
+ // Connected nodes
3263
+ network: "M12 2a2 2 0 110 4 2 2 0 010-4zM4 10a2 2 0 110 4 2 2 0 010-4zM20 10a2 2 0 110 4 2 2 0 010-4zM12 18a2 2 0 110 4 2 2 0 010-4zM12 6v12M4 12h16M6 11l5-5M18 11l-5-5M6 13l5 5M18 13l-5 5"
3264
+ };
3265
+ function renderIcon(g, iconName, x, y, size = 20) {
3266
+ const d = ICON_PATHS[iconName];
3267
+ if (!d) return;
3268
+ const iconG = g.append("g").attr("transform", `translate(${x - size / 2}, ${y - size / 2}) scale(${size / 24})`);
3269
+ iconG.append("path").attr("d", d).attr("fill", "none").attr("stroke", "var(--glyph-icon-stroke, #fff)").attr("stroke-width", "var(--glyph-icon-stroke-width, 1.5)").attr("stroke-linecap", "round").attr("stroke-linejoin", "round");
3270
+ }
3271
+
3272
+ // src/architecture/render.ts
3273
+ var NODE_PALETTE = [
3274
+ "#00d4aa",
3275
+ "#b44dff",
3276
+ "#22c55e",
3277
+ "#e040fb",
3278
+ "#00e5ff",
3279
+ "#84cc16",
3280
+ "#f472b6",
3281
+ "#fb923c",
3282
+ "#818cf8",
3283
+ "#38bdf8"
3284
+ ];
3285
+ function zoneBackground(depth) {
3286
+ const alphas = [0.06, 0.1, 0.14, 0.18];
3287
+ const alpha = alphas[Math.min(depth, alphas.length - 1)] ?? 0.18;
3288
+ return `rgba(0,212,170,${alpha})`;
3289
+ }
3290
+ function getThemeVar3(container, varName, fallback) {
3291
+ return getComputedStyle(container).getPropertyValue(varName).trim() || fallback;
3292
+ }
3293
+ var ARROW_MARKER_ID3 = "glyph-arch-arrowhead";
3294
+ function renderArchitecture(svgElement, layout) {
3295
+ const svg = d32.select(svgElement);
3296
+ svg.selectAll("*").remove();
3297
+ const width = Math.max(layout.width, 200);
3298
+ const height = Math.max(layout.height, 200);
3299
+ svg.attr("viewBox", `0 0 ${width} ${height}`);
3300
+ const defs = svg.append("defs");
3301
+ defs.append("marker").attr("id", ARROW_MARKER_ID3).attr("viewBox", "0 0 10 10").attr("refX", 10).attr("refY", 5).attr("markerWidth", 8).attr("markerHeight", 8).attr("orient", "auto-start-reverse").append("path").attr("d", "M 0 0 L 10 5 L 0 10 Z").attr("fill", "var(--glyph-edge-color, #6b7a94)");
3302
+ const container = svgElement.parentElement ?? svgElement;
3303
+ const nodeRadius = getThemeVar3(container, "--glyph-node-radius", "3");
3304
+ const nodeStrokeWidth = getThemeVar3(container, "--glyph-node-stroke-width", "1.5");
3305
+ const nodeFillOpacity = getThemeVar3(container, "--glyph-node-fill-opacity", "0.85");
3306
+ const root = svg.append("g").attr("class", "glyph-architecture-root");
3307
+ const zoomBehavior = d32.zoom().scaleExtent([0.1, 4]).on("zoom", (event) => {
3308
+ root.attr("transform", event.transform.toString());
3309
+ });
3310
+ svg.call(zoomBehavior);
3311
+ const sortedZones = [...layout.zones].sort((a, b) => a.depth - b.depth);
3312
+ const zoneGroup = root.append("g").attr("class", "glyph-architecture-zones");
3313
+ for (const zone of sortedZones) {
3314
+ const zoneG = zoneGroup.append("g").attr("class", "glyph-architecture-zone");
3315
+ zoneG.append("rect").attr("x", zone.x).attr("y", zone.y).attr("width", zone.width).attr("height", zone.height).attr("rx", nodeRadius).attr("ry", nodeRadius).attr("fill", zoneBackground(zone.depth)).attr("stroke", "var(--glyph-accent-muted, #1a4a3a)").attr("stroke-width", 1).attr("stroke-dasharray", "6,3");
3316
+ zoneG.append("text").attr("x", zone.x + 10).attr("y", zone.y + 18).attr("font-size", "12px").attr("font-weight", "bold").attr("font-family", "Inter, system-ui, sans-serif").attr("fill", "var(--glyph-text-muted, #7a8599)").text(zone.label);
3317
+ }
3318
+ const edgeGroup = root.append("g").attr("class", "glyph-architecture-edges");
3319
+ for (const edge of layout.edges) {
3320
+ if (edge.points.length === 0) continue;
3321
+ const edgeG = edgeGroup.append("g").attr("class", "glyph-architecture-edge");
3322
+ const lineGen = d32.line().x((d) => d.x).y((d) => d.y);
3323
+ const strokeWidth = edge.type === "data" ? 2.5 : 1.5;
3324
+ const dashArray = edge.type === "async" ? "5,5" : null;
3325
+ edgeG.append("path").attr("d", lineGen(edge.points) ?? "").attr("fill", "none").attr("stroke", edge.style?.["stroke"] ?? "var(--glyph-edge-color, #6b7a94)").attr("stroke-width", edge.style?.["stroke-width"] ?? String(strokeWidth)).attr("marker-end", `url(#${ARROW_MARKER_ID3})`).attr("stroke-dasharray", dashArray);
3326
+ if (edge.label && edge.points.length > 1) {
3327
+ let totalLen = 0;
3328
+ const segments = [];
3329
+ for (let i = 1; i < edge.points.length; i++) {
3330
+ const prev = edge.points[i - 1];
3331
+ const curr = edge.points[i];
3332
+ if (prev && curr) {
3333
+ const dx = curr.x - prev.x;
3334
+ const dy = curr.y - prev.y;
3335
+ const len = Math.sqrt(dx * dx + dy * dy);
3336
+ segments.push(len);
3337
+ totalLen += len;
3338
+ }
3339
+ }
3340
+ let remaining = totalLen / 2;
3341
+ let labelX = edge.points[0]?.x ?? 0;
3342
+ let labelY = edge.points[0]?.y ?? 0;
3343
+ for (let i = 0; i < segments.length; i++) {
3344
+ const segLen = segments[i] ?? 0;
3345
+ const prev = edge.points[i];
3346
+ const curr = edge.points[i + 1];
3347
+ if (prev && curr) {
3348
+ if (remaining <= segLen && segLen > 0) {
3349
+ const t = remaining / segLen;
3350
+ labelX = prev.x + t * (curr.x - prev.x);
3351
+ labelY = prev.y + t * (curr.y - prev.y);
3352
+ break;
3353
+ }
3354
+ remaining -= segLen;
3355
+ }
3356
+ }
3357
+ edgeG.append("text").attr("x", labelX).attr("y", labelY - 8).attr("text-anchor", "middle").attr("font-size", "11px").attr("font-family", "Inter, system-ui, sans-serif").attr("fill", "var(--glyph-text-muted, #7a8599)").text(edge.label);
3358
+ }
3359
+ }
3360
+ const nodeGroup = root.append("g").attr("class", "glyph-architecture-nodes");
3361
+ for (let nodeIdx = 0; nodeIdx < layout.nodes.length; nodeIdx++) {
3362
+ const node = layout.nodes[nodeIdx];
3363
+ if (!node) continue;
3364
+ const nodeG = nodeGroup.append("g").attr("class", "glyph-architecture-node");
3365
+ const color3 = NODE_PALETTE[nodeIdx % NODE_PALETTE.length] ?? "#00d4aa";
3366
+ const defaultStroke = d32.color(color3)?.darker(0.5)?.toString() ?? "var(--glyph-edge-color, #6b7a94)";
3367
+ nodeG.append("rect").attr("x", node.x).attr("y", node.y).attr("width", node.width).attr("height", node.height).attr("rx", nodeRadius).attr("ry", nodeRadius).attr("fill", color3).attr("stroke", defaultStroke).attr("stroke-width", nodeStrokeWidth).attr("opacity", nodeFillOpacity);
3368
+ if (node.icon) {
3369
+ renderIcon(nodeG, node.icon, node.x + node.width / 2, node.y + 20, 18);
3370
+ nodeG.append("text").attr("x", node.x + node.width / 2).attr("y", node.y + node.height - 10).attr("text-anchor", "middle").attr("font-size", "11px").attr("font-family", "Inter, system-ui, sans-serif").attr("fill", "var(--glyph-node-label-color, #fff)").attr("pointer-events", "none").text(node.label);
3371
+ } else {
3372
+ nodeG.append("text").attr("x", node.x + node.width / 2).attr("y", node.y + node.height / 2).attr("dy", "0.35em").attr("text-anchor", "middle").attr("font-size", "13px").attr("font-family", "Inter, system-ui, sans-serif").attr("fill", "var(--glyph-node-label-color, #fff)").attr("pointer-events", "none").text(node.label);
3373
+ }
3374
+ }
3375
+ }
3376
+ function Architecture({
3377
+ data,
3378
+ container
3379
+ }) {
3380
+ const svgRef = useRef(null);
3381
+ const [layout, setLayout] = useState(null);
3382
+ useEffect(() => {
3383
+ let cancelled = false;
3384
+ computeArchitectureLayout(data).then((result) => {
3385
+ if (!cancelled) setLayout(result);
3386
+ });
3387
+ return () => {
3388
+ cancelled = true;
3389
+ };
3390
+ }, [data]);
3391
+ useEffect(() => {
3392
+ if (!svgRef.current || !layout) return;
3393
+ renderArchitecture(svgRef.current, layout);
3394
+ }, [layout]);
3395
+ const nodeCount = countLeafNodes(data.children);
3396
+ const edgeCount = data.edges.length;
3397
+ const ariaLabel = data.title ? `${data.title}: architecture diagram with ${nodeCount} nodes and ${edgeCount} connections` : `Architecture diagram with ${nodeCount} nodes and ${edgeCount} connections`;
3398
+ const flatNodes = flattenNodes(data.children);
3399
+ return /* @__PURE__ */ jsxs("div", { className: "glyph-architecture-container", children: [
3400
+ !layout && /* @__PURE__ */ jsx(
3401
+ "div",
3402
+ {
3403
+ style: {
3404
+ padding: "2rem",
3405
+ textAlign: "center",
3406
+ color: "var(--glyph-text-muted, #7a8599)",
3407
+ fontFamily: "Inter, system-ui, sans-serif",
3408
+ fontSize: "13px"
3409
+ },
3410
+ children: "Computing layout..."
3411
+ }
3412
+ ),
3413
+ /* @__PURE__ */ jsx(
3414
+ "svg",
3415
+ {
3416
+ ref: svgRef,
3417
+ role: "img",
3418
+ "aria-label": ariaLabel,
3419
+ width: "100%",
3420
+ height: "100%",
3421
+ style: {
3422
+ minHeight: container.tier === "compact" ? 200 : 300,
3423
+ maxHeight: container.tier === "compact" ? 500 : 700,
3424
+ display: layout ? "block" : "none"
3425
+ }
3426
+ }
3427
+ ),
3428
+ /* @__PURE__ */ jsxs("table", { className: "sr-only", "aria-label": "Architecture data", style: SR_ONLY_STYLE5, children: [
3429
+ /* @__PURE__ */ jsx("caption", { children: "Architecture nodes and connections" }),
3430
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
3431
+ /* @__PURE__ */ jsx("th", { scope: "col", children: "Node" }),
3432
+ /* @__PURE__ */ jsx("th", { scope: "col", children: "Zone" }),
3433
+ /* @__PURE__ */ jsx("th", { scope: "col", children: "Connections" })
3434
+ ] }) }),
3435
+ /* @__PURE__ */ jsx("tbody", { children: flatNodes.map((node) => {
3436
+ const connections = data.edges.filter((e) => e.from === node.id || e.to === node.id).map((e) => {
3437
+ const target = e.from === node.id ? e.to : e.from;
3438
+ const dir = e.from === node.id ? "->" : "<-";
3439
+ return `${dir} ${target}${e.label ? ` (${e.label})` : ""}`;
3440
+ }).join(", ");
3441
+ return /* @__PURE__ */ jsxs("tr", { children: [
3442
+ /* @__PURE__ */ jsx("td", { children: node.label }),
3443
+ /* @__PURE__ */ jsx("td", { children: node.zone ?? "" }),
3444
+ /* @__PURE__ */ jsx("td", { children: connections })
3445
+ ] }, node.id);
3446
+ }) })
3447
+ ] })
3448
+ ] });
3449
+ }
3450
+ function flattenNodes(children, zone) {
3451
+ const result = [];
3452
+ for (const child of children) {
3453
+ if (child.type === "zone" && child.children) {
3454
+ result.push(...flattenNodes(child.children, child.label));
3455
+ } else {
3456
+ result.push({ id: child.id, label: child.label, zone });
3457
+ }
3458
+ }
3459
+ return result;
3460
+ }
3461
+ function countLeafNodes(children) {
3462
+ let count = 0;
3463
+ for (const child of children) {
3464
+ if (child.type === "zone" && child.children) {
3465
+ count += countLeafNodes(child.children);
3466
+ } else {
3467
+ count++;
3468
+ }
3469
+ }
3470
+ return count;
3471
+ }
3472
+ var SR_ONLY_STYLE5 = {
3473
+ position: "absolute",
3474
+ width: "1px",
3475
+ height: "1px",
3476
+ padding: 0,
3477
+ margin: "-1px",
3478
+ overflow: "hidden",
3479
+ clip: "rect(0, 0, 0, 0)",
3480
+ whiteSpace: "nowrap",
3481
+ border: 0
3482
+ };
3483
+
3484
+ // src/architecture/index.ts
3485
+ var architectureDefinition = {
3486
+ type: "ui:architecture",
3487
+ schema: architectureSchema,
3488
+ render: Architecture
3489
+ };
3490
+ var MAX_VISIBLE_DEPTH = 4;
3491
+ var NODE_RADIUS = 28;
3492
+ var ROOT_RADIUS = 34;
3493
+ var RADIAL_LEVEL_RADIUS = 130;
3494
+ var TREE_LEVEL_HEIGHT = 90;
3495
+ var TREE_NODE_HGAP = 140;
3496
+ var SVG_PADDING = 60;
3497
+ var FONT_SIZE = 12;
3498
+ var FONT_FAMILY = "Inter, system-ui, sans-serif";
3499
+ var BADGE_FONT_SIZE = 10;
3500
+ function countHiddenDescendants(node, currentDepth) {
3501
+ if (currentDepth >= MAX_VISIBLE_DEPTH) {
3502
+ return countAllDescendants(node);
3503
+ }
3504
+ let count = 0;
3505
+ for (const child of node.children ?? []) {
3506
+ count += countHiddenDescendants(child, currentDepth + 1);
3507
+ }
3508
+ return count;
3509
+ }
3510
+ function countAllDescendants(node) {
3511
+ let count = 1;
3512
+ for (const child of node.children ?? []) {
3513
+ count += countAllDescendants(child);
3514
+ }
3515
+ return count;
3516
+ }
3517
+ function layoutRadial(data) {
3518
+ const nodes = [];
3519
+ const cx = 0;
3520
+ const cy = 0;
3521
+ nodes.push({
3522
+ label: data.root,
3523
+ x: cx,
3524
+ y: cy,
3525
+ depth: 0,
3526
+ hiddenCount: 0
3527
+ });
3528
+ function placeChildren(children, parentX, parentY, depth, startAngle, endAngle) {
3529
+ if (depth > MAX_VISIBLE_DEPTH) return;
3530
+ const count = children.length;
3531
+ if (count === 0) return;
3532
+ const radius = RADIAL_LEVEL_RADIUS * depth;
3533
+ for (let i = 0; i < count; i++) {
3534
+ const child = children[i];
3535
+ if (!child) continue;
3536
+ const angle = count === 1 ? (startAngle + endAngle) / 2 : startAngle + (endAngle - startAngle) * i / (count - 1);
3537
+ const x = cx + radius * Math.cos(angle);
3538
+ const y = cy + radius * Math.sin(angle);
3539
+ const hidden = countHiddenDescendants(child, depth);
3540
+ nodes.push({
3541
+ label: child.label,
3542
+ x,
3543
+ y,
3544
+ depth,
3545
+ parentX,
3546
+ parentY,
3547
+ hiddenCount: hidden
3548
+ });
3549
+ const childChildren = child.children ?? [];
3550
+ if (childChildren.length > 0 && depth < MAX_VISIBLE_DEPTH) {
3551
+ const spreadAngle = Math.PI / Math.max(count, 2);
3552
+ placeChildren(
3553
+ childChildren,
3554
+ x,
3555
+ y,
3556
+ depth + 1,
3557
+ angle - spreadAngle / 2,
3558
+ angle + spreadAngle / 2
3559
+ );
3560
+ }
3561
+ }
3562
+ }
3563
+ const topChildren = data.children;
3564
+ placeChildren(topChildren, cx, cy, 1, 0, 2 * Math.PI * (1 - 1 / Math.max(topChildren.length, 2)));
3565
+ let minX = 0;
3566
+ let maxX = 0;
3567
+ let minY = 0;
3568
+ let maxY = 0;
3569
+ for (const n of nodes) {
3570
+ if (n.x - ROOT_RADIUS < minX) minX = n.x - ROOT_RADIUS;
3571
+ if (n.x + ROOT_RADIUS > maxX) maxX = n.x + ROOT_RADIUS;
3572
+ if (n.y - ROOT_RADIUS < minY) minY = n.y - ROOT_RADIUS;
3573
+ if (n.y + ROOT_RADIUS > maxY) maxY = n.y + ROOT_RADIUS;
3574
+ }
3575
+ const offsetX = -minX + SVG_PADDING;
3576
+ const offsetY = -minY + SVG_PADDING;
3577
+ for (const n of nodes) {
3578
+ n.x += offsetX;
3579
+ n.y += offsetY;
3580
+ if (n.parentX !== void 0) n.parentX += offsetX;
3581
+ if (n.parentY !== void 0) n.parentY += offsetY;
3582
+ }
3583
+ return {
3584
+ nodes,
3585
+ width: maxX - minX + SVG_PADDING * 2,
3586
+ height: maxY - minY + SVG_PADDING * 2
3587
+ };
3588
+ }
3589
+ function layoutTree(data) {
3590
+ const nodes = [];
3591
+ function countLeaves(children, depth) {
3592
+ if (depth >= MAX_VISIBLE_DEPTH) return 1;
3593
+ let total = 0;
3594
+ for (const child of children) {
3595
+ const childChildren = child.children ?? [];
3596
+ if (childChildren.length === 0 || depth + 1 >= MAX_VISIBLE_DEPTH) {
3597
+ total += 1;
3598
+ } else {
3599
+ total += countLeaves(childChildren, depth + 1);
3600
+ }
3601
+ }
3602
+ return Math.max(total, 1);
3603
+ }
3604
+ const totalLeaves = Math.max(countLeaves(data.children, 1), 1);
3605
+ const totalWidth = totalLeaves * TREE_NODE_HGAP;
3606
+ const rootX = totalWidth / 2 + SVG_PADDING;
3607
+ const rootY = SVG_PADDING;
3608
+ nodes.push({
3609
+ label: data.root,
3610
+ x: rootX,
3611
+ y: rootY,
3612
+ depth: 0,
3613
+ hiddenCount: 0
3614
+ });
3615
+ function placeTreeChildren(children, parentX, parentY, depth, leftBound, rightBound) {
3616
+ if (depth > MAX_VISIBLE_DEPTH) return;
3617
+ const count = children.length;
3618
+ if (count === 0) return;
3619
+ const y = parentY + TREE_LEVEL_HEIGHT;
3620
+ const slotWidth = (rightBound - leftBound) / count;
3621
+ for (let i = 0; i < count; i++) {
3622
+ const child = children[i];
3623
+ if (!child) continue;
3624
+ const x = leftBound + slotWidth * (i + 0.5);
3625
+ const hidden = countHiddenDescendants(child, depth);
3626
+ nodes.push({
3627
+ label: child.label,
3628
+ x,
3629
+ y,
3630
+ depth,
3631
+ parentX,
3632
+ parentY,
3633
+ hiddenCount: hidden
3634
+ });
3635
+ const childChildren = child.children ?? [];
3636
+ if (childChildren.length > 0 && depth < MAX_VISIBLE_DEPTH) {
3637
+ placeTreeChildren(
3638
+ childChildren,
3639
+ x,
3640
+ y,
3641
+ depth + 1,
3642
+ leftBound + slotWidth * i,
3643
+ leftBound + slotWidth * (i + 1)
3644
+ );
3645
+ }
3646
+ }
3647
+ }
3648
+ placeTreeChildren(data.children, rootX, rootY, 1, SVG_PADDING, totalWidth + SVG_PADDING);
3649
+ let maxY = rootY;
3650
+ for (const n of nodes) {
3651
+ if (n.y > maxY) maxY = n.y;
3652
+ }
3653
+ return {
3654
+ nodes,
3655
+ width: totalWidth + SVG_PADDING * 2,
3656
+ height: maxY + SVG_PADDING + NODE_RADIUS
3657
+ };
3658
+ }
3659
+ function renderAccessibleList(root, children) {
3660
+ return /* @__PURE__ */ jsx("ul", { style: SR_ONLY_STYLE6, role: "list", "aria-label": "Mind map structure", children: /* @__PURE__ */ jsxs("li", { children: [
3661
+ root,
3662
+ children.length > 0 && renderAccessibleChildren(children)
3663
+ ] }) });
3664
+ }
3665
+ function renderAccessibleChildren(children) {
3666
+ return /* @__PURE__ */ jsx("ul", { children: children.map((child, i) => /* @__PURE__ */ jsxs("li", { children: [
3667
+ child.label,
3668
+ (child.children ?? []).length > 0 && renderAccessibleChildren(child.children ?? [])
3669
+ ] }, i)) });
3670
+ }
3671
+ function nodeColor(depth) {
3672
+ if (depth === 0) return "var(--glyph-accent, #6c8aff)";
3673
+ if (depth === 1) return "var(--glyph-surface-raised, #162038)";
3674
+ return "var(--glyph-surface, #0f1526)";
3675
+ }
3676
+ function nodeStroke(depth) {
3677
+ if (depth === 0) return "var(--glyph-accent, #6c8aff)";
3678
+ return "var(--glyph-border-strong, #2a3550)";
3679
+ }
3680
+ function countAllNodes(children) {
3681
+ let count = 0;
3682
+ for (const child of children) {
3683
+ count += 1;
3684
+ count += countAllNodes(child.children ?? []);
3685
+ }
3686
+ return count;
3687
+ }
3688
+ function MindMap({ data, container }) {
3689
+ const isTree = data.layout === "tree";
3690
+ const { nodes, width, height } = isTree ? layoutTree(data) : layoutRadial(data);
3691
+ const totalNodes = 1 + countAllNodes(data.children);
3692
+ const ariaLabel = `Mind map: ${data.root} with ${String(totalNodes)} nodes`;
3693
+ return /* @__PURE__ */ jsxs("div", { className: "glyph-mindmap-container", children: [
3694
+ /* @__PURE__ */ jsxs(
3695
+ "svg",
3696
+ {
3697
+ role: "img",
3698
+ "aria-label": ariaLabel,
3699
+ viewBox: `0 0 ${width} ${height}`,
3700
+ width: "100%",
3701
+ style: {
3702
+ minHeight: container.tier === "compact" ? 150 : 200,
3703
+ maxHeight: container.tier === "compact" ? 500 : 800,
3704
+ display: "block"
3705
+ },
3706
+ children: [
3707
+ nodes.map((node, idx) => {
3708
+ if (node.parentX === void 0 || node.parentY === void 0) return null;
3709
+ return /* @__PURE__ */ jsx(
3710
+ "line",
3711
+ {
3712
+ x1: node.parentX,
3713
+ y1: node.parentY,
3714
+ x2: node.x,
3715
+ y2: node.y,
3716
+ stroke: "var(--glyph-border, #1a2035)",
3717
+ strokeWidth: 1.5
3718
+ },
3719
+ `edge-${idx}`
3720
+ );
3721
+ }),
3722
+ nodes.map((node, idx) => {
3723
+ const r = node.depth === 0 ? ROOT_RADIUS : NODE_RADIUS;
3724
+ const isRoot = node.depth === 0;
3725
+ return /* @__PURE__ */ jsxs("g", { children: [
3726
+ /* @__PURE__ */ jsx(
3727
+ "circle",
3728
+ {
3729
+ cx: node.x,
3730
+ cy: node.y,
3731
+ r,
3732
+ fill: nodeColor(node.depth),
3733
+ stroke: nodeStroke(node.depth),
3734
+ strokeWidth: isRoot ? 2 : 1.5
3735
+ }
3736
+ ),
3737
+ /* @__PURE__ */ jsx(
3738
+ "text",
3739
+ {
3740
+ x: node.x,
3741
+ y: node.y,
3742
+ dy: "0.35em",
3743
+ textAnchor: "middle",
3744
+ fontSize: isRoot ? FONT_SIZE + 1 : FONT_SIZE,
3745
+ fontFamily: FONT_FAMILY,
3746
+ fontWeight: isRoot ? 700 : 500,
3747
+ fill: isRoot ? "var(--glyph-text-on-accent, #ffffff)" : "var(--glyph-text, #d4dae3)",
3748
+ children: node.label.length > 12 ? `${node.label.slice(0, 11)}...` : node.label
3749
+ }
3750
+ ),
3751
+ node.hiddenCount > 0 && /* @__PURE__ */ jsxs("g", { children: [
3752
+ /* @__PURE__ */ jsx(
3753
+ "circle",
3754
+ {
3755
+ cx: node.x + r * 0.7,
3756
+ cy: node.y - r * 0.7,
3757
+ r: 10,
3758
+ fill: "var(--glyph-accent, #6c8aff)"
3759
+ }
3760
+ ),
3761
+ /* @__PURE__ */ jsxs(
3762
+ "text",
3763
+ {
3764
+ x: node.x + r * 0.7,
3765
+ y: node.y - r * 0.7,
3766
+ dy: "0.35em",
3767
+ textAnchor: "middle",
3768
+ fontSize: BADGE_FONT_SIZE,
3769
+ fontFamily: FONT_FAMILY,
3770
+ fontWeight: 600,
3771
+ fill: "var(--glyph-text-on-accent, #ffffff)",
3772
+ children: [
3773
+ "+",
3774
+ node.hiddenCount
3775
+ ]
3776
+ }
3777
+ )
3778
+ ] })
3779
+ ] }, `node-${idx}`);
3780
+ })
3781
+ ]
3782
+ }
3783
+ ),
3784
+ renderAccessibleList(data.root, data.children)
3785
+ ] });
3786
+ }
3787
+ var SR_ONLY_STYLE6 = {
3788
+ position: "absolute",
3789
+ width: "1px",
3790
+ height: "1px",
3791
+ padding: 0,
3792
+ margin: "-1px",
3793
+ overflow: "hidden",
3794
+ clip: "rect(0, 0, 0, 0)",
3795
+ whiteSpace: "nowrap",
3796
+ border: 0
3797
+ };
3798
+
3799
+ // src/mindmap/index.ts
3800
+ var mindMapDefinition = {
3801
+ type: "ui:mindmap",
3802
+ schema: mindmapSchema,
3803
+ render: MindMap
3804
+ };
3805
+ function renderLatex(expression) {
3806
+ try {
3807
+ const html = katex.renderToString(expression, {
3808
+ throwOnError: false,
3809
+ displayMode: true
3810
+ });
3811
+ return { html, error: false };
3812
+ } catch {
3813
+ return { html: "", error: true };
3814
+ }
3815
+ }
3816
+ var containerStyle = {
3817
+ display: "flex",
3818
+ flexDirection: "column",
3819
+ alignItems: "center",
3820
+ gap: "0.5rem",
3821
+ padding: "1rem",
3822
+ fontFamily: "Inter, system-ui, sans-serif"
3823
+ };
3824
+ var labelStyle2 = {
3825
+ fontSize: "0.875rem",
3826
+ color: "var(--glyph-text-muted, #6b7a94)",
3827
+ marginTop: "0.25rem"
3828
+ };
3829
+ var stepsContainerStyle = {
3830
+ display: "flex",
3831
+ flexDirection: "column",
3832
+ gap: "0.75rem",
3833
+ width: "100%"
3834
+ };
3835
+ var stepRowStyle = {
3836
+ display: "flex",
3837
+ alignItems: "center",
3838
+ justifyContent: "center",
3839
+ gap: "1.5rem"
3840
+ };
3841
+ var annotationStyle = {
3842
+ fontSize: "0.875rem",
3843
+ color: "var(--glyph-text-muted, #6b7a94)",
3844
+ fontStyle: "italic",
3845
+ minWidth: "120px"
3846
+ };
3847
+ var fallbackStyle = {
3848
+ fontFamily: '"Fira Code", "Cascadia Code", Consolas, monospace',
3849
+ fontSize: "0.875rem",
3850
+ padding: "0.5rem 1rem",
3851
+ borderRadius: "4px",
3852
+ background: "var(--glyph-surface-raised, #162038)",
3853
+ color: "var(--glyph-text, #d4dae3)",
3854
+ border: "1px solid var(--glyph-border, #1a2035)"
3855
+ };
3856
+ function Equation({ data }) {
3857
+ if (data.expression !== void 0) {
3858
+ const { html, error } = renderLatex(data.expression);
3859
+ const ariaLabel = `Equation: ${data.expression}`;
3860
+ return /* @__PURE__ */ jsxs("div", { style: containerStyle, role: "math", "aria-label": ariaLabel, children: [
3861
+ error || html === "" ? /* @__PURE__ */ jsx("code", { style: fallbackStyle, children: data.expression }) : /* @__PURE__ */ jsx("span", { dangerouslySetInnerHTML: { __html: html } }),
3862
+ data.label !== void 0 && /* @__PURE__ */ jsx("div", { style: labelStyle2, children: data.label })
3863
+ ] });
3864
+ }
3865
+ if (data.steps !== void 0 && data.steps.length > 0) {
3866
+ const allExpressions = data.steps.map((s) => s.expression).join("; ");
3867
+ const ariaLabel = `Equation steps: ${allExpressions}`;
3868
+ return /* @__PURE__ */ jsxs("div", { style: containerStyle, role: "math", "aria-label": ariaLabel, children: [
3869
+ /* @__PURE__ */ jsx("div", { style: stepsContainerStyle, children: data.steps.map((step, idx) => {
3870
+ const { html, error } = renderLatex(step.expression);
3871
+ return /* @__PURE__ */ jsxs("div", { style: stepRowStyle, children: [
3872
+ error || html === "" ? /* @__PURE__ */ jsx("code", { style: fallbackStyle, children: step.expression }) : /* @__PURE__ */ jsx("span", { dangerouslySetInnerHTML: { __html: html } }),
3873
+ step.annotation !== void 0 && /* @__PURE__ */ jsx("span", { style: annotationStyle, children: step.annotation })
3874
+ ] }, idx);
3875
+ }) }),
3876
+ data.label !== void 0 && /* @__PURE__ */ jsx("div", { style: labelStyle2, children: data.label })
3877
+ ] });
3878
+ }
3879
+ return /* @__PURE__ */ jsx("div", { style: containerStyle, role: "math", "aria-label": "Empty equation", children: /* @__PURE__ */ jsx("span", { style: { color: "var(--glyph-text-muted, #6b7a94)" }, children: "No equation provided" }) });
3880
+ }
3881
+
3882
+ // src/equation/index.ts
3883
+ var equationDefinition = {
3884
+ type: "ui:equation",
3885
+ schema: equationSchema,
3886
+ render: Equation
3887
+ };
3888
+
3889
+ // src/quiz/styles.ts
3890
+ var containerStyle2 = {
3891
+ fontFamily: "var(--glyph-font-body, system-ui, sans-serif)",
3892
+ color: "var(--glyph-text, #1a2035)",
3893
+ border: "1px solid var(--glyph-border, #d0d8e4)",
3894
+ borderRadius: "var(--glyph-radius-md, 0.5rem)",
3895
+ overflow: "hidden"
3896
+ };
3897
+ var headerStyle = {
3898
+ fontWeight: 700,
3899
+ fontSize: "1.125rem",
3900
+ padding: "var(--glyph-spacing-md, 1rem)",
3901
+ borderBottom: "1px solid var(--glyph-border, #d0d8e4)",
3902
+ color: "var(--glyph-heading, #0a0e1a)"
3903
+ };
3904
+ function questionGroupStyle(isLast) {
3905
+ return {
3906
+ padding: "var(--glyph-spacing-md, 1rem)",
3907
+ borderBottom: isLast ? "none" : "1px solid var(--glyph-border, #d0d8e4)"
3908
+ };
3909
+ }
3910
+ var questionTextStyle = {
3911
+ fontWeight: 600,
3912
+ fontSize: "0.9375rem",
3913
+ marginBottom: "0.75rem"
3914
+ };
3915
+ function optionLabelStyle(isSelected, submitted, isCorrectOption, isIncorrectSelection) {
3916
+ return {
3917
+ display: "flex",
3918
+ alignItems: "center",
3919
+ gap: "0.5rem",
3920
+ padding: "0.5rem 0.75rem",
3921
+ marginBottom: "0.375rem",
3922
+ borderRadius: "var(--glyph-radius-md, 0.5rem)",
3923
+ cursor: submitted ? "default" : "pointer",
3924
+ background: submitted ? isCorrectOption ? "var(--glyph-quiz-correct-bg, #dcfce7)" : isIncorrectSelection ? "var(--glyph-quiz-incorrect-bg, #fee2e2)" : "transparent" : isSelected ? "var(--glyph-surface, #e8ecf3)" : "transparent",
3925
+ border: "1px solid",
3926
+ borderColor: submitted ? isCorrectOption ? "var(--glyph-quiz-correct, #16a34a)" : isIncorrectSelection ? "var(--glyph-quiz-incorrect, #dc2626)" : "var(--glyph-border, #d0d8e4)" : isSelected ? "var(--glyph-border, #d0d8e4)" : "transparent",
3927
+ fontSize: "0.875rem",
3928
+ lineHeight: 1.6
3929
+ };
3930
+ }
3931
+ function buttonStyle(disabled) {
3932
+ return {
3933
+ marginTop: "0.75rem",
3934
+ padding: "0.5rem 1.25rem",
3935
+ borderRadius: "var(--glyph-radius-md, 0.5rem)",
3936
+ border: "1px solid var(--glyph-border, #d0d8e4)",
3937
+ background: disabled ? "var(--glyph-surface, #e8ecf3)" : "var(--glyph-surface, #e8ecf3)",
3938
+ color: disabled ? "var(--glyph-border, #d0d8e4)" : "var(--glyph-text, #1a2035)",
3939
+ cursor: disabled ? "not-allowed" : "pointer",
3940
+ fontWeight: 600,
3941
+ fontSize: "0.875rem"
3942
+ };
3943
+ }
3944
+ function feedbackStyle(correct) {
3945
+ return {
3946
+ marginTop: "0.75rem",
3947
+ padding: "0.5rem 0.75rem",
3948
+ borderRadius: "var(--glyph-radius-md, 0.5rem)",
3949
+ background: correct ? "var(--glyph-quiz-correct-bg, #dcfce7)" : "var(--glyph-quiz-incorrect-bg, #fee2e2)",
3950
+ color: correct ? "var(--glyph-quiz-correct, #16a34a)" : "var(--glyph-quiz-incorrect, #dc2626)",
3951
+ fontWeight: 600,
3952
+ fontSize: "0.875rem"
3953
+ };
3954
+ }
3955
+ var explanationStyle = {
3956
+ marginTop: "0.5rem",
3957
+ padding: "0.5rem 0.75rem",
3958
+ fontSize: "0.8125rem",
3959
+ lineHeight: 1.6,
3960
+ color: "var(--glyph-text, #1a2035)",
3961
+ background: "var(--glyph-surface, #e8ecf3)",
3962
+ borderRadius: "var(--glyph-radius-md, 0.5rem)"
3963
+ };
3964
+ var scoreStyle = {
3965
+ padding: "var(--glyph-spacing-md, 1rem)",
3966
+ borderTop: "1px solid var(--glyph-border, #d0d8e4)",
3967
+ fontWeight: 700,
3968
+ fontSize: "1rem",
3969
+ color: "var(--glyph-heading, #0a0e1a)"
3970
+ };
3971
+ function isCorrect(question, selected) {
3972
+ if (selected === null) return false;
3973
+ switch (question.type) {
3974
+ case "multiple-choice":
3975
+ return selected === question.answer;
3976
+ case "true-false":
3977
+ return selected === question.answer;
3978
+ case "multi-select": {
3979
+ if (!Array.isArray(selected)) return false;
3980
+ const sorted = [...selected].sort();
3981
+ const expected = [...question.answer].sort();
3982
+ return sorted.length === expected.length && sorted.every((v, i) => v === expected[i]);
3983
+ }
3984
+ }
3985
+ }
3986
+ function Quiz({ data, block }) {
3987
+ const { questions, showScore = true, title } = data;
3988
+ const baseId = `glyph-quiz-${block.id}`;
3989
+ const [states, setStates] = useState(
3990
+ () => questions.map((q) => ({
3991
+ selected: q.type === "multi-select" ? [] : null,
3992
+ submitted: false
3993
+ }))
3994
+ );
3995
+ const updateState = (index, patch) => {
3996
+ setStates((prev) => prev.map((s, i) => i === index ? { ...s, ...patch } : s));
3997
+ };
3998
+ const score = states.reduce((acc, s, i) => {
3999
+ const q = questions[i];
4000
+ if (!q || !s.submitted) return acc;
4001
+ return acc + (isCorrect(q, s.selected) ? 1 : 0);
4002
+ }, 0);
4003
+ const submittedCount = states.filter((s) => s.submitted).length;
4004
+ function renderMultipleChoice(question, qIndex, state) {
4005
+ const selected = typeof state.selected === "number" ? state.selected : null;
4006
+ return /* @__PURE__ */ jsx("div", { role: "radiogroup", "aria-label": question.question, children: question.options.map((option, oIndex) => {
4007
+ const isSelected = selected === oIndex;
4008
+ const isCorrectOption = state.submitted && oIndex === question.answer;
4009
+ const isIncorrectSelection = state.submitted && isSelected && oIndex !== question.answer;
4010
+ return /* @__PURE__ */ jsxs(
4011
+ "label",
4012
+ {
4013
+ style: optionLabelStyle(
4014
+ isSelected,
4015
+ state.submitted,
4016
+ isCorrectOption,
4017
+ isIncorrectSelection
4018
+ ),
4019
+ children: [
4020
+ /* @__PURE__ */ jsx(
4021
+ "input",
4022
+ {
4023
+ type: "radio",
4024
+ role: "radio",
4025
+ name: `${baseId}-q${String(qIndex)}`,
4026
+ checked: isSelected,
4027
+ disabled: state.submitted,
4028
+ onChange: () => updateState(qIndex, { selected: oIndex }),
4029
+ "aria-checked": isSelected
4030
+ }
4031
+ ),
4032
+ option
4033
+ ]
4034
+ },
4035
+ oIndex
4036
+ );
4037
+ }) });
4038
+ }
4039
+ function renderTrueFalse(question, qIndex, state) {
4040
+ const selected = typeof state.selected === "boolean" ? state.selected : null;
4041
+ return /* @__PURE__ */ jsx("div", { role: "radiogroup", "aria-label": question.question, children: [true, false].map((value) => {
4042
+ const isSelected = selected === value;
4043
+ const isCorrectOption = state.submitted && value === question.answer;
4044
+ const isIncorrectSelection = state.submitted && isSelected && value !== question.answer;
4045
+ return /* @__PURE__ */ jsxs(
4046
+ "label",
4047
+ {
4048
+ style: optionLabelStyle(
4049
+ isSelected,
4050
+ state.submitted,
4051
+ isCorrectOption,
4052
+ isIncorrectSelection
4053
+ ),
4054
+ children: [
4055
+ /* @__PURE__ */ jsx(
4056
+ "input",
4057
+ {
4058
+ type: "radio",
4059
+ role: "radio",
4060
+ name: `${baseId}-q${String(qIndex)}`,
4061
+ checked: isSelected,
4062
+ disabled: state.submitted,
4063
+ onChange: () => updateState(qIndex, { selected: value }),
4064
+ "aria-checked": isSelected
4065
+ }
4066
+ ),
4067
+ value ? "True" : "False"
4068
+ ]
4069
+ },
4070
+ String(value)
4071
+ );
4072
+ }) });
4073
+ }
4074
+ function renderMultiSelect(question, qIndex, state) {
4075
+ const selected = Array.isArray(state.selected) ? state.selected : [];
4076
+ const toggleOption = (oIndex) => {
4077
+ const next = selected.includes(oIndex) ? selected.filter((v) => v !== oIndex) : [...selected, oIndex];
4078
+ updateState(qIndex, { selected: next });
4079
+ };
4080
+ return /* @__PURE__ */ jsx("div", { children: question.options.map((option, oIndex) => {
4081
+ const isSelected = selected.includes(oIndex);
4082
+ const isCorrectOption = state.submitted && question.answer.includes(oIndex);
4083
+ const isIncorrectSelection = state.submitted && isSelected && !question.answer.includes(oIndex);
4084
+ return /* @__PURE__ */ jsxs(
4085
+ "label",
4086
+ {
4087
+ style: optionLabelStyle(
4088
+ isSelected,
4089
+ state.submitted,
4090
+ isCorrectOption,
4091
+ isIncorrectSelection
4092
+ ),
4093
+ children: [
4094
+ /* @__PURE__ */ jsx(
4095
+ "input",
4096
+ {
4097
+ type: "checkbox",
4098
+ role: "checkbox",
4099
+ checked: isSelected,
4100
+ disabled: state.submitted,
4101
+ onChange: () => toggleOption(oIndex),
4102
+ "aria-checked": isSelected
4103
+ }
4104
+ ),
4105
+ option
4106
+ ]
4107
+ },
4108
+ oIndex
4109
+ );
4110
+ }) });
4111
+ }
4112
+ function renderQuestion(question, qIndex) {
4113
+ const state = states[qIndex] ?? { selected: null, submitted: false };
4114
+ const isLast = qIndex === questions.length - 1;
4115
+ const hasSelection = question.type === "multi-select" ? Array.isArray(state.selected) && state.selected.length > 0 : state.selected !== null;
4116
+ const correct = state.submitted ? isCorrect(question, state.selected) : false;
4117
+ return /* @__PURE__ */ jsxs(
4118
+ "div",
4119
+ {
4120
+ role: "group",
4121
+ "aria-label": `Question ${String(qIndex + 1)}`,
4122
+ style: questionGroupStyle(isLast && !(showScore && submittedCount > 0)),
4123
+ children: [
4124
+ /* @__PURE__ */ jsxs("div", { style: questionTextStyle, children: [
4125
+ questions.length > 1 ? `${String(qIndex + 1)}. ` : "",
4126
+ question.question
4127
+ ] }),
4128
+ question.type === "multiple-choice" && renderMultipleChoice(question, qIndex, state),
4129
+ question.type === "true-false" && renderTrueFalse(question, qIndex, state),
4130
+ question.type === "multi-select" && renderMultiSelect(question, qIndex, state),
4131
+ !state.submitted && /* @__PURE__ */ jsx(
4132
+ "button",
4133
+ {
4134
+ type: "button",
4135
+ disabled: !hasSelection,
4136
+ style: buttonStyle(!hasSelection),
4137
+ onClick: () => updateState(qIndex, { submitted: true }),
4138
+ children: "Submit"
4139
+ }
4140
+ ),
4141
+ /* @__PURE__ */ jsxs("div", { "aria-live": "polite", children: [
4142
+ state.submitted && /* @__PURE__ */ jsx("div", { style: feedbackStyle(correct), children: correct ? "Correct!" : "Incorrect" }),
4143
+ state.submitted && question.explanation && /* @__PURE__ */ jsx("div", { style: explanationStyle, children: question.explanation })
4144
+ ] })
4145
+ ]
4146
+ },
4147
+ qIndex
4148
+ );
4149
+ }
4150
+ return /* @__PURE__ */ jsxs("div", { id: baseId, role: "region", "aria-label": title ?? "Quiz", style: containerStyle2, children: [
4151
+ title && /* @__PURE__ */ jsx("div", { style: headerStyle, children: title }),
4152
+ questions.map((q, i) => renderQuestion(q, i)),
4153
+ showScore && submittedCount > 0 && /* @__PURE__ */ jsxs("div", { style: scoreStyle, children: [
4154
+ "Score: ",
4155
+ String(score),
4156
+ " / ",
4157
+ String(questions.length)
4158
+ ] })
4159
+ ] });
4160
+ }
4161
+
4162
+ // src/quiz/index.ts
4163
+ var quizDefinition = {
4164
+ type: "ui:quiz",
4165
+ schema: quizSchema,
4166
+ render: Quiz
4167
+ };
4168
+ function getVariantStyle(variant) {
4169
+ switch (variant) {
4170
+ case "outlined":
4171
+ return {
4172
+ border: "2px solid var(--glyph-accent, #0a9d7c)"
4173
+ };
4174
+ case "elevated":
4175
+ return {
4176
+ border: "1px solid var(--glyph-border, #d0d8e4)",
4177
+ boxShadow: "var(--glyph-shadow-md, 0 4px 12px rgba(0,0,0,0.15))"
4178
+ };
4179
+ default:
4180
+ return {
4181
+ border: "1px solid var(--glyph-border, #d0d8e4)"
4182
+ };
4183
+ }
4184
+ }
4185
+ function Card({ data, block, container }) {
4186
+ const { title, cards, variant = "default", columns } = data;
4187
+ const baseId = `glyph-card-${block.id}`;
4188
+ const authorCols = columns ?? Math.min(cards.length, 3);
4189
+ let colCount;
4190
+ switch (container.tier) {
4191
+ case "compact":
4192
+ colCount = 1;
4193
+ break;
4194
+ case "standard":
4195
+ colCount = Math.min(authorCols, 2);
4196
+ break;
4197
+ default:
4198
+ colCount = authorCols;
4199
+ }
4200
+ const containerStyle3 = {
4201
+ fontFamily: "var(--glyph-font-body, system-ui, sans-serif)",
4202
+ color: "var(--glyph-text, #1a2035)"
4203
+ };
4204
+ const gapCount = colCount - 1;
4205
+ const gridStyle = {
4206
+ display: "grid",
4207
+ gridTemplateColumns: `repeat(auto-fill, minmax(max(200px, calc((100% - ${String(gapCount)}rem) / ${String(colCount)})), 1fr))`,
4208
+ gap: "var(--glyph-spacing-md, 1rem)"
4209
+ };
4210
+ const cardBaseStyle = {
4211
+ ...getVariantStyle(variant),
4212
+ borderRadius: "var(--glyph-radius-md, 0.5rem)",
4213
+ overflow: "hidden",
4214
+ background: "var(--glyph-surface, #e8ecf3)",
4215
+ display: "flex",
4216
+ flexDirection: "column"
4217
+ };
4218
+ const cardBodyStyle = {
4219
+ padding: "var(--glyph-spacing-md, 1rem)",
4220
+ flex: 1,
4221
+ display: "flex",
4222
+ flexDirection: "column"
4223
+ };
4224
+ const imageStyle = {
4225
+ width: "100%",
4226
+ aspectRatio: "16 / 9",
4227
+ objectFit: "cover",
4228
+ display: "block"
4229
+ };
4230
+ const iconStyle = {
4231
+ fontSize: "1.5rem",
4232
+ marginBottom: "var(--glyph-spacing-xs, 0.25rem)"
4233
+ };
4234
+ const titleStyle2 = {
4235
+ fontWeight: 700,
4236
+ fontSize: "1rem",
4237
+ color: "var(--glyph-heading, #0a0e1a)",
4238
+ margin: 0
4239
+ };
4240
+ const subtitleStyle = {
4241
+ fontSize: "0.8125rem",
4242
+ color: "var(--glyph-text-muted, #6b7a94)",
4243
+ marginTop: "var(--glyph-spacing-xs, 0.25rem)"
4244
+ };
4245
+ const bodyStyle2 = {
4246
+ fontSize: "0.875rem",
4247
+ lineHeight: 1.6,
4248
+ marginTop: "var(--glyph-spacing-sm, 0.5rem)",
4249
+ flex: 1
4250
+ };
4251
+ const actionsStyle = {
4252
+ display: "flex",
4253
+ gap: "var(--glyph-spacing-sm, 0.5rem)",
4254
+ marginTop: "var(--glyph-spacing-sm, 0.5rem)",
4255
+ flexWrap: "wrap"
4256
+ };
4257
+ const linkStyle = {
4258
+ fontSize: "0.8125rem",
4259
+ fontWeight: 600,
4260
+ color: "var(--glyph-link, #0a9d7c)",
4261
+ textDecoration: "none"
4262
+ };
4263
+ return /* @__PURE__ */ jsxs("div", { id: baseId, role: "region", "aria-label": title ?? "Cards", style: containerStyle3, children: [
4264
+ title && /* @__PURE__ */ jsx(
4265
+ "div",
4266
+ {
4267
+ style: {
4268
+ fontWeight: 700,
4269
+ fontSize: "1.125rem",
4270
+ marginBottom: "var(--glyph-spacing-sm, 0.5rem)",
4271
+ color: "var(--glyph-heading, #0a0e1a)"
4272
+ },
4273
+ children: title
4274
+ }
4275
+ ),
4276
+ /* @__PURE__ */ jsx("div", { role: "list", style: gridStyle, children: cards.map((card, i) => /* @__PURE__ */ jsxs("article", { role: "listitem", style: cardBaseStyle, children: [
4277
+ card.image && /* @__PURE__ */ jsx("img", { src: card.image, alt: card.title, loading: "lazy", style: imageStyle }),
4278
+ /* @__PURE__ */ jsxs("div", { style: cardBodyStyle, children: [
4279
+ card.icon && /* @__PURE__ */ jsx("div", { style: iconStyle, children: card.icon }),
4280
+ /* @__PURE__ */ jsx("h3", { style: titleStyle2, children: card.title }),
4281
+ card.subtitle && /* @__PURE__ */ jsx("div", { style: subtitleStyle, children: card.subtitle }),
4282
+ card.body && /* @__PURE__ */ jsx("div", { style: bodyStyle2, children: card.body }),
4283
+ card.actions && card.actions.length > 0 && /* @__PURE__ */ jsx("div", { style: actionsStyle, children: card.actions.map((action, j) => /* @__PURE__ */ jsx(
4284
+ "a",
4285
+ {
4286
+ href: action.url,
4287
+ target: "_blank",
4288
+ rel: "noopener noreferrer",
4289
+ style: linkStyle,
4290
+ children: action.label
4291
+ },
4292
+ j
4293
+ )) })
4294
+ ] })
4295
+ ] }, i)) })
4296
+ ] });
4297
+ }
4298
+
4299
+ // src/card/index.ts
4300
+ var cardDefinition = {
4301
+ type: "ui:card",
4302
+ schema: cardSchema,
4303
+ render: Card
4304
+ };
4305
+ function groupConsecutiveItems(items) {
4306
+ const groups = [];
4307
+ for (const item of items) {
4308
+ const last = groups.length > 0 ? groups[groups.length - 1] : void 0;
4309
+ if (last && last.type === item.type) {
4310
+ last.items.push(item);
4311
+ } else {
4312
+ groups.push({ type: item.type, items: [item] });
4313
+ }
4314
+ }
4315
+ return groups;
4316
+ }
4317
+ function classifySectionWidth(section) {
4318
+ let textCount = 0;
4319
+ let progressCount = 0;
4320
+ let pieCount = 0;
4321
+ let statCount = 0;
4322
+ for (const item of section.items) {
4323
+ switch (item.type) {
4324
+ case "text":
4325
+ textCount++;
4326
+ break;
4327
+ case "progress":
4328
+ progressCount++;
4329
+ break;
4330
+ case "pie":
4331
+ pieCount++;
4332
+ break;
4333
+ case "stat":
4334
+ statCount++;
4335
+ break;
4336
+ }
4337
+ }
4338
+ if (textCount > 0) return "wide";
4339
+ if (progressCount >= 4) return "wide";
4340
+ if (pieCount >= 3) return "wide";
4341
+ if (statCount >= 4) return "wide";
4342
+ return "narrow";
4343
+ }
4344
+ function computeSectionLayout(sections) {
4345
+ return sections.map((section) => {
4346
+ const width = classifySectionWidth(section);
4347
+ return width === "wide" ? "1 / -1" : void 0;
4348
+ });
4349
+ }
4350
+ var PROGRESS_COLORS = [
4351
+ "var(--glyph-infographic-color-1, #3b82f6)",
4352
+ "var(--glyph-infographic-color-2, #22c55e)",
4353
+ "var(--glyph-infographic-color-3, #f59e0b)",
4354
+ "var(--glyph-infographic-color-4, #ef4444)"
4355
+ ];
4356
+ function renderStatGroup(items, keyPrefix) {
4357
+ const rowStyle = {
4358
+ display: "flex",
4359
+ flexWrap: "wrap",
4360
+ gap: "var(--glyph-spacing-md, 1rem)"
4361
+ };
4362
+ const statStyle = {
4363
+ flex: "1 1 120px",
4364
+ minWidth: 0
4365
+ };
4366
+ const valueStyle = {
4367
+ fontSize: "1.75rem",
4368
+ fontWeight: 700,
4369
+ color: "var(--glyph-infographic-value-color, #1d4ed8)",
4370
+ lineHeight: 1.2
4371
+ };
4372
+ const labelStyle3 = {
4373
+ fontSize: "0.8125rem",
4374
+ color: "var(--glyph-infographic-label-color, #475569)",
4375
+ marginTop: "var(--glyph-spacing-xs, 0.25rem)",
4376
+ textTransform: "uppercase",
4377
+ letterSpacing: "0.06em",
4378
+ fontWeight: 600
4379
+ };
4380
+ const descStyle = {
4381
+ fontSize: "0.75rem",
4382
+ color: "var(--glyph-infographic-desc-color, #64748b)",
4383
+ marginTop: "var(--glyph-spacing-xs, 0.25rem)",
4384
+ fontStyle: "italic"
4385
+ };
4386
+ return /* @__PURE__ */ jsx("div", { style: rowStyle, "data-group": "stat", children: items.map((item, i) => /* @__PURE__ */ jsxs("div", { style: statStyle, children: [
4387
+ /* @__PURE__ */ jsx("div", { style: valueStyle, children: item.value }),
4388
+ /* @__PURE__ */ jsx("div", { style: labelStyle3, children: item.label }),
4389
+ item.description && /* @__PURE__ */ jsx("div", { style: descStyle, children: item.description })
4390
+ ] }, `${keyPrefix}-${String(i)}`)) }, keyPrefix);
4391
+ }
4392
+ function renderProgressGroup(items, keyPrefix, colorOffset) {
4393
+ const trackStyle = {
4394
+ height: "0.5rem",
4395
+ borderRadius: "var(--glyph-radius-sm, 0.375rem)",
4396
+ background: "var(--glyph-infographic-track, #e0e4ea)",
4397
+ overflow: "hidden"
4398
+ };
4399
+ return /* @__PURE__ */ jsx("div", { "data-group": "progress", children: items.map((item, i) => {
4400
+ const colorIndex = (colorOffset + i) % PROGRESS_COLORS.length;
4401
+ const fillColor = item.color ?? PROGRESS_COLORS[colorIndex];
4402
+ const fillStyle = {
4403
+ height: "100%",
4404
+ width: `${String(item.value)}%`,
4405
+ borderRadius: "var(--glyph-radius-sm, 0.375rem)",
4406
+ background: fillColor,
4407
+ transition: "width 0.3s ease"
4408
+ };
4409
+ return /* @__PURE__ */ jsxs(
4410
+ "div",
4411
+ {
4412
+ style: { marginBottom: "var(--glyph-spacing-sm, 0.5rem)" },
4413
+ children: [
4414
+ /* @__PURE__ */ jsxs(
4415
+ "div",
4416
+ {
4417
+ style: {
4418
+ display: "flex",
4419
+ justifyContent: "space-between",
4420
+ marginBottom: "var(--glyph-spacing-xs, 0.25rem)",
4421
+ fontSize: "0.875rem"
4422
+ },
4423
+ children: [
4424
+ /* @__PURE__ */ jsx("span", { style: { color: "var(--glyph-text, #1a2035)", fontWeight: 600 }, children: item.label }),
4425
+ /* @__PURE__ */ jsxs(
4426
+ "span",
4427
+ {
4428
+ style: {
4429
+ color: "var(--glyph-infographic-value-color, #1d4ed8)",
4430
+ fontWeight: 700
4431
+ },
4432
+ children: [
4433
+ String(item.value),
4434
+ "%"
4435
+ ]
4436
+ }
4437
+ )
4438
+ ]
4439
+ }
4440
+ ),
4441
+ /* @__PURE__ */ jsx("div", { style: trackStyle, children: /* @__PURE__ */ jsx(
4442
+ "div",
4443
+ {
4444
+ role: "progressbar",
4445
+ "aria-valuenow": item.value,
4446
+ "aria-valuemin": 0,
4447
+ "aria-valuemax": 100,
4448
+ "aria-label": `${item.label}: ${String(item.value)}%`,
4449
+ style: fillStyle
4450
+ }
4451
+ ) })
4452
+ ]
4453
+ },
4454
+ `${keyPrefix}-${String(i)}`
4455
+ );
4456
+ }) }, keyPrefix);
4457
+ }
4458
+ function renderFactGroup(items, keyPrefix) {
4459
+ const listStyle = {
4460
+ listStyle: "none",
4461
+ margin: 0,
4462
+ padding: 0
4463
+ };
4464
+ const itemStyle2 = {
4465
+ padding: "var(--glyph-spacing-xs, 0.25rem) 0",
4466
+ fontSize: "0.875rem",
4467
+ color: "var(--glyph-text, #1a2035)",
4468
+ fontWeight: 500
4469
+ };
4470
+ return /* @__PURE__ */ jsx("ul", { style: listStyle, "data-group": "fact", children: items.map((item, i) => /* @__PURE__ */ jsxs("li", { style: itemStyle2, children: [
4471
+ item.icon && /* @__PURE__ */ jsx(
4472
+ "span",
4473
+ {
4474
+ style: {
4475
+ marginRight: "var(--glyph-spacing-xs, 0.25rem)",
4476
+ color: "var(--glyph-infographic-accent, #3b82f6)"
4477
+ },
4478
+ "aria-hidden": "true",
4479
+ children: item.icon
4480
+ }
4481
+ ),
4482
+ item.text
4483
+ ] }, `${keyPrefix}-${String(i)}`)) }, keyPrefix);
4484
+ }
4485
+ function renderTextGroup(items, keyPrefix) {
4486
+ const pStyle = {
4487
+ margin: "0 0 var(--glyph-spacing-sm, 0.5rem) 0",
4488
+ fontSize: "0.875rem",
4489
+ lineHeight: 1.6,
4490
+ color: "var(--glyph-text, #1a2035)",
4491
+ borderLeft: "3px solid var(--glyph-infographic-accent, #3b82f6)",
4492
+ paddingLeft: "var(--glyph-spacing-sm, 0.5rem)"
4493
+ };
4494
+ return /* @__PURE__ */ jsx("div", { "data-group": "text", children: items.map((item, i) => /* @__PURE__ */ jsx("p", { style: pStyle, children: item.content }, `${keyPrefix}-${String(i)}`)) }, keyPrefix);
4495
+ }
4496
+ function renderPieGroup(items, keyPrefix, colorOffset) {
4497
+ const wrapperStyle = {
4498
+ display: "flex",
4499
+ flexWrap: "wrap",
4500
+ gap: "var(--glyph-spacing-md, 1rem)"
4501
+ };
4502
+ return /* @__PURE__ */ jsx("div", { style: wrapperStyle, "data-group": "pie", children: items.map((item, i) => {
4503
+ const size = item.size ?? 160;
4504
+ const outerRadius = size / 2;
4505
+ const innerRadius = item.donut ?? true ? outerRadius * 0.55 : 0;
4506
+ const cx = outerRadius;
4507
+ const cy = outerRadius;
4508
+ const total = item.slices.reduce((sum, s) => sum + s.value, 0);
4509
+ let cumAngle = -Math.PI / 2;
4510
+ const paths = [];
4511
+ for (let si = 0; si < item.slices.length; si++) {
4512
+ const slice = item.slices[si];
4513
+ if (!slice) continue;
4514
+ const sliceAngle = slice.value / total * 2 * Math.PI;
4515
+ const startAngle = cumAngle;
4516
+ const endAngle = cumAngle + sliceAngle;
4517
+ cumAngle = endAngle;
4518
+ const sliceColorIndex = (colorOffset + si) % PROGRESS_COLORS.length;
4519
+ const fillColor = slice.color ?? PROGRESS_COLORS[sliceColorIndex];
4520
+ const x1 = cx + outerRadius * Math.cos(startAngle);
4521
+ const y1 = cy + outerRadius * Math.sin(startAngle);
4522
+ const x2 = cx + outerRadius * Math.cos(endAngle);
4523
+ const y2 = cy + outerRadius * Math.sin(endAngle);
4524
+ const largeArc = sliceAngle > Math.PI ? 1 : 0;
4525
+ let d;
4526
+ if (innerRadius > 0) {
4527
+ const ix1 = cx + innerRadius * Math.cos(endAngle);
4528
+ const iy1 = cy + innerRadius * Math.sin(endAngle);
4529
+ const ix2 = cx + innerRadius * Math.cos(startAngle);
4530
+ const iy2 = cy + innerRadius * Math.sin(startAngle);
4531
+ d = [
4532
+ `M ${String(x1)} ${String(y1)}`,
4533
+ `A ${String(outerRadius)} ${String(outerRadius)} 0 ${String(largeArc)} 1 ${String(x2)} ${String(y2)}`,
4534
+ `L ${String(ix1)} ${String(iy1)}`,
4535
+ `A ${String(innerRadius)} ${String(innerRadius)} 0 ${String(largeArc)} 0 ${String(ix2)} ${String(iy2)}`,
4536
+ "Z"
4537
+ ].join(" ");
4538
+ } else {
4539
+ d = [
4540
+ `M ${String(cx)} ${String(cy)}`,
4541
+ `L ${String(x1)} ${String(y1)}`,
4542
+ `A ${String(outerRadius)} ${String(outerRadius)} 0 ${String(largeArc)} 1 ${String(x2)} ${String(y2)}`,
4543
+ "Z"
4544
+ ].join(" ");
4545
+ }
4546
+ paths.push(/* @__PURE__ */ jsx("path", { d, fill: fillColor }, si));
4547
+ }
4548
+ const ariaLabel = item.label ? `${item.label}: ${item.slices.map((s) => `${s.label} ${String(s.value)}`).join(", ")}` : `Pie chart: ${item.slices.map((s) => `${s.label} ${String(s.value)}`).join(", ")}`;
4549
+ const legendStyle = {
4550
+ display: "flex",
4551
+ flexWrap: "wrap",
4552
+ gap: "var(--glyph-spacing-sm, 0.5rem)",
4553
+ marginTop: "var(--glyph-spacing-xs, 0.25rem)",
4554
+ fontSize: "0.75rem",
4555
+ color: "var(--glyph-text-muted, #6b7a94)"
4556
+ };
4557
+ return /* @__PURE__ */ jsxs("div", { style: { textAlign: "center" }, children: [
4558
+ item.label && /* @__PURE__ */ jsx(
4559
+ "div",
4560
+ {
4561
+ style: {
4562
+ fontSize: "0.875rem",
4563
+ fontWeight: 600,
4564
+ color: "var(--glyph-infographic-heading-color, #1e293b)",
4565
+ marginBottom: "var(--glyph-spacing-xs, 0.25rem)"
4566
+ },
4567
+ children: item.label
4568
+ }
4569
+ ),
4570
+ /* @__PURE__ */ jsx(
4571
+ "svg",
4572
+ {
4573
+ width: size,
4574
+ height: size,
4575
+ viewBox: `0 0 ${String(size)} ${String(size)}`,
4576
+ role: "img",
4577
+ "aria-label": ariaLabel,
4578
+ children: paths
4579
+ }
4580
+ ),
4581
+ /* @__PURE__ */ jsx("div", { style: legendStyle, children: item.slices.map((slice, si) => {
4582
+ const sliceColorIndex = (colorOffset + si) % PROGRESS_COLORS.length;
4583
+ const legendColor = slice.color ?? PROGRESS_COLORS[sliceColorIndex];
4584
+ return /* @__PURE__ */ jsxs(
4585
+ "span",
4586
+ {
4587
+ style: {
4588
+ display: "inline-flex",
4589
+ alignItems: "center",
4590
+ gap: "0.25rem",
4591
+ fontWeight: 500
4592
+ },
4593
+ children: [
4594
+ /* @__PURE__ */ jsx(
4595
+ "span",
4596
+ {
4597
+ style: {
4598
+ display: "inline-block",
4599
+ width: "0.625rem",
4600
+ height: "0.625rem",
4601
+ borderRadius: "2px",
4602
+ background: legendColor
4603
+ },
4604
+ "aria-hidden": "true"
4605
+ }
4606
+ ),
4607
+ slice.label
4608
+ ]
4609
+ },
4610
+ si
4611
+ );
4612
+ }) })
4613
+ ] }, `${keyPrefix}-${String(i)}`);
4614
+ }) }, keyPrefix);
4615
+ }
4616
+ function renderDividerGroup(items, keyPrefix) {
4617
+ return /* @__PURE__ */ jsx("div", { "data-group": "divider", children: items.map((item, i) => /* @__PURE__ */ jsx(
4618
+ "hr",
4619
+ {
4620
+ role: "separator",
4621
+ style: {
4622
+ border: "none",
4623
+ borderTop: `1px ${item.style ?? "solid"} var(--glyph-infographic-section-divider, #d0d8e4)`,
4624
+ margin: "var(--glyph-spacing-sm, 0.5rem) 0"
4625
+ }
4626
+ },
4627
+ `${keyPrefix}-${String(i)}`
4628
+ )) }, keyPrefix);
4629
+ }
4630
+ function renderRatingGroup(items, keyPrefix) {
4631
+ const starColor = "var(--glyph-infographic-star, #f59e0b)";
4632
+ const emptyColor = "var(--glyph-text-muted, #6b7a94)";
4633
+ return /* @__PURE__ */ jsx("div", { "data-group": "rating", children: items.map((item, i) => {
4634
+ const max2 = item.max ?? 5;
4635
+ const fullStars = Math.floor(item.value);
4636
+ const hasHalf = item.value - fullStars >= 0.5;
4637
+ const stars = [];
4638
+ for (let s = 0; s < max2; s++) {
4639
+ if (s < fullStars) {
4640
+ stars.push(
4641
+ /* @__PURE__ */ jsx("span", { style: { color: starColor }, "aria-hidden": "true", children: "\u2605" }, s)
4642
+ );
4643
+ } else if (s === fullStars && hasHalf) {
4644
+ stars.push(
4645
+ /* @__PURE__ */ jsxs(
4646
+ "span",
4647
+ {
4648
+ style: { position: "relative", display: "inline-block" },
4649
+ "aria-hidden": "true",
4650
+ children: [
4651
+ /* @__PURE__ */ jsx("span", { style: { color: emptyColor }, children: "\u2605" }),
4652
+ /* @__PURE__ */ jsx(
4653
+ "span",
4654
+ {
4655
+ style: {
4656
+ position: "absolute",
4657
+ left: 0,
4658
+ top: 0,
4659
+ overflow: "hidden",
4660
+ width: "50%",
4661
+ color: starColor
4662
+ },
4663
+ children: "\u2605"
4664
+ }
4665
+ )
4666
+ ]
4667
+ },
4668
+ s
4669
+ )
4670
+ );
4671
+ } else {
4672
+ stars.push(
4673
+ /* @__PURE__ */ jsx("span", { style: { color: emptyColor }, "aria-hidden": "true", children: "\u2605" }, s)
4674
+ );
4675
+ }
4676
+ }
4677
+ return /* @__PURE__ */ jsxs(
4678
+ "div",
4679
+ {
4680
+ style: {
4681
+ display: "flex",
4682
+ alignItems: "baseline",
4683
+ gap: "var(--glyph-spacing-sm, 0.5rem)",
4684
+ marginBottom: "var(--glyph-spacing-sm, 0.5rem)"
4685
+ },
4686
+ children: [
4687
+ /* @__PURE__ */ jsx(
4688
+ "span",
4689
+ {
4690
+ style: {
4691
+ fontSize: "1.5rem",
4692
+ fontWeight: 700,
4693
+ color: "var(--glyph-heading, #0a0e1a)",
4694
+ lineHeight: 1
4695
+ },
4696
+ children: String(item.value)
4697
+ }
4698
+ ),
4699
+ /* @__PURE__ */ jsxs("div", { children: [
4700
+ /* @__PURE__ */ jsx(
4701
+ "div",
4702
+ {
4703
+ style: {
4704
+ fontSize: "0.875rem",
4705
+ color: "var(--glyph-text, #1a2035)",
4706
+ fontWeight: 600
4707
+ },
4708
+ children: item.label
4709
+ }
4710
+ ),
4711
+ /* @__PURE__ */ jsx(
4712
+ "div",
4713
+ {
4714
+ style: { fontSize: "1.125rem", letterSpacing: "0.05em" },
4715
+ "aria-label": `${String(item.value)} out of ${String(max2)} stars`,
4716
+ children: stars
4717
+ }
4718
+ ),
4719
+ item.description && /* @__PURE__ */ jsx(
4720
+ "div",
4721
+ {
4722
+ style: {
4723
+ fontSize: "0.75rem",
4724
+ color: "var(--glyph-infographic-desc-color, #64748b)",
4725
+ marginTop: "var(--glyph-spacing-xs, 0.25rem)",
4726
+ fontStyle: "italic"
4727
+ },
4728
+ children: item.description
4729
+ }
4730
+ )
4731
+ ] })
4732
+ ]
4733
+ },
4734
+ `${keyPrefix}-${String(i)}`
4735
+ );
4736
+ }) }, keyPrefix);
4737
+ }
4738
+ function Infographic({
4739
+ data,
4740
+ block,
4741
+ container
4742
+ }) {
4743
+ const { title, sections } = data;
4744
+ const baseId = `glyph-infographic-${block.id}`;
4745
+ const useGrid = sections.length >= 2 && container.tier !== "compact";
4746
+ const sectionLayouts = useMemo(() => computeSectionLayout(sections), [sections]);
4747
+ const containerStyle3 = {
4748
+ fontFamily: "var(--glyph-font-body, system-ui, sans-serif)",
4749
+ color: "var(--glyph-text, #1a2035)",
4750
+ background: "var(--glyph-surface, #e8ecf3)",
4751
+ border: "1px solid var(--glyph-border, #d0d8e4)",
4752
+ borderRadius: "var(--glyph-radius-md, 0.5rem)",
4753
+ padding: "var(--glyph-spacing-md, 1rem)"
4754
+ };
4755
+ const titleStyle2 = {
4756
+ fontFamily: "var(--glyph-font-heading, system-ui, sans-serif)",
4757
+ fontWeight: 700,
4758
+ fontSize: "1.25rem",
4759
+ color: "var(--glyph-heading, #0a0e1a)",
4760
+ margin: 0,
4761
+ paddingBottom: "var(--glyph-spacing-sm, 0.5rem)",
4762
+ marginBottom: "var(--glyph-spacing-md, 1rem)",
4763
+ borderBottom: "2px solid var(--glyph-infographic-accent, #3b82f6)",
4764
+ ...useGrid ? { gridColumn: "1 / -1" } : {}
4765
+ };
4766
+ const sectionDividerStyle = {
4767
+ borderTop: "1px solid var(--glyph-infographic-section-divider, #d0d8e4)",
4768
+ paddingTop: "var(--glyph-spacing-md, 1rem)",
4769
+ marginTop: "var(--glyph-spacing-md, 1rem)"
4770
+ };
4771
+ const sectionsGridStyle = {
4772
+ display: "grid",
4773
+ gridTemplateColumns: "repeat(2, 1fr)",
4774
+ gap: "var(--glyph-spacing-md, 1rem)",
4775
+ alignItems: "start"
4776
+ };
4777
+ const sectionCardStyle = {
4778
+ background: "var(--glyph-infographic-section-bg, rgba(255, 255, 255, 0.5))",
4779
+ borderRadius: "var(--glyph-radius-sm, 0.375rem)",
4780
+ padding: "var(--glyph-spacing-sm, 0.5rem)"
4781
+ };
4782
+ const headingStyle = {
4783
+ fontWeight: 700,
4784
+ fontSize: "1rem",
4785
+ color: "var(--glyph-infographic-heading-color, #1e293b)",
4786
+ marginBottom: "var(--glyph-spacing-sm, 0.5rem)",
4787
+ borderLeft: "3px solid var(--glyph-infographic-accent, #3b82f6)",
4788
+ paddingLeft: "var(--glyph-spacing-sm, 0.5rem)"
4789
+ };
4790
+ const printCss = useGrid ? `@media print { #${CSS.escape(baseId)} [data-layout="grid"] { display: grid !important; grid-template-columns: repeat(2, 1fr) !important; gap: 0.5rem !important; } #${CSS.escape(baseId)} [data-layout="grid"] > div { break-inside: avoid; } }` : "";
4791
+ let progressColorOffset = 0;
4792
+ return /* @__PURE__ */ jsxs("div", { id: baseId, role: "region", "aria-label": title ?? "Infographic", style: containerStyle3, children: [
4793
+ printCss && /* @__PURE__ */ jsx("style", { children: printCss }),
4794
+ /* @__PURE__ */ jsxs("div", { "data-layout": useGrid ? "grid" : "stack", style: useGrid ? sectionsGridStyle : void 0, children: [
4795
+ title && /* @__PURE__ */ jsx("div", { style: titleStyle2, children: title }),
4796
+ sections.map((section, si) => {
4797
+ const groups = groupConsecutiveItems(section.items);
4798
+ const gridColumn = sectionLayouts[si];
4799
+ const sectionStyle = {
4800
+ ...useGrid ? sectionCardStyle : {},
4801
+ ...gridColumn ? { gridColumn } : {},
4802
+ ...!useGrid && si > 0 ? sectionDividerStyle : {}
4803
+ };
4804
+ return /* @__PURE__ */ jsxs("div", { style: Object.keys(sectionStyle).length > 0 ? sectionStyle : void 0, children: [
4805
+ section.heading && /* @__PURE__ */ jsx("div", { style: headingStyle, children: section.heading }),
4806
+ groups.map((group, gi) => {
4807
+ const key = `s${String(si)}-g${String(gi)}`;
4808
+ switch (group.type) {
4809
+ case "stat":
4810
+ return renderStatGroup(group.items, key);
4811
+ case "progress": {
4812
+ const el = renderProgressGroup(
4813
+ group.items,
4814
+ key,
4815
+ progressColorOffset
4816
+ );
4817
+ progressColorOffset += group.items.length;
4818
+ return el;
4819
+ }
4820
+ case "fact":
4821
+ return renderFactGroup(group.items, key);
4822
+ case "text":
4823
+ return renderTextGroup(group.items, key);
4824
+ case "pie": {
4825
+ const el = renderPieGroup(group.items, key, progressColorOffset);
4826
+ progressColorOffset += group.items.reduce(
4827
+ (sum, item) => sum + item.slices.length,
4828
+ 0
4829
+ );
4830
+ return el;
4831
+ }
4832
+ case "divider":
4833
+ return renderDividerGroup(group.items, key);
4834
+ case "rating":
4835
+ return renderRatingGroup(group.items, key);
4836
+ default:
4837
+ return null;
4838
+ }
4839
+ })
4840
+ ] }, si);
4841
+ })
4842
+ ] })
4843
+ ] });
4844
+ }
4845
+
4846
+ // src/infographic/index.ts
4847
+ var infographicDefinition = {
4848
+ type: "ui:infographic",
4849
+ schema: infographicSchema,
4850
+ render: Infographic
4851
+ };
4852
+
4853
+ export { Accordion, Architecture, Callout, Card, Chart, CodeDiff, Comparison, Equation, FileTree, Flowchart, Graph, Infographic, Kpi, MindMap, Quiz, Relation, Sequence, Steps, Table, Tabs, Timeline, accordionDefinition, architectureDefinition, calloutDefinition, cardDefinition, chartDefinition, codeDiffDefinition, comparisonDefinition, computeArchitectureLayout, computeDagreLayout, computeDiff, computeForceLayout, equationDefinition, fileTreeDefinition, flowchartDefinition, graphDefinition, infographicDefinition, kpiDefinition, mindMapDefinition, quizDefinition, relationDefinition, sequenceDefinition, stepsDefinition, tableDefinition, tabsDefinition, timelineDefinition };
4854
+ //# sourceMappingURL=index.js.map
4855
+ //# sourceMappingURL=index.js.map