@glyphjs/components 0.1.0 → 0.2.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 CHANGED
@@ -1,4 +1,4 @@
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';
1
+ import { accordionSchema, annotateSchema, architectureSchema, calloutSchema, cardSchema, chartSchema, codediffSchema, comparisonSchema, equationSchema, filetreeSchema, flowchartSchema, formSchema, graphSchema, infographicSchema, kanbanSchema, kpiSchema, matrixSchema, mindmapSchema, pollSchema, quizSchema, rankerSchema, ratingSchema, relationSchema, sequenceSchema, sliderSchema, stepsSchema, tableSchema, tabsSchema, timelineSchema } from '@glyphjs/schemas';
2
2
  import { jsxs, jsx } from 'react/jsx-runtime';
3
3
  import { useRef, useState, useEffect, useCallback, useMemo } from 'react';
4
4
  import * as d32 from 'd3';
@@ -21,7 +21,7 @@ var CALLOUT_LABELS = {
21
21
  };
22
22
  function Callout({ data }) {
23
23
  const { type, title, content } = data;
24
- const containerStyle3 = {
24
+ const containerStyle11 = {
25
25
  backgroundColor: `var(--glyph-callout-${type}-bg)`,
26
26
  borderLeft: `4px solid var(--glyph-callout-${type}-border)`,
27
27
  borderRadius: "var(--glyph-radius-md, 0.1875rem)",
@@ -38,7 +38,7 @@ function Callout({ data }) {
38
38
  fontSize: "1.25em",
39
39
  lineHeight: 1
40
40
  };
41
- const bodyStyle2 = {
41
+ const bodyStyle3 = {
42
42
  flex: 1,
43
43
  minWidth: 0
44
44
  };
@@ -46,9 +46,9 @@ function Callout({ data }) {
46
46
  fontWeight: 700,
47
47
  marginBottom: "var(--glyph-spacing-xs, 0.25rem)"
48
48
  };
49
- return /* @__PURE__ */ jsxs("div", { role: "note", "aria-label": CALLOUT_LABELS[type], style: containerStyle3, children: [
49
+ return /* @__PURE__ */ jsxs("div", { role: "note", "aria-label": CALLOUT_LABELS[type], style: containerStyle11, children: [
50
50
  /* @__PURE__ */ jsx("span", { style: iconStyle, "aria-hidden": "true", children: CALLOUT_ICONS[type] }),
51
- /* @__PURE__ */ jsxs("div", { style: bodyStyle2, children: [
51
+ /* @__PURE__ */ jsxs("div", { style: bodyStyle3, children: [
52
52
  title && /* @__PURE__ */ jsx("div", { style: titleStyle2, children: title }),
53
53
  /* @__PURE__ */ jsx("div", { children: content })
54
54
  ] })
@@ -253,9 +253,104 @@ function computeScales(width, height, type, series, xKey, yKey, margin) {
253
253
  const yScale = d32.scaleLinear().domain([yMin, yMax]).nice().range([innerHeight, 0]);
254
254
  return { xScale, xScalePoint, yScale, innerWidth, innerHeight };
255
255
  }
256
+ function renderAllSeries(g, type, series, scales, xKey, yKey, showTooltip, hideTooltip) {
257
+ const { xScale, xScalePoint, yScale, innerHeight } = scales;
258
+ series.forEach((s, i) => {
259
+ const color3 = COLOR_SCHEME[i % COLOR_SCHEME.length] ?? "#333";
260
+ switch (type) {
261
+ case "line":
262
+ renderLineSeries(
263
+ g,
264
+ s.data,
265
+ xScalePoint,
266
+ yScale,
267
+ yKey,
268
+ xKey,
269
+ color3,
270
+ i,
271
+ s.name,
272
+ showTooltip,
273
+ hideTooltip
274
+ );
275
+ break;
276
+ case "area":
277
+ renderAreaSeries(
278
+ g,
279
+ s.data,
280
+ xScalePoint,
281
+ yScale,
282
+ yKey,
283
+ xKey,
284
+ innerHeight,
285
+ color3,
286
+ i,
287
+ s.name,
288
+ showTooltip,
289
+ hideTooltip
290
+ );
291
+ break;
292
+ case "bar":
293
+ renderBarSeries(
294
+ g,
295
+ s.data,
296
+ xScale,
297
+ yScale,
298
+ yKey,
299
+ xKey,
300
+ color3,
301
+ i,
302
+ series.length,
303
+ innerHeight,
304
+ s.name,
305
+ showTooltip,
306
+ hideTooltip
307
+ );
308
+ break;
309
+ case "ohlc":
310
+ renderOHLCSeries(
311
+ g,
312
+ s.data,
313
+ xScale,
314
+ xScalePoint,
315
+ yScale,
316
+ scales.innerWidth,
317
+ s.name,
318
+ showTooltip,
319
+ hideTooltip
320
+ );
321
+ break;
322
+ }
323
+ });
324
+ }
325
+ function attachChartInteraction(g, type, series, xKey, yKey, block, onInteraction) {
326
+ if (!onInteraction) return;
327
+ series.forEach((s, seriesIdx) => {
328
+ const className = type === "bar" ? `bar-${String(seriesIdx)}` : `dot-${String(seriesIdx)}`;
329
+ g.selectAll(`.${className}`).on(
330
+ "click",
331
+ (_event, d) => {
332
+ const dataIdx = s.data.indexOf(d);
333
+ onInteraction({
334
+ kind: "chart-select",
335
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
336
+ blockId: block.id,
337
+ blockType: block.type,
338
+ payload: {
339
+ seriesIndex: seriesIdx,
340
+ dataIndex: dataIdx >= 0 ? dataIdx : 0,
341
+ label: String(d[xKey] ?? ""),
342
+ value: getNumericValue(d, yKey)
343
+ }
344
+ });
345
+ }
346
+ );
347
+ });
348
+ }
256
349
  function Chart({
257
350
  data,
258
- container: containerCtx
351
+ block,
352
+ container: containerCtx,
353
+ onInteraction
259
354
  }) {
260
355
  const containerRef = useRef(null);
261
356
  const svgRef = useRef(null);
@@ -308,76 +403,19 @@ function Chart({
308
403
  if (!svg || series.length === 0) return;
309
404
  const sel = d32.select(svg);
310
405
  sel.selectAll("*").remove();
311
- const { xScale, xScalePoint, yScale, innerWidth, innerHeight } = scales;
312
406
  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
- });
407
+ renderAxes(
408
+ g,
409
+ scales.xScale,
410
+ scales.yScale,
411
+ xAxis,
412
+ yAxis,
413
+ scales.innerWidth,
414
+ scales.innerHeight
415
+ );
416
+ renderGridLines(g, scales.yScale, scales.innerWidth);
417
+ renderAllSeries(g, type, series, scales, xKey, yKey, showTooltip, hideTooltip);
418
+ attachChartInteraction(g, type, series, xKey, yKey, block, onInteraction);
381
419
  if (legend) {
382
420
  renderLegend(sel, series, margin.left, margin.top, isCompact ? "10px" : void 0);
383
421
  }
@@ -393,7 +431,9 @@ function Chart({
393
431
  margin,
394
432
  isCompact,
395
433
  showTooltip,
396
- hideTooltip
434
+ hideTooltip,
435
+ onInteraction,
436
+ block
397
437
  ]);
398
438
  const ariaLabel = `${type} chart with ${String(series.length)} series: ${series.map((s) => s.name).join(", ")}`;
399
439
  return /* @__PURE__ */ jsxs(
@@ -467,14 +507,14 @@ var STATUS_LABELS = {
467
507
  };
468
508
  function Steps({ data }) {
469
509
  const { steps } = data;
470
- const listStyle = {
510
+ const listStyle2 = {
471
511
  listStyle: "none",
472
512
  padding: 0,
473
513
  margin: "var(--glyph-spacing-sm, 0.5rem) 0",
474
514
  fontFamily: "var(--glyph-font-body, system-ui, sans-serif)",
475
515
  color: "var(--glyph-text, #1a2035)"
476
516
  };
477
- return /* @__PURE__ */ jsx("ol", { role: "list", style: listStyle, children: steps.map((step, index) => {
517
+ return /* @__PURE__ */ jsx("ol", { role: "list", style: listStyle2, children: steps.map((step, index) => {
478
518
  const status = step.status ?? "pending";
479
519
  const isLast = index === steps.length - 1;
480
520
  return /* @__PURE__ */ jsxs(
@@ -621,7 +661,95 @@ function TableAggregationFooter({
621
661
  );
622
662
  }) }) });
623
663
  }
624
- function Table({ data, container }) {
664
+ function TableHead({
665
+ columns,
666
+ sort,
667
+ hasFilters,
668
+ filters,
669
+ onSort,
670
+ onHeaderKeyDown,
671
+ onFilterChange
672
+ }) {
673
+ return /* @__PURE__ */ jsxs("thead", { children: [
674
+ /* @__PURE__ */ jsx("tr", { children: columns.map((col) => {
675
+ const isSorted = sort.column === col.key;
676
+ const direction = isSorted ? sort.direction : "none";
677
+ return /* @__PURE__ */ jsxs(
678
+ "th",
679
+ {
680
+ scope: "col",
681
+ "aria-sort": col.sortable ? direction : void 0,
682
+ tabIndex: col.sortable ? 0 : void 0,
683
+ role: col.sortable ? "columnheader" : void 0,
684
+ onClick: col.sortable ? () => onSort(col.key) : void 0,
685
+ onKeyDown: col.sortable ? (e) => onHeaderKeyDown(e, col.key) : void 0,
686
+ style: {
687
+ padding: "var(--glyph-table-cell-padding, 8px 12px)",
688
+ textAlign: "left",
689
+ borderBottom: "2px solid var(--glyph-table-border, #d0d8e4)",
690
+ background: "var(--glyph-table-header-bg, #e8ecf3)",
691
+ color: "var(--glyph-table-header-color, inherit)",
692
+ cursor: col.sortable ? "pointer" : "default",
693
+ userSelect: col.sortable ? "none" : void 0,
694
+ whiteSpace: "nowrap"
695
+ },
696
+ children: [
697
+ col.label,
698
+ col.sortable ? sortIndicator(direction) : ""
699
+ ]
700
+ },
701
+ col.key
702
+ );
703
+ }) }),
704
+ hasFilters && /* @__PURE__ */ jsx("tr", { children: columns.map((col) => /* @__PURE__ */ jsx(
705
+ "th",
706
+ {
707
+ scope: "col",
708
+ style: { padding: "4px 8px", fontWeight: "normal" },
709
+ children: col.filterable ? /* @__PURE__ */ jsx(
710
+ "input",
711
+ {
712
+ type: "text",
713
+ "aria-label": `Filter ${col.label}`,
714
+ placeholder: `Filter ${col.label}...`,
715
+ value: filters[col.key] ?? "",
716
+ onChange: (e) => onFilterChange(col.key, e.target.value),
717
+ style: {
718
+ width: "100%",
719
+ padding: "4px 6px",
720
+ border: "1px solid var(--glyph-table-border, #d0d8e4)",
721
+ borderRadius: "3px",
722
+ fontSize: "inherit",
723
+ boxSizing: "border-box",
724
+ background: "var(--glyph-surface, #e8ecf3)",
725
+ color: "var(--glyph-text, inherit)"
726
+ }
727
+ }
728
+ ) : /* @__PURE__ */ jsx(
729
+ "span",
730
+ {
731
+ style: {
732
+ position: "absolute",
733
+ width: "1px",
734
+ height: "1px",
735
+ overflow: "hidden",
736
+ clip: "rect(0,0,0,0)",
737
+ whiteSpace: "nowrap"
738
+ },
739
+ children: `No filter for ${col.label}`
740
+ }
741
+ )
742
+ },
743
+ `filter-${col.key}`
744
+ )) })
745
+ ] });
746
+ }
747
+ function Table({
748
+ data,
749
+ block,
750
+ container,
751
+ onInteraction
752
+ }) {
625
753
  const { columns, rows, aggregation } = data;
626
754
  const [sort, setSort] = useState({ column: "", direction: "none" });
627
755
  const [filters, setFilters] = useState({});
@@ -654,12 +782,27 @@ function Table({ data, container }) {
654
782
  });
655
783
  }, [filteredRows, sort]);
656
784
  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
- });
785
+ const newDirection = sort.column === columnKey ? nextDirection(sort.direction) : "ascending";
786
+ setSort({ column: columnKey, direction: newDirection });
787
+ if (onInteraction) {
788
+ const eventDir = newDirection === "ascending" ? "asc" : newDirection === "descending" ? "desc" : "none";
789
+ onInteraction({
790
+ kind: "table-sort",
791
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
792
+ blockId: block.id,
793
+ blockType: block.type,
794
+ payload: {
795
+ column: columnKey,
796
+ direction: eventDir,
797
+ state: {
798
+ sort: newDirection === "none" ? null : { column: columnKey, direction: eventDir },
799
+ filters,
800
+ visibleRowCount: filteredRows.length,
801
+ totalRowCount: rows.length
802
+ }
803
+ }
804
+ });
805
+ }
663
806
  };
664
807
  const handleHeaderKeyDown = (e, columnKey) => {
665
808
  if (e.key === "Enter" || e.key === " ") {
@@ -668,7 +811,38 @@ function Table({ data, container }) {
668
811
  }
669
812
  };
670
813
  const handleFilterChange = (columnKey, value) => {
671
- setFilters((prev) => ({ ...prev, [columnKey]: value }));
814
+ const newFilters = { ...filters, [columnKey]: value };
815
+ setFilters(newFilters);
816
+ if (onInteraction) {
817
+ const newVisibleCount = rows.filter(
818
+ (row) => columns.every((col) => {
819
+ if (!col.filterable) return true;
820
+ const fv = newFilters[col.key];
821
+ if (!fv) return true;
822
+ return String(row[col.key] ?? "").toLowerCase().includes(fv.toLowerCase());
823
+ })
824
+ ).length;
825
+ const eventSort = sort.direction === "none" || !sort.column ? null : {
826
+ column: sort.column,
827
+ direction: sort.direction === "ascending" ? "asc" : "desc"
828
+ };
829
+ onInteraction({
830
+ kind: "table-filter",
831
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
832
+ blockId: block.id,
833
+ blockType: block.type,
834
+ payload: {
835
+ column: columnKey,
836
+ value,
837
+ state: {
838
+ sort: eventSort,
839
+ filters: newFilters,
840
+ visibleRowCount: newVisibleCount,
841
+ totalRowCount: rows.length
842
+ }
843
+ }
844
+ });
845
+ }
672
846
  };
673
847
  const hasFilters = columns.some((c) => c.filterable);
674
848
  const aggMap = useMemo(() => {
@@ -692,79 +866,18 @@ function Table({ data, container }) {
692
866
  fontSize: isCompact ? "0.8125rem" : "var(--glyph-table-font-size, 0.9rem)"
693
867
  },
694
868
  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
- ] }),
869
+ /* @__PURE__ */ jsx(
870
+ TableHead,
871
+ {
872
+ columns,
873
+ sort,
874
+ hasFilters,
875
+ filters,
876
+ onSort: handleSort,
877
+ onHeaderKeyDown: handleHeaderKeyDown,
878
+ onFilterChange: handleFilterChange
879
+ }
880
+ ),
768
881
  /* @__PURE__ */ jsx("tbody", { children: sortedRows.map((row, rowIdx) => /* @__PURE__ */ jsx(
769
882
  "tr",
770
883
  {
@@ -802,18 +915,28 @@ var tableDefinition = {
802
915
  schema: tableSchema,
803
916
  render: Table
804
917
  };
805
- function Tabs({ data, block }) {
918
+ function Tabs({ data, block, onInteraction }) {
806
919
  const [activeIndex, setActiveIndex] = useState(0);
807
920
  const tabRefs = useRef([]);
808
921
  const tabs = data.tabs;
809
922
  const baseId = `glyph-tabs-${block.id}`;
810
- const focusTab = useCallback(
923
+ const selectTab = useCallback(
811
924
  (index) => {
812
925
  const clampedIndex = Math.max(0, Math.min(index, tabs.length - 1));
813
926
  setActiveIndex(clampedIndex);
814
927
  tabRefs.current[clampedIndex]?.focus();
928
+ const tab = tabs[clampedIndex];
929
+ if (tab) {
930
+ onInteraction?.({
931
+ kind: "tab-select",
932
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
933
+ blockId: block.id,
934
+ blockType: block.type,
935
+ payload: { tabIndex: clampedIndex, tabLabel: tab.label }
936
+ });
937
+ }
815
938
  },
816
- [tabs.length]
939
+ [tabs, block.id, block.type, onInteraction]
817
940
  );
818
941
  const handleKeyDown = useCallback(
819
942
  (e) => {
@@ -821,28 +944,28 @@ function Tabs({ data, block }) {
821
944
  case "ArrowRight": {
822
945
  e.preventDefault();
823
946
  const next = (activeIndex + 1) % tabs.length;
824
- focusTab(next);
947
+ selectTab(next);
825
948
  break;
826
949
  }
827
950
  case "ArrowLeft": {
828
951
  e.preventDefault();
829
952
  const prev = (activeIndex - 1 + tabs.length) % tabs.length;
830
- focusTab(prev);
953
+ selectTab(prev);
831
954
  break;
832
955
  }
833
956
  case "Home": {
834
957
  e.preventDefault();
835
- focusTab(0);
958
+ selectTab(0);
836
959
  break;
837
960
  }
838
961
  case "End": {
839
962
  e.preventDefault();
840
- focusTab(tabs.length - 1);
963
+ selectTab(tabs.length - 1);
841
964
  break;
842
965
  }
843
966
  }
844
967
  },
845
- [activeIndex, focusTab, tabs.length]
968
+ [activeIndex, selectTab, tabs.length]
846
969
  );
847
970
  return /* @__PURE__ */ jsxs(
848
971
  "div",
@@ -881,7 +1004,7 @@ function Tabs({ data, block }) {
881
1004
  "aria-selected": isActive,
882
1005
  "aria-controls": panelId,
883
1006
  tabIndex: isActive ? 0 : -1,
884
- onClick: () => setActiveIndex(index),
1007
+ onClick: () => selectTab(index),
885
1008
  onKeyDown: handleKeyDown,
886
1009
  style: {
887
1010
  padding: "10px 18px",
@@ -993,7 +1116,7 @@ function Timeline({ data }) {
993
1116
  position: dates.length === 1 ? totalLength / 2 : timeScale(e._parsed),
994
1117
  side: isVertical ? i % 2 === 0 ? "left" : "right" : i % 2 === 0 ? "top" : "bottom"
995
1118
  }));
996
- const containerStyle3 = {
1119
+ const containerStyle11 = {
997
1120
  position: "relative",
998
1121
  fontFamily: "var(--glyph-font-body, system-ui, sans-serif)",
999
1122
  color: "var(--glyph-text, #1a2035)",
@@ -1020,7 +1143,7 @@ function Timeline({ data }) {
1020
1143
  "div",
1021
1144
  {
1022
1145
  ref: containerRef,
1023
- style: containerStyle3,
1146
+ style: containerStyle11,
1024
1147
  role: "img",
1025
1148
  "aria-label": `Timeline with ${events.length} events`,
1026
1149
  children: [
@@ -1348,7 +1471,7 @@ function getThemeVar(container, varName, fallback) {
1348
1471
  return getComputedStyle(container).getPropertyValue(varName).trim() || fallback;
1349
1472
  }
1350
1473
  var ARROW_MARKER_ID = "glyph-graph-arrowhead";
1351
- function renderGraph(svgElement, layout, groupIndex, outgoingRefs, onNavigate) {
1474
+ function renderGraph(svgElement, layout, groupIndex, outgoingRefs, onNavigate, onNodeClick) {
1352
1475
  const svg = d32.select(svgElement);
1353
1476
  svg.selectAll("*").remove();
1354
1477
  const width = Math.max(layout.width, 200);
@@ -1399,19 +1522,24 @@ function renderGraph(svgElement, layout, groupIndex, outgoingRefs, onNavigate) {
1399
1522
  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
1523
  }
1401
1524
  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) {
1525
+ if (isNavigable || onNodeClick) {
1403
1526
  nodeG.attr("cursor", "pointer");
1404
1527
  nodeG.on("click", () => {
1405
- const ref = refByAnchor.get(node.id);
1406
- if (ref) onNavigate(ref);
1528
+ if (isNavigable) {
1529
+ const ref = refByAnchor.get(node.id);
1530
+ if (ref) onNavigate(ref);
1531
+ }
1532
+ onNodeClick?.(node.id, node.label);
1407
1533
  });
1408
1534
  }
1409
1535
  }
1410
1536
  }
1411
1537
  function Graph({
1412
1538
  data,
1539
+ block,
1413
1540
  outgoingRefs,
1414
1541
  onNavigate,
1542
+ onInteraction,
1415
1543
  container
1416
1544
  }) {
1417
1545
  const svgRef = useRef(null);
@@ -1423,10 +1551,29 @@ function Graph({
1423
1551
  }
1424
1552
  return computeDagreLayout(data.nodes, data.edges, direction);
1425
1553
  }, [data]);
1554
+ const handleNodeClick = useMemo(() => {
1555
+ if (!onInteraction) return void 0;
1556
+ return (nodeId, nodeLabel) => {
1557
+ onInteraction({
1558
+ kind: "graph-node-click",
1559
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1560
+ blockId: block.id,
1561
+ blockType: block.type,
1562
+ payload: { nodeId, nodeLabel }
1563
+ });
1564
+ };
1565
+ }, [onInteraction, block.id, block.type]);
1426
1566
  useEffect(() => {
1427
1567
  if (!svgRef.current) return;
1428
- renderGraph(svgRef.current, layoutResult, groupIndex.current, outgoingRefs, onNavigate);
1429
- }, [layoutResult, outgoingRefs, onNavigate]);
1568
+ renderGraph(
1569
+ svgRef.current,
1570
+ layoutResult,
1571
+ groupIndex.current,
1572
+ outgoingRefs,
1573
+ onNavigate,
1574
+ handleNodeClick
1575
+ );
1576
+ }, [layoutResult, outgoingRefs, onNavigate, handleNodeClick]);
1430
1577
  const ariaLabel = `${data.type} graph with ${data.nodes.length} nodes and ${data.edges.length} edges`;
1431
1578
  return /* @__PURE__ */ jsxs("div", { className: "glyph-graph-container", children: [
1432
1579
  /* @__PURE__ */ jsx(
@@ -1770,7 +1917,7 @@ function Kpi({ data, block, container }) {
1770
1917
  default:
1771
1918
  colCount = authorCols;
1772
1919
  }
1773
- const containerStyle3 = {
1920
+ const containerStyle11 = {
1774
1921
  fontFamily: "var(--glyph-font-body, system-ui, sans-serif)",
1775
1922
  color: "var(--glyph-text, #1a2035)"
1776
1923
  };
@@ -1780,13 +1927,13 @@ function Kpi({ data, block, container }) {
1780
1927
  gridTemplateColumns: `repeat(auto-fill, minmax(max(120px, calc((100% - ${String(gapCount)}rem) / ${String(colCount)})), 1fr))`,
1781
1928
  gap: "var(--glyph-spacing-md, 1rem)"
1782
1929
  };
1783
- const cardStyle = {
1930
+ const cardStyle2 = {
1784
1931
  background: "var(--glyph-surface-raised, #f4f6fa)",
1785
1932
  border: "1px solid var(--glyph-border, #d0d8e4)",
1786
1933
  borderRadius: "var(--glyph-radius-md, 0.5rem)",
1787
1934
  padding: "var(--glyph-spacing-md, 1rem)"
1788
1935
  };
1789
- const labelStyle3 = {
1936
+ const labelStyle4 = {
1790
1937
  fontSize: "0.8125rem",
1791
1938
  color: "var(--glyph-text-muted, #6b7a94)",
1792
1939
  marginBottom: "var(--glyph-spacing-xs, 0.25rem)"
@@ -1797,7 +1944,7 @@ function Kpi({ data, block, container }) {
1797
1944
  color: "var(--glyph-heading, #0a0e1a)",
1798
1945
  lineHeight: 1.2
1799
1946
  };
1800
- return /* @__PURE__ */ jsxs("div", { id: baseId, role: "region", "aria-label": title ?? "Key metrics", style: containerStyle3, children: [
1947
+ return /* @__PURE__ */ jsxs("div", { id: baseId, role: "region", "aria-label": title ?? "Key metrics", style: containerStyle11, children: [
1801
1948
  title && /* @__PURE__ */ jsx(
1802
1949
  "div",
1803
1950
  {
@@ -1817,8 +1964,8 @@ function Kpi({ data, block, container }) {
1817
1964
  marginTop: "var(--glyph-spacing-xs, 0.25rem)",
1818
1965
  color: `var(--glyph-kpi-${sentiment}, inherit)`
1819
1966
  };
1820
- return /* @__PURE__ */ jsxs("div", { role: "group", "aria-label": buildAriaLabel(metric), style: cardStyle, children: [
1821
- /* @__PURE__ */ jsx("div", { style: labelStyle3, children: metric.label }),
1967
+ return /* @__PURE__ */ jsxs("div", { role: "group", "aria-label": buildAriaLabel(metric), style: cardStyle2, children: [
1968
+ /* @__PURE__ */ jsx("div", { style: labelStyle4, children: metric.label }),
1822
1969
  /* @__PURE__ */ jsxs("div", { style: valueStyle, children: [
1823
1970
  metric.value,
1824
1971
  metric.unit && /* @__PURE__ */ jsx("span", { style: { fontSize: "0.875rem", fontWeight: 400, marginLeft: "0.25rem" }, children: metric.unit })
@@ -1838,25 +1985,41 @@ var kpiDefinition = {
1838
1985
  schema: kpiSchema,
1839
1986
  render: Kpi
1840
1987
  };
1841
- function Accordion({ data, block }) {
1988
+ function Accordion({
1989
+ data,
1990
+ block,
1991
+ onInteraction
1992
+ }) {
1842
1993
  const { title, sections, defaultOpen = [], multiple = true } = data;
1843
1994
  const baseId = `glyph-accordion-${block.id}`;
1844
1995
  const containerRef = useRef(null);
1845
1996
  const handleToggle = useCallback(
1846
- (e) => {
1847
- if (multiple) return;
1997
+ (e, sectionIndex) => {
1848
1998
  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;
1999
+ const expanded = target.open;
2000
+ if (!multiple && expanded && containerRef.current) {
2001
+ const allDetails = containerRef.current.querySelectorAll("details");
2002
+ for (const details of allDetails) {
2003
+ if (details !== target && details.open) {
2004
+ details.open = false;
2005
+ }
1854
2006
  }
1855
2007
  }
2008
+ onInteraction?.({
2009
+ kind: "accordion-toggle",
2010
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2011
+ blockId: block.id,
2012
+ blockType: block.type,
2013
+ payload: {
2014
+ sectionIndex,
2015
+ sectionTitle: sections[sectionIndex]?.title ?? "",
2016
+ expanded
2017
+ }
2018
+ });
1856
2019
  },
1857
- [multiple]
2020
+ [multiple, sections, block.id, block.type, onInteraction]
1858
2021
  );
1859
- const containerStyle3 = {
2022
+ const containerStyle11 = {
1860
2023
  fontFamily: "var(--glyph-font-body, system-ui, sans-serif)",
1861
2024
  color: "var(--glyph-text, #1a2035)",
1862
2025
  border: "1px solid var(--glyph-border, #d0d8e4)",
@@ -1890,7 +2053,7 @@ function Accordion({ data, block }) {
1890
2053
  ref: containerRef,
1891
2054
  role: "region",
1892
2055
  "aria-label": title ?? "Accordion",
1893
- style: containerStyle3,
2056
+ style: containerStyle11,
1894
2057
  children: [
1895
2058
  title && /* @__PURE__ */ jsx(
1896
2059
  "div",
@@ -1909,7 +2072,7 @@ function Accordion({ data, block }) {
1909
2072
  "details",
1910
2073
  {
1911
2074
  open: defaultOpen.includes(i),
1912
- onToggle: handleToggle,
2075
+ onToggle: (e) => handleToggle(e, i),
1913
2076
  style: sectionStyle(i === sections.length - 1),
1914
2077
  children: [
1915
2078
  /* @__PURE__ */ jsxs("summary", { style: summaryStyle, children: [
@@ -1978,17 +2141,18 @@ function renderValue(value) {
1978
2141
  function Comparison({
1979
2142
  data,
1980
2143
  block,
1981
- container
2144
+ container,
2145
+ onInteraction
1982
2146
  }) {
1983
2147
  const { title, options, features } = data;
1984
2148
  const baseId = `glyph-comparison-${block.id}`;
1985
2149
  const isCompact = container.tier === "compact";
1986
2150
  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 = {
2151
+ const containerStyle11 = {
1988
2152
  fontFamily: "var(--glyph-font-body, system-ui, sans-serif)",
1989
2153
  color: "var(--glyph-text, #1a2035)"
1990
2154
  };
1991
- const tableStyle = {
2155
+ const tableStyle2 = {
1992
2156
  width: "100%",
1993
2157
  borderCollapse: "collapse",
1994
2158
  border: "1px solid var(--glyph-table-border, #d0d8e4)",
@@ -1996,7 +2160,7 @@ function Comparison({
1996
2160
  overflow: "hidden",
1997
2161
  fontSize: isCompact ? "0.8125rem" : "0.875rem"
1998
2162
  };
1999
- const thStyle = {
2163
+ const thStyle2 = {
2000
2164
  padding: cellPadding,
2001
2165
  textAlign: "center",
2002
2166
  fontWeight: 600,
@@ -2005,7 +2169,7 @@ function Comparison({
2005
2169
  color: "var(--glyph-heading, #0a0e1a)"
2006
2170
  };
2007
2171
  const featureThStyle = {
2008
- ...thStyle,
2172
+ ...thStyle2,
2009
2173
  textAlign: "left"
2010
2174
  };
2011
2175
  const rowThStyle = {
@@ -2015,13 +2179,13 @@ function Comparison({
2015
2179
  borderBottom: "1px solid var(--glyph-table-border, #d0d8e4)",
2016
2180
  fontSize: "0.8125rem"
2017
2181
  };
2018
- const cellStyle = (rowIndex) => ({
2182
+ const cellStyle2 = (rowIndex) => ({
2019
2183
  padding: cellPadding,
2020
2184
  textAlign: "center",
2021
2185
  borderBottom: "1px solid var(--glyph-table-border, #d0d8e4)",
2022
2186
  background: rowIndex % 2 === 1 ? "var(--glyph-table-row-alt-bg, transparent)" : "transparent"
2023
2187
  });
2024
- return /* @__PURE__ */ jsxs("div", { id: baseId, role: "region", "aria-label": title ?? "Comparison", style: containerStyle3, children: [
2188
+ return /* @__PURE__ */ jsxs("div", { id: baseId, role: "region", "aria-label": title ?? "Comparison", style: containerStyle11, children: [
2025
2189
  title && /* @__PURE__ */ jsx(
2026
2190
  "div",
2027
2191
  {
@@ -2034,23 +2198,46 @@ function Comparison({
2034
2198
  children: title
2035
2199
  }
2036
2200
  ),
2037
- /* @__PURE__ */ jsx("div", { style: { overflowX: "auto" }, children: /* @__PURE__ */ jsxs("table", { role: "grid", style: tableStyle, children: [
2201
+ /* @__PURE__ */ jsx("div", { style: { overflowX: "auto" }, children: /* @__PURE__ */ jsxs("table", { role: "grid", style: tableStyle2, children: [
2038
2202
  /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
2039
2203
  /* @__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))
2204
+ options.map((option, i) => /* @__PURE__ */ jsxs(
2205
+ "th",
2206
+ {
2207
+ style: {
2208
+ ...thStyle2,
2209
+ ...onInteraction ? { cursor: "pointer" } : {}
2210
+ },
2211
+ scope: "col",
2212
+ onClick: onInteraction ? () => {
2213
+ onInteraction({
2214
+ kind: "comparison-select",
2215
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2216
+ blockId: block.id,
2217
+ blockType: block.type,
2218
+ payload: {
2219
+ optionIndex: i,
2220
+ optionName: option.name
2221
+ }
2222
+ });
2223
+ } : void 0,
2224
+ children: [
2225
+ /* @__PURE__ */ jsx("div", { children: option.name }),
2226
+ option.description && /* @__PURE__ */ jsx(
2227
+ "div",
2228
+ {
2229
+ style: {
2230
+ fontWeight: 400,
2231
+ fontSize: "0.75rem",
2232
+ color: "var(--glyph-text-muted, #6b7a94)"
2233
+ },
2234
+ children: option.description
2235
+ }
2236
+ )
2237
+ ]
2238
+ },
2239
+ i
2240
+ ))
2054
2241
  ] }) }),
2055
2242
  /* @__PURE__ */ jsx("tbody", { children: features.map((feature, rowIndex) => /* @__PURE__ */ jsxs("tr", { children: [
2056
2243
  /* @__PURE__ */ jsx(
@@ -2066,7 +2253,7 @@ function Comparison({
2066
2253
  ),
2067
2254
  options.map((_, colIndex) => {
2068
2255
  const value = feature.values[colIndex] ?? "";
2069
- return /* @__PURE__ */ jsx("td", { style: cellStyle(rowIndex), children: value ? renderValue(value) : null }, colIndex);
2256
+ return /* @__PURE__ */ jsx("td", { style: cellStyle2(rowIndex), children: value ? renderValue(value) : null }, colIndex);
2070
2257
  })
2071
2258
  ] }, rowIndex)) })
2072
2259
  ] }) })
@@ -2144,7 +2331,7 @@ function CodeDiff({ data, block }) {
2144
2331
  const baseId = `glyph-codediff-${block.id}`;
2145
2332
  const diffLines = useMemo(() => computeDiff(before, after), [before, after]);
2146
2333
  const summary = useMemo(() => summarizeDiff(diffLines), [diffLines]);
2147
- const containerStyle3 = {
2334
+ const containerStyle11 = {
2148
2335
  fontFamily: 'var(--glyph-font-mono, ui-monospace, "Cascadia Code", "Fira Code", monospace)',
2149
2336
  fontSize: "0.8125rem",
2150
2337
  lineHeight: 1.5,
@@ -2163,7 +2350,7 @@ function CodeDiff({ data, block }) {
2163
2350
  fontWeight: 600,
2164
2351
  color: "var(--glyph-text-muted, #6b7a94)"
2165
2352
  };
2166
- const tableStyle = {
2353
+ const tableStyle2 = {
2167
2354
  width: "100%",
2168
2355
  borderCollapse: "collapse",
2169
2356
  tableLayout: "fixed"
@@ -2202,13 +2389,13 @@ function CodeDiff({ data, block }) {
2202
2389
  if (kind === "del") return "var(--glyph-codediff-del-color, inherit)";
2203
2390
  return void 0;
2204
2391
  }
2205
- return /* @__PURE__ */ jsxs("div", { id: baseId, role: "region", "aria-label": summary, style: containerStyle3, children: [
2392
+ return /* @__PURE__ */ jsxs("div", { id: baseId, role: "region", "aria-label": summary, style: containerStyle11, children: [
2206
2393
  (beforeLabel || afterLabel) && /* @__PURE__ */ jsxs("div", { style: labelBarStyle, children: [
2207
2394
  beforeLabel && /* @__PURE__ */ jsx("span", { children: beforeLabel }),
2208
2395
  beforeLabel && afterLabel && /* @__PURE__ */ jsx("span", { children: "\u2192" }),
2209
2396
  afterLabel && /* @__PURE__ */ jsx("span", { children: afterLabel })
2210
2397
  ] }),
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(
2398
+ /* @__PURE__ */ jsx("div", { style: { overflowX: "auto" }, children: /* @__PURE__ */ jsx("table", { role: "grid", style: tableStyle2, children: /* @__PURE__ */ jsx("tbody", { children: diffLines.map((line6, i) => /* @__PURE__ */ jsxs(
2212
2399
  "tr",
2213
2400
  {
2214
2401
  "aria-label": ARIA_LABELS[line6.kind],
@@ -2474,15 +2661,25 @@ function TreeItem({
2474
2661
  setSize,
2475
2662
  posInSet,
2476
2663
  onFocusChange,
2477
- flatItems
2664
+ flatItems,
2665
+ parentPath,
2666
+ onSelect
2478
2667
  }) {
2479
2668
  const [expanded, setExpanded] = useState(defaultExpanded);
2480
2669
  const itemRef = useRef(null);
2481
2670
  const isDir = Array.isArray(node.children) && node.children.length > 0;
2482
2671
  const isFocused = flatIndex === focusedIndex;
2672
+ const path = parentPath ? `${parentPath}/${node.name}` : node.name;
2483
2673
  const handleToggle = useCallback(() => {
2484
- if (isDir) setExpanded((prev) => !prev);
2485
- }, [isDir]);
2674
+ if (isDir) {
2675
+ setExpanded((prev) => {
2676
+ onSelect?.(path, "directory", !prev);
2677
+ return !prev;
2678
+ });
2679
+ } else {
2680
+ onSelect?.(path, "file");
2681
+ }
2682
+ }, [isDir, path, onSelect]);
2486
2683
  const handleKeyDown = useCallback(
2487
2684
  (e) => {
2488
2685
  let handled = true;
@@ -2614,7 +2811,9 @@ function TreeItem({
2614
2811
  setSize: node.children?.length ?? 0,
2615
2812
  posInSet: childIdx + 1,
2616
2813
  onFocusChange,
2617
- flatItems
2814
+ flatItems,
2815
+ parentPath: path,
2816
+ onSelect
2618
2817
  },
2619
2818
  child.name
2620
2819
  );
@@ -2647,7 +2846,11 @@ function getChildFlatIndex(flatItems, parentFlatIndex, childIdx, children) {
2647
2846
  }
2648
2847
  return parentFlatIndex + childIdx + 1;
2649
2848
  }
2650
- function FileTree({ data }) {
2849
+ function FileTree({
2850
+ data,
2851
+ block,
2852
+ onInteraction
2853
+ }) {
2651
2854
  const [focusedIndex, setFocusedIndex] = useState(0);
2652
2855
  const containerRef = useRef(null);
2653
2856
  const flatItems = flattenTree(data.tree, data.root ? 1 : 0);
@@ -2658,6 +2861,18 @@ function FileTree({ data }) {
2658
2861
  const el = container.querySelector(`[data-flat-index="${String(index)}"]`);
2659
2862
  el?.focus();
2660
2863
  }, []);
2864
+ const handleSelect = useCallback(
2865
+ (path, type, expanded) => {
2866
+ onInteraction?.({
2867
+ kind: "filetree-select",
2868
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2869
+ blockId: block.id,
2870
+ blockType: block.type,
2871
+ payload: { path, type, expanded }
2872
+ });
2873
+ },
2874
+ [onInteraction, block.id, block.type]
2875
+ );
2661
2876
  return /* @__PURE__ */ jsx(
2662
2877
  "div",
2663
2878
  {
@@ -2714,7 +2929,9 @@ function FileTree({ data }) {
2714
2929
  setSize: data.tree.length,
2715
2930
  posInSet: idx + 1,
2716
2931
  onFocusChange: handleFocusChange,
2717
- flatItems
2932
+ flatItems,
2933
+ parentPath: data.root ?? "",
2934
+ onSelect: handleSelect
2718
2935
  },
2719
2936
  node.name
2720
2937
  );
@@ -2735,7 +2952,9 @@ function FileTree({ data }) {
2735
2952
  setSize: data.tree.length,
2736
2953
  posInSet: idx + 1,
2737
2954
  onFocusChange: handleFocusChange,
2738
- flatItems
2955
+ flatItems,
2956
+ parentPath: "",
2957
+ onSelect: handleSelect
2739
2958
  },
2740
2959
  node.name
2741
2960
  );
@@ -3983,7 +4202,115 @@ function isCorrect(question, selected) {
3983
4202
  }
3984
4203
  }
3985
4204
  }
3986
- function Quiz({ data, block }) {
4205
+ function renderMultipleChoice(question, qIndex, state, updateState, baseId) {
4206
+ const selected = typeof state.selected === "number" ? state.selected : null;
4207
+ return /* @__PURE__ */ jsx("div", { role: "radiogroup", "aria-label": question.question, children: question.options.map((option, oIndex) => {
4208
+ const isSelected = selected === oIndex;
4209
+ const isCorrectOption = state.submitted && oIndex === question.answer;
4210
+ const isIncorrectSelection = state.submitted && isSelected && oIndex !== question.answer;
4211
+ return /* @__PURE__ */ jsxs(
4212
+ "label",
4213
+ {
4214
+ style: optionLabelStyle(
4215
+ isSelected,
4216
+ state.submitted,
4217
+ isCorrectOption,
4218
+ isIncorrectSelection
4219
+ ),
4220
+ children: [
4221
+ /* @__PURE__ */ jsx(
4222
+ "input",
4223
+ {
4224
+ type: "radio",
4225
+ role: "radio",
4226
+ name: `${baseId}-q${String(qIndex)}`,
4227
+ checked: isSelected,
4228
+ disabled: state.submitted,
4229
+ onChange: () => updateState(qIndex, { selected: oIndex }),
4230
+ "aria-checked": isSelected
4231
+ }
4232
+ ),
4233
+ option
4234
+ ]
4235
+ },
4236
+ oIndex
4237
+ );
4238
+ }) });
4239
+ }
4240
+ function renderTrueFalse(question, qIndex, state, updateState, baseId) {
4241
+ const selected = typeof state.selected === "boolean" ? state.selected : null;
4242
+ return /* @__PURE__ */ jsx("div", { role: "radiogroup", "aria-label": question.question, children: [true, false].map((value) => {
4243
+ const isSelected = selected === value;
4244
+ const isCorrectOption = state.submitted && value === question.answer;
4245
+ const isIncorrectSelection = state.submitted && isSelected && value !== question.answer;
4246
+ return /* @__PURE__ */ jsxs(
4247
+ "label",
4248
+ {
4249
+ style: optionLabelStyle(
4250
+ isSelected,
4251
+ state.submitted,
4252
+ isCorrectOption,
4253
+ isIncorrectSelection
4254
+ ),
4255
+ children: [
4256
+ /* @__PURE__ */ jsx(
4257
+ "input",
4258
+ {
4259
+ type: "radio",
4260
+ role: "radio",
4261
+ name: `${baseId}-q${String(qIndex)}`,
4262
+ checked: isSelected,
4263
+ disabled: state.submitted,
4264
+ onChange: () => updateState(qIndex, { selected: value }),
4265
+ "aria-checked": isSelected
4266
+ }
4267
+ ),
4268
+ value ? "True" : "False"
4269
+ ]
4270
+ },
4271
+ String(value)
4272
+ );
4273
+ }) });
4274
+ }
4275
+ function renderMultiSelect(question, qIndex, state, updateState) {
4276
+ const selected = Array.isArray(state.selected) ? state.selected : [];
4277
+ const toggleOption = (oIndex) => {
4278
+ const next = selected.includes(oIndex) ? selected.filter((v) => v !== oIndex) : [...selected, oIndex];
4279
+ updateState(qIndex, { selected: next });
4280
+ };
4281
+ return /* @__PURE__ */ jsx("div", { children: question.options.map((option, oIndex) => {
4282
+ const isSelected = selected.includes(oIndex);
4283
+ const isCorrectOption = state.submitted && question.answer.includes(oIndex);
4284
+ const isIncorrectSelection = state.submitted && isSelected && !question.answer.includes(oIndex);
4285
+ return /* @__PURE__ */ jsxs(
4286
+ "label",
4287
+ {
4288
+ style: optionLabelStyle(
4289
+ isSelected,
4290
+ state.submitted,
4291
+ isCorrectOption,
4292
+ isIncorrectSelection
4293
+ ),
4294
+ children: [
4295
+ /* @__PURE__ */ jsx(
4296
+ "input",
4297
+ {
4298
+ type: "checkbox",
4299
+ role: "checkbox",
4300
+ checked: isSelected,
4301
+ disabled: state.submitted,
4302
+ onChange: () => toggleOption(oIndex),
4303
+ "aria-checked": isSelected
4304
+ }
4305
+ ),
4306
+ option
4307
+ ]
4308
+ },
4309
+ oIndex
4310
+ );
4311
+ }) });
4312
+ }
4313
+ function Quiz({ data, block, onInteraction }) {
3987
4314
  const { questions, showScore = true, title } = data;
3988
4315
  const baseId = `glyph-quiz-${block.id}`;
3989
4316
  const [states, setStates] = useState(
@@ -4001,114 +4328,6 @@ function Quiz({ data, block }) {
4001
4328
  return acc + (isCorrect(q, s.selected) ? 1 : 0);
4002
4329
  }, 0);
4003
4330
  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
4331
  function renderQuestion(question, qIndex) {
4113
4332
  const state = states[qIndex] ?? { selected: null, submitted: false };
4114
4333
  const isLast = qIndex === questions.length - 1;
@@ -4125,16 +4344,52 @@ function Quiz({ data, block }) {
4125
4344
  questions.length > 1 ? `${String(qIndex + 1)}. ` : "",
4126
4345
  question.question
4127
4346
  ] }),
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),
4347
+ question.type === "multiple-choice" && renderMultipleChoice(question, qIndex, state, updateState, baseId),
4348
+ question.type === "true-false" && renderTrueFalse(question, qIndex, state, updateState, baseId),
4349
+ question.type === "multi-select" && renderMultiSelect(question, qIndex, state, updateState),
4131
4350
  !state.submitted && /* @__PURE__ */ jsx(
4132
4351
  "button",
4133
4352
  {
4134
4353
  type: "button",
4135
4354
  disabled: !hasSelection,
4136
4355
  style: buttonStyle(!hasSelection),
4137
- onClick: () => updateState(qIndex, { submitted: true }),
4356
+ onClick: () => {
4357
+ updateState(qIndex, { submitted: true });
4358
+ if (onInteraction) {
4359
+ const correct2 = isCorrect(question, state.selected);
4360
+ const newScore = states.reduce((acc, s, i) => {
4361
+ if (i === qIndex) return acc + (correct2 ? 1 : 0);
4362
+ const q = questions[i];
4363
+ if (!q || !s.submitted) return acc;
4364
+ return acc + (isCorrect(q, s.selected) ? 1 : 0);
4365
+ }, 0);
4366
+ let selected;
4367
+ switch (question.type) {
4368
+ case "multiple-choice":
4369
+ selected = typeof state.selected === "number" ? [question.options[state.selected] ?? String(state.selected)] : [];
4370
+ break;
4371
+ case "true-false":
4372
+ selected = typeof state.selected === "boolean" ? [state.selected ? "True" : "False"] : [];
4373
+ break;
4374
+ case "multi-select":
4375
+ selected = Array.isArray(state.selected) ? state.selected.map((idx) => question.options[idx] ?? String(idx)) : [];
4376
+ break;
4377
+ }
4378
+ onInteraction({
4379
+ kind: "quiz-submit",
4380
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
4381
+ blockId: block.id,
4382
+ blockType: block.type,
4383
+ payload: {
4384
+ questionIndex: qIndex,
4385
+ question: question.question,
4386
+ selected,
4387
+ correct: correct2,
4388
+ score: { correct: newScore, total: questions.length }
4389
+ }
4390
+ });
4391
+ }
4392
+ },
4138
4393
  children: "Submit"
4139
4394
  }
4140
4395
  ),
@@ -4197,7 +4452,7 @@ function Card({ data, block, container }) {
4197
4452
  default:
4198
4453
  colCount = authorCols;
4199
4454
  }
4200
- const containerStyle3 = {
4455
+ const containerStyle11 = {
4201
4456
  fontFamily: "var(--glyph-font-body, system-ui, sans-serif)",
4202
4457
  color: "var(--glyph-text, #1a2035)"
4203
4458
  };
@@ -4242,7 +4497,7 @@ function Card({ data, block, container }) {
4242
4497
  color: "var(--glyph-text-muted, #6b7a94)",
4243
4498
  marginTop: "var(--glyph-spacing-xs, 0.25rem)"
4244
4499
  };
4245
- const bodyStyle2 = {
4500
+ const bodyStyle3 = {
4246
4501
  fontSize: "0.875rem",
4247
4502
  lineHeight: 1.6,
4248
4503
  marginTop: "var(--glyph-spacing-sm, 0.5rem)",
@@ -4260,7 +4515,7 @@ function Card({ data, block, container }) {
4260
4515
  color: "var(--glyph-link, #0a9d7c)",
4261
4516
  textDecoration: "none"
4262
4517
  };
4263
- return /* @__PURE__ */ jsxs("div", { id: baseId, role: "region", "aria-label": title ?? "Cards", style: containerStyle3, children: [
4518
+ return /* @__PURE__ */ jsxs("div", { id: baseId, role: "region", "aria-label": title ?? "Cards", style: containerStyle11, children: [
4264
4519
  title && /* @__PURE__ */ jsx(
4265
4520
  "div",
4266
4521
  {
@@ -4279,7 +4534,7 @@ function Card({ data, block, container }) {
4279
4534
  card.icon && /* @__PURE__ */ jsx("div", { style: iconStyle, children: card.icon }),
4280
4535
  /* @__PURE__ */ jsx("h3", { style: titleStyle2, children: card.title }),
4281
4536
  card.subtitle && /* @__PURE__ */ jsx("div", { style: subtitleStyle, children: card.subtitle }),
4282
- card.body && /* @__PURE__ */ jsx("div", { style: bodyStyle2, children: card.body }),
4537
+ card.body && /* @__PURE__ */ jsx("div", { style: bodyStyle3, children: card.body }),
4283
4538
  card.actions && card.actions.length > 0 && /* @__PURE__ */ jsx("div", { style: actionsStyle, children: card.actions.map((action, j) => /* @__PURE__ */ jsx(
4284
4539
  "a",
4285
4540
  {
@@ -4369,7 +4624,7 @@ function renderStatGroup(items, keyPrefix) {
4369
4624
  color: "var(--glyph-infographic-value-color, #1d4ed8)",
4370
4625
  lineHeight: 1.2
4371
4626
  };
4372
- const labelStyle3 = {
4627
+ const labelStyle4 = {
4373
4628
  fontSize: "0.8125rem",
4374
4629
  color: "var(--glyph-infographic-label-color, #475569)",
4375
4630
  marginTop: "var(--glyph-spacing-xs, 0.25rem)",
@@ -4385,7 +4640,7 @@ function renderStatGroup(items, keyPrefix) {
4385
4640
  };
4386
4641
  return /* @__PURE__ */ jsx("div", { style: rowStyle, "data-group": "stat", children: items.map((item, i) => /* @__PURE__ */ jsxs("div", { style: statStyle, children: [
4387
4642
  /* @__PURE__ */ jsx("div", { style: valueStyle, children: item.value }),
4388
- /* @__PURE__ */ jsx("div", { style: labelStyle3, children: item.label }),
4643
+ /* @__PURE__ */ jsx("div", { style: labelStyle4, children: item.label }),
4389
4644
  item.description && /* @__PURE__ */ jsx("div", { style: descStyle, children: item.description })
4390
4645
  ] }, `${keyPrefix}-${String(i)}`)) }, keyPrefix);
4391
4646
  }
@@ -4456,18 +4711,18 @@ function renderProgressGroup(items, keyPrefix, colorOffset) {
4456
4711
  }) }, keyPrefix);
4457
4712
  }
4458
4713
  function renderFactGroup(items, keyPrefix) {
4459
- const listStyle = {
4714
+ const listStyle2 = {
4460
4715
  listStyle: "none",
4461
4716
  margin: 0,
4462
4717
  padding: 0
4463
4718
  };
4464
- const itemStyle2 = {
4719
+ const itemStyle4 = {
4465
4720
  padding: "var(--glyph-spacing-xs, 0.25rem) 0",
4466
4721
  fontSize: "0.875rem",
4467
4722
  color: "var(--glyph-text, #1a2035)",
4468
4723
  fontWeight: 500
4469
4724
  };
4470
- return /* @__PURE__ */ jsx("ul", { style: listStyle, "data-group": "fact", children: items.map((item, i) => /* @__PURE__ */ jsxs("li", { style: itemStyle2, children: [
4725
+ return /* @__PURE__ */ jsx("ul", { style: listStyle2, "data-group": "fact", children: items.map((item, i) => /* @__PURE__ */ jsxs("li", { style: itemStyle4, children: [
4471
4726
  item.icon && /* @__PURE__ */ jsx(
4472
4727
  "span",
4473
4728
  {
@@ -4744,7 +4999,7 @@ function Infographic({
4744
4999
  const baseId = `glyph-infographic-${block.id}`;
4745
5000
  const useGrid = sections.length >= 2 && container.tier !== "compact";
4746
5001
  const sectionLayouts = useMemo(() => computeSectionLayout(sections), [sections]);
4747
- const containerStyle3 = {
5002
+ const containerStyle11 = {
4748
5003
  fontFamily: "var(--glyph-font-body, system-ui, sans-serif)",
4749
5004
  color: "var(--glyph-text, #1a2035)",
4750
5005
  background: "var(--glyph-surface, #e8ecf3)",
@@ -4789,7 +5044,7 @@ function Infographic({
4789
5044
  };
4790
5045
  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
5046
  let progressColorOffset = 0;
4792
- return /* @__PURE__ */ jsxs("div", { id: baseId, role: "region", "aria-label": title ?? "Infographic", style: containerStyle3, children: [
5047
+ return /* @__PURE__ */ jsxs("div", { id: baseId, role: "region", "aria-label": title ?? "Infographic", style: containerStyle11, children: [
4793
5048
  printCss && /* @__PURE__ */ jsx("style", { children: printCss }),
4794
5049
  /* @__PURE__ */ jsxs("div", { "data-layout": useGrid ? "grid" : "stack", style: useGrid ? sectionsGridStyle : void 0, children: [
4795
5050
  title && /* @__PURE__ */ jsx("div", { style: titleStyle2, children: title }),
@@ -4850,6 +5105,1731 @@ var infographicDefinition = {
4850
5105
  render: Infographic
4851
5106
  };
4852
5107
 
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 };
5108
+ // src/poll/styles.ts
5109
+ var containerStyle3 = {
5110
+ fontFamily: "var(--glyph-font-body, system-ui, sans-serif)",
5111
+ color: "var(--glyph-text, #1a2035)",
5112
+ border: "1px solid var(--glyph-border, #d0d8e4)",
5113
+ borderRadius: "var(--glyph-radius-md, 0.5rem)",
5114
+ overflow: "hidden"
5115
+ };
5116
+ var headerStyle2 = {
5117
+ fontWeight: 700,
5118
+ fontSize: "1.125rem",
5119
+ padding: "var(--glyph-spacing-md, 1rem)",
5120
+ borderBottom: "1px solid var(--glyph-border, #d0d8e4)",
5121
+ color: "var(--glyph-heading, #0a0e1a)"
5122
+ };
5123
+ var questionStyle = {
5124
+ fontWeight: 600,
5125
+ fontSize: "0.9375rem",
5126
+ padding: "var(--glyph-spacing-md, 1rem)",
5127
+ paddingBottom: "0.5rem"
5128
+ };
5129
+ var optionsStyle = {
5130
+ padding: "0 var(--glyph-spacing-md, 1rem)",
5131
+ paddingBottom: "var(--glyph-spacing-md, 1rem)"
5132
+ };
5133
+ function optionLabelStyle2(selected) {
5134
+ return {
5135
+ display: "flex",
5136
+ alignItems: "center",
5137
+ gap: "0.5rem",
5138
+ padding: "0.5rem 0.75rem",
5139
+ marginBottom: "0.375rem",
5140
+ borderRadius: "var(--glyph-radius-md, 0.5rem)",
5141
+ cursor: "pointer",
5142
+ background: selected ? "var(--glyph-surface, #e8ecf3)" : "transparent",
5143
+ border: "1px solid",
5144
+ borderColor: selected ? "var(--glyph-border, #d0d8e4)" : "transparent",
5145
+ fontSize: "0.875rem",
5146
+ lineHeight: 1.6
5147
+ };
5148
+ }
5149
+ var voteButtonStyle = {
5150
+ margin: "0 var(--glyph-spacing-md, 1rem) var(--glyph-spacing-md, 1rem)",
5151
+ padding: "0.5rem 1.25rem",
5152
+ borderRadius: "var(--glyph-radius-md, 0.5rem)",
5153
+ border: "1px solid var(--glyph-border, #d0d8e4)",
5154
+ background: "var(--glyph-surface, #e8ecf3)",
5155
+ color: "var(--glyph-text, #1a2035)",
5156
+ cursor: "pointer",
5157
+ fontWeight: 600,
5158
+ fontSize: "0.875rem"
5159
+ };
5160
+ var resultsStyle = {
5161
+ padding: "var(--glyph-spacing-md, 1rem)",
5162
+ borderTop: "1px solid var(--glyph-border, #d0d8e4)"
5163
+ };
5164
+ var resultRowStyle = {
5165
+ marginBottom: "0.5rem"
5166
+ };
5167
+ var resultLabelStyle = {
5168
+ display: "flex",
5169
+ justifyContent: "space-between",
5170
+ fontSize: "0.8125rem",
5171
+ marginBottom: "0.25rem"
5172
+ };
5173
+ var barTrackStyle = {
5174
+ height: "0.5rem",
5175
+ borderRadius: "0.25rem",
5176
+ background: "var(--glyph-poll-bar-bg, var(--glyph-surface, #e8ecf3))",
5177
+ overflow: "hidden"
5178
+ };
5179
+ function barFillStyle(percentage) {
5180
+ return {
5181
+ height: "100%",
5182
+ width: `${String(percentage)}%`,
5183
+ borderRadius: "0.25rem",
5184
+ background: "var(--glyph-poll-bar-fill, var(--glyph-accent, #0a9d7c))",
5185
+ transition: "width 0.3s ease"
5186
+ };
5187
+ }
5188
+ function Poll({ data, block, onInteraction }) {
5189
+ const { question, options, multiple = false, showResults = true, title } = data;
5190
+ const baseId = `glyph-poll-${block.id}`;
5191
+ const [selected, setSelected] = useState([]);
5192
+ const [votes, setVotes] = useState(() => options.map(() => 0));
5193
+ const [hasVoted, setHasVoted] = useState(false);
5194
+ const toggleOption = (index) => {
5195
+ if (hasVoted) return;
5196
+ if (multiple) {
5197
+ setSelected(
5198
+ (prev) => prev.includes(index) ? prev.filter((i) => i !== index) : [...prev, index]
5199
+ );
5200
+ } else {
5201
+ setSelected([index]);
5202
+ }
5203
+ };
5204
+ const handleVote = () => {
5205
+ if (selected.length === 0 || hasVoted) return;
5206
+ const newVotes = [...votes];
5207
+ for (const idx of selected) {
5208
+ newVotes[idx] = (newVotes[idx] ?? 0) + 1;
5209
+ }
5210
+ setVotes(newVotes);
5211
+ setHasVoted(true);
5212
+ if (onInteraction) {
5213
+ onInteraction({
5214
+ kind: "poll-vote",
5215
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
5216
+ blockId: block.id,
5217
+ blockType: block.type,
5218
+ payload: {
5219
+ selectedOptions: selected.map((i) => options[i] ?? String(i)),
5220
+ selectedIndices: [...selected]
5221
+ }
5222
+ });
5223
+ }
5224
+ };
5225
+ const totalVotes = votes.reduce((a, b) => a + b, 0);
5226
+ return /* @__PURE__ */ jsxs("div", { id: baseId, role: "region", "aria-label": title ?? "Poll", style: containerStyle3, children: [
5227
+ title && /* @__PURE__ */ jsx("div", { style: headerStyle2, children: title }),
5228
+ /* @__PURE__ */ jsx("div", { style: questionStyle, children: question }),
5229
+ /* @__PURE__ */ jsx("div", { role: "group", "aria-label": question, style: optionsStyle, children: options.map((option, index) => /* @__PURE__ */ jsxs("label", { style: optionLabelStyle2(selected.includes(index)), children: [
5230
+ /* @__PURE__ */ jsx(
5231
+ "input",
5232
+ {
5233
+ type: multiple ? "checkbox" : "radio",
5234
+ name: `${baseId}-option`,
5235
+ checked: selected.includes(index),
5236
+ disabled: hasVoted,
5237
+ onChange: () => toggleOption(index),
5238
+ "aria-checked": selected.includes(index)
5239
+ }
5240
+ ),
5241
+ option
5242
+ ] }, index)) }),
5243
+ !hasVoted && /* @__PURE__ */ jsx(
5244
+ "button",
5245
+ {
5246
+ type: "button",
5247
+ disabled: selected.length === 0,
5248
+ style: {
5249
+ ...voteButtonStyle,
5250
+ opacity: selected.length === 0 ? 0.5 : 1,
5251
+ cursor: selected.length === 0 ? "not-allowed" : "pointer"
5252
+ },
5253
+ onClick: handleVote,
5254
+ children: "Vote"
5255
+ }
5256
+ ),
5257
+ showResults && hasVoted && /* @__PURE__ */ jsx("div", { role: "status", "aria-live": "polite", style: resultsStyle, children: options.map((option, index) => {
5258
+ const count = votes[index] ?? 0;
5259
+ const percentage = totalVotes > 0 ? count / totalVotes * 100 : 0;
5260
+ return /* @__PURE__ */ jsxs("div", { style: resultRowStyle, children: [
5261
+ /* @__PURE__ */ jsxs("div", { style: resultLabelStyle, children: [
5262
+ /* @__PURE__ */ jsx("span", { children: option }),
5263
+ /* @__PURE__ */ jsxs("span", { children: [
5264
+ String(count),
5265
+ " vote",
5266
+ count !== 1 ? "s" : "",
5267
+ " (",
5268
+ String(Math.round(percentage)),
5269
+ "%)"
5270
+ ] })
5271
+ ] }),
5272
+ /* @__PURE__ */ jsx(
5273
+ "div",
5274
+ {
5275
+ style: barTrackStyle,
5276
+ role: "progressbar",
5277
+ "aria-valuenow": percentage,
5278
+ "aria-valuemin": 0,
5279
+ "aria-valuemax": 100,
5280
+ "aria-label": `${option}: ${String(Math.round(percentage))}%`,
5281
+ children: /* @__PURE__ */ jsx("div", { style: barFillStyle(percentage) })
5282
+ }
5283
+ )
5284
+ ] }, index);
5285
+ }) })
5286
+ ] });
5287
+ }
5288
+
5289
+ // src/poll/index.ts
5290
+ var pollDefinition = {
5291
+ type: "ui:poll",
5292
+ schema: pollSchema,
5293
+ render: Poll
5294
+ };
5295
+
5296
+ // src/rating/styles.ts
5297
+ var containerStyle4 = {
5298
+ fontFamily: "var(--glyph-font-body, system-ui, sans-serif)",
5299
+ color: "var(--glyph-text, #1a2035)",
5300
+ border: "1px solid var(--glyph-border, #d0d8e4)",
5301
+ borderRadius: "var(--glyph-radius-md, 0.5rem)",
5302
+ overflow: "hidden"
5303
+ };
5304
+ var headerStyle3 = {
5305
+ fontWeight: 700,
5306
+ fontSize: "1.125rem",
5307
+ padding: "var(--glyph-spacing-md, 1rem)",
5308
+ borderBottom: "1px solid var(--glyph-border, #d0d8e4)",
5309
+ color: "var(--glyph-heading, #0a0e1a)"
5310
+ };
5311
+ function itemStyle2(isLast) {
5312
+ return {
5313
+ padding: "var(--glyph-spacing-md, 1rem)",
5314
+ borderBottom: isLast ? "none" : "1px solid var(--glyph-border, #d0d8e4)"
5315
+ };
5316
+ }
5317
+ var itemLabelStyle = {
5318
+ fontWeight: 600,
5319
+ fontSize: "0.9375rem",
5320
+ marginBottom: "0.25rem"
5321
+ };
5322
+ var itemDescriptionStyle = {
5323
+ fontSize: "0.8125rem",
5324
+ color: "var(--glyph-text-muted, #6b7a94)",
5325
+ marginBottom: "0.5rem"
5326
+ };
5327
+ var starsContainerStyle = {
5328
+ display: "flex",
5329
+ gap: "0.25rem",
5330
+ alignItems: "center"
5331
+ };
5332
+ function starButtonStyle(filled, hovered) {
5333
+ return {
5334
+ background: "none",
5335
+ border: "none",
5336
+ padding: "0.125rem",
5337
+ cursor: "pointer",
5338
+ fontSize: "1.25rem",
5339
+ lineHeight: 1,
5340
+ color: filled || hovered ? "var(--glyph-rating-star-fill, #f59e0b)" : "var(--glyph-rating-star-empty, var(--glyph-border, #d0d8e4))",
5341
+ transition: "color 0.15s ease"
5342
+ };
5343
+ }
5344
+ function numberButtonStyle(selected) {
5345
+ return {
5346
+ minWidth: "2rem",
5347
+ height: "2rem",
5348
+ display: "flex",
5349
+ alignItems: "center",
5350
+ justifyContent: "center",
5351
+ borderRadius: "var(--glyph-radius-sm, 0.375rem)",
5352
+ border: "1px solid var(--glyph-border, #d0d8e4)",
5353
+ background: selected ? "var(--glyph-accent, #0a9d7c)" : "transparent",
5354
+ color: selected ? "#fff" : "var(--glyph-text, #1a2035)",
5355
+ cursor: "pointer",
5356
+ fontWeight: 600,
5357
+ fontSize: "0.875rem",
5358
+ transition: "background 0.15s ease, color 0.15s ease"
5359
+ };
5360
+ }
5361
+ var scaleLabelsStyle = {
5362
+ display: "flex",
5363
+ justifyContent: "space-between",
5364
+ fontSize: "0.75rem",
5365
+ color: "var(--glyph-text-muted, #6b7a94)",
5366
+ marginTop: "0.375rem"
5367
+ };
5368
+ function Rating({
5369
+ data,
5370
+ block,
5371
+ onInteraction
5372
+ }) {
5373
+ const { title, scale = 5, mode = "star", labels, items } = data;
5374
+ const baseId = `glyph-rating-${block.id}`;
5375
+ const [ratings, setRatings] = useState(() => items.map(() => null));
5376
+ const [hoveredStar, setHoveredStar] = useState(null);
5377
+ const handleRate = (itemIndex, value) => {
5378
+ const newRatings = [...ratings];
5379
+ newRatings[itemIndex] = value;
5380
+ setRatings(newRatings);
5381
+ if (onInteraction) {
5382
+ onInteraction({
5383
+ kind: "rating-change",
5384
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
5385
+ blockId: block.id,
5386
+ blockType: block.type,
5387
+ payload: {
5388
+ itemIndex,
5389
+ itemLabel: items[itemIndex]?.label ?? "",
5390
+ value,
5391
+ allRatings: items.map((item, i) => ({
5392
+ label: item.label,
5393
+ value: i === itemIndex ? value : newRatings[i] ?? null
5394
+ }))
5395
+ }
5396
+ });
5397
+ }
5398
+ };
5399
+ return /* @__PURE__ */ jsxs("div", { id: baseId, role: "region", "aria-label": title ?? "Rating", style: containerStyle4, children: [
5400
+ title && /* @__PURE__ */ jsx("div", { style: headerStyle3, children: title }),
5401
+ items.map((item, itemIndex) => {
5402
+ const currentRating = ratings[itemIndex] ?? null;
5403
+ const isLast = itemIndex === items.length - 1;
5404
+ return /* @__PURE__ */ jsxs("div", { style: itemStyle2(isLast), children: [
5405
+ /* @__PURE__ */ jsx("div", { style: itemLabelStyle, children: item.label }),
5406
+ item.description && /* @__PURE__ */ jsx("div", { style: itemDescriptionStyle, children: item.description }),
5407
+ /* @__PURE__ */ jsx("div", { role: "radiogroup", "aria-label": `Rate ${item.label}`, style: starsContainerStyle, children: Array.from({ length: scale }, (_, starIndex) => {
5408
+ const value = starIndex + 1;
5409
+ const isHovered = hoveredStar !== null && hoveredStar.itemIndex === itemIndex && value <= hoveredStar.value;
5410
+ const isFilled = currentRating !== null && value <= currentRating;
5411
+ if (mode === "number") {
5412
+ return /* @__PURE__ */ jsx(
5413
+ "button",
5414
+ {
5415
+ type: "button",
5416
+ role: "radio",
5417
+ "aria-checked": currentRating === value,
5418
+ "aria-label": `${String(value)} out of ${String(scale)}`,
5419
+ style: numberButtonStyle(currentRating === value),
5420
+ onClick: () => handleRate(itemIndex, value),
5421
+ children: String(value)
5422
+ },
5423
+ starIndex
5424
+ );
5425
+ }
5426
+ return /* @__PURE__ */ jsx(
5427
+ "button",
5428
+ {
5429
+ type: "button",
5430
+ role: "radio",
5431
+ "aria-checked": currentRating === value,
5432
+ "aria-label": `${String(value)} out of ${String(scale)} stars`,
5433
+ style: starButtonStyle(isFilled, isHovered),
5434
+ onClick: () => handleRate(itemIndex, value),
5435
+ onMouseEnter: () => setHoveredStar({ itemIndex, value }),
5436
+ onMouseLeave: () => setHoveredStar(null),
5437
+ children: "\u2605"
5438
+ },
5439
+ starIndex
5440
+ );
5441
+ }) }),
5442
+ labels && /* @__PURE__ */ jsxs("div", { style: scaleLabelsStyle, children: [
5443
+ /* @__PURE__ */ jsx("span", { children: labels.low }),
5444
+ /* @__PURE__ */ jsx("span", { children: labels.high })
5445
+ ] }),
5446
+ /* @__PURE__ */ jsx(
5447
+ "div",
5448
+ {
5449
+ "aria-live": "polite",
5450
+ style: {
5451
+ position: "absolute",
5452
+ width: "1px",
5453
+ height: "1px",
5454
+ padding: 0,
5455
+ margin: "-1px",
5456
+ overflow: "hidden",
5457
+ clip: "rect(0,0,0,0)",
5458
+ whiteSpace: "nowrap",
5459
+ border: 0
5460
+ },
5461
+ children: currentRating !== null && `${item.label} rated ${String(currentRating)} out of ${String(scale)}`
5462
+ }
5463
+ )
5464
+ ] }, itemIndex);
5465
+ })
5466
+ ] });
5467
+ }
5468
+
5469
+ // src/rating/index.ts
5470
+ var ratingDefinition = {
5471
+ type: "ui:rating",
5472
+ schema: ratingSchema,
5473
+ render: Rating
5474
+ };
5475
+
5476
+ // src/ranker/styles.ts
5477
+ var containerStyle5 = {
5478
+ fontFamily: "var(--glyph-font-body, system-ui, sans-serif)",
5479
+ color: "var(--glyph-text, #1a2035)",
5480
+ border: "1px solid var(--glyph-border, #d0d8e4)",
5481
+ borderRadius: "var(--glyph-radius-md, 0.5rem)",
5482
+ overflow: "hidden"
5483
+ };
5484
+ var headerStyle4 = {
5485
+ fontWeight: 700,
5486
+ fontSize: "1.125rem",
5487
+ padding: "var(--glyph-spacing-md, 1rem)",
5488
+ borderBottom: "1px solid var(--glyph-border, #d0d8e4)",
5489
+ color: "var(--glyph-heading, #0a0e1a)"
5490
+ };
5491
+ var listStyle = {
5492
+ listStyle: "none",
5493
+ margin: 0,
5494
+ padding: 0
5495
+ };
5496
+ function itemStyle3(isDragging, isGrabbed) {
5497
+ return {
5498
+ display: "flex",
5499
+ alignItems: "center",
5500
+ gap: "0.75rem",
5501
+ padding: "0.75rem var(--glyph-spacing-md, 1rem)",
5502
+ borderBottom: "1px solid var(--glyph-border, #d0d8e4)",
5503
+ background: isDragging ? "var(--glyph-accent-subtle, #e6f6f2)" : "transparent",
5504
+ cursor: isGrabbed ? "grabbing" : "grab",
5505
+ userSelect: "none",
5506
+ transition: "background 0.15s ease",
5507
+ outline: isGrabbed ? "2px solid var(--glyph-accent, #0a9d7c)" : "none",
5508
+ outlineOffset: "-2px"
5509
+ };
5510
+ }
5511
+ var rankBadgeStyle = {
5512
+ minWidth: "1.75rem",
5513
+ height: "1.75rem",
5514
+ display: "flex",
5515
+ alignItems: "center",
5516
+ justifyContent: "center",
5517
+ borderRadius: "50%",
5518
+ background: "var(--glyph-surface, #e8ecf3)",
5519
+ fontWeight: 700,
5520
+ fontSize: "0.8125rem",
5521
+ color: "var(--glyph-text-muted, #6b7a94)",
5522
+ flexShrink: 0
5523
+ };
5524
+ var itemContentStyle = {
5525
+ flex: 1,
5526
+ minWidth: 0
5527
+ };
5528
+ var itemLabelStyle2 = {
5529
+ fontWeight: 600,
5530
+ fontSize: "0.9375rem"
5531
+ };
5532
+ var itemDescriptionStyle2 = {
5533
+ fontSize: "0.8125rem",
5534
+ color: "var(--glyph-text-muted, #6b7a94)",
5535
+ marginTop: "0.125rem"
5536
+ };
5537
+ var gripStyle = {
5538
+ color: "var(--glyph-text-muted, #6b7a94)",
5539
+ fontSize: "1rem",
5540
+ flexShrink: 0
5541
+ };
5542
+ function Ranker({
5543
+ data,
5544
+ block,
5545
+ onInteraction
5546
+ }) {
5547
+ const { title, items: initialItems } = data;
5548
+ const baseId = `glyph-ranker-${block.id}`;
5549
+ const [items, setItems] = useState(initialItems);
5550
+ const [grabbedIndex, setGrabbedIndex] = useState(null);
5551
+ const moveItem = useCallback(
5552
+ (fromIndex, toIndex) => {
5553
+ if (fromIndex === toIndex) return;
5554
+ setItems((prevItems) => {
5555
+ const newItems = [...prevItems];
5556
+ const [moved] = newItems.splice(fromIndex, 1);
5557
+ if (!moved) return prevItems;
5558
+ newItems.splice(toIndex, 0, moved);
5559
+ if (onInteraction) {
5560
+ onInteraction({
5561
+ kind: "ranker-reorder",
5562
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
5563
+ blockId: block.id,
5564
+ blockType: block.type,
5565
+ payload: {
5566
+ orderedItems: newItems.map((item, i) => ({
5567
+ id: item.id,
5568
+ label: item.label,
5569
+ rank: i + 1
5570
+ })),
5571
+ movedItem: {
5572
+ id: moved.id,
5573
+ label: moved.label,
5574
+ fromRank: fromIndex + 1,
5575
+ toRank: toIndex + 1
5576
+ }
5577
+ }
5578
+ });
5579
+ }
5580
+ return newItems;
5581
+ });
5582
+ },
5583
+ [block.id, block.type, onInteraction]
5584
+ );
5585
+ const handleKeyDown = (e, index) => {
5586
+ if (e.key === " " || e.key === "Enter") {
5587
+ e.preventDefault();
5588
+ if (grabbedIndex === null) {
5589
+ setGrabbedIndex(index);
5590
+ } else {
5591
+ setGrabbedIndex(null);
5592
+ }
5593
+ } else if (e.key === "Escape") {
5594
+ setGrabbedIndex(null);
5595
+ } else if (e.key === "ArrowUp" && grabbedIndex !== null) {
5596
+ e.preventDefault();
5597
+ if (grabbedIndex > 0) {
5598
+ moveItem(grabbedIndex, grabbedIndex - 1);
5599
+ setGrabbedIndex(grabbedIndex - 1);
5600
+ }
5601
+ } else if (e.key === "ArrowDown" && grabbedIndex !== null) {
5602
+ e.preventDefault();
5603
+ if (grabbedIndex < items.length - 1) {
5604
+ moveItem(grabbedIndex, grabbedIndex + 1);
5605
+ setGrabbedIndex(grabbedIndex + 1);
5606
+ }
5607
+ }
5608
+ };
5609
+ return /* @__PURE__ */ jsxs("div", { id: baseId, role: "region", "aria-label": title ?? "Ranker", style: containerStyle5, children: [
5610
+ title && /* @__PURE__ */ jsx("div", { style: headerStyle4, children: title }),
5611
+ /* @__PURE__ */ jsx("ul", { role: "list", "aria-label": title ?? "Rank items", style: listStyle, children: items.map((item, index) => /* @__PURE__ */ jsxs(
5612
+ "li",
5613
+ {
5614
+ role: "listitem",
5615
+ "aria-grabbed": grabbedIndex === index,
5616
+ "aria-label": `${item.label}, rank ${String(index + 1)}`,
5617
+ tabIndex: 0,
5618
+ style: itemStyle3(false, grabbedIndex === index),
5619
+ onKeyDown: (e) => handleKeyDown(e, index),
5620
+ children: [
5621
+ /* @__PURE__ */ jsx("span", { style: gripStyle, "aria-hidden": "true", children: "\u283F" }),
5622
+ /* @__PURE__ */ jsx("span", { style: rankBadgeStyle, children: String(index + 1) }),
5623
+ /* @__PURE__ */ jsxs("div", { style: itemContentStyle, children: [
5624
+ /* @__PURE__ */ jsx("div", { style: itemLabelStyle2, children: item.label }),
5625
+ item.description && /* @__PURE__ */ jsx("div", { style: itemDescriptionStyle2, children: item.description })
5626
+ ] })
5627
+ ]
5628
+ },
5629
+ item.id
5630
+ )) }),
5631
+ /* @__PURE__ */ jsx(
5632
+ "div",
5633
+ {
5634
+ "aria-live": "assertive",
5635
+ style: {
5636
+ position: "absolute",
5637
+ width: "1px",
5638
+ height: "1px",
5639
+ padding: 0,
5640
+ margin: "-1px",
5641
+ overflow: "hidden",
5642
+ clip: "rect(0,0,0,0)",
5643
+ whiteSpace: "nowrap",
5644
+ border: 0
5645
+ },
5646
+ children: grabbedIndex !== null && `${items[grabbedIndex]?.label ?? ""} grabbed, rank ${String(grabbedIndex + 1)} of ${String(items.length)}. Use arrow keys to move.`
5647
+ }
5648
+ )
5649
+ ] });
5650
+ }
5651
+
5652
+ // src/ranker/index.ts
5653
+ var rankerDefinition = {
5654
+ type: "ui:ranker",
5655
+ schema: rankerSchema,
5656
+ render: Ranker
5657
+ };
5658
+
5659
+ // src/slider/styles.ts
5660
+ var containerStyle6 = {
5661
+ fontFamily: "var(--glyph-font-body, system-ui, sans-serif)",
5662
+ color: "var(--glyph-text, #1a2035)",
5663
+ border: "1px solid var(--glyph-border, #d0d8e4)",
5664
+ borderRadius: "var(--glyph-radius-md, 0.5rem)",
5665
+ overflow: "hidden"
5666
+ };
5667
+ var headerStyle5 = {
5668
+ fontWeight: 700,
5669
+ fontSize: "1.125rem",
5670
+ padding: "var(--glyph-spacing-md, 1rem)",
5671
+ borderBottom: "1px solid var(--glyph-border, #d0d8e4)",
5672
+ color: "var(--glyph-heading, #0a0e1a)"
5673
+ };
5674
+ function parameterStyle(isLast) {
5675
+ return {
5676
+ padding: "var(--glyph-spacing-md, 1rem)",
5677
+ borderBottom: isLast ? "none" : "1px solid var(--glyph-border, #d0d8e4)"
5678
+ };
5679
+ }
5680
+ var parameterHeaderStyle = {
5681
+ display: "flex",
5682
+ justifyContent: "space-between",
5683
+ alignItems: "center",
5684
+ marginBottom: "0.5rem"
5685
+ };
5686
+ var parameterLabelStyle = {
5687
+ fontWeight: 600,
5688
+ fontSize: "0.9375rem"
5689
+ };
5690
+ var parameterValueStyle = {
5691
+ fontSize: "0.9375rem",
5692
+ fontWeight: 600,
5693
+ color: "var(--glyph-accent, #0a9d7c)",
5694
+ fontVariantNumeric: "tabular-nums"
5695
+ };
5696
+ var rangeInputStyle = {
5697
+ width: "100%",
5698
+ margin: 0,
5699
+ accentColor: "var(--glyph-slider-fill, var(--glyph-accent, #0a9d7c))"
5700
+ };
5701
+ var rangeLabelsStyle = {
5702
+ display: "flex",
5703
+ justifyContent: "space-between",
5704
+ fontSize: "0.75rem",
5705
+ color: "var(--glyph-text-muted, #6b7a94)",
5706
+ marginTop: "0.25rem"
5707
+ };
5708
+ function Slider({
5709
+ data,
5710
+ block,
5711
+ onInteraction
5712
+ }) {
5713
+ const { title, parameters } = data;
5714
+ const baseId = `glyph-slider-${block.id}`;
5715
+ const [values, setValues] = useState(
5716
+ () => parameters.map((p) => p.value ?? p.min ?? 0)
5717
+ );
5718
+ const handleChange = (paramIndex, newValue) => {
5719
+ const newValues = [...values];
5720
+ newValues[paramIndex] = newValue;
5721
+ setValues(newValues);
5722
+ const param = parameters[paramIndex];
5723
+ if (!param) return;
5724
+ if (onInteraction) {
5725
+ onInteraction({
5726
+ kind: "slider-change",
5727
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
5728
+ blockId: block.id,
5729
+ blockType: block.type,
5730
+ payload: {
5731
+ parameterId: param.id,
5732
+ parameterLabel: param.label,
5733
+ value: newValue,
5734
+ allValues: parameters.map((p, i) => ({
5735
+ id: p.id,
5736
+ label: p.label,
5737
+ value: i === paramIndex ? newValue : newValues[i] ?? 0
5738
+ }))
5739
+ }
5740
+ });
5741
+ }
5742
+ };
5743
+ const formatValue = (value, unit) => {
5744
+ return unit ? `${String(value)}${unit}` : String(value);
5745
+ };
5746
+ return /* @__PURE__ */ jsxs("div", { id: baseId, role: "region", "aria-label": title ?? "Slider", style: containerStyle6, children: [
5747
+ title && /* @__PURE__ */ jsx("div", { style: headerStyle5, children: title }),
5748
+ parameters.map((param, index) => {
5749
+ const min2 = param.min ?? 0;
5750
+ const max2 = param.max ?? 100;
5751
+ const step = param.step ?? 1;
5752
+ const currentValue = values[index] ?? min2;
5753
+ const isLast = index === parameters.length - 1;
5754
+ return /* @__PURE__ */ jsxs("div", { style: parameterStyle(isLast), children: [
5755
+ /* @__PURE__ */ jsxs("div", { style: parameterHeaderStyle, children: [
5756
+ /* @__PURE__ */ jsx("label", { htmlFor: `${baseId}-${param.id}`, style: parameterLabelStyle, children: param.label }),
5757
+ /* @__PURE__ */ jsx("span", { style: parameterValueStyle, "aria-live": "polite", children: formatValue(currentValue, param.unit) })
5758
+ ] }),
5759
+ /* @__PURE__ */ jsx(
5760
+ "input",
5761
+ {
5762
+ id: `${baseId}-${param.id}`,
5763
+ type: "range",
5764
+ min: min2,
5765
+ max: max2,
5766
+ step,
5767
+ value: currentValue,
5768
+ onChange: (e) => handleChange(index, Number(e.target.value)),
5769
+ "aria-valuemin": min2,
5770
+ "aria-valuemax": max2,
5771
+ "aria-valuenow": currentValue,
5772
+ "aria-valuetext": formatValue(currentValue, param.unit),
5773
+ style: rangeInputStyle
5774
+ }
5775
+ ),
5776
+ /* @__PURE__ */ jsxs("div", { style: rangeLabelsStyle, children: [
5777
+ /* @__PURE__ */ jsx("span", { children: formatValue(min2, param.unit) }),
5778
+ /* @__PURE__ */ jsx("span", { children: formatValue(max2, param.unit) })
5779
+ ] })
5780
+ ] }, param.id);
5781
+ })
5782
+ ] });
5783
+ }
5784
+
5785
+ // src/slider/index.ts
5786
+ var sliderDefinition = {
5787
+ type: "ui:slider",
5788
+ schema: sliderSchema,
5789
+ render: Slider
5790
+ };
5791
+
5792
+ // src/matrix/styles.ts
5793
+ var containerStyle7 = {
5794
+ fontFamily: "var(--glyph-font-body, system-ui, sans-serif)",
5795
+ color: "var(--glyph-text, #1a2035)",
5796
+ border: "1px solid var(--glyph-border, #d0d8e4)",
5797
+ borderRadius: "var(--glyph-radius-md, 0.5rem)",
5798
+ overflow: "auto"
5799
+ };
5800
+ var headerStyle6 = {
5801
+ fontWeight: 700,
5802
+ fontSize: "1.125rem",
5803
+ padding: "var(--glyph-spacing-md, 1rem)",
5804
+ borderBottom: "1px solid var(--glyph-border, #d0d8e4)",
5805
+ color: "var(--glyph-heading, #0a0e1a)"
5806
+ };
5807
+ var tableStyle = {
5808
+ width: "100%",
5809
+ borderCollapse: "collapse",
5810
+ fontSize: "0.875rem"
5811
+ };
5812
+ var thStyle = {
5813
+ padding: "0.625rem 0.75rem",
5814
+ textAlign: "center",
5815
+ fontWeight: 600,
5816
+ borderBottom: "2px solid var(--glyph-border, #d0d8e4)",
5817
+ background: "var(--glyph-table-header-bg, var(--glyph-surface, #e8ecf3))",
5818
+ whiteSpace: "nowrap"
5819
+ };
5820
+ var rowHeaderStyle = {
5821
+ padding: "0.625rem 0.75rem",
5822
+ textAlign: "left",
5823
+ fontWeight: 600,
5824
+ borderBottom: "1px solid var(--glyph-border, #d0d8e4)",
5825
+ borderRight: "1px solid var(--glyph-border, #d0d8e4)",
5826
+ whiteSpace: "nowrap"
5827
+ };
5828
+ var cellStyle = {
5829
+ padding: "0.375rem 0.5rem",
5830
+ textAlign: "center",
5831
+ borderBottom: "1px solid var(--glyph-border, #d0d8e4)"
5832
+ };
5833
+ var inputStyle = {
5834
+ width: "3.5rem",
5835
+ padding: "0.25rem 0.375rem",
5836
+ textAlign: "center",
5837
+ border: "1px solid var(--glyph-border, #d0d8e4)",
5838
+ borderRadius: "var(--glyph-radius-sm, 0.375rem)",
5839
+ background: "transparent",
5840
+ color: "var(--glyph-text, #1a2035)",
5841
+ fontSize: "0.875rem",
5842
+ fontVariantNumeric: "tabular-nums"
5843
+ };
5844
+ var weightStyle = {
5845
+ fontSize: "0.6875rem",
5846
+ color: "var(--glyph-text-muted, #6b7a94)",
5847
+ fontWeight: 400
5848
+ };
5849
+ var totalCellStyle = {
5850
+ padding: "0.625rem 0.75rem",
5851
+ textAlign: "center",
5852
+ fontWeight: 700,
5853
+ borderBottom: "1px solid var(--glyph-border, #d0d8e4)",
5854
+ background: "var(--glyph-surface, #e8ecf3)",
5855
+ fontVariantNumeric: "tabular-nums"
5856
+ };
5857
+ var totalHeaderStyle = {
5858
+ ...thStyle,
5859
+ borderLeft: "2px solid var(--glyph-border, #d0d8e4)"
5860
+ };
5861
+ function computeWeightedTotals(rows, columns, values) {
5862
+ return rows.map((row) => {
5863
+ let total = 0;
5864
+ for (const col of columns) {
5865
+ const score = values[row.id]?.[col.id] ?? 0;
5866
+ const weight = col.weight ?? 1;
5867
+ total += score * weight;
5868
+ }
5869
+ return { rowId: row.id, rowLabel: row.label, total: Math.round(total * 100) / 100 };
5870
+ });
5871
+ }
5872
+ function Matrix({
5873
+ data,
5874
+ block,
5875
+ onInteraction
5876
+ }) {
5877
+ const { title, scale = 5, showTotals = true, columns, rows } = data;
5878
+ const baseId = `glyph-matrix-${block.id}`;
5879
+ const [values, setValues] = useState(() => {
5880
+ const init = {};
5881
+ for (const row of rows) {
5882
+ const rowMap = {};
5883
+ for (const col of columns) {
5884
+ rowMap[col.id] = 0;
5885
+ }
5886
+ init[row.id] = rowMap;
5887
+ }
5888
+ return init;
5889
+ });
5890
+ const handleChange = useCallback(
5891
+ (rowId, columnId, value) => {
5892
+ const clamped = Math.max(0, Math.min(scale, value));
5893
+ setValues((prevValues) => {
5894
+ const newValues = Object.fromEntries(
5895
+ Object.entries(prevValues).map(([k, v]) => [k, { ...v }])
5896
+ );
5897
+ if (!newValues[rowId]) newValues[rowId] = {};
5898
+ newValues[rowId] = { ...newValues[rowId], [columnId]: clamped };
5899
+ const row = rows.find((r) => r.id === rowId);
5900
+ const col = columns.find((c) => c.id === columnId);
5901
+ if (onInteraction && row && col) {
5902
+ const payloadValues = Object.fromEntries(
5903
+ Object.entries(newValues).map(([k, v]) => [k, { ...v }])
5904
+ );
5905
+ onInteraction({
5906
+ kind: "matrix-change",
5907
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
5908
+ blockId: block.id,
5909
+ blockType: block.type,
5910
+ payload: {
5911
+ rowId,
5912
+ rowLabel: row.label,
5913
+ columnId,
5914
+ columnLabel: col.label,
5915
+ value: clamped,
5916
+ allValues: payloadValues,
5917
+ weightedTotals: computeWeightedTotals(rows, columns, newValues)
5918
+ }
5919
+ });
5920
+ }
5921
+ return newValues;
5922
+ });
5923
+ },
5924
+ [scale, rows, columns, block.id, block.type, onInteraction]
5925
+ );
5926
+ const totals = computeWeightedTotals(rows, columns, values);
5927
+ return /* @__PURE__ */ jsxs("div", { id: baseId, role: "region", "aria-label": title ?? "Decision Matrix", style: containerStyle7, children: [
5928
+ title && /* @__PURE__ */ jsx("div", { style: headerStyle6, children: title }),
5929
+ /* @__PURE__ */ jsxs("table", { role: "grid", style: tableStyle, children: [
5930
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
5931
+ /* @__PURE__ */ jsx("th", { style: thStyle }),
5932
+ columns.map((col) => /* @__PURE__ */ jsxs("th", { style: thStyle, children: [
5933
+ col.label,
5934
+ (col.weight ?? 1) !== 1 && /* @__PURE__ */ jsxs("div", { style: weightStyle, children: [
5935
+ "\xD7",
5936
+ String(col.weight)
5937
+ ] })
5938
+ ] }, col.id)),
5939
+ showTotals && /* @__PURE__ */ jsx("th", { style: totalHeaderStyle, children: "Total" })
5940
+ ] }) }),
5941
+ /* @__PURE__ */ jsx("tbody", { children: rows.map((row) => {
5942
+ const rowTotal = totals.find((t) => t.rowId === row.id)?.total ?? 0;
5943
+ return /* @__PURE__ */ jsxs("tr", { children: [
5944
+ /* @__PURE__ */ jsx("th", { scope: "row", style: rowHeaderStyle, children: row.label }),
5945
+ columns.map((col) => {
5946
+ const cellValue = values[row.id]?.[col.id] ?? 0;
5947
+ return /* @__PURE__ */ jsx("td", { style: cellStyle, children: /* @__PURE__ */ jsx(
5948
+ "input",
5949
+ {
5950
+ type: "number",
5951
+ min: 0,
5952
+ max: scale,
5953
+ value: cellValue,
5954
+ onChange: (e) => handleChange(row.id, col.id, Number(e.target.value)),
5955
+ "aria-label": `Score for ${row.label} on ${col.label}`,
5956
+ style: inputStyle
5957
+ }
5958
+ ) }, col.id);
5959
+ }),
5960
+ showTotals && /* @__PURE__ */ jsx("td", { style: totalCellStyle, children: String(rowTotal) })
5961
+ ] }, row.id);
5962
+ }) })
5963
+ ] })
5964
+ ] });
5965
+ }
5966
+
5967
+ // src/matrix/index.ts
5968
+ var matrixDefinition = {
5969
+ type: "ui:matrix",
5970
+ schema: matrixSchema,
5971
+ render: Matrix
5972
+ };
5973
+
5974
+ // src/form/styles.ts
5975
+ var containerStyle8 = {
5976
+ fontFamily: "var(--glyph-font-body, system-ui, sans-serif)",
5977
+ color: "var(--glyph-text, #1a2035)",
5978
+ border: "1px solid var(--glyph-border, #d0d8e4)",
5979
+ borderRadius: "var(--glyph-radius-md, 0.5rem)",
5980
+ overflow: "hidden"
5981
+ };
5982
+ var headerStyle7 = {
5983
+ fontWeight: 700,
5984
+ fontSize: "1.125rem",
5985
+ padding: "var(--glyph-spacing-md, 1rem)",
5986
+ paddingBottom: "0.25rem",
5987
+ color: "var(--glyph-heading, #0a0e1a)"
5988
+ };
5989
+ var descriptionStyle = {
5990
+ fontSize: "0.875rem",
5991
+ color: "var(--glyph-text-muted, #6b7a94)",
5992
+ padding: "0 var(--glyph-spacing-md, 1rem)",
5993
+ paddingBottom: "var(--glyph-spacing-sm, 0.5rem)"
5994
+ };
5995
+ var formStyle = {
5996
+ padding: "var(--glyph-spacing-md, 1rem)"
5997
+ };
5998
+ var fieldStyle = {
5999
+ marginBottom: "var(--glyph-spacing-md, 1rem)"
6000
+ };
6001
+ var labelStyle3 = {
6002
+ display: "block",
6003
+ fontWeight: 600,
6004
+ fontSize: "0.875rem",
6005
+ marginBottom: "0.375rem"
6006
+ };
6007
+ var requiredStyle = {
6008
+ color: "var(--glyph-form-error, #dc2626)",
6009
+ marginLeft: "0.25rem"
6010
+ };
6011
+ var textInputStyle = {
6012
+ width: "100%",
6013
+ padding: "0.5rem 0.75rem",
6014
+ border: "1px solid var(--glyph-border, #d0d8e4)",
6015
+ borderRadius: "var(--glyph-radius-sm, 0.375rem)",
6016
+ background: "transparent",
6017
+ color: "var(--glyph-text, #1a2035)",
6018
+ fontSize: "0.875rem",
6019
+ fontFamily: "inherit",
6020
+ boxSizing: "border-box"
6021
+ };
6022
+ var selectInputStyle = {
6023
+ ...textInputStyle,
6024
+ appearance: "auto"
6025
+ };
6026
+ var checkboxLabelStyle = {
6027
+ display: "flex",
6028
+ alignItems: "center",
6029
+ gap: "0.5rem",
6030
+ fontSize: "0.875rem",
6031
+ cursor: "pointer"
6032
+ };
6033
+ var rangeValueStyle = {
6034
+ fontSize: "0.875rem",
6035
+ fontWeight: 600,
6036
+ color: "var(--glyph-accent, #0a9d7c)",
6037
+ marginLeft: "0.5rem",
6038
+ fontVariantNumeric: "tabular-nums"
6039
+ };
6040
+ var submitButtonStyle = {
6041
+ padding: "0.625rem 1.5rem",
6042
+ borderRadius: "var(--glyph-radius-md, 0.5rem)",
6043
+ border: "1px solid var(--glyph-accent, #0a9d7c)",
6044
+ background: "var(--glyph-accent, #0a9d7c)",
6045
+ color: "#fff",
6046
+ cursor: "pointer",
6047
+ fontWeight: 600,
6048
+ fontSize: "0.875rem",
6049
+ marginTop: "var(--glyph-spacing-sm, 0.5rem)"
6050
+ };
6051
+ function invalidStyle(isInvalid) {
6052
+ if (!isInvalid) return {};
6053
+ return {
6054
+ borderColor: "var(--glyph-form-error, #dc2626)"
6055
+ };
6056
+ }
6057
+ function renderField({
6058
+ field,
6059
+ baseId,
6060
+ values,
6061
+ validation,
6062
+ submitted,
6063
+ updateValue
6064
+ }) {
6065
+ const isInvalid = validation[field.id] === true;
6066
+ const fieldId = `${baseId}-${field.id}`;
6067
+ switch (field.type) {
6068
+ case "text":
6069
+ return /* @__PURE__ */ jsxs("div", { style: fieldStyle, children: [
6070
+ /* @__PURE__ */ jsxs("label", { htmlFor: fieldId, style: labelStyle3, children: [
6071
+ field.label,
6072
+ field.required && /* @__PURE__ */ jsx("span", { style: requiredStyle, "aria-hidden": "true", children: "*" })
6073
+ ] }),
6074
+ /* @__PURE__ */ jsx(
6075
+ "input",
6076
+ {
6077
+ id: fieldId,
6078
+ type: "text",
6079
+ value: values[field.id] ?? "",
6080
+ onChange: (e) => updateValue(field.id, e.target.value),
6081
+ placeholder: field.placeholder,
6082
+ disabled: submitted,
6083
+ "aria-required": field.required,
6084
+ "aria-invalid": isInvalid,
6085
+ style: { ...textInputStyle, ...invalidStyle(isInvalid) }
6086
+ }
6087
+ )
6088
+ ] }, field.id);
6089
+ case "textarea":
6090
+ return /* @__PURE__ */ jsxs("div", { style: fieldStyle, children: [
6091
+ /* @__PURE__ */ jsxs("label", { htmlFor: fieldId, style: labelStyle3, children: [
6092
+ field.label,
6093
+ field.required && /* @__PURE__ */ jsx("span", { style: requiredStyle, "aria-hidden": "true", children: "*" })
6094
+ ] }),
6095
+ /* @__PURE__ */ jsx(
6096
+ "textarea",
6097
+ {
6098
+ id: fieldId,
6099
+ value: values[field.id] ?? "",
6100
+ onChange: (e) => updateValue(field.id, e.target.value),
6101
+ placeholder: field.placeholder,
6102
+ rows: field.rows ?? 4,
6103
+ disabled: submitted,
6104
+ "aria-required": field.required,
6105
+ "aria-invalid": isInvalid,
6106
+ style: {
6107
+ ...textInputStyle,
6108
+ ...invalidStyle(isInvalid),
6109
+ resize: "vertical",
6110
+ fontFamily: "inherit"
6111
+ }
6112
+ }
6113
+ )
6114
+ ] }, field.id);
6115
+ case "select":
6116
+ return /* @__PURE__ */ jsxs("div", { style: fieldStyle, children: [
6117
+ /* @__PURE__ */ jsxs("label", { htmlFor: fieldId, style: labelStyle3, children: [
6118
+ field.label,
6119
+ field.required && /* @__PURE__ */ jsx("span", { style: requiredStyle, "aria-hidden": "true", children: "*" })
6120
+ ] }),
6121
+ /* @__PURE__ */ jsx(
6122
+ "select",
6123
+ {
6124
+ id: fieldId,
6125
+ value: values[field.id] ?? "",
6126
+ onChange: (e) => updateValue(field.id, e.target.value),
6127
+ disabled: submitted,
6128
+ "aria-required": field.required,
6129
+ style: selectInputStyle,
6130
+ children: field.options.map((opt) => /* @__PURE__ */ jsx("option", { value: opt, children: opt }, opt))
6131
+ }
6132
+ )
6133
+ ] }, field.id);
6134
+ case "checkbox":
6135
+ return /* @__PURE__ */ jsx("div", { style: fieldStyle, children: /* @__PURE__ */ jsxs("label", { style: checkboxLabelStyle, children: [
6136
+ /* @__PURE__ */ jsx(
6137
+ "input",
6138
+ {
6139
+ id: fieldId,
6140
+ type: "checkbox",
6141
+ checked: values[field.id] ?? false,
6142
+ onChange: (e) => updateValue(field.id, e.target.checked),
6143
+ disabled: submitted
6144
+ }
6145
+ ),
6146
+ field.label
6147
+ ] }) }, field.id);
6148
+ case "range": {
6149
+ const min2 = field.min ?? 0;
6150
+ const max2 = field.max ?? 100;
6151
+ const step = field.step ?? 1;
6152
+ const currentValue = values[field.id] ?? min2;
6153
+ const displayValue = field.unit ? `${String(currentValue)}${field.unit}` : String(currentValue);
6154
+ return /* @__PURE__ */ jsxs("div", { style: fieldStyle, children: [
6155
+ /* @__PURE__ */ jsxs("label", { htmlFor: fieldId, style: labelStyle3, children: [
6156
+ field.label,
6157
+ /* @__PURE__ */ jsx("span", { style: rangeValueStyle, children: displayValue })
6158
+ ] }),
6159
+ /* @__PURE__ */ jsx(
6160
+ "input",
6161
+ {
6162
+ id: fieldId,
6163
+ type: "range",
6164
+ min: min2,
6165
+ max: max2,
6166
+ step,
6167
+ value: currentValue,
6168
+ onChange: (e) => updateValue(field.id, Number(e.target.value)),
6169
+ disabled: submitted,
6170
+ "aria-valuemin": min2,
6171
+ "aria-valuemax": max2,
6172
+ "aria-valuenow": currentValue,
6173
+ "aria-valuetext": displayValue,
6174
+ style: { width: "100%", accentColor: "var(--glyph-accent, #0a9d7c)" }
6175
+ }
6176
+ )
6177
+ ] }, field.id);
6178
+ }
6179
+ }
6180
+ }
6181
+ function Form({ data, block, onInteraction }) {
6182
+ const { title, description, submitLabel = "Submit", fields } = data;
6183
+ const baseId = `glyph-form-${block.id}`;
6184
+ const [values, setValues] = useState(() => {
6185
+ const init = {};
6186
+ for (const field of fields) {
6187
+ switch (field.type) {
6188
+ case "text":
6189
+ case "textarea":
6190
+ init[field.id] = field.default ?? "";
6191
+ break;
6192
+ case "select":
6193
+ init[field.id] = field.default ?? field.options[0] ?? "";
6194
+ break;
6195
+ case "checkbox":
6196
+ init[field.id] = field.default ?? false;
6197
+ break;
6198
+ case "range":
6199
+ init[field.id] = field.default ?? field.min ?? 0;
6200
+ break;
6201
+ }
6202
+ }
6203
+ return init;
6204
+ });
6205
+ const [submitted, setSubmitted] = useState(false);
6206
+ const [validation, setValidation] = useState({});
6207
+ const updateValue = (fieldId, value) => {
6208
+ setValues((prev) => ({ ...prev, [fieldId]: value }));
6209
+ if (validation[fieldId]) {
6210
+ setValidation((prev) => ({ ...prev, [fieldId]: false }));
6211
+ }
6212
+ };
6213
+ const handleSubmit = (e) => {
6214
+ e.preventDefault();
6215
+ const errors = {};
6216
+ for (const field of fields) {
6217
+ if ("required" in field && field.required) {
6218
+ const val = values[field.id];
6219
+ if (val === "" || val === void 0) {
6220
+ errors[field.id] = true;
6221
+ }
6222
+ }
6223
+ }
6224
+ if (Object.keys(errors).length > 0) {
6225
+ setValidation(errors);
6226
+ return;
6227
+ }
6228
+ setSubmitted(true);
6229
+ if (onInteraction) {
6230
+ onInteraction({
6231
+ kind: "form-submit",
6232
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
6233
+ blockId: block.id,
6234
+ blockType: block.type,
6235
+ payload: {
6236
+ values: { ...values },
6237
+ fields: fields.map((f) => ({
6238
+ id: f.id,
6239
+ label: f.label,
6240
+ type: f.type,
6241
+ value: values[f.id] !== void 0 ? values[f.id] : ""
6242
+ }))
6243
+ }
6244
+ });
6245
+ }
6246
+ };
6247
+ return /* @__PURE__ */ jsxs("div", { id: baseId, role: "region", "aria-label": title ?? "Form", style: containerStyle8, children: [
6248
+ title && /* @__PURE__ */ jsx("div", { style: headerStyle7, children: title }),
6249
+ description && /* @__PURE__ */ jsx("div", { style: descriptionStyle, children: description }),
6250
+ /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, style: formStyle, noValidate: true, children: [
6251
+ fields.map(
6252
+ (field) => renderField({ field, baseId, values, validation, submitted, updateValue })
6253
+ ),
6254
+ /* @__PURE__ */ jsx(
6255
+ "button",
6256
+ {
6257
+ type: "submit",
6258
+ disabled: submitted,
6259
+ style: {
6260
+ ...submitButtonStyle,
6261
+ opacity: submitted ? 0.5 : 1,
6262
+ cursor: submitted ? "default" : "pointer"
6263
+ },
6264
+ children: submitted ? "Submitted" : submitLabel
6265
+ }
6266
+ )
6267
+ ] })
6268
+ ] });
6269
+ }
6270
+
6271
+ // src/form/index.ts
6272
+ var formDefinition = {
6273
+ type: "ui:form",
6274
+ schema: formSchema,
6275
+ render: Form
6276
+ };
6277
+
6278
+ // src/kanban/styles.ts
6279
+ var containerStyle9 = {
6280
+ fontFamily: "var(--glyph-font-body, system-ui, sans-serif)",
6281
+ color: "var(--glyph-text, #1a2035)",
6282
+ border: "1px solid var(--glyph-border, #d0d8e4)",
6283
+ borderRadius: "var(--glyph-radius-md, 0.5rem)",
6284
+ overflow: "hidden"
6285
+ };
6286
+ var headerStyle8 = {
6287
+ fontWeight: 700,
6288
+ fontSize: "1.125rem",
6289
+ padding: "var(--glyph-spacing-md, 1rem)",
6290
+ borderBottom: "1px solid var(--glyph-border, #d0d8e4)",
6291
+ color: "var(--glyph-heading, #0a0e1a)"
6292
+ };
6293
+ var boardStyle = {
6294
+ display: "flex",
6295
+ gap: "var(--glyph-spacing-sm, 0.5rem)",
6296
+ padding: "var(--glyph-spacing-md, 1rem)",
6297
+ overflowX: "auto",
6298
+ minHeight: "200px"
6299
+ };
6300
+ function columnStyle(isOver) {
6301
+ return {
6302
+ flex: "1 1 0",
6303
+ minWidth: "180px",
6304
+ background: isOver ? "var(--glyph-accent-subtle, #e6f6f2)" : "var(--glyph-kanban-column-bg, var(--glyph-surface, #e8ecf3))",
6305
+ borderRadius: "var(--glyph-radius-md, 0.5rem)",
6306
+ padding: "var(--glyph-spacing-sm, 0.5rem)",
6307
+ display: "flex",
6308
+ flexDirection: "column",
6309
+ transition: "background 0.15s ease"
6310
+ };
6311
+ }
6312
+ var columnHeaderStyle = {
6313
+ fontWeight: 700,
6314
+ fontSize: "0.8125rem",
6315
+ textTransform: "uppercase",
6316
+ letterSpacing: "0.5px",
6317
+ padding: "0.375rem 0.5rem",
6318
+ marginBottom: "0.375rem",
6319
+ display: "flex",
6320
+ justifyContent: "space-between",
6321
+ alignItems: "center"
6322
+ };
6323
+ var columnCountStyle = {
6324
+ fontSize: "0.6875rem",
6325
+ fontWeight: 400,
6326
+ color: "var(--glyph-text-muted, #6b7a94)"
6327
+ };
6328
+ function cardStyle(isGrabbed, priority) {
6329
+ const priorityColors = {
6330
+ high: "var(--glyph-kanban-priority-high, #dc2626)",
6331
+ medium: "var(--glyph-kanban-priority-medium, #f59e0b)",
6332
+ low: "var(--glyph-kanban-priority-low, #22c55e)"
6333
+ };
6334
+ return {
6335
+ background: "var(--glyph-kanban-card-bg, var(--glyph-surface-raised, #f4f6fa))",
6336
+ border: `1px solid var(--glyph-kanban-card-border, var(--glyph-border, #d0d8e4))`,
6337
+ borderRadius: "var(--glyph-radius-sm, 0.375rem)",
6338
+ padding: "0.625rem 0.75rem",
6339
+ marginBottom: "0.375rem",
6340
+ cursor: isGrabbed ? "grabbing" : "grab",
6341
+ userSelect: "none",
6342
+ boxShadow: isGrabbed ? "var(--glyph-kanban-drag-shadow, var(--glyph-shadow-md, 0 4px 12px rgba(0,0,0,0.15)))" : "none",
6343
+ borderLeft: priority && priorityColors[priority] ? `3px solid ${priorityColors[priority]}` : void 0,
6344
+ outline: isGrabbed ? "2px solid var(--glyph-accent, #0a9d7c)" : "none",
6345
+ outlineOffset: "-2px"
6346
+ };
6347
+ }
6348
+ var cardTitleStyle = {
6349
+ fontWeight: 600,
6350
+ fontSize: "0.875rem",
6351
+ marginBottom: "0.25rem"
6352
+ };
6353
+ var cardDescStyle = {
6354
+ fontSize: "0.75rem",
6355
+ color: "var(--glyph-text-muted, #6b7a94)",
6356
+ lineHeight: 1.4
6357
+ };
6358
+ var tagContainerStyle = {
6359
+ display: "flex",
6360
+ flexWrap: "wrap",
6361
+ gap: "0.25rem",
6362
+ marginTop: "0.375rem"
6363
+ };
6364
+ var tagStyle = {
6365
+ fontSize: "0.625rem",
6366
+ fontWeight: 600,
6367
+ padding: "0.125rem 0.375rem",
6368
+ borderRadius: "9999px",
6369
+ background: "var(--glyph-accent-subtle, #e6f6f2)",
6370
+ color: "var(--glyph-accent, #0a9d7c)"
6371
+ };
6372
+ var limitStyle = {
6373
+ fontSize: "0.625rem",
6374
+ color: "var(--glyph-text-muted, #6b7a94)"
6375
+ };
6376
+ function Kanban({
6377
+ data,
6378
+ block,
6379
+ onInteraction
6380
+ }) {
6381
+ const { title } = data;
6382
+ const baseId = `glyph-kanban-${block.id}`;
6383
+ const [columns, setColumns] = useState(
6384
+ () => data.columns.map((col) => ({ ...col, cards: [...col.cards] }))
6385
+ );
6386
+ const [grabbed, setGrabbed] = useState(null);
6387
+ const moveCard = (cardId, sourceColId, destColId, destIndex) => {
6388
+ const newColumns = columns.map((col) => ({
6389
+ ...col,
6390
+ cards: [...col.cards]
6391
+ }));
6392
+ const sourceCol = newColumns.find((c) => c.id === sourceColId);
6393
+ const destCol = newColumns.find((c) => c.id === destColId);
6394
+ if (!sourceCol || !destCol) return;
6395
+ const cardIndex = sourceCol.cards.findIndex((c) => c.id === cardId);
6396
+ if (cardIndex === -1) return;
6397
+ const [card] = sourceCol.cards.splice(cardIndex, 1);
6398
+ if (!card) return;
6399
+ destCol.cards.splice(destIndex, 0, card);
6400
+ setColumns(newColumns);
6401
+ if (onInteraction) {
6402
+ onInteraction({
6403
+ kind: "kanban-move",
6404
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
6405
+ blockId: block.id,
6406
+ blockType: block.type,
6407
+ payload: {
6408
+ cardId: card.id,
6409
+ cardTitle: card.title,
6410
+ sourceColumnId: sourceColId,
6411
+ sourceColumnTitle: sourceCol.title,
6412
+ destinationColumnId: destColId,
6413
+ destinationColumnTitle: destCol.title,
6414
+ position: destIndex,
6415
+ allColumns: newColumns.map((c) => ({
6416
+ id: c.id,
6417
+ title: c.title,
6418
+ cardIds: c.cards.map((card2) => card2.id)
6419
+ }))
6420
+ }
6421
+ });
6422
+ }
6423
+ };
6424
+ const handleCardKeyDown = (e, cardId, columnId, cardIndex) => {
6425
+ if (e.key === " " || e.key === "Enter") {
6426
+ e.preventDefault();
6427
+ if (grabbed === null) {
6428
+ setGrabbed({ cardId, columnId, cardIndex });
6429
+ } else {
6430
+ setGrabbed(null);
6431
+ }
6432
+ } else if (e.key === "Escape") {
6433
+ setGrabbed(null);
6434
+ } else if (grabbed && grabbed.cardId === cardId) {
6435
+ const colIndex = columns.findIndex((c) => c.id === grabbed.columnId);
6436
+ const col = columns[colIndex];
6437
+ if (!col) return;
6438
+ if (e.key === "ArrowUp") {
6439
+ e.preventDefault();
6440
+ if (grabbed.cardIndex > 0) {
6441
+ moveCard(cardId, grabbed.columnId, grabbed.columnId, grabbed.cardIndex - 1);
6442
+ setGrabbed({ ...grabbed, cardIndex: grabbed.cardIndex - 1 });
6443
+ }
6444
+ } else if (e.key === "ArrowDown") {
6445
+ e.preventDefault();
6446
+ if (grabbed.cardIndex < col.cards.length - 1) {
6447
+ moveCard(cardId, grabbed.columnId, grabbed.columnId, grabbed.cardIndex + 1);
6448
+ setGrabbed({ ...grabbed, cardIndex: grabbed.cardIndex + 1 });
6449
+ }
6450
+ } else if (e.key === "ArrowLeft") {
6451
+ e.preventDefault();
6452
+ if (colIndex > 0) {
6453
+ const prevCol = columns[colIndex - 1];
6454
+ if (!prevCol) return;
6455
+ const newIndex = prevCol.cards.length;
6456
+ moveCard(cardId, grabbed.columnId, prevCol.id, newIndex);
6457
+ setGrabbed({ cardId, columnId: prevCol.id, cardIndex: newIndex });
6458
+ }
6459
+ } else if (e.key === "ArrowRight") {
6460
+ e.preventDefault();
6461
+ if (colIndex < columns.length - 1) {
6462
+ const nextCol = columns[colIndex + 1];
6463
+ if (!nextCol) return;
6464
+ const newIndex = nextCol.cards.length;
6465
+ moveCard(cardId, grabbed.columnId, nextCol.id, newIndex);
6466
+ setGrabbed({ cardId, columnId: nextCol.id, cardIndex: newIndex });
6467
+ }
6468
+ }
6469
+ }
6470
+ };
6471
+ return /* @__PURE__ */ jsxs("div", { id: baseId, role: "region", "aria-label": title ?? "Kanban Board", style: containerStyle9, children: [
6472
+ title && /* @__PURE__ */ jsx("div", { style: headerStyle8, children: title }),
6473
+ /* @__PURE__ */ jsx("div", { style: boardStyle, children: columns.map((col) => /* @__PURE__ */ jsxs("div", { style: columnStyle(false), children: [
6474
+ /* @__PURE__ */ jsxs("div", { style: columnHeaderStyle, children: [
6475
+ /* @__PURE__ */ jsx("span", { children: col.title }),
6476
+ /* @__PURE__ */ jsxs("span", { style: columnCountStyle, children: [
6477
+ String(col.cards.length),
6478
+ col.limit !== void 0 && /* @__PURE__ */ jsxs("span", { style: limitStyle, children: [
6479
+ " / ",
6480
+ String(col.limit)
6481
+ ] })
6482
+ ] })
6483
+ ] }),
6484
+ /* @__PURE__ */ jsx("div", { role: "list", "aria-label": col.title, children: col.cards.map((card, cardIndex) => {
6485
+ const isGrabbed = grabbed !== null && grabbed.cardId === card.id;
6486
+ return /* @__PURE__ */ jsxs(
6487
+ "div",
6488
+ {
6489
+ role: "listitem",
6490
+ "aria-grabbed": isGrabbed,
6491
+ "aria-label": `${card.title}${card.priority ? `, ${card.priority} priority` : ""}`,
6492
+ tabIndex: 0,
6493
+ style: cardStyle(isGrabbed, card.priority),
6494
+ onKeyDown: (e) => handleCardKeyDown(e, card.id, col.id, cardIndex),
6495
+ children: [
6496
+ /* @__PURE__ */ jsx("div", { style: cardTitleStyle, children: card.title }),
6497
+ card.description && /* @__PURE__ */ jsx("div", { style: cardDescStyle, children: card.description }),
6498
+ card.tags && card.tags.length > 0 && /* @__PURE__ */ jsx("div", { style: tagContainerStyle, children: card.tags.map((tag) => /* @__PURE__ */ jsx("span", { style: tagStyle, children: tag }, tag)) })
6499
+ ]
6500
+ },
6501
+ card.id
6502
+ );
6503
+ }) })
6504
+ ] }, col.id)) }),
6505
+ /* @__PURE__ */ jsx(
6506
+ "div",
6507
+ {
6508
+ "aria-live": "assertive",
6509
+ style: {
6510
+ position: "absolute",
6511
+ width: "1px",
6512
+ height: "1px",
6513
+ padding: 0,
6514
+ margin: "-1px",
6515
+ overflow: "hidden",
6516
+ clip: "rect(0,0,0,0)",
6517
+ whiteSpace: "nowrap",
6518
+ border: 0
6519
+ },
6520
+ children: grabbed !== null && `${columns.find((c) => c.id === grabbed.columnId)?.cards[grabbed.cardIndex]?.title ?? "Card"} grabbed in ${columns.find((c) => c.id === grabbed.columnId)?.title ?? "column"}. Use arrow keys to move.`
6521
+ }
6522
+ )
6523
+ ] });
6524
+ }
6525
+
6526
+ // src/kanban/index.ts
6527
+ var kanbanDefinition = {
6528
+ type: "ui:kanban",
6529
+ schema: kanbanSchema,
6530
+ render: Kanban
6531
+ };
6532
+
6533
+ // src/annotate/styles.ts
6534
+ var containerStyle10 = {
6535
+ fontFamily: "var(--glyph-font-body, system-ui, sans-serif)",
6536
+ color: "var(--glyph-text, #1a2035)",
6537
+ border: "1px solid var(--glyph-border, #d0d8e4)",
6538
+ borderRadius: "var(--glyph-radius-md, 0.5rem)",
6539
+ overflow: "hidden"
6540
+ };
6541
+ var headerStyle9 = {
6542
+ fontWeight: 700,
6543
+ fontSize: "1.125rem",
6544
+ padding: "var(--glyph-spacing-md, 1rem)",
6545
+ borderBottom: "1px solid var(--glyph-border, #d0d8e4)",
6546
+ color: "var(--glyph-heading, #0a0e1a)"
6547
+ };
6548
+ var bodyStyle2 = {
6549
+ display: "flex",
6550
+ minHeight: "200px"
6551
+ };
6552
+ var textPaneStyle = {
6553
+ flex: 1,
6554
+ padding: "var(--glyph-spacing-md, 1rem)",
6555
+ fontFamily: "var(--glyph-font-mono, ui-monospace, monospace)",
6556
+ fontSize: "0.8125rem",
6557
+ lineHeight: 1.8,
6558
+ whiteSpace: "pre-wrap",
6559
+ wordBreak: "break-word",
6560
+ position: "relative",
6561
+ cursor: "text"
6562
+ };
6563
+ var labelPickerStyle = {
6564
+ position: "absolute",
6565
+ zIndex: 10,
6566
+ background: "var(--glyph-surface-raised, #f4f6fa)",
6567
+ border: "1px solid var(--glyph-border, #d0d8e4)",
6568
+ borderRadius: "var(--glyph-radius-sm, 0.375rem)",
6569
+ boxShadow: "var(--glyph-shadow-md, 0 4px 12px rgba(0,0,0,0.15))",
6570
+ padding: "0.25rem 0",
6571
+ minWidth: "120px"
6572
+ };
6573
+ function labelOptionStyle() {
6574
+ return {
6575
+ display: "flex",
6576
+ alignItems: "center",
6577
+ gap: "0.5rem",
6578
+ padding: "0.375rem 0.75rem",
6579
+ cursor: "pointer",
6580
+ fontSize: "0.8125rem",
6581
+ background: "transparent",
6582
+ border: "none",
6583
+ width: "100%",
6584
+ textAlign: "left",
6585
+ color: "var(--glyph-text, #1a2035)"
6586
+ };
6587
+ }
6588
+ function colorDotStyle(color3) {
6589
+ return {
6590
+ width: "0.625rem",
6591
+ height: "0.625rem",
6592
+ borderRadius: "50%",
6593
+ background: color3,
6594
+ flexShrink: 0
6595
+ };
6596
+ }
6597
+ var sidebarStyle = {
6598
+ width: "220px",
6599
+ borderLeft: "1px solid var(--glyph-border, #d0d8e4)",
6600
+ background: "var(--glyph-annotate-sidebar-bg, var(--glyph-surface, #e8ecf3))",
6601
+ overflow: "auto"
6602
+ };
6603
+ var sidebarHeaderStyle = {
6604
+ fontWeight: 700,
6605
+ fontSize: "0.75rem",
6606
+ textTransform: "uppercase",
6607
+ letterSpacing: "0.5px",
6608
+ padding: "0.75rem",
6609
+ borderBottom: "1px solid var(--glyph-border, #d0d8e4)",
6610
+ color: "var(--glyph-text-muted, #6b7a94)"
6611
+ };
6612
+ function annotationItemStyle(color3) {
6613
+ return {
6614
+ padding: "0.5rem 0.75rem",
6615
+ borderBottom: "1px solid var(--glyph-border, #d0d8e4)",
6616
+ borderLeft: `3px solid ${color3}`,
6617
+ fontSize: "0.75rem"
6618
+ };
6619
+ }
6620
+ var annotationTextStyle = {
6621
+ fontFamily: "var(--glyph-font-mono, ui-monospace, monospace)",
6622
+ fontSize: "0.6875rem",
6623
+ color: "var(--glyph-text-muted, #6b7a94)",
6624
+ marginTop: "0.25rem",
6625
+ overflow: "hidden",
6626
+ textOverflow: "ellipsis",
6627
+ whiteSpace: "nowrap"
6628
+ };
6629
+ var annotationNoteStyle = {
6630
+ fontSize: "0.6875rem",
6631
+ fontStyle: "italic",
6632
+ color: "var(--glyph-text-muted, #6b7a94)",
6633
+ marginTop: "0.125rem"
6634
+ };
6635
+ function computeSegments(text, annotations) {
6636
+ if (annotations.length === 0) {
6637
+ return [{ text, start: 0, annotation: null }];
6638
+ }
6639
+ const sorted = [...annotations].sort((a, b) => a.start - b.start);
6640
+ const segments = [];
6641
+ let cursor = 0;
6642
+ for (const ann of sorted) {
6643
+ if (ann.start > cursor) {
6644
+ segments.push({ text: text.slice(cursor, ann.start), start: cursor, annotation: null });
6645
+ }
6646
+ segments.push({
6647
+ text: text.slice(ann.start, ann.end),
6648
+ start: ann.start,
6649
+ annotation: ann
6650
+ });
6651
+ cursor = ann.end;
6652
+ }
6653
+ if (cursor < text.length) {
6654
+ segments.push({ text: text.slice(cursor), start: cursor, annotation: null });
6655
+ }
6656
+ return segments;
6657
+ }
6658
+ function Annotate({
6659
+ data,
6660
+ block,
6661
+ onInteraction
6662
+ }) {
6663
+ const { title, labels, text } = data;
6664
+ const baseId = `glyph-annotate-${block.id}`;
6665
+ const [annotations, setAnnotations] = useState(data.annotations ?? []);
6666
+ const [pickerPos, setPickerPos] = useState(null);
6667
+ const [pendingSelection, setPendingSelection] = useState(null);
6668
+ const textRef = useRef(null);
6669
+ const handleMouseUp = useCallback(() => {
6670
+ const selection = window.getSelection();
6671
+ if (!selection || selection.isCollapsed || !textRef.current) return;
6672
+ const range = selection.getRangeAt(0);
6673
+ if (!range || !textRef.current.contains(range.commonAncestorContainer)) return;
6674
+ const selectedText = selection.toString();
6675
+ if (!selectedText.trim()) return;
6676
+ const preCaretRange = document.createRange();
6677
+ preCaretRange.selectNodeContents(textRef.current);
6678
+ preCaretRange.setEnd(range.startContainer, range.startOffset);
6679
+ const startOffset = preCaretRange.toString().length;
6680
+ const endOffset = startOffset + selectedText.length;
6681
+ const rect = range.getBoundingClientRect();
6682
+ const containerRect = textRef.current.getBoundingClientRect();
6683
+ setPendingSelection({ start: startOffset, end: endOffset, text: selectedText });
6684
+ setPickerPos({
6685
+ x: rect.left - containerRect.left,
6686
+ y: rect.bottom - containerRect.top + 4
6687
+ });
6688
+ }, []);
6689
+ const selectLabel = (labelName) => {
6690
+ if (!pendingSelection) return;
6691
+ const newAnnotation = {
6692
+ start: pendingSelection.start,
6693
+ end: pendingSelection.end,
6694
+ label: labelName
6695
+ };
6696
+ const newAnnotations = [...annotations, newAnnotation];
6697
+ setAnnotations(newAnnotations);
6698
+ setPickerPos(null);
6699
+ setPendingSelection(null);
6700
+ window.getSelection()?.removeAllRanges();
6701
+ if (onInteraction) {
6702
+ onInteraction({
6703
+ kind: "annotate-create",
6704
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
6705
+ blockId: block.id,
6706
+ blockType: block.type,
6707
+ payload: {
6708
+ start: newAnnotation.start,
6709
+ end: newAnnotation.end,
6710
+ selectedText: pendingSelection.text,
6711
+ label: labelName,
6712
+ allAnnotations: newAnnotations.map((a) => ({
6713
+ start: a.start,
6714
+ end: a.end,
6715
+ text: text.slice(a.start, a.end),
6716
+ label: a.label
6717
+ }))
6718
+ }
6719
+ });
6720
+ }
6721
+ };
6722
+ const closePicker = (e) => {
6723
+ if (e.target.closest("[data-label-picker]")) return;
6724
+ setPickerPos(null);
6725
+ setPendingSelection(null);
6726
+ };
6727
+ const segments = computeSegments(text, annotations);
6728
+ const labelColorMap = new Map(labels.map((l) => [l.name, l.color]));
6729
+ return /* @__PURE__ */ jsxs(
6730
+ "div",
6731
+ {
6732
+ id: baseId,
6733
+ role: "region",
6734
+ "aria-label": title ?? "Annotate",
6735
+ style: containerStyle10,
6736
+ onClick: closePicker,
6737
+ children: [
6738
+ title && /* @__PURE__ */ jsx("div", { style: headerStyle9, children: title }),
6739
+ /* @__PURE__ */ jsxs("div", { style: bodyStyle2, children: [
6740
+ /* @__PURE__ */ jsxs("div", { ref: textRef, role: "document", style: textPaneStyle, onMouseUp: handleMouseUp, children: [
6741
+ segments.map((seg, i) => {
6742
+ if (seg.annotation) {
6743
+ const color3 = labelColorMap.get(seg.annotation.label) ?? "#888";
6744
+ return /* @__PURE__ */ jsx(
6745
+ "mark",
6746
+ {
6747
+ style: {
6748
+ backgroundColor: `${color3}33`,
6749
+ borderBottom: `2px solid ${color3}`,
6750
+ padding: "0 1px"
6751
+ },
6752
+ title: `${seg.annotation.label}${seg.annotation.note ? `: ${seg.annotation.note}` : ""}`,
6753
+ children: seg.text
6754
+ },
6755
+ i
6756
+ );
6757
+ }
6758
+ return /* @__PURE__ */ jsx("span", { children: seg.text }, i);
6759
+ }),
6760
+ pickerPos && /* @__PURE__ */ jsx(
6761
+ "div",
6762
+ {
6763
+ role: "menu",
6764
+ "data-label-picker": true,
6765
+ style: {
6766
+ ...labelPickerStyle,
6767
+ left: `${String(pickerPos.x)}px`,
6768
+ top: `${String(pickerPos.y)}px`
6769
+ },
6770
+ children: labels.map((label) => /* @__PURE__ */ jsxs(
6771
+ "button",
6772
+ {
6773
+ role: "menuitem",
6774
+ style: labelOptionStyle(),
6775
+ onClick: (e) => {
6776
+ e.stopPropagation();
6777
+ selectLabel(label.name);
6778
+ },
6779
+ children: [
6780
+ /* @__PURE__ */ jsx("span", { style: colorDotStyle(label.color) }),
6781
+ label.name
6782
+ ]
6783
+ },
6784
+ label.name
6785
+ ))
6786
+ }
6787
+ )
6788
+ ] }),
6789
+ /* @__PURE__ */ jsxs("div", { style: sidebarStyle, role: "complementary", "aria-label": "Annotations", children: [
6790
+ /* @__PURE__ */ jsxs("div", { style: sidebarHeaderStyle, children: [
6791
+ "Annotations (",
6792
+ String(annotations.length),
6793
+ ")"
6794
+ ] }),
6795
+ /* @__PURE__ */ jsxs("div", { role: "list", children: [
6796
+ annotations.map((ann, i) => {
6797
+ const color3 = labelColorMap.get(ann.label) ?? "#888";
6798
+ return /* @__PURE__ */ jsxs("div", { role: "listitem", style: annotationItemStyle(color3), children: [
6799
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "0.375rem" }, children: [
6800
+ /* @__PURE__ */ jsx("span", { style: colorDotStyle(color3) }),
6801
+ /* @__PURE__ */ jsx("strong", { style: { fontSize: "0.75rem" }, children: ann.label })
6802
+ ] }),
6803
+ /* @__PURE__ */ jsx("div", { style: annotationTextStyle, children: text.slice(ann.start, ann.end) }),
6804
+ ann.note && /* @__PURE__ */ jsx("div", { style: annotationNoteStyle, children: ann.note })
6805
+ ] }, i);
6806
+ }),
6807
+ annotations.length === 0 && /* @__PURE__ */ jsx(
6808
+ "div",
6809
+ {
6810
+ style: {
6811
+ padding: "0.75rem",
6812
+ fontSize: "0.75rem",
6813
+ color: "var(--glyph-text-muted, #6b7a94)"
6814
+ },
6815
+ children: "Select text to add annotations."
6816
+ }
6817
+ )
6818
+ ] })
6819
+ ] })
6820
+ ] })
6821
+ ]
6822
+ }
6823
+ );
6824
+ }
6825
+
6826
+ // src/annotate/index.ts
6827
+ var annotateDefinition = {
6828
+ type: "ui:annotate",
6829
+ schema: annotateSchema,
6830
+ render: Annotate
6831
+ };
6832
+
6833
+ export { Accordion, Annotate, Architecture, Callout, Card, Chart, CodeDiff, Comparison, Equation, FileTree, Flowchart, Form, Graph, Infographic, Kanban, Kpi, Matrix, MindMap, Poll, Quiz, Ranker, Rating, Relation, Sequence, Slider, Steps, Table, Tabs, Timeline, accordionDefinition, annotateDefinition, architectureDefinition, calloutDefinition, cardDefinition, chartDefinition, codeDiffDefinition, comparisonDefinition, computeArchitectureLayout, computeDagreLayout, computeDiff, computeForceLayout, equationDefinition, fileTreeDefinition, flowchartDefinition, formDefinition, graphDefinition, infographicDefinition, kanbanDefinition, kpiDefinition, matrixDefinition, mindMapDefinition, pollDefinition, quizDefinition, rankerDefinition, ratingDefinition, relationDefinition, sequenceDefinition, sliderDefinition, stepsDefinition, tableDefinition, tabsDefinition, timelineDefinition };
4854
6834
  //# sourceMappingURL=index.js.map
4855
6835
  //# sourceMappingURL=index.js.map