@glyphjs/components 0.1.0 → 0.3.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.cjs CHANGED
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var schemas = require('@glyphjs/schemas');
4
+ var runtime = require('@glyphjs/runtime');
4
5
  var jsxRuntime = require('react/jsx-runtime');
5
6
  var react = require('react');
6
7
  var d32 = require('d3');
@@ -46,7 +47,7 @@ var CALLOUT_LABELS = {
46
47
  };
47
48
  function Callout({ data }) {
48
49
  const { type, title, content } = data;
49
- const containerStyle3 = {
50
+ const containerStyle11 = {
50
51
  backgroundColor: `var(--glyph-callout-${type}-bg)`,
51
52
  borderLeft: `4px solid var(--glyph-callout-${type}-border)`,
52
53
  borderRadius: "var(--glyph-radius-md, 0.1875rem)",
@@ -63,7 +64,7 @@ function Callout({ data }) {
63
64
  fontSize: "1.25em",
64
65
  lineHeight: 1
65
66
  };
66
- const bodyStyle2 = {
67
+ const bodyStyle3 = {
67
68
  flex: 1,
68
69
  minWidth: 0
69
70
  };
@@ -71,11 +72,11 @@ function Callout({ data }) {
71
72
  fontWeight: 700,
72
73
  marginBottom: "var(--glyph-spacing-xs, 0.25rem)"
73
74
  };
74
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "note", "aria-label": CALLOUT_LABELS[type], style: containerStyle3, children: [
75
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "note", "aria-label": CALLOUT_LABELS[type], style: containerStyle11, children: [
75
76
  /* @__PURE__ */ jsxRuntime.jsx("span", { style: iconStyle, "aria-hidden": "true", children: CALLOUT_ICONS[type] }),
76
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: bodyStyle2, children: [
77
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: bodyStyle3, children: [
77
78
  title && /* @__PURE__ */ jsxRuntime.jsx("div", { style: titleStyle2, children: title }),
78
- /* @__PURE__ */ jsxRuntime.jsx("div", { children: content })
79
+ /* @__PURE__ */ jsxRuntime.jsx("div", { children: /* @__PURE__ */ jsxRuntime.jsx(runtime.RichText, { content }) })
79
80
  ] })
80
81
  ] });
81
82
  }
@@ -278,9 +279,104 @@ function computeScales(width, height, type, series, xKey, yKey, margin) {
278
279
  const yScale = d32__namespace.scaleLinear().domain([yMin, yMax]).nice().range([innerHeight, 0]);
279
280
  return { xScale, xScalePoint, yScale, innerWidth, innerHeight };
280
281
  }
282
+ function renderAllSeries(g, type, series, scales, xKey, yKey, showTooltip, hideTooltip) {
283
+ const { xScale, xScalePoint, yScale, innerHeight } = scales;
284
+ series.forEach((s, i) => {
285
+ const color3 = COLOR_SCHEME[i % COLOR_SCHEME.length] ?? "#333";
286
+ switch (type) {
287
+ case "line":
288
+ renderLineSeries(
289
+ g,
290
+ s.data,
291
+ xScalePoint,
292
+ yScale,
293
+ yKey,
294
+ xKey,
295
+ color3,
296
+ i,
297
+ s.name,
298
+ showTooltip,
299
+ hideTooltip
300
+ );
301
+ break;
302
+ case "area":
303
+ renderAreaSeries(
304
+ g,
305
+ s.data,
306
+ xScalePoint,
307
+ yScale,
308
+ yKey,
309
+ xKey,
310
+ innerHeight,
311
+ color3,
312
+ i,
313
+ s.name,
314
+ showTooltip,
315
+ hideTooltip
316
+ );
317
+ break;
318
+ case "bar":
319
+ renderBarSeries(
320
+ g,
321
+ s.data,
322
+ xScale,
323
+ yScale,
324
+ yKey,
325
+ xKey,
326
+ color3,
327
+ i,
328
+ series.length,
329
+ innerHeight,
330
+ s.name,
331
+ showTooltip,
332
+ hideTooltip
333
+ );
334
+ break;
335
+ case "ohlc":
336
+ renderOHLCSeries(
337
+ g,
338
+ s.data,
339
+ xScale,
340
+ xScalePoint,
341
+ yScale,
342
+ scales.innerWidth,
343
+ s.name,
344
+ showTooltip,
345
+ hideTooltip
346
+ );
347
+ break;
348
+ }
349
+ });
350
+ }
351
+ function attachChartInteraction(g, type, series, xKey, yKey, block, onInteraction) {
352
+ if (!onInteraction) return;
353
+ series.forEach((s, seriesIdx) => {
354
+ const className = type === "bar" ? `bar-${String(seriesIdx)}` : `dot-${String(seriesIdx)}`;
355
+ g.selectAll(`.${className}`).on(
356
+ "click",
357
+ (_event, d) => {
358
+ const dataIdx = s.data.indexOf(d);
359
+ onInteraction({
360
+ kind: "chart-select",
361
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
362
+ blockId: block.id,
363
+ blockType: block.type,
364
+ payload: {
365
+ seriesIndex: seriesIdx,
366
+ dataIndex: dataIdx >= 0 ? dataIdx : 0,
367
+ label: String(d[xKey] ?? ""),
368
+ value: getNumericValue(d, yKey)
369
+ }
370
+ });
371
+ }
372
+ );
373
+ });
374
+ }
281
375
  function Chart({
282
376
  data,
283
- container: containerCtx
377
+ block,
378
+ container: containerCtx,
379
+ onInteraction
284
380
  }) {
285
381
  const containerRef = react.useRef(null);
286
382
  const svgRef = react.useRef(null);
@@ -333,76 +429,19 @@ function Chart({
333
429
  if (!svg || series.length === 0) return;
334
430
  const sel = d32__namespace.select(svg);
335
431
  sel.selectAll("*").remove();
336
- const { xScale, xScalePoint, yScale, innerWidth, innerHeight } = scales;
337
432
  const g = sel.append("g").attr("transform", `translate(${String(margin.left)},${String(margin.top)})`);
338
- renderAxes(g, xScale, yScale, xAxis, yAxis, innerWidth, innerHeight);
339
- renderGridLines(g, yScale, innerWidth);
340
- series.forEach((s, i) => {
341
- const color3 = COLOR_SCHEME[i % COLOR_SCHEME.length] ?? "#333";
342
- switch (type) {
343
- case "line":
344
- renderLineSeries(
345
- g,
346
- s.data,
347
- xScalePoint,
348
- yScale,
349
- yKey,
350
- xKey,
351
- color3,
352
- i,
353
- s.name,
354
- showTooltip,
355
- hideTooltip
356
- );
357
- break;
358
- case "area":
359
- renderAreaSeries(
360
- g,
361
- s.data,
362
- xScalePoint,
363
- yScale,
364
- yKey,
365
- xKey,
366
- innerHeight,
367
- color3,
368
- i,
369
- s.name,
370
- showTooltip,
371
- hideTooltip
372
- );
373
- break;
374
- case "bar":
375
- renderBarSeries(
376
- g,
377
- s.data,
378
- xScale,
379
- yScale,
380
- yKey,
381
- xKey,
382
- color3,
383
- i,
384
- series.length,
385
- innerHeight,
386
- s.name,
387
- showTooltip,
388
- hideTooltip
389
- );
390
- break;
391
- case "ohlc":
392
- renderOHLCSeries(
393
- g,
394
- s.data,
395
- xScale,
396
- xScalePoint,
397
- yScale,
398
- innerWidth,
399
- s.name,
400
- showTooltip,
401
- hideTooltip
402
- );
403
- break;
404
- }
405
- });
433
+ renderAxes(
434
+ g,
435
+ scales.xScale,
436
+ scales.yScale,
437
+ xAxis,
438
+ yAxis,
439
+ scales.innerWidth,
440
+ scales.innerHeight
441
+ );
442
+ renderGridLines(g, scales.yScale, scales.innerWidth);
443
+ renderAllSeries(g, type, series, scales, xKey, yKey, showTooltip, hideTooltip);
444
+ attachChartInteraction(g, type, series, xKey, yKey, block, onInteraction);
406
445
  if (legend) {
407
446
  renderLegend(sel, series, margin.left, margin.top, isCompact ? "10px" : void 0);
408
447
  }
@@ -418,7 +457,9 @@ function Chart({
418
457
  margin,
419
458
  isCompact,
420
459
  showTooltip,
421
- hideTooltip
460
+ hideTooltip,
461
+ onInteraction,
462
+ block
422
463
  ]);
423
464
  const ariaLabel = `${type} chart with ${String(series.length)} series: ${series.map((s) => s.name).join(", ")}`;
424
465
  return /* @__PURE__ */ jsxRuntime.jsxs(
@@ -492,14 +533,14 @@ var STATUS_LABELS = {
492
533
  };
493
534
  function Steps({ data }) {
494
535
  const { steps } = data;
495
- const listStyle = {
536
+ const listStyle2 = {
496
537
  listStyle: "none",
497
538
  padding: 0,
498
539
  margin: "var(--glyph-spacing-sm, 0.5rem) 0",
499
540
  fontFamily: "var(--glyph-font-body, system-ui, sans-serif)",
500
541
  color: "var(--glyph-text, #1a2035)"
501
542
  };
502
- return /* @__PURE__ */ jsxRuntime.jsx("ol", { role: "list", style: listStyle, children: steps.map((step, index) => {
543
+ return /* @__PURE__ */ jsxRuntime.jsx("ol", { role: "list", style: listStyle2, children: steps.map((step, index) => {
503
544
  const status = step.status ?? "pending";
504
545
  const isLast = index === steps.length - 1;
505
546
  return /* @__PURE__ */ jsxRuntime.jsxs(
@@ -513,7 +554,7 @@ function Steps({ data }) {
513
554
  /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", style: indicatorStyle(status), children: status === "completed" ? "\u2713" : "" }),
514
555
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: bodyStyle, children: [
515
556
  /* @__PURE__ */ jsxRuntime.jsx("div", { style: titleStyle(status), children: step.title }),
516
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: contentStyle(status), children: step.content })
557
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: contentStyle(status), children: /* @__PURE__ */ jsxRuntime.jsx(runtime.RichText, { content: step.content }) })
517
558
  ] })
518
559
  ]
519
560
  },
@@ -646,7 +687,95 @@ function TableAggregationFooter({
646
687
  );
647
688
  }) }) });
648
689
  }
649
- function Table({ data, container }) {
690
+ function TableHead({
691
+ columns,
692
+ sort,
693
+ hasFilters,
694
+ filters,
695
+ onSort,
696
+ onHeaderKeyDown,
697
+ onFilterChange
698
+ }) {
699
+ return /* @__PURE__ */ jsxRuntime.jsxs("thead", { children: [
700
+ /* @__PURE__ */ jsxRuntime.jsx("tr", { children: columns.map((col) => {
701
+ const isSorted = sort.column === col.key;
702
+ const direction = isSorted ? sort.direction : "none";
703
+ return /* @__PURE__ */ jsxRuntime.jsxs(
704
+ "th",
705
+ {
706
+ scope: "col",
707
+ "aria-sort": col.sortable ? direction : void 0,
708
+ tabIndex: col.sortable ? 0 : void 0,
709
+ role: col.sortable ? "columnheader" : void 0,
710
+ onClick: col.sortable ? () => onSort(col.key) : void 0,
711
+ onKeyDown: col.sortable ? (e) => onHeaderKeyDown(e, col.key) : void 0,
712
+ style: {
713
+ padding: "var(--glyph-table-cell-padding, 8px 12px)",
714
+ textAlign: "left",
715
+ borderBottom: "2px solid var(--glyph-table-border, #d0d8e4)",
716
+ background: "var(--glyph-table-header-bg, #e8ecf3)",
717
+ color: "var(--glyph-table-header-color, inherit)",
718
+ cursor: col.sortable ? "pointer" : "default",
719
+ userSelect: col.sortable ? "none" : void 0,
720
+ whiteSpace: "nowrap"
721
+ },
722
+ children: [
723
+ col.label,
724
+ col.sortable ? sortIndicator(direction) : ""
725
+ ]
726
+ },
727
+ col.key
728
+ );
729
+ }) }),
730
+ hasFilters && /* @__PURE__ */ jsxRuntime.jsx("tr", { children: columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx(
731
+ "th",
732
+ {
733
+ scope: "col",
734
+ style: { padding: "4px 8px", fontWeight: "normal" },
735
+ children: col.filterable ? /* @__PURE__ */ jsxRuntime.jsx(
736
+ "input",
737
+ {
738
+ type: "text",
739
+ "aria-label": `Filter ${col.label}`,
740
+ placeholder: `Filter ${col.label}...`,
741
+ value: filters[col.key] ?? "",
742
+ onChange: (e) => onFilterChange(col.key, e.target.value),
743
+ style: {
744
+ width: "100%",
745
+ padding: "4px 6px",
746
+ border: "1px solid var(--glyph-table-border, #d0d8e4)",
747
+ borderRadius: "3px",
748
+ fontSize: "inherit",
749
+ boxSizing: "border-box",
750
+ background: "var(--glyph-surface, #e8ecf3)",
751
+ color: "var(--glyph-text, inherit)"
752
+ }
753
+ }
754
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
755
+ "span",
756
+ {
757
+ style: {
758
+ position: "absolute",
759
+ width: "1px",
760
+ height: "1px",
761
+ overflow: "hidden",
762
+ clip: "rect(0,0,0,0)",
763
+ whiteSpace: "nowrap"
764
+ },
765
+ children: `No filter for ${col.label}`
766
+ }
767
+ )
768
+ },
769
+ `filter-${col.key}`
770
+ )) })
771
+ ] });
772
+ }
773
+ function Table({
774
+ data,
775
+ block,
776
+ container,
777
+ onInteraction
778
+ }) {
650
779
  const { columns, rows, aggregation } = data;
651
780
  const [sort, setSort] = react.useState({ column: "", direction: "none" });
652
781
  const [filters, setFilters] = react.useState({});
@@ -679,12 +808,27 @@ function Table({ data, container }) {
679
808
  });
680
809
  }, [filteredRows, sort]);
681
810
  const handleSort = (columnKey) => {
682
- setSort((prev) => {
683
- if (prev.column === columnKey) {
684
- return { column: columnKey, direction: nextDirection(prev.direction) };
685
- }
686
- return { column: columnKey, direction: "ascending" };
687
- });
811
+ const newDirection = sort.column === columnKey ? nextDirection(sort.direction) : "ascending";
812
+ setSort({ column: columnKey, direction: newDirection });
813
+ if (onInteraction) {
814
+ const eventDir = newDirection === "ascending" ? "asc" : newDirection === "descending" ? "desc" : "none";
815
+ onInteraction({
816
+ kind: "table-sort",
817
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
818
+ blockId: block.id,
819
+ blockType: block.type,
820
+ payload: {
821
+ column: columnKey,
822
+ direction: eventDir,
823
+ state: {
824
+ sort: newDirection === "none" ? null : { column: columnKey, direction: eventDir },
825
+ filters,
826
+ visibleRowCount: filteredRows.length,
827
+ totalRowCount: rows.length
828
+ }
829
+ }
830
+ });
831
+ }
688
832
  };
689
833
  const handleHeaderKeyDown = (e, columnKey) => {
690
834
  if (e.key === "Enter" || e.key === " ") {
@@ -693,7 +837,38 @@ function Table({ data, container }) {
693
837
  }
694
838
  };
695
839
  const handleFilterChange = (columnKey, value) => {
696
- setFilters((prev) => ({ ...prev, [columnKey]: value }));
840
+ const newFilters = { ...filters, [columnKey]: value };
841
+ setFilters(newFilters);
842
+ if (onInteraction) {
843
+ const newVisibleCount = rows.filter(
844
+ (row) => columns.every((col) => {
845
+ if (!col.filterable) return true;
846
+ const fv = newFilters[col.key];
847
+ if (!fv) return true;
848
+ return String(row[col.key] ?? "").toLowerCase().includes(fv.toLowerCase());
849
+ })
850
+ ).length;
851
+ const eventSort = sort.direction === "none" || !sort.column ? null : {
852
+ column: sort.column,
853
+ direction: sort.direction === "ascending" ? "asc" : "desc"
854
+ };
855
+ onInteraction({
856
+ kind: "table-filter",
857
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
858
+ blockId: block.id,
859
+ blockType: block.type,
860
+ payload: {
861
+ column: columnKey,
862
+ value,
863
+ state: {
864
+ sort: eventSort,
865
+ filters: newFilters,
866
+ visibleRowCount: newVisibleCount,
867
+ totalRowCount: rows.length
868
+ }
869
+ }
870
+ });
871
+ }
697
872
  };
698
873
  const hasFilters = columns.some((c) => c.filterable);
699
874
  const aggMap = react.useMemo(() => {
@@ -717,79 +892,18 @@ function Table({ data, container }) {
717
892
  fontSize: isCompact ? "0.8125rem" : "var(--glyph-table-font-size, 0.9rem)"
718
893
  },
719
894
  children: [
720
- /* @__PURE__ */ jsxRuntime.jsxs("thead", { children: [
721
- /* @__PURE__ */ jsxRuntime.jsx("tr", { children: columns.map((col) => {
722
- const isSorted = sort.column === col.key;
723
- const direction = isSorted ? sort.direction : "none";
724
- return /* @__PURE__ */ jsxRuntime.jsxs(
725
- "th",
726
- {
727
- scope: "col",
728
- "aria-sort": col.sortable ? direction : void 0,
729
- tabIndex: col.sortable ? 0 : void 0,
730
- role: col.sortable ? "columnheader" : void 0,
731
- onClick: col.sortable ? () => handleSort(col.key) : void 0,
732
- onKeyDown: col.sortable ? (e) => handleHeaderKeyDown(e, col.key) : void 0,
733
- style: {
734
- padding: "var(--glyph-table-cell-padding, 8px 12px)",
735
- textAlign: "left",
736
- borderBottom: "2px solid var(--glyph-table-border, #d0d8e4)",
737
- background: "var(--glyph-table-header-bg, #e8ecf3)",
738
- color: "var(--glyph-table-header-color, inherit)",
739
- cursor: col.sortable ? "pointer" : "default",
740
- userSelect: col.sortable ? "none" : void 0,
741
- whiteSpace: "nowrap"
742
- },
743
- children: [
744
- col.label,
745
- col.sortable ? sortIndicator(direction) : ""
746
- ]
747
- },
748
- col.key
749
- );
750
- }) }),
751
- hasFilters && /* @__PURE__ */ jsxRuntime.jsx("tr", { children: columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx(
752
- "th",
753
- {
754
- scope: "col",
755
- style: { padding: "4px 8px", fontWeight: "normal" },
756
- children: col.filterable ? /* @__PURE__ */ jsxRuntime.jsx(
757
- "input",
758
- {
759
- type: "text",
760
- "aria-label": `Filter ${col.label}`,
761
- placeholder: `Filter ${col.label}...`,
762
- value: filters[col.key] ?? "",
763
- onChange: (e) => handleFilterChange(col.key, e.target.value),
764
- style: {
765
- width: "100%",
766
- padding: "4px 6px",
767
- border: "1px solid var(--glyph-table-border, #d0d8e4)",
768
- borderRadius: "3px",
769
- fontSize: "inherit",
770
- boxSizing: "border-box",
771
- background: "var(--glyph-surface, #e8ecf3)",
772
- color: "var(--glyph-text, inherit)"
773
- }
774
- }
775
- ) : /* @__PURE__ */ jsxRuntime.jsx(
776
- "span",
777
- {
778
- style: {
779
- position: "absolute",
780
- width: "1px",
781
- height: "1px",
782
- overflow: "hidden",
783
- clip: "rect(0,0,0,0)",
784
- whiteSpace: "nowrap"
785
- },
786
- children: `No filter for ${col.label}`
787
- }
788
- )
789
- },
790
- `filter-${col.key}`
791
- )) })
792
- ] }),
895
+ /* @__PURE__ */ jsxRuntime.jsx(
896
+ TableHead,
897
+ {
898
+ columns,
899
+ sort,
900
+ hasFilters,
901
+ filters,
902
+ onSort: handleSort,
903
+ onHeaderKeyDown: handleHeaderKeyDown,
904
+ onFilterChange: handleFilterChange
905
+ }
906
+ ),
793
907
  /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: sortedRows.map((row, rowIdx) => /* @__PURE__ */ jsxRuntime.jsx(
794
908
  "tr",
795
909
  {
@@ -827,18 +941,28 @@ var tableDefinition = {
827
941
  schema: schemas.tableSchema,
828
942
  render: Table
829
943
  };
830
- function Tabs({ data, block }) {
944
+ function Tabs({ data, block, onInteraction }) {
831
945
  const [activeIndex, setActiveIndex] = react.useState(0);
832
946
  const tabRefs = react.useRef([]);
833
947
  const tabs = data.tabs;
834
948
  const baseId = `glyph-tabs-${block.id}`;
835
- const focusTab = react.useCallback(
949
+ const selectTab = react.useCallback(
836
950
  (index) => {
837
951
  const clampedIndex = Math.max(0, Math.min(index, tabs.length - 1));
838
952
  setActiveIndex(clampedIndex);
839
953
  tabRefs.current[clampedIndex]?.focus();
954
+ const tab = tabs[clampedIndex];
955
+ if (tab) {
956
+ onInteraction?.({
957
+ kind: "tab-select",
958
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
959
+ blockId: block.id,
960
+ blockType: block.type,
961
+ payload: { tabIndex: clampedIndex, tabLabel: tab.label }
962
+ });
963
+ }
840
964
  },
841
- [tabs.length]
965
+ [tabs, block.id, block.type, onInteraction]
842
966
  );
843
967
  const handleKeyDown = react.useCallback(
844
968
  (e) => {
@@ -846,28 +970,28 @@ function Tabs({ data, block }) {
846
970
  case "ArrowRight": {
847
971
  e.preventDefault();
848
972
  const next = (activeIndex + 1) % tabs.length;
849
- focusTab(next);
973
+ selectTab(next);
850
974
  break;
851
975
  }
852
976
  case "ArrowLeft": {
853
977
  e.preventDefault();
854
978
  const prev = (activeIndex - 1 + tabs.length) % tabs.length;
855
- focusTab(prev);
979
+ selectTab(prev);
856
980
  break;
857
981
  }
858
982
  case "Home": {
859
983
  e.preventDefault();
860
- focusTab(0);
984
+ selectTab(0);
861
985
  break;
862
986
  }
863
987
  case "End": {
864
988
  e.preventDefault();
865
- focusTab(tabs.length - 1);
989
+ selectTab(tabs.length - 1);
866
990
  break;
867
991
  }
868
992
  }
869
993
  },
870
- [activeIndex, focusTab, tabs.length]
994
+ [activeIndex, selectTab, tabs.length]
871
995
  );
872
996
  return /* @__PURE__ */ jsxRuntime.jsxs(
873
997
  "div",
@@ -906,7 +1030,7 @@ function Tabs({ data, block }) {
906
1030
  "aria-selected": isActive,
907
1031
  "aria-controls": panelId,
908
1032
  tabIndex: isActive ? 0 : -1,
909
- onClick: () => setActiveIndex(index),
1033
+ onClick: () => selectTab(index),
910
1034
  onKeyDown: handleKeyDown,
911
1035
  style: {
912
1036
  padding: "10px 18px",
@@ -1018,7 +1142,7 @@ function Timeline({ data }) {
1018
1142
  position: dates.length === 1 ? totalLength / 2 : timeScale(e._parsed),
1019
1143
  side: isVertical ? i % 2 === 0 ? "left" : "right" : i % 2 === 0 ? "top" : "bottom"
1020
1144
  }));
1021
- const containerStyle3 = {
1145
+ const containerStyle11 = {
1022
1146
  position: "relative",
1023
1147
  fontFamily: "var(--glyph-font-body, system-ui, sans-serif)",
1024
1148
  color: "var(--glyph-text, #1a2035)",
@@ -1045,7 +1169,7 @@ function Timeline({ data }) {
1045
1169
  "div",
1046
1170
  {
1047
1171
  ref: containerRef,
1048
- style: containerStyle3,
1172
+ style: containerStyle11,
1049
1173
  role: "img",
1050
1174
  "aria-label": `Timeline with ${events.length} events`,
1051
1175
  children: [
@@ -1089,7 +1213,7 @@ function Timeline({ data }) {
1089
1213
  fontWeight: 700,
1090
1214
  marginTop: 2
1091
1215
  },
1092
- children: pe.event.title
1216
+ children: /* @__PURE__ */ jsxRuntime.jsx(runtime.RichText, { content: pe.event.title })
1093
1217
  }
1094
1218
  ),
1095
1219
  pe.event.description && /* @__PURE__ */ jsxRuntime.jsx(
@@ -1100,7 +1224,7 @@ function Timeline({ data }) {
1100
1224
  color: "var(--glyph-timeline-desc-color, #7a8599)",
1101
1225
  marginTop: 2
1102
1226
  },
1103
- children: pe.event.description
1227
+ children: /* @__PURE__ */ jsxRuntime.jsx(runtime.RichText, { content: pe.event.description })
1104
1228
  }
1105
1229
  )
1106
1230
  ] })
@@ -1118,12 +1242,16 @@ function Timeline({ data }) {
1118
1242
  clipPath: "inset(50%)",
1119
1243
  whiteSpace: "nowrap"
1120
1244
  },
1121
- children: sorted.map((e, idx) => /* @__PURE__ */ jsxRuntime.jsxs("li", { children: [
1122
- /* @__PURE__ */ jsxRuntime.jsx("time", { dateTime: isoDate(e.date), children: formatDate(e.date) }),
1123
- " \u2014 ",
1124
- /* @__PURE__ */ jsxRuntime.jsx("strong", { children: e.title }),
1125
- e.description ? `: ${e.description}` : ""
1126
- ] }, idx))
1245
+ children: sorted.map((e, idx) => {
1246
+ const titleText = typeof e.title === "string" ? e.title : "Event";
1247
+ const descText = typeof e.description === "string" ? e.description : "";
1248
+ return /* @__PURE__ */ jsxRuntime.jsxs("li", { children: [
1249
+ /* @__PURE__ */ jsxRuntime.jsx("time", { dateTime: isoDate(e.date), children: formatDate(e.date) }),
1250
+ " \u2014 ",
1251
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: titleText }),
1252
+ descText ? `: ${descText}` : ""
1253
+ ] }, idx);
1254
+ })
1127
1255
  }
1128
1256
  )
1129
1257
  ]
@@ -1373,7 +1501,7 @@ function getThemeVar(container, varName, fallback) {
1373
1501
  return getComputedStyle(container).getPropertyValue(varName).trim() || fallback;
1374
1502
  }
1375
1503
  var ARROW_MARKER_ID = "glyph-graph-arrowhead";
1376
- function renderGraph(svgElement, layout, groupIndex, outgoingRefs, onNavigate) {
1504
+ function renderGraph(svgElement, layout, groupIndex, outgoingRefs, onNavigate, onNodeClick) {
1377
1505
  const svg = d32__namespace.select(svgElement);
1378
1506
  svg.selectAll("*").remove();
1379
1507
  const width = Math.max(layout.width, 200);
@@ -1424,19 +1552,24 @@ function renderGraph(svgElement, layout, groupIndex, outgoingRefs, onNavigate) {
1424
1552
  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);
1425
1553
  }
1426
1554
  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);
1427
- if (isNavigable) {
1555
+ if (isNavigable || onNodeClick) {
1428
1556
  nodeG.attr("cursor", "pointer");
1429
1557
  nodeG.on("click", () => {
1430
- const ref = refByAnchor.get(node.id);
1431
- if (ref) onNavigate(ref);
1558
+ if (isNavigable) {
1559
+ const ref = refByAnchor.get(node.id);
1560
+ if (ref) onNavigate(ref);
1561
+ }
1562
+ onNodeClick?.(node.id, node.label);
1432
1563
  });
1433
1564
  }
1434
1565
  }
1435
1566
  }
1436
1567
  function Graph({
1437
1568
  data,
1569
+ block,
1438
1570
  outgoingRefs,
1439
1571
  onNavigate,
1572
+ onInteraction,
1440
1573
  container
1441
1574
  }) {
1442
1575
  const svgRef = react.useRef(null);
@@ -1448,10 +1581,29 @@ function Graph({
1448
1581
  }
1449
1582
  return computeDagreLayout(data.nodes, data.edges, direction);
1450
1583
  }, [data]);
1584
+ const handleNodeClick = react.useMemo(() => {
1585
+ if (!onInteraction) return void 0;
1586
+ return (nodeId, nodeLabel) => {
1587
+ onInteraction({
1588
+ kind: "graph-node-click",
1589
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1590
+ blockId: block.id,
1591
+ blockType: block.type,
1592
+ payload: { nodeId, nodeLabel }
1593
+ });
1594
+ };
1595
+ }, [onInteraction, block.id, block.type]);
1451
1596
  react.useEffect(() => {
1452
1597
  if (!svgRef.current) return;
1453
- renderGraph(svgRef.current, layoutResult, groupIndex.current, outgoingRefs, onNavigate);
1454
- }, [layoutResult, outgoingRefs, onNavigate]);
1598
+ renderGraph(
1599
+ svgRef.current,
1600
+ layoutResult,
1601
+ groupIndex.current,
1602
+ outgoingRefs,
1603
+ onNavigate,
1604
+ handleNodeClick
1605
+ );
1606
+ }, [layoutResult, outgoingRefs, onNavigate, handleNodeClick]);
1455
1607
  const ariaLabel = `${data.type} graph with ${data.nodes.length} nodes and ${data.edges.length} edges`;
1456
1608
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "glyph-graph-container", children: [
1457
1609
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -1771,7 +1923,8 @@ function resolveSentiment(metric) {
1771
1923
  return "neutral";
1772
1924
  }
1773
1925
  function buildAriaLabel(metric) {
1774
- let label = `${metric.label}: ${metric.value}`;
1926
+ const labelText = typeof metric.label === "string" ? metric.label : "Metric";
1927
+ let label = `${labelText}: ${metric.value}`;
1775
1928
  if (metric.unit) label += ` ${metric.unit}`;
1776
1929
  if (metric.delta && metric.trend) {
1777
1930
  label += `, ${metric.trend} ${metric.delta}`;
@@ -1795,7 +1948,7 @@ function Kpi({ data, block, container }) {
1795
1948
  default:
1796
1949
  colCount = authorCols;
1797
1950
  }
1798
- const containerStyle3 = {
1951
+ const containerStyle11 = {
1799
1952
  fontFamily: "var(--glyph-font-body, system-ui, sans-serif)",
1800
1953
  color: "var(--glyph-text, #1a2035)"
1801
1954
  };
@@ -1805,13 +1958,13 @@ function Kpi({ data, block, container }) {
1805
1958
  gridTemplateColumns: `repeat(auto-fill, minmax(max(120px, calc((100% - ${String(gapCount)}rem) / ${String(colCount)})), 1fr))`,
1806
1959
  gap: "var(--glyph-spacing-md, 1rem)"
1807
1960
  };
1808
- const cardStyle = {
1961
+ const cardStyle2 = {
1809
1962
  background: "var(--glyph-surface-raised, #f4f6fa)",
1810
1963
  border: "1px solid var(--glyph-border, #d0d8e4)",
1811
1964
  borderRadius: "var(--glyph-radius-md, 0.5rem)",
1812
1965
  padding: "var(--glyph-spacing-md, 1rem)"
1813
1966
  };
1814
- const labelStyle3 = {
1967
+ const labelStyle4 = {
1815
1968
  fontSize: "0.8125rem",
1816
1969
  color: "var(--glyph-text-muted, #6b7a94)",
1817
1970
  marginBottom: "var(--glyph-spacing-xs, 0.25rem)"
@@ -1822,7 +1975,7 @@ function Kpi({ data, block, container }) {
1822
1975
  color: "var(--glyph-heading, #0a0e1a)",
1823
1976
  lineHeight: 1.2
1824
1977
  };
1825
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { id: baseId, role: "region", "aria-label": title ?? "Key metrics", style: containerStyle3, children: [
1978
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { id: baseId, role: "region", "aria-label": title ?? "Key metrics", style: containerStyle11, children: [
1826
1979
  title && /* @__PURE__ */ jsxRuntime.jsx(
1827
1980
  "div",
1828
1981
  {
@@ -1842,8 +1995,8 @@ function Kpi({ data, block, container }) {
1842
1995
  marginTop: "var(--glyph-spacing-xs, 0.25rem)",
1843
1996
  color: `var(--glyph-kpi-${sentiment}, inherit)`
1844
1997
  };
1845
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "group", "aria-label": buildAriaLabel(metric), style: cardStyle, children: [
1846
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: labelStyle3, children: metric.label }),
1998
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "group", "aria-label": buildAriaLabel(metric), style: cardStyle2, children: [
1999
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: labelStyle4, children: /* @__PURE__ */ jsxRuntime.jsx(runtime.RichText, { content: metric.label }) }),
1847
2000
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: valueStyle, children: [
1848
2001
  metric.value,
1849
2002
  metric.unit && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "0.875rem", fontWeight: 400, marginLeft: "0.25rem" }, children: metric.unit })
@@ -1863,25 +2016,41 @@ var kpiDefinition = {
1863
2016
  schema: schemas.kpiSchema,
1864
2017
  render: Kpi
1865
2018
  };
1866
- function Accordion({ data, block }) {
2019
+ function Accordion({
2020
+ data,
2021
+ block,
2022
+ onInteraction
2023
+ }) {
1867
2024
  const { title, sections, defaultOpen = [], multiple = true } = data;
1868
2025
  const baseId = `glyph-accordion-${block.id}`;
1869
2026
  const containerRef = react.useRef(null);
1870
2027
  const handleToggle = react.useCallback(
1871
- (e) => {
1872
- if (multiple) return;
2028
+ (e, sectionIndex) => {
1873
2029
  const target = e.currentTarget;
1874
- if (!target.open || !containerRef.current) return;
1875
- const allDetails = containerRef.current.querySelectorAll("details");
1876
- for (const details of allDetails) {
1877
- if (details !== target && details.open) {
1878
- details.open = false;
2030
+ const expanded = target.open;
2031
+ if (!multiple && expanded && containerRef.current) {
2032
+ const allDetails = containerRef.current.querySelectorAll("details");
2033
+ for (const details of allDetails) {
2034
+ if (details !== target && details.open) {
2035
+ details.open = false;
2036
+ }
1879
2037
  }
1880
2038
  }
2039
+ onInteraction?.({
2040
+ kind: "accordion-toggle",
2041
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2042
+ blockId: block.id,
2043
+ blockType: block.type,
2044
+ payload: {
2045
+ sectionIndex,
2046
+ sectionTitle: sections[sectionIndex]?.title ?? "",
2047
+ expanded
2048
+ }
2049
+ });
1881
2050
  },
1882
- [multiple]
2051
+ [multiple, sections, block.id, block.type, onInteraction]
1883
2052
  );
1884
- const containerStyle3 = {
2053
+ const containerStyle11 = {
1885
2054
  fontFamily: "var(--glyph-font-body, system-ui, sans-serif)",
1886
2055
  color: "var(--glyph-text, #1a2035)",
1887
2056
  border: "1px solid var(--glyph-border, #d0d8e4)",
@@ -1915,7 +2084,7 @@ function Accordion({ data, block }) {
1915
2084
  ref: containerRef,
1916
2085
  role: "region",
1917
2086
  "aria-label": title ?? "Accordion",
1918
- style: containerStyle3,
2087
+ style: containerStyle11,
1919
2088
  children: [
1920
2089
  title && /* @__PURE__ */ jsxRuntime.jsx(
1921
2090
  "div",
@@ -1934,14 +2103,14 @@ function Accordion({ data, block }) {
1934
2103
  "details",
1935
2104
  {
1936
2105
  open: defaultOpen.includes(i),
1937
- onToggle: handleToggle,
2106
+ onToggle: (e) => handleToggle(e, i),
1938
2107
  style: sectionStyle(i === sections.length - 1),
1939
2108
  children: [
1940
2109
  /* @__PURE__ */ jsxRuntime.jsxs("summary", { style: summaryStyle, children: [
1941
2110
  /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", style: { fontSize: "0.75rem", width: "1rem", flexShrink: 0 }, children: "\u25B8" }),
1942
2111
  section.title
1943
2112
  ] }),
1944
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: contentStyle2, children: section.content })
2113
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: contentStyle2, children: /* @__PURE__ */ jsxRuntime.jsx(runtime.RichText, { content: section.content }) })
1945
2114
  ]
1946
2115
  },
1947
2116
  i
@@ -1960,6 +2129,7 @@ var accordionDefinition = {
1960
2129
  var YES_VALUES = /* @__PURE__ */ new Set(["yes", "true", "full"]);
1961
2130
  var NO_VALUES = /* @__PURE__ */ new Set(["no", "false", "none"]);
1962
2131
  function classifyValue(value) {
2132
+ if (typeof value !== "string") return "text";
1963
2133
  const lower = value.toLowerCase().trim();
1964
2134
  if (YES_VALUES.has(lower)) return "yes";
1965
2135
  if (NO_VALUES.has(lower)) return "no";
@@ -1997,23 +2167,24 @@ function renderValue(value) {
1997
2167
  }
1998
2168
  );
1999
2169
  default:
2000
- return /* @__PURE__ */ jsxRuntime.jsx("span", { children: value });
2170
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { children: /* @__PURE__ */ jsxRuntime.jsx(runtime.RichText, { content: value }) });
2001
2171
  }
2002
2172
  }
2003
2173
  function Comparison({
2004
2174
  data,
2005
2175
  block,
2006
- container
2176
+ container,
2177
+ onInteraction
2007
2178
  }) {
2008
2179
  const { title, options, features } = data;
2009
2180
  const baseId = `glyph-comparison-${block.id}`;
2010
2181
  const isCompact = container.tier === "compact";
2011
2182
  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)";
2012
- const containerStyle3 = {
2183
+ const containerStyle11 = {
2013
2184
  fontFamily: "var(--glyph-font-body, system-ui, sans-serif)",
2014
2185
  color: "var(--glyph-text, #1a2035)"
2015
2186
  };
2016
- const tableStyle = {
2187
+ const tableStyle2 = {
2017
2188
  width: "100%",
2018
2189
  borderCollapse: "collapse",
2019
2190
  border: "1px solid var(--glyph-table-border, #d0d8e4)",
@@ -2021,7 +2192,7 @@ function Comparison({
2021
2192
  overflow: "hidden",
2022
2193
  fontSize: isCompact ? "0.8125rem" : "0.875rem"
2023
2194
  };
2024
- const thStyle = {
2195
+ const thStyle2 = {
2025
2196
  padding: cellPadding,
2026
2197
  textAlign: "center",
2027
2198
  fontWeight: 600,
@@ -2030,7 +2201,7 @@ function Comparison({
2030
2201
  color: "var(--glyph-heading, #0a0e1a)"
2031
2202
  };
2032
2203
  const featureThStyle = {
2033
- ...thStyle,
2204
+ ...thStyle2,
2034
2205
  textAlign: "left"
2035
2206
  };
2036
2207
  const rowThStyle = {
@@ -2040,13 +2211,13 @@ function Comparison({
2040
2211
  borderBottom: "1px solid var(--glyph-table-border, #d0d8e4)",
2041
2212
  fontSize: "0.8125rem"
2042
2213
  };
2043
- const cellStyle = (rowIndex) => ({
2214
+ const cellStyle2 = (rowIndex) => ({
2044
2215
  padding: cellPadding,
2045
2216
  textAlign: "center",
2046
2217
  borderBottom: "1px solid var(--glyph-table-border, #d0d8e4)",
2047
2218
  background: rowIndex % 2 === 1 ? "var(--glyph-table-row-alt-bg, transparent)" : "transparent"
2048
2219
  });
2049
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { id: baseId, role: "region", "aria-label": title ?? "Comparison", style: containerStyle3, children: [
2220
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { id: baseId, role: "region", "aria-label": title ?? "Comparison", style: containerStyle11, children: [
2050
2221
  title && /* @__PURE__ */ jsxRuntime.jsx(
2051
2222
  "div",
2052
2223
  {
@@ -2059,23 +2230,46 @@ function Comparison({
2059
2230
  children: title
2060
2231
  }
2061
2232
  ),
2062
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { overflowX: "auto" }, children: /* @__PURE__ */ jsxRuntime.jsxs("table", { role: "grid", style: tableStyle, children: [
2233
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { overflowX: "auto" }, children: /* @__PURE__ */ jsxRuntime.jsxs("table", { role: "grid", style: tableStyle2, children: [
2063
2234
  /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
2064
2235
  /* @__PURE__ */ jsxRuntime.jsx("th", { style: featureThStyle, scope: "col", children: "Feature" }),
2065
- options.map((option, i) => /* @__PURE__ */ jsxRuntime.jsxs("th", { style: thStyle, scope: "col", children: [
2066
- /* @__PURE__ */ jsxRuntime.jsx("div", { children: option.name }),
2067
- option.description && /* @__PURE__ */ jsxRuntime.jsx(
2068
- "div",
2069
- {
2070
- style: {
2071
- fontWeight: 400,
2072
- fontSize: "0.75rem",
2073
- color: "var(--glyph-text-muted, #6b7a94)"
2074
- },
2075
- children: option.description
2076
- }
2077
- )
2078
- ] }, i))
2236
+ options.map((option, i) => /* @__PURE__ */ jsxRuntime.jsxs(
2237
+ "th",
2238
+ {
2239
+ style: {
2240
+ ...thStyle2,
2241
+ ...onInteraction ? { cursor: "pointer" } : {}
2242
+ },
2243
+ scope: "col",
2244
+ onClick: onInteraction ? () => {
2245
+ onInteraction({
2246
+ kind: "comparison-select",
2247
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2248
+ blockId: block.id,
2249
+ blockType: block.type,
2250
+ payload: {
2251
+ optionIndex: i,
2252
+ optionName: option.name
2253
+ }
2254
+ });
2255
+ } : void 0,
2256
+ children: [
2257
+ /* @__PURE__ */ jsxRuntime.jsx("div", { children: option.name }),
2258
+ option.description && /* @__PURE__ */ jsxRuntime.jsx(
2259
+ "div",
2260
+ {
2261
+ style: {
2262
+ fontWeight: 400,
2263
+ fontSize: "0.75rem",
2264
+ color: "var(--glyph-text-muted, #6b7a94)"
2265
+ },
2266
+ children: /* @__PURE__ */ jsxRuntime.jsx(runtime.RichText, { content: option.description })
2267
+ }
2268
+ )
2269
+ ]
2270
+ },
2271
+ i
2272
+ ))
2079
2273
  ] }) }),
2080
2274
  /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: features.map((feature, rowIndex) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
2081
2275
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -2090,8 +2284,8 @@ function Comparison({
2090
2284
  }
2091
2285
  ),
2092
2286
  options.map((_, colIndex) => {
2093
- const value = feature.values[colIndex] ?? "";
2094
- return /* @__PURE__ */ jsxRuntime.jsx("td", { style: cellStyle(rowIndex), children: value ? renderValue(value) : null }, colIndex);
2287
+ const value = feature.values[colIndex];
2288
+ return /* @__PURE__ */ jsxRuntime.jsx("td", { style: cellStyle2(rowIndex), children: value !== void 0 && value !== "" ? renderValue(value) : null }, colIndex);
2095
2289
  })
2096
2290
  ] }, rowIndex)) })
2097
2291
  ] }) })
@@ -2169,7 +2363,7 @@ function CodeDiff({ data, block }) {
2169
2363
  const baseId = `glyph-codediff-${block.id}`;
2170
2364
  const diffLines = react.useMemo(() => computeDiff(before, after), [before, after]);
2171
2365
  const summary = react.useMemo(() => summarizeDiff(diffLines), [diffLines]);
2172
- const containerStyle3 = {
2366
+ const containerStyle11 = {
2173
2367
  fontFamily: 'var(--glyph-font-mono, ui-monospace, "Cascadia Code", "Fira Code", monospace)',
2174
2368
  fontSize: "0.8125rem",
2175
2369
  lineHeight: 1.5,
@@ -2188,7 +2382,7 @@ function CodeDiff({ data, block }) {
2188
2382
  fontWeight: 600,
2189
2383
  color: "var(--glyph-text-muted, #6b7a94)"
2190
2384
  };
2191
- const tableStyle = {
2385
+ const tableStyle2 = {
2192
2386
  width: "100%",
2193
2387
  borderCollapse: "collapse",
2194
2388
  tableLayout: "fixed"
@@ -2227,13 +2421,13 @@ function CodeDiff({ data, block }) {
2227
2421
  if (kind === "del") return "var(--glyph-codediff-del-color, inherit)";
2228
2422
  return void 0;
2229
2423
  }
2230
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { id: baseId, role: "region", "aria-label": summary, style: containerStyle3, children: [
2424
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { id: baseId, role: "region", "aria-label": summary, style: containerStyle11, children: [
2231
2425
  (beforeLabel || afterLabel) && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: labelBarStyle, children: [
2232
2426
  beforeLabel && /* @__PURE__ */ jsxRuntime.jsx("span", { children: beforeLabel }),
2233
2427
  beforeLabel && afterLabel && /* @__PURE__ */ jsxRuntime.jsx("span", { children: "\u2192" }),
2234
2428
  afterLabel && /* @__PURE__ */ jsxRuntime.jsx("span", { children: afterLabel })
2235
2429
  ] }),
2236
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { overflowX: "auto" }, children: /* @__PURE__ */ jsxRuntime.jsx("table", { role: "grid", style: tableStyle, children: /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: diffLines.map((line6, i) => /* @__PURE__ */ jsxRuntime.jsxs(
2430
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { overflowX: "auto" }, children: /* @__PURE__ */ jsxRuntime.jsx("table", { role: "grid", style: tableStyle2, children: /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: diffLines.map((line6, i) => /* @__PURE__ */ jsxRuntime.jsxs(
2237
2431
  "tr",
2238
2432
  {
2239
2433
  "aria-label": ARIA_LABELS[line6.kind],
@@ -2499,15 +2693,25 @@ function TreeItem({
2499
2693
  setSize,
2500
2694
  posInSet,
2501
2695
  onFocusChange,
2502
- flatItems
2696
+ flatItems,
2697
+ parentPath,
2698
+ onSelect
2503
2699
  }) {
2504
2700
  const [expanded, setExpanded] = react.useState(defaultExpanded);
2505
2701
  const itemRef = react.useRef(null);
2506
2702
  const isDir = Array.isArray(node.children) && node.children.length > 0;
2507
2703
  const isFocused = flatIndex === focusedIndex;
2704
+ const path = parentPath ? `${parentPath}/${node.name}` : node.name;
2508
2705
  const handleToggle = react.useCallback(() => {
2509
- if (isDir) setExpanded((prev) => !prev);
2510
- }, [isDir]);
2706
+ if (isDir) {
2707
+ setExpanded((prev) => {
2708
+ onSelect?.(path, "directory", !prev);
2709
+ return !prev;
2710
+ });
2711
+ } else {
2712
+ onSelect?.(path, "file");
2713
+ }
2714
+ }, [isDir, path, onSelect]);
2511
2715
  const handleKeyDown = react.useCallback(
2512
2716
  (e) => {
2513
2717
  let handled = true;
@@ -2639,7 +2843,9 @@ function TreeItem({
2639
2843
  setSize: node.children?.length ?? 0,
2640
2844
  posInSet: childIdx + 1,
2641
2845
  onFocusChange,
2642
- flatItems
2846
+ flatItems,
2847
+ parentPath: path,
2848
+ onSelect
2643
2849
  },
2644
2850
  child.name
2645
2851
  );
@@ -2672,7 +2878,11 @@ function getChildFlatIndex(flatItems, parentFlatIndex, childIdx, children) {
2672
2878
  }
2673
2879
  return parentFlatIndex + childIdx + 1;
2674
2880
  }
2675
- function FileTree({ data }) {
2881
+ function FileTree({
2882
+ data,
2883
+ block,
2884
+ onInteraction
2885
+ }) {
2676
2886
  const [focusedIndex, setFocusedIndex] = react.useState(0);
2677
2887
  const containerRef = react.useRef(null);
2678
2888
  const flatItems = flattenTree(data.tree, data.root ? 1 : 0);
@@ -2683,6 +2893,18 @@ function FileTree({ data }) {
2683
2893
  const el = container.querySelector(`[data-flat-index="${String(index)}"]`);
2684
2894
  el?.focus();
2685
2895
  }, []);
2896
+ const handleSelect = react.useCallback(
2897
+ (path, type, expanded) => {
2898
+ onInteraction?.({
2899
+ kind: "filetree-select",
2900
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2901
+ blockId: block.id,
2902
+ blockType: block.type,
2903
+ payload: { path, type, expanded }
2904
+ });
2905
+ },
2906
+ [onInteraction, block.id, block.type]
2907
+ );
2686
2908
  return /* @__PURE__ */ jsxRuntime.jsx(
2687
2909
  "div",
2688
2910
  {
@@ -2739,7 +2961,9 @@ function FileTree({ data }) {
2739
2961
  setSize: data.tree.length,
2740
2962
  posInSet: idx + 1,
2741
2963
  onFocusChange: handleFocusChange,
2742
- flatItems
2964
+ flatItems,
2965
+ parentPath: data.root ?? "",
2966
+ onSelect: handleSelect
2743
2967
  },
2744
2968
  node.name
2745
2969
  );
@@ -2760,7 +2984,9 @@ function FileTree({ data }) {
2760
2984
  setSize: data.tree.length,
2761
2985
  posInSet: idx + 1,
2762
2986
  onFocusChange: handleFocusChange,
2763
- flatItems
2987
+ flatItems,
2988
+ parentPath: "",
2989
+ onSelect: handleSelect
2764
2990
  },
2765
2991
  node.name
2766
2992
  );
@@ -4008,7 +4234,117 @@ function isCorrect(question, selected) {
4008
4234
  }
4009
4235
  }
4010
4236
  }
4011
- function Quiz({ data, block }) {
4237
+ function renderMultipleChoice(question, qIndex, state, updateState, baseId) {
4238
+ const selected = typeof state.selected === "number" ? state.selected : null;
4239
+ const ariaLabel = typeof question.question === "string" ? question.question : "Question";
4240
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "radiogroup", "aria-label": ariaLabel, children: question.options.map((option, oIndex) => {
4241
+ const isSelected = selected === oIndex;
4242
+ const isCorrectOption = state.submitted && oIndex === question.answer;
4243
+ const isIncorrectSelection = state.submitted && isSelected && oIndex !== question.answer;
4244
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4245
+ "label",
4246
+ {
4247
+ style: optionLabelStyle(
4248
+ isSelected,
4249
+ state.submitted,
4250
+ isCorrectOption,
4251
+ isIncorrectSelection
4252
+ ),
4253
+ children: [
4254
+ /* @__PURE__ */ jsxRuntime.jsx(
4255
+ "input",
4256
+ {
4257
+ type: "radio",
4258
+ role: "radio",
4259
+ name: `${baseId}-q${String(qIndex)}`,
4260
+ checked: isSelected,
4261
+ disabled: state.submitted,
4262
+ onChange: () => updateState(qIndex, { selected: oIndex }),
4263
+ "aria-checked": isSelected
4264
+ }
4265
+ ),
4266
+ /* @__PURE__ */ jsxRuntime.jsx(runtime.RichText, { content: option })
4267
+ ]
4268
+ },
4269
+ oIndex
4270
+ );
4271
+ }) });
4272
+ }
4273
+ function renderTrueFalse(question, qIndex, state, updateState, baseId) {
4274
+ const selected = typeof state.selected === "boolean" ? state.selected : null;
4275
+ const ariaLabel = typeof question.question === "string" ? question.question : "Question";
4276
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "radiogroup", "aria-label": ariaLabel, children: [true, false].map((value) => {
4277
+ const isSelected = selected === value;
4278
+ const isCorrectOption = state.submitted && value === question.answer;
4279
+ const isIncorrectSelection = state.submitted && isSelected && value !== question.answer;
4280
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4281
+ "label",
4282
+ {
4283
+ style: optionLabelStyle(
4284
+ isSelected,
4285
+ state.submitted,
4286
+ isCorrectOption,
4287
+ isIncorrectSelection
4288
+ ),
4289
+ children: [
4290
+ /* @__PURE__ */ jsxRuntime.jsx(
4291
+ "input",
4292
+ {
4293
+ type: "radio",
4294
+ role: "radio",
4295
+ name: `${baseId}-q${String(qIndex)}`,
4296
+ checked: isSelected,
4297
+ disabled: state.submitted,
4298
+ onChange: () => updateState(qIndex, { selected: value }),
4299
+ "aria-checked": isSelected
4300
+ }
4301
+ ),
4302
+ value ? "True" : "False"
4303
+ ]
4304
+ },
4305
+ String(value)
4306
+ );
4307
+ }) });
4308
+ }
4309
+ function renderMultiSelect(question, qIndex, state, updateState) {
4310
+ const selected = Array.isArray(state.selected) ? state.selected : [];
4311
+ const toggleOption = (oIndex) => {
4312
+ const next = selected.includes(oIndex) ? selected.filter((v) => v !== oIndex) : [...selected, oIndex];
4313
+ updateState(qIndex, { selected: next });
4314
+ };
4315
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { children: question.options.map((option, oIndex) => {
4316
+ const isSelected = selected.includes(oIndex);
4317
+ const isCorrectOption = state.submitted && question.answer.includes(oIndex);
4318
+ const isIncorrectSelection = state.submitted && isSelected && !question.answer.includes(oIndex);
4319
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4320
+ "label",
4321
+ {
4322
+ style: optionLabelStyle(
4323
+ isSelected,
4324
+ state.submitted,
4325
+ isCorrectOption,
4326
+ isIncorrectSelection
4327
+ ),
4328
+ children: [
4329
+ /* @__PURE__ */ jsxRuntime.jsx(
4330
+ "input",
4331
+ {
4332
+ type: "checkbox",
4333
+ role: "checkbox",
4334
+ checked: isSelected,
4335
+ disabled: state.submitted,
4336
+ onChange: () => toggleOption(oIndex),
4337
+ "aria-checked": isSelected
4338
+ }
4339
+ ),
4340
+ /* @__PURE__ */ jsxRuntime.jsx(runtime.RichText, { content: option })
4341
+ ]
4342
+ },
4343
+ oIndex
4344
+ );
4345
+ }) });
4346
+ }
4347
+ function Quiz({ data, block, onInteraction }) {
4012
4348
  const { questions, showScore = true, title } = data;
4013
4349
  const baseId = `glyph-quiz-${block.id}`;
4014
4350
  const [states, setStates] = react.useState(
@@ -4026,114 +4362,6 @@ function Quiz({ data, block }) {
4026
4362
  return acc + (isCorrect(q, s.selected) ? 1 : 0);
4027
4363
  }, 0);
4028
4364
  const submittedCount = states.filter((s) => s.submitted).length;
4029
- function renderMultipleChoice(question, qIndex, state) {
4030
- const selected = typeof state.selected === "number" ? state.selected : null;
4031
- return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "radiogroup", "aria-label": question.question, children: question.options.map((option, oIndex) => {
4032
- const isSelected = selected === oIndex;
4033
- const isCorrectOption = state.submitted && oIndex === question.answer;
4034
- const isIncorrectSelection = state.submitted && isSelected && oIndex !== question.answer;
4035
- return /* @__PURE__ */ jsxRuntime.jsxs(
4036
- "label",
4037
- {
4038
- style: optionLabelStyle(
4039
- isSelected,
4040
- state.submitted,
4041
- isCorrectOption,
4042
- isIncorrectSelection
4043
- ),
4044
- children: [
4045
- /* @__PURE__ */ jsxRuntime.jsx(
4046
- "input",
4047
- {
4048
- type: "radio",
4049
- role: "radio",
4050
- name: `${baseId}-q${String(qIndex)}`,
4051
- checked: isSelected,
4052
- disabled: state.submitted,
4053
- onChange: () => updateState(qIndex, { selected: oIndex }),
4054
- "aria-checked": isSelected
4055
- }
4056
- ),
4057
- option
4058
- ]
4059
- },
4060
- oIndex
4061
- );
4062
- }) });
4063
- }
4064
- function renderTrueFalse(question, qIndex, state) {
4065
- const selected = typeof state.selected === "boolean" ? state.selected : null;
4066
- return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "radiogroup", "aria-label": question.question, children: [true, false].map((value) => {
4067
- const isSelected = selected === value;
4068
- const isCorrectOption = state.submitted && value === question.answer;
4069
- const isIncorrectSelection = state.submitted && isSelected && value !== question.answer;
4070
- return /* @__PURE__ */ jsxRuntime.jsxs(
4071
- "label",
4072
- {
4073
- style: optionLabelStyle(
4074
- isSelected,
4075
- state.submitted,
4076
- isCorrectOption,
4077
- isIncorrectSelection
4078
- ),
4079
- children: [
4080
- /* @__PURE__ */ jsxRuntime.jsx(
4081
- "input",
4082
- {
4083
- type: "radio",
4084
- role: "radio",
4085
- name: `${baseId}-q${String(qIndex)}`,
4086
- checked: isSelected,
4087
- disabled: state.submitted,
4088
- onChange: () => updateState(qIndex, { selected: value }),
4089
- "aria-checked": isSelected
4090
- }
4091
- ),
4092
- value ? "True" : "False"
4093
- ]
4094
- },
4095
- String(value)
4096
- );
4097
- }) });
4098
- }
4099
- function renderMultiSelect(question, qIndex, state) {
4100
- const selected = Array.isArray(state.selected) ? state.selected : [];
4101
- const toggleOption = (oIndex) => {
4102
- const next = selected.includes(oIndex) ? selected.filter((v) => v !== oIndex) : [...selected, oIndex];
4103
- updateState(qIndex, { selected: next });
4104
- };
4105
- return /* @__PURE__ */ jsxRuntime.jsx("div", { children: question.options.map((option, oIndex) => {
4106
- const isSelected = selected.includes(oIndex);
4107
- const isCorrectOption = state.submitted && question.answer.includes(oIndex);
4108
- const isIncorrectSelection = state.submitted && isSelected && !question.answer.includes(oIndex);
4109
- return /* @__PURE__ */ jsxRuntime.jsxs(
4110
- "label",
4111
- {
4112
- style: optionLabelStyle(
4113
- isSelected,
4114
- state.submitted,
4115
- isCorrectOption,
4116
- isIncorrectSelection
4117
- ),
4118
- children: [
4119
- /* @__PURE__ */ jsxRuntime.jsx(
4120
- "input",
4121
- {
4122
- type: "checkbox",
4123
- role: "checkbox",
4124
- checked: isSelected,
4125
- disabled: state.submitted,
4126
- onChange: () => toggleOption(oIndex),
4127
- "aria-checked": isSelected
4128
- }
4129
- ),
4130
- option
4131
- ]
4132
- },
4133
- oIndex
4134
- );
4135
- }) });
4136
- }
4137
4365
  function renderQuestion(question, qIndex) {
4138
4366
  const state = states[qIndex] ?? { selected: null, submitted: false };
4139
4367
  const isLast = qIndex === questions.length - 1;
@@ -4148,24 +4376,69 @@ function Quiz({ data, block }) {
4148
4376
  children: [
4149
4377
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: questionTextStyle, children: [
4150
4378
  questions.length > 1 ? `${String(qIndex + 1)}. ` : "",
4151
- question.question
4379
+ /* @__PURE__ */ jsxRuntime.jsx(runtime.RichText, { content: question.question })
4152
4380
  ] }),
4153
- question.type === "multiple-choice" && renderMultipleChoice(question, qIndex, state),
4154
- question.type === "true-false" && renderTrueFalse(question, qIndex, state),
4155
- question.type === "multi-select" && renderMultiSelect(question, qIndex, state),
4381
+ question.type === "multiple-choice" && renderMultipleChoice(question, qIndex, state, updateState, baseId),
4382
+ question.type === "true-false" && renderTrueFalse(question, qIndex, state, updateState, baseId),
4383
+ question.type === "multi-select" && renderMultiSelect(question, qIndex, state, updateState),
4156
4384
  !state.submitted && /* @__PURE__ */ jsxRuntime.jsx(
4157
4385
  "button",
4158
4386
  {
4159
4387
  type: "button",
4160
4388
  disabled: !hasSelection,
4161
4389
  style: buttonStyle(!hasSelection),
4162
- onClick: () => updateState(qIndex, { submitted: true }),
4390
+ onClick: () => {
4391
+ updateState(qIndex, { submitted: true });
4392
+ if (onInteraction) {
4393
+ const correct2 = isCorrect(question, state.selected);
4394
+ const newScore = states.reduce((acc, s, i) => {
4395
+ if (i === qIndex) return acc + (correct2 ? 1 : 0);
4396
+ const q = questions[i];
4397
+ if (!q || !s.submitted) return acc;
4398
+ return acc + (isCorrect(q, s.selected) ? 1 : 0);
4399
+ }, 0);
4400
+ let selected;
4401
+ switch (question.type) {
4402
+ case "multiple-choice":
4403
+ if (typeof state.selected === "number") {
4404
+ const opt = question.options[state.selected];
4405
+ selected = [typeof opt === "string" ? opt : String(state.selected)];
4406
+ } else {
4407
+ selected = [];
4408
+ }
4409
+ break;
4410
+ case "true-false":
4411
+ selected = typeof state.selected === "boolean" ? [state.selected ? "True" : "False"] : [];
4412
+ break;
4413
+ case "multi-select":
4414
+ selected = Array.isArray(state.selected) ? state.selected.map((idx) => {
4415
+ const opt = question.options[idx];
4416
+ return typeof opt === "string" ? opt : String(idx);
4417
+ }) : [];
4418
+ break;
4419
+ }
4420
+ const questionText = typeof question.question === "string" ? question.question : "Question";
4421
+ onInteraction({
4422
+ kind: "quiz-submit",
4423
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
4424
+ blockId: block.id,
4425
+ blockType: block.type,
4426
+ payload: {
4427
+ questionIndex: qIndex,
4428
+ question: questionText,
4429
+ selected,
4430
+ correct: correct2,
4431
+ score: { correct: newScore, total: questions.length }
4432
+ }
4433
+ });
4434
+ }
4435
+ },
4163
4436
  children: "Submit"
4164
4437
  }
4165
4438
  ),
4166
4439
  /* @__PURE__ */ jsxRuntime.jsxs("div", { "aria-live": "polite", children: [
4167
4440
  state.submitted && /* @__PURE__ */ jsxRuntime.jsx("div", { style: feedbackStyle(correct), children: correct ? "Correct!" : "Incorrect" }),
4168
- state.submitted && question.explanation && /* @__PURE__ */ jsxRuntime.jsx("div", { style: explanationStyle, children: question.explanation })
4441
+ state.submitted && question.explanation && /* @__PURE__ */ jsxRuntime.jsx("div", { style: explanationStyle, children: /* @__PURE__ */ jsxRuntime.jsx(runtime.RichText, { content: question.explanation }) })
4169
4442
  ] })
4170
4443
  ]
4171
4444
  },
@@ -4222,7 +4495,7 @@ function Card({ data, block, container }) {
4222
4495
  default:
4223
4496
  colCount = authorCols;
4224
4497
  }
4225
- const containerStyle3 = {
4498
+ const containerStyle11 = {
4226
4499
  fontFamily: "var(--glyph-font-body, system-ui, sans-serif)",
4227
4500
  color: "var(--glyph-text, #1a2035)"
4228
4501
  };
@@ -4267,7 +4540,7 @@ function Card({ data, block, container }) {
4267
4540
  color: "var(--glyph-text-muted, #6b7a94)",
4268
4541
  marginTop: "var(--glyph-spacing-xs, 0.25rem)"
4269
4542
  };
4270
- const bodyStyle2 = {
4543
+ const bodyStyle3 = {
4271
4544
  fontSize: "0.875rem",
4272
4545
  lineHeight: 1.6,
4273
4546
  marginTop: "var(--glyph-spacing-sm, 0.5rem)",
@@ -4285,7 +4558,7 @@ function Card({ data, block, container }) {
4285
4558
  color: "var(--glyph-link, #0a9d7c)",
4286
4559
  textDecoration: "none"
4287
4560
  };
4288
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { id: baseId, role: "region", "aria-label": title ?? "Cards", style: containerStyle3, children: [
4561
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { id: baseId, role: "region", "aria-label": title ?? "Cards", style: containerStyle11, children: [
4289
4562
  title && /* @__PURE__ */ jsxRuntime.jsx(
4290
4563
  "div",
4291
4564
  {
@@ -4303,8 +4576,8 @@ function Card({ data, block, container }) {
4303
4576
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: cardBodyStyle, children: [
4304
4577
  card.icon && /* @__PURE__ */ jsxRuntime.jsx("div", { style: iconStyle, children: card.icon }),
4305
4578
  /* @__PURE__ */ jsxRuntime.jsx("h3", { style: titleStyle2, children: card.title }),
4306
- card.subtitle && /* @__PURE__ */ jsxRuntime.jsx("div", { style: subtitleStyle, children: card.subtitle }),
4307
- card.body && /* @__PURE__ */ jsxRuntime.jsx("div", { style: bodyStyle2, children: card.body }),
4579
+ card.subtitle && /* @__PURE__ */ jsxRuntime.jsx("div", { style: subtitleStyle, children: /* @__PURE__ */ jsxRuntime.jsx(runtime.RichText, { content: card.subtitle }) }),
4580
+ card.body && /* @__PURE__ */ jsxRuntime.jsx("div", { style: bodyStyle3, children: /* @__PURE__ */ jsxRuntime.jsx(runtime.RichText, { content: card.body }) }),
4308
4581
  card.actions && card.actions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: actionsStyle, children: card.actions.map((action, j) => /* @__PURE__ */ jsxRuntime.jsx(
4309
4582
  "a",
4310
4583
  {
@@ -4394,7 +4667,7 @@ function renderStatGroup(items, keyPrefix) {
4394
4667
  color: "var(--glyph-infographic-value-color, #1d4ed8)",
4395
4668
  lineHeight: 1.2
4396
4669
  };
4397
- const labelStyle3 = {
4670
+ const labelStyle4 = {
4398
4671
  fontSize: "0.8125rem",
4399
4672
  color: "var(--glyph-infographic-label-color, #475569)",
4400
4673
  marginTop: "var(--glyph-spacing-xs, 0.25rem)",
@@ -4410,8 +4683,8 @@ function renderStatGroup(items, keyPrefix) {
4410
4683
  };
4411
4684
  return /* @__PURE__ */ jsxRuntime.jsx("div", { style: rowStyle, "data-group": "stat", children: items.map((item, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { style: statStyle, children: [
4412
4685
  /* @__PURE__ */ jsxRuntime.jsx("div", { style: valueStyle, children: item.value }),
4413
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: labelStyle3, children: item.label }),
4414
- item.description && /* @__PURE__ */ jsxRuntime.jsx("div", { style: descStyle, children: item.description })
4686
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: labelStyle4, children: item.label }),
4687
+ item.description && /* @__PURE__ */ jsxRuntime.jsx("div", { style: descStyle, children: /* @__PURE__ */ jsxRuntime.jsx(runtime.RichText, { content: item.description }) })
4415
4688
  ] }, `${keyPrefix}-${String(i)}`)) }, keyPrefix);
4416
4689
  }
4417
4690
  function renderProgressGroup(items, keyPrefix, colorOffset) {
@@ -4481,18 +4754,18 @@ function renderProgressGroup(items, keyPrefix, colorOffset) {
4481
4754
  }) }, keyPrefix);
4482
4755
  }
4483
4756
  function renderFactGroup(items, keyPrefix) {
4484
- const listStyle = {
4757
+ const listStyle2 = {
4485
4758
  listStyle: "none",
4486
4759
  margin: 0,
4487
4760
  padding: 0
4488
4761
  };
4489
- const itemStyle2 = {
4762
+ const itemStyle4 = {
4490
4763
  padding: "var(--glyph-spacing-xs, 0.25rem) 0",
4491
4764
  fontSize: "0.875rem",
4492
4765
  color: "var(--glyph-text, #1a2035)",
4493
4766
  fontWeight: 500
4494
4767
  };
4495
- return /* @__PURE__ */ jsxRuntime.jsx("ul", { style: listStyle, "data-group": "fact", children: items.map((item, i) => /* @__PURE__ */ jsxRuntime.jsxs("li", { style: itemStyle2, children: [
4768
+ return /* @__PURE__ */ jsxRuntime.jsx("ul", { style: listStyle2, "data-group": "fact", children: items.map((item, i) => /* @__PURE__ */ jsxRuntime.jsxs("li", { style: itemStyle4, children: [
4496
4769
  item.icon && /* @__PURE__ */ jsxRuntime.jsx(
4497
4770
  "span",
4498
4771
  {
@@ -4504,7 +4777,7 @@ function renderFactGroup(items, keyPrefix) {
4504
4777
  children: item.icon
4505
4778
  }
4506
4779
  ),
4507
- item.text
4780
+ /* @__PURE__ */ jsxRuntime.jsx(runtime.RichText, { content: item.text })
4508
4781
  ] }, `${keyPrefix}-${String(i)}`)) }, keyPrefix);
4509
4782
  }
4510
4783
  function renderTextGroup(items, keyPrefix) {
@@ -4769,7 +5042,7 @@ function Infographic({
4769
5042
  const baseId = `glyph-infographic-${block.id}`;
4770
5043
  const useGrid = sections.length >= 2 && container.tier !== "compact";
4771
5044
  const sectionLayouts = react.useMemo(() => computeSectionLayout(sections), [sections]);
4772
- const containerStyle3 = {
5045
+ const containerStyle11 = {
4773
5046
  fontFamily: "var(--glyph-font-body, system-ui, sans-serif)",
4774
5047
  color: "var(--glyph-text, #1a2035)",
4775
5048
  background: "var(--glyph-surface, #e8ecf3)",
@@ -4814,7 +5087,7 @@ function Infographic({
4814
5087
  };
4815
5088
  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; } }` : "";
4816
5089
  let progressColorOffset = 0;
4817
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { id: baseId, role: "region", "aria-label": title ?? "Infographic", style: containerStyle3, children: [
5090
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { id: baseId, role: "region", "aria-label": title ?? "Infographic", style: containerStyle11, children: [
4818
5091
  printCss && /* @__PURE__ */ jsxRuntime.jsx("style", { children: printCss }),
4819
5092
  /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-layout": useGrid ? "grid" : "stack", style: useGrid ? sectionsGridStyle : void 0, children: [
4820
5093
  title && /* @__PURE__ */ jsxRuntime.jsx("div", { style: titleStyle2, children: title }),
@@ -4875,7 +5148,1751 @@ var infographicDefinition = {
4875
5148
  render: Infographic
4876
5149
  };
4877
5150
 
5151
+ // src/poll/styles.ts
5152
+ var containerStyle3 = {
5153
+ fontFamily: "var(--glyph-font-body, system-ui, sans-serif)",
5154
+ color: "var(--glyph-text, #1a2035)",
5155
+ border: "1px solid var(--glyph-border, #d0d8e4)",
5156
+ borderRadius: "var(--glyph-radius-md, 0.5rem)",
5157
+ overflow: "hidden"
5158
+ };
5159
+ var headerStyle2 = {
5160
+ fontWeight: 700,
5161
+ fontSize: "1.125rem",
5162
+ padding: "var(--glyph-spacing-md, 1rem)",
5163
+ borderBottom: "1px solid var(--glyph-border, #d0d8e4)",
5164
+ color: "var(--glyph-heading, #0a0e1a)"
5165
+ };
5166
+ var questionStyle = {
5167
+ fontWeight: 600,
5168
+ fontSize: "0.9375rem",
5169
+ padding: "var(--glyph-spacing-md, 1rem)",
5170
+ paddingBottom: "0.5rem"
5171
+ };
5172
+ var optionsStyle = {
5173
+ padding: "0 var(--glyph-spacing-md, 1rem)",
5174
+ paddingBottom: "var(--glyph-spacing-md, 1rem)"
5175
+ };
5176
+ function optionLabelStyle2(selected) {
5177
+ return {
5178
+ display: "flex",
5179
+ alignItems: "center",
5180
+ gap: "0.5rem",
5181
+ padding: "0.5rem 0.75rem",
5182
+ marginBottom: "0.375rem",
5183
+ borderRadius: "var(--glyph-radius-md, 0.5rem)",
5184
+ cursor: "pointer",
5185
+ background: selected ? "var(--glyph-surface, #e8ecf3)" : "transparent",
5186
+ border: "1px solid",
5187
+ borderColor: selected ? "var(--glyph-border, #d0d8e4)" : "transparent",
5188
+ fontSize: "0.875rem",
5189
+ lineHeight: 1.6
5190
+ };
5191
+ }
5192
+ var voteButtonStyle = {
5193
+ margin: "0 var(--glyph-spacing-md, 1rem) var(--glyph-spacing-md, 1rem)",
5194
+ padding: "0.5rem 1.25rem",
5195
+ borderRadius: "var(--glyph-radius-md, 0.5rem)",
5196
+ border: "1px solid var(--glyph-border, #d0d8e4)",
5197
+ background: "var(--glyph-surface, #e8ecf3)",
5198
+ color: "var(--glyph-text, #1a2035)",
5199
+ cursor: "pointer",
5200
+ fontWeight: 600,
5201
+ fontSize: "0.875rem"
5202
+ };
5203
+ var resultsStyle = {
5204
+ padding: "var(--glyph-spacing-md, 1rem)",
5205
+ borderTop: "1px solid var(--glyph-border, #d0d8e4)"
5206
+ };
5207
+ var resultRowStyle = {
5208
+ marginBottom: "0.5rem"
5209
+ };
5210
+ var resultLabelStyle = {
5211
+ display: "flex",
5212
+ justifyContent: "space-between",
5213
+ fontSize: "0.8125rem",
5214
+ marginBottom: "0.25rem"
5215
+ };
5216
+ var barTrackStyle = {
5217
+ height: "0.5rem",
5218
+ borderRadius: "0.25rem",
5219
+ background: "var(--glyph-poll-bar-bg, var(--glyph-surface, #e8ecf3))",
5220
+ overflow: "hidden"
5221
+ };
5222
+ function barFillStyle(percentage) {
5223
+ return {
5224
+ height: "100%",
5225
+ width: `${String(percentage)}%`,
5226
+ borderRadius: "0.25rem",
5227
+ background: "var(--glyph-poll-bar-fill, var(--glyph-accent, #0a9d7c))",
5228
+ transition: "width 0.3s ease"
5229
+ };
5230
+ }
5231
+ function Poll({ data, block, onInteraction }) {
5232
+ const { question, options, multiple = false, showResults = true, title } = data;
5233
+ const baseId = `glyph-poll-${block.id}`;
5234
+ const [selected, setSelected] = react.useState([]);
5235
+ const [votes, setVotes] = react.useState(() => options.map(() => 0));
5236
+ const [hasVoted, setHasVoted] = react.useState(false);
5237
+ const toggleOption = (index) => {
5238
+ if (hasVoted) return;
5239
+ if (multiple) {
5240
+ setSelected(
5241
+ (prev) => prev.includes(index) ? prev.filter((i) => i !== index) : [...prev, index]
5242
+ );
5243
+ } else {
5244
+ setSelected([index]);
5245
+ }
5246
+ };
5247
+ const handleVote = () => {
5248
+ if (selected.length === 0 || hasVoted) return;
5249
+ const newVotes = [...votes];
5250
+ for (const idx of selected) {
5251
+ newVotes[idx] = (newVotes[idx] ?? 0) + 1;
5252
+ }
5253
+ setVotes(newVotes);
5254
+ setHasVoted(true);
5255
+ if (onInteraction) {
5256
+ onInteraction({
5257
+ kind: "poll-vote",
5258
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
5259
+ blockId: block.id,
5260
+ blockType: block.type,
5261
+ payload: {
5262
+ selectedOptions: selected.map((i) => {
5263
+ const opt = options[i];
5264
+ return typeof opt === "string" ? opt : String(i);
5265
+ }),
5266
+ selectedIndices: [...selected]
5267
+ }
5268
+ });
5269
+ }
5270
+ };
5271
+ const totalVotes = votes.reduce((a, b) => a + b, 0);
5272
+ const questionAriaLabel = typeof question === "string" ? question : "Poll question";
5273
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { id: baseId, role: "region", "aria-label": title ?? "Poll", style: containerStyle3, children: [
5274
+ title && /* @__PURE__ */ jsxRuntime.jsx("div", { style: headerStyle2, children: title }),
5275
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: questionStyle, children: /* @__PURE__ */ jsxRuntime.jsx(runtime.RichText, { content: question }) }),
5276
+ /* @__PURE__ */ jsxRuntime.jsx("div", { role: "group", "aria-label": questionAriaLabel, style: optionsStyle, children: options.map((option, index) => /* @__PURE__ */ jsxRuntime.jsxs("label", { style: optionLabelStyle2(selected.includes(index)), children: [
5277
+ /* @__PURE__ */ jsxRuntime.jsx(
5278
+ "input",
5279
+ {
5280
+ type: multiple ? "checkbox" : "radio",
5281
+ name: `${baseId}-option`,
5282
+ checked: selected.includes(index),
5283
+ disabled: hasVoted,
5284
+ onChange: () => toggleOption(index),
5285
+ "aria-checked": selected.includes(index)
5286
+ }
5287
+ ),
5288
+ /* @__PURE__ */ jsxRuntime.jsx(runtime.RichText, { content: option })
5289
+ ] }, index)) }),
5290
+ !hasVoted && /* @__PURE__ */ jsxRuntime.jsx(
5291
+ "button",
5292
+ {
5293
+ type: "button",
5294
+ disabled: selected.length === 0,
5295
+ style: {
5296
+ ...voteButtonStyle,
5297
+ opacity: selected.length === 0 ? 0.5 : 1,
5298
+ cursor: selected.length === 0 ? "not-allowed" : "pointer"
5299
+ },
5300
+ onClick: handleVote,
5301
+ children: "Vote"
5302
+ }
5303
+ ),
5304
+ showResults && hasVoted && /* @__PURE__ */ jsxRuntime.jsx("div", { role: "status", "aria-live": "polite", style: resultsStyle, children: options.map((option, index) => {
5305
+ const count = votes[index] ?? 0;
5306
+ const percentage = totalVotes > 0 ? count / totalVotes * 100 : 0;
5307
+ const optionLabel = typeof option === "string" ? option : "Option";
5308
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: resultRowStyle, children: [
5309
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: resultLabelStyle, children: [
5310
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: /* @__PURE__ */ jsxRuntime.jsx(runtime.RichText, { content: option }) }),
5311
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
5312
+ String(count),
5313
+ " vote",
5314
+ count !== 1 ? "s" : "",
5315
+ " (",
5316
+ String(Math.round(percentage)),
5317
+ "%)"
5318
+ ] })
5319
+ ] }),
5320
+ /* @__PURE__ */ jsxRuntime.jsx(
5321
+ "div",
5322
+ {
5323
+ style: barTrackStyle,
5324
+ role: "progressbar",
5325
+ "aria-valuenow": percentage,
5326
+ "aria-valuemin": 0,
5327
+ "aria-valuemax": 100,
5328
+ "aria-label": `${optionLabel}: ${String(Math.round(percentage))}%`,
5329
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: barFillStyle(percentage) })
5330
+ }
5331
+ )
5332
+ ] }, index);
5333
+ }) })
5334
+ ] });
5335
+ }
5336
+
5337
+ // src/poll/index.ts
5338
+ var pollDefinition = {
5339
+ type: "ui:poll",
5340
+ schema: schemas.pollSchema,
5341
+ render: Poll
5342
+ };
5343
+
5344
+ // src/rating/styles.ts
5345
+ var containerStyle4 = {
5346
+ fontFamily: "var(--glyph-font-body, system-ui, sans-serif)",
5347
+ color: "var(--glyph-text, #1a2035)",
5348
+ border: "1px solid var(--glyph-border, #d0d8e4)",
5349
+ borderRadius: "var(--glyph-radius-md, 0.5rem)",
5350
+ overflow: "hidden"
5351
+ };
5352
+ var headerStyle3 = {
5353
+ fontWeight: 700,
5354
+ fontSize: "1.125rem",
5355
+ padding: "var(--glyph-spacing-md, 1rem)",
5356
+ borderBottom: "1px solid var(--glyph-border, #d0d8e4)",
5357
+ color: "var(--glyph-heading, #0a0e1a)"
5358
+ };
5359
+ function itemStyle2(isLast) {
5360
+ return {
5361
+ padding: "var(--glyph-spacing-md, 1rem)",
5362
+ borderBottom: isLast ? "none" : "1px solid var(--glyph-border, #d0d8e4)"
5363
+ };
5364
+ }
5365
+ var itemLabelStyle = {
5366
+ fontWeight: 600,
5367
+ fontSize: "0.9375rem",
5368
+ marginBottom: "0.25rem"
5369
+ };
5370
+ var itemDescriptionStyle = {
5371
+ fontSize: "0.8125rem",
5372
+ color: "var(--glyph-text-muted, #6b7a94)",
5373
+ marginBottom: "0.5rem"
5374
+ };
5375
+ var starsContainerStyle = {
5376
+ display: "flex",
5377
+ gap: "0.25rem",
5378
+ alignItems: "center"
5379
+ };
5380
+ function starButtonStyle(filled, hovered) {
5381
+ return {
5382
+ background: "none",
5383
+ border: "none",
5384
+ padding: "0.125rem",
5385
+ cursor: "pointer",
5386
+ fontSize: "1.25rem",
5387
+ lineHeight: 1,
5388
+ color: filled || hovered ? "var(--glyph-rating-star-fill, #f59e0b)" : "var(--glyph-rating-star-empty, var(--glyph-border, #d0d8e4))",
5389
+ transition: "color 0.15s ease"
5390
+ };
5391
+ }
5392
+ function numberButtonStyle(selected) {
5393
+ return {
5394
+ minWidth: "2rem",
5395
+ height: "2rem",
5396
+ display: "flex",
5397
+ alignItems: "center",
5398
+ justifyContent: "center",
5399
+ borderRadius: "var(--glyph-radius-sm, 0.375rem)",
5400
+ border: "1px solid var(--glyph-border, #d0d8e4)",
5401
+ background: selected ? "var(--glyph-accent, #0a9d7c)" : "transparent",
5402
+ color: selected ? "#fff" : "var(--glyph-text, #1a2035)",
5403
+ cursor: "pointer",
5404
+ fontWeight: 600,
5405
+ fontSize: "0.875rem",
5406
+ transition: "background 0.15s ease, color 0.15s ease"
5407
+ };
5408
+ }
5409
+ var scaleLabelsStyle = {
5410
+ display: "flex",
5411
+ justifyContent: "space-between",
5412
+ fontSize: "0.75rem",
5413
+ color: "var(--glyph-text-muted, #6b7a94)",
5414
+ marginTop: "0.375rem"
5415
+ };
5416
+ function Rating({
5417
+ data,
5418
+ block,
5419
+ onInteraction
5420
+ }) {
5421
+ const { title, scale = 5, mode = "star", labels, items } = data;
5422
+ const baseId = `glyph-rating-${block.id}`;
5423
+ const [ratings, setRatings] = react.useState(() => items.map(() => null));
5424
+ const [hoveredStar, setHoveredStar] = react.useState(null);
5425
+ const handleRate = (itemIndex, value) => {
5426
+ const newRatings = [...ratings];
5427
+ newRatings[itemIndex] = value;
5428
+ setRatings(newRatings);
5429
+ if (onInteraction) {
5430
+ const item = items[itemIndex];
5431
+ const itemLabel = item ? typeof item.label === "string" ? item.label : "Item" : "";
5432
+ onInteraction({
5433
+ kind: "rating-change",
5434
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
5435
+ blockId: block.id,
5436
+ blockType: block.type,
5437
+ payload: {
5438
+ itemIndex,
5439
+ itemLabel,
5440
+ value,
5441
+ allRatings: items.map((item2, i) => ({
5442
+ label: typeof item2.label === "string" ? item2.label : "Item",
5443
+ value: i === itemIndex ? value : newRatings[i] ?? null
5444
+ }))
5445
+ }
5446
+ });
5447
+ }
5448
+ };
5449
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { id: baseId, role: "region", "aria-label": title ?? "Rating", style: containerStyle4, children: [
5450
+ title && /* @__PURE__ */ jsxRuntime.jsx("div", { style: headerStyle3, children: title }),
5451
+ items.map((item, itemIndex) => {
5452
+ const currentRating = ratings[itemIndex] ?? null;
5453
+ const isLast = itemIndex === items.length - 1;
5454
+ const itemLabelText = typeof item.label === "string" ? item.label : "Item";
5455
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: itemStyle2(isLast), children: [
5456
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: itemLabelStyle, children: /* @__PURE__ */ jsxRuntime.jsx(runtime.RichText, { content: item.label }) }),
5457
+ item.description && /* @__PURE__ */ jsxRuntime.jsx("div", { style: itemDescriptionStyle, children: /* @__PURE__ */ jsxRuntime.jsx(runtime.RichText, { content: item.description }) }),
5458
+ /* @__PURE__ */ jsxRuntime.jsx("div", { role: "radiogroup", "aria-label": `Rate ${itemLabelText}`, style: starsContainerStyle, children: Array.from({ length: scale }, (_, starIndex) => {
5459
+ const value = starIndex + 1;
5460
+ const isHovered = hoveredStar !== null && hoveredStar.itemIndex === itemIndex && value <= hoveredStar.value;
5461
+ const isFilled = currentRating !== null && value <= currentRating;
5462
+ if (mode === "number") {
5463
+ return /* @__PURE__ */ jsxRuntime.jsx(
5464
+ "button",
5465
+ {
5466
+ type: "button",
5467
+ role: "radio",
5468
+ "aria-checked": currentRating === value,
5469
+ "aria-label": `${String(value)} out of ${String(scale)}`,
5470
+ style: numberButtonStyle(currentRating === value),
5471
+ onClick: () => handleRate(itemIndex, value),
5472
+ children: String(value)
5473
+ },
5474
+ starIndex
5475
+ );
5476
+ }
5477
+ return /* @__PURE__ */ jsxRuntime.jsx(
5478
+ "button",
5479
+ {
5480
+ type: "button",
5481
+ role: "radio",
5482
+ "aria-checked": currentRating === value,
5483
+ "aria-label": `${String(value)} out of ${String(scale)} stars`,
5484
+ style: starButtonStyle(isFilled, isHovered),
5485
+ onClick: () => handleRate(itemIndex, value),
5486
+ onMouseEnter: () => setHoveredStar({ itemIndex, value }),
5487
+ onMouseLeave: () => setHoveredStar(null),
5488
+ children: "\u2605"
5489
+ },
5490
+ starIndex
5491
+ );
5492
+ }) }),
5493
+ labels && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: scaleLabelsStyle, children: [
5494
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: labels.low }),
5495
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: labels.high })
5496
+ ] }),
5497
+ /* @__PURE__ */ jsxRuntime.jsx(
5498
+ "div",
5499
+ {
5500
+ "aria-live": "polite",
5501
+ style: {
5502
+ position: "absolute",
5503
+ width: "1px",
5504
+ height: "1px",
5505
+ padding: 0,
5506
+ margin: "-1px",
5507
+ overflow: "hidden",
5508
+ clip: "rect(0,0,0,0)",
5509
+ whiteSpace: "nowrap",
5510
+ border: 0
5511
+ },
5512
+ children: currentRating !== null && `${itemLabelText} rated ${String(currentRating)} out of ${String(scale)}`
5513
+ }
5514
+ )
5515
+ ] }, itemIndex);
5516
+ })
5517
+ ] });
5518
+ }
5519
+
5520
+ // src/rating/index.ts
5521
+ var ratingDefinition = {
5522
+ type: "ui:rating",
5523
+ schema: schemas.ratingSchema,
5524
+ render: Rating
5525
+ };
5526
+
5527
+ // src/ranker/styles.ts
5528
+ var containerStyle5 = {
5529
+ fontFamily: "var(--glyph-font-body, system-ui, sans-serif)",
5530
+ color: "var(--glyph-text, #1a2035)",
5531
+ border: "1px solid var(--glyph-border, #d0d8e4)",
5532
+ borderRadius: "var(--glyph-radius-md, 0.5rem)",
5533
+ overflow: "hidden"
5534
+ };
5535
+ var headerStyle4 = {
5536
+ fontWeight: 700,
5537
+ fontSize: "1.125rem",
5538
+ padding: "var(--glyph-spacing-md, 1rem)",
5539
+ borderBottom: "1px solid var(--glyph-border, #d0d8e4)",
5540
+ color: "var(--glyph-heading, #0a0e1a)"
5541
+ };
5542
+ var listStyle = {
5543
+ listStyle: "none",
5544
+ margin: 0,
5545
+ padding: 0
5546
+ };
5547
+ function itemStyle3(isDragging, isGrabbed) {
5548
+ return {
5549
+ display: "flex",
5550
+ alignItems: "center",
5551
+ gap: "0.75rem",
5552
+ padding: "0.75rem var(--glyph-spacing-md, 1rem)",
5553
+ borderBottom: "1px solid var(--glyph-border, #d0d8e4)",
5554
+ background: isDragging ? "var(--glyph-accent-subtle, #e6f6f2)" : "transparent",
5555
+ cursor: isGrabbed ? "grabbing" : "grab",
5556
+ userSelect: "none",
5557
+ transition: "background 0.15s ease",
5558
+ outline: isGrabbed ? "2px solid var(--glyph-accent, #0a9d7c)" : "none",
5559
+ outlineOffset: "-2px"
5560
+ };
5561
+ }
5562
+ var rankBadgeStyle = {
5563
+ minWidth: "1.75rem",
5564
+ height: "1.75rem",
5565
+ display: "flex",
5566
+ alignItems: "center",
5567
+ justifyContent: "center",
5568
+ borderRadius: "50%",
5569
+ background: "var(--glyph-surface, #e8ecf3)",
5570
+ fontWeight: 700,
5571
+ fontSize: "0.8125rem",
5572
+ color: "var(--glyph-text-muted, #6b7a94)",
5573
+ flexShrink: 0
5574
+ };
5575
+ var itemContentStyle = {
5576
+ flex: 1,
5577
+ minWidth: 0
5578
+ };
5579
+ var itemLabelStyle2 = {
5580
+ fontWeight: 600,
5581
+ fontSize: "0.9375rem"
5582
+ };
5583
+ var itemDescriptionStyle2 = {
5584
+ fontSize: "0.8125rem",
5585
+ color: "var(--glyph-text-muted, #6b7a94)",
5586
+ marginTop: "0.125rem"
5587
+ };
5588
+ var gripStyle = {
5589
+ color: "var(--glyph-text-muted, #6b7a94)",
5590
+ fontSize: "1rem",
5591
+ flexShrink: 0
5592
+ };
5593
+ function Ranker({
5594
+ data,
5595
+ block,
5596
+ onInteraction
5597
+ }) {
5598
+ const { title, items: initialItems } = data;
5599
+ const baseId = `glyph-ranker-${block.id}`;
5600
+ const [items, setItems] = react.useState(initialItems);
5601
+ const [grabbedIndex, setGrabbedIndex] = react.useState(null);
5602
+ const moveItem = react.useCallback(
5603
+ (fromIndex, toIndex) => {
5604
+ if (fromIndex === toIndex) return;
5605
+ setItems((prevItems) => {
5606
+ const newItems = [...prevItems];
5607
+ const [moved] = newItems.splice(fromIndex, 1);
5608
+ if (!moved) return prevItems;
5609
+ newItems.splice(toIndex, 0, moved);
5610
+ if (onInteraction) {
5611
+ onInteraction({
5612
+ kind: "ranker-reorder",
5613
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
5614
+ blockId: block.id,
5615
+ blockType: block.type,
5616
+ payload: {
5617
+ orderedItems: newItems.map((item, i) => ({
5618
+ id: item.id,
5619
+ label: typeof item.label === "string" ? item.label : "Item",
5620
+ rank: i + 1
5621
+ })),
5622
+ movedItem: {
5623
+ id: moved.id,
5624
+ label: typeof moved.label === "string" ? moved.label : "Item",
5625
+ fromRank: fromIndex + 1,
5626
+ toRank: toIndex + 1
5627
+ }
5628
+ }
5629
+ });
5630
+ }
5631
+ return newItems;
5632
+ });
5633
+ },
5634
+ [block.id, block.type, onInteraction]
5635
+ );
5636
+ const handleKeyDown = (e, index) => {
5637
+ if (e.key === " " || e.key === "Enter") {
5638
+ e.preventDefault();
5639
+ if (grabbedIndex === null) {
5640
+ setGrabbedIndex(index);
5641
+ } else {
5642
+ setGrabbedIndex(null);
5643
+ }
5644
+ } else if (e.key === "Escape") {
5645
+ setGrabbedIndex(null);
5646
+ } else if (e.key === "ArrowUp" && grabbedIndex !== null) {
5647
+ e.preventDefault();
5648
+ if (grabbedIndex > 0) {
5649
+ moveItem(grabbedIndex, grabbedIndex - 1);
5650
+ setGrabbedIndex(grabbedIndex - 1);
5651
+ }
5652
+ } else if (e.key === "ArrowDown" && grabbedIndex !== null) {
5653
+ e.preventDefault();
5654
+ if (grabbedIndex < items.length - 1) {
5655
+ moveItem(grabbedIndex, grabbedIndex + 1);
5656
+ setGrabbedIndex(grabbedIndex + 1);
5657
+ }
5658
+ }
5659
+ };
5660
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { id: baseId, role: "region", "aria-label": title ?? "Ranker", style: containerStyle5, children: [
5661
+ title && /* @__PURE__ */ jsxRuntime.jsx("div", { style: headerStyle4, children: title }),
5662
+ /* @__PURE__ */ jsxRuntime.jsx("ul", { role: "list", "aria-label": title ?? "Rank items", style: listStyle, children: items.map((item, index) => {
5663
+ const itemLabelText = typeof item.label === "string" ? item.label : "Item";
5664
+ return /* @__PURE__ */ jsxRuntime.jsxs(
5665
+ "li",
5666
+ {
5667
+ role: "listitem",
5668
+ "aria-grabbed": grabbedIndex === index,
5669
+ "aria-label": `${itemLabelText}, rank ${String(index + 1)}`,
5670
+ tabIndex: 0,
5671
+ style: itemStyle3(false, grabbedIndex === index),
5672
+ onKeyDown: (e) => handleKeyDown(e, index),
5673
+ children: [
5674
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: gripStyle, "aria-hidden": "true", children: "\u283F" }),
5675
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: rankBadgeStyle, children: String(index + 1) }),
5676
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: itemContentStyle, children: [
5677
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: itemLabelStyle2, children: /* @__PURE__ */ jsxRuntime.jsx(runtime.RichText, { content: item.label }) }),
5678
+ item.description && /* @__PURE__ */ jsxRuntime.jsx("div", { style: itemDescriptionStyle2, children: item.description })
5679
+ ] })
5680
+ ]
5681
+ },
5682
+ item.id
5683
+ );
5684
+ }) }),
5685
+ /* @__PURE__ */ jsxRuntime.jsx(
5686
+ "div",
5687
+ {
5688
+ "aria-live": "assertive",
5689
+ style: {
5690
+ position: "absolute",
5691
+ width: "1px",
5692
+ height: "1px",
5693
+ padding: 0,
5694
+ margin: "-1px",
5695
+ overflow: "hidden",
5696
+ clip: "rect(0,0,0,0)",
5697
+ whiteSpace: "nowrap",
5698
+ border: 0
5699
+ },
5700
+ children: grabbedIndex !== null && items[grabbedIndex] !== void 0 ? `${typeof items[grabbedIndex].label === "string" ? items[grabbedIndex].label : "Item"} grabbed, rank ${String(grabbedIndex + 1)} of ${String(items.length)}. Use arrow keys to move.` : ""
5701
+ }
5702
+ )
5703
+ ] });
5704
+ }
5705
+
5706
+ // src/ranker/index.ts
5707
+ var rankerDefinition = {
5708
+ type: "ui:ranker",
5709
+ schema: schemas.rankerSchema,
5710
+ render: Ranker
5711
+ };
5712
+
5713
+ // src/slider/styles.ts
5714
+ var containerStyle6 = {
5715
+ fontFamily: "var(--glyph-font-body, system-ui, sans-serif)",
5716
+ color: "var(--glyph-text, #1a2035)",
5717
+ border: "1px solid var(--glyph-border, #d0d8e4)",
5718
+ borderRadius: "var(--glyph-radius-md, 0.5rem)",
5719
+ overflow: "hidden"
5720
+ };
5721
+ var headerStyle5 = {
5722
+ fontWeight: 700,
5723
+ fontSize: "1.125rem",
5724
+ padding: "var(--glyph-spacing-md, 1rem)",
5725
+ borderBottom: "1px solid var(--glyph-border, #d0d8e4)",
5726
+ color: "var(--glyph-heading, #0a0e1a)"
5727
+ };
5728
+ function parameterStyle(isLast) {
5729
+ return {
5730
+ padding: "var(--glyph-spacing-md, 1rem)",
5731
+ borderBottom: isLast ? "none" : "1px solid var(--glyph-border, #d0d8e4)"
5732
+ };
5733
+ }
5734
+ var parameterHeaderStyle = {
5735
+ display: "flex",
5736
+ justifyContent: "space-between",
5737
+ alignItems: "center",
5738
+ marginBottom: "0.5rem"
5739
+ };
5740
+ var parameterLabelStyle = {
5741
+ fontWeight: 600,
5742
+ fontSize: "0.9375rem"
5743
+ };
5744
+ var parameterValueStyle = {
5745
+ fontSize: "0.9375rem",
5746
+ fontWeight: 600,
5747
+ color: "var(--glyph-accent, #0a9d7c)",
5748
+ fontVariantNumeric: "tabular-nums"
5749
+ };
5750
+ var rangeInputStyle = {
5751
+ width: "100%",
5752
+ margin: 0,
5753
+ accentColor: "var(--glyph-slider-fill, var(--glyph-accent, #0a9d7c))"
5754
+ };
5755
+ var rangeLabelsStyle = {
5756
+ display: "flex",
5757
+ justifyContent: "space-between",
5758
+ fontSize: "0.75rem",
5759
+ color: "var(--glyph-text-muted, #6b7a94)",
5760
+ marginTop: "0.25rem"
5761
+ };
5762
+ function Slider({
5763
+ data,
5764
+ block,
5765
+ onInteraction
5766
+ }) {
5767
+ const { title, parameters } = data;
5768
+ const baseId = `glyph-slider-${block.id}`;
5769
+ const [values, setValues] = react.useState(
5770
+ () => parameters.map((p) => p.value ?? p.min ?? 0)
5771
+ );
5772
+ const handleChange = (paramIndex, newValue) => {
5773
+ const newValues = [...values];
5774
+ newValues[paramIndex] = newValue;
5775
+ setValues(newValues);
5776
+ const param = parameters[paramIndex];
5777
+ if (!param) return;
5778
+ if (onInteraction) {
5779
+ onInteraction({
5780
+ kind: "slider-change",
5781
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
5782
+ blockId: block.id,
5783
+ blockType: block.type,
5784
+ payload: {
5785
+ parameterId: param.id,
5786
+ parameterLabel: typeof param.label === "string" ? param.label : "Parameter",
5787
+ value: newValue,
5788
+ allValues: parameters.map((p, i) => ({
5789
+ id: p.id,
5790
+ label: typeof p.label === "string" ? p.label : "Parameter",
5791
+ value: i === paramIndex ? newValue : newValues[i] ?? 0
5792
+ }))
5793
+ }
5794
+ });
5795
+ }
5796
+ };
5797
+ const formatValue = (value, unit) => {
5798
+ return unit ? `${String(value)}${unit}` : String(value);
5799
+ };
5800
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { id: baseId, role: "region", "aria-label": title ?? "Slider", style: containerStyle6, children: [
5801
+ title && /* @__PURE__ */ jsxRuntime.jsx("div", { style: headerStyle5, children: title }),
5802
+ parameters.map((param, index) => {
5803
+ const min2 = param.min ?? 0;
5804
+ const max2 = param.max ?? 100;
5805
+ const step = param.step ?? 1;
5806
+ const currentValue = values[index] ?? min2;
5807
+ const isLast = index === parameters.length - 1;
5808
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: parameterStyle(isLast), children: [
5809
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: parameterHeaderStyle, children: [
5810
+ /* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: `${baseId}-${param.id}`, style: parameterLabelStyle, children: /* @__PURE__ */ jsxRuntime.jsx(runtime.RichText, { content: param.label }) }),
5811
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: parameterValueStyle, "aria-live": "polite", children: formatValue(currentValue, param.unit) })
5812
+ ] }),
5813
+ /* @__PURE__ */ jsxRuntime.jsx(
5814
+ "input",
5815
+ {
5816
+ id: `${baseId}-${param.id}`,
5817
+ type: "range",
5818
+ min: min2,
5819
+ max: max2,
5820
+ step,
5821
+ value: currentValue,
5822
+ onChange: (e) => handleChange(index, Number(e.target.value)),
5823
+ "aria-valuemin": min2,
5824
+ "aria-valuemax": max2,
5825
+ "aria-valuenow": currentValue,
5826
+ "aria-valuetext": formatValue(currentValue, param.unit),
5827
+ style: rangeInputStyle
5828
+ }
5829
+ ),
5830
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: rangeLabelsStyle, children: [
5831
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatValue(min2, param.unit) }),
5832
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatValue(max2, param.unit) })
5833
+ ] })
5834
+ ] }, param.id);
5835
+ })
5836
+ ] });
5837
+ }
5838
+
5839
+ // src/slider/index.ts
5840
+ var sliderDefinition = {
5841
+ type: "ui:slider",
5842
+ schema: schemas.sliderSchema,
5843
+ render: Slider
5844
+ };
5845
+
5846
+ // src/matrix/styles.ts
5847
+ var containerStyle7 = {
5848
+ fontFamily: "var(--glyph-font-body, system-ui, sans-serif)",
5849
+ color: "var(--glyph-text, #1a2035)",
5850
+ border: "1px solid var(--glyph-border, #d0d8e4)",
5851
+ borderRadius: "var(--glyph-radius-md, 0.5rem)",
5852
+ overflow: "auto"
5853
+ };
5854
+ var headerStyle6 = {
5855
+ fontWeight: 700,
5856
+ fontSize: "1.125rem",
5857
+ padding: "var(--glyph-spacing-md, 1rem)",
5858
+ borderBottom: "1px solid var(--glyph-border, #d0d8e4)",
5859
+ color: "var(--glyph-heading, #0a0e1a)"
5860
+ };
5861
+ var tableStyle = {
5862
+ width: "100%",
5863
+ borderCollapse: "collapse",
5864
+ fontSize: "0.875rem"
5865
+ };
5866
+ var thStyle = {
5867
+ padding: "0.625rem 0.75rem",
5868
+ textAlign: "center",
5869
+ fontWeight: 600,
5870
+ borderBottom: "2px solid var(--glyph-border, #d0d8e4)",
5871
+ background: "var(--glyph-table-header-bg, var(--glyph-surface, #e8ecf3))",
5872
+ whiteSpace: "nowrap"
5873
+ };
5874
+ var rowHeaderStyle = {
5875
+ padding: "0.625rem 0.75rem",
5876
+ textAlign: "left",
5877
+ fontWeight: 600,
5878
+ borderBottom: "1px solid var(--glyph-border, #d0d8e4)",
5879
+ borderRight: "1px solid var(--glyph-border, #d0d8e4)",
5880
+ whiteSpace: "nowrap"
5881
+ };
5882
+ var cellStyle = {
5883
+ padding: "0.375rem 0.5rem",
5884
+ textAlign: "center",
5885
+ borderBottom: "1px solid var(--glyph-border, #d0d8e4)"
5886
+ };
5887
+ var inputStyle = {
5888
+ width: "3.5rem",
5889
+ padding: "0.25rem 0.375rem",
5890
+ textAlign: "center",
5891
+ border: "1px solid var(--glyph-border, #d0d8e4)",
5892
+ borderRadius: "var(--glyph-radius-sm, 0.375rem)",
5893
+ background: "transparent",
5894
+ color: "var(--glyph-text, #1a2035)",
5895
+ fontSize: "0.875rem",
5896
+ fontVariantNumeric: "tabular-nums"
5897
+ };
5898
+ var weightStyle = {
5899
+ fontSize: "0.6875rem",
5900
+ color: "var(--glyph-text-muted, #6b7a94)",
5901
+ fontWeight: 400
5902
+ };
5903
+ var totalCellStyle = {
5904
+ padding: "0.625rem 0.75rem",
5905
+ textAlign: "center",
5906
+ fontWeight: 700,
5907
+ borderBottom: "1px solid var(--glyph-border, #d0d8e4)",
5908
+ background: "var(--glyph-surface, #e8ecf3)",
5909
+ fontVariantNumeric: "tabular-nums"
5910
+ };
5911
+ var totalHeaderStyle = {
5912
+ ...thStyle,
5913
+ borderLeft: "2px solid var(--glyph-border, #d0d8e4)"
5914
+ };
5915
+ function computeWeightedTotals(rows, columns, values) {
5916
+ return rows.map((row) => {
5917
+ let total = 0;
5918
+ for (const col of columns) {
5919
+ const score = values[row.id]?.[col.id] ?? 0;
5920
+ const weight = col.weight ?? 1;
5921
+ total += score * weight;
5922
+ }
5923
+ const rowLabel = typeof row.label === "string" ? row.label : "Row";
5924
+ return { rowId: row.id, rowLabel, total: Math.round(total * 100) / 100 };
5925
+ });
5926
+ }
5927
+ function Matrix({
5928
+ data,
5929
+ block,
5930
+ onInteraction
5931
+ }) {
5932
+ const { title, scale = 5, showTotals = true, columns, rows } = data;
5933
+ const baseId = `glyph-matrix-${block.id}`;
5934
+ const [values, setValues] = react.useState(() => {
5935
+ const init = {};
5936
+ for (const row of rows) {
5937
+ const rowMap = {};
5938
+ for (const col of columns) {
5939
+ rowMap[col.id] = 0;
5940
+ }
5941
+ init[row.id] = rowMap;
5942
+ }
5943
+ return init;
5944
+ });
5945
+ const handleChange = react.useCallback(
5946
+ (rowId, columnId, value) => {
5947
+ const clamped = Math.max(0, Math.min(scale, value));
5948
+ setValues((prevValues) => {
5949
+ const newValues = Object.fromEntries(
5950
+ Object.entries(prevValues).map(([k, v]) => [k, { ...v }])
5951
+ );
5952
+ if (!newValues[rowId]) newValues[rowId] = {};
5953
+ newValues[rowId] = { ...newValues[rowId], [columnId]: clamped };
5954
+ const row = rows.find((r) => r.id === rowId);
5955
+ const col = columns.find((c) => c.id === columnId);
5956
+ if (onInteraction && row && col) {
5957
+ const payloadValues = Object.fromEntries(
5958
+ Object.entries(newValues).map(([k, v]) => [k, { ...v }])
5959
+ );
5960
+ onInteraction({
5961
+ kind: "matrix-change",
5962
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
5963
+ blockId: block.id,
5964
+ blockType: block.type,
5965
+ payload: {
5966
+ rowId,
5967
+ rowLabel: typeof row.label === "string" ? row.label : "Row",
5968
+ columnId,
5969
+ columnLabel: typeof col.label === "string" ? col.label : "Column",
5970
+ value: clamped,
5971
+ allValues: payloadValues,
5972
+ weightedTotals: computeWeightedTotals(rows, columns, newValues)
5973
+ }
5974
+ });
5975
+ }
5976
+ return newValues;
5977
+ });
5978
+ },
5979
+ [scale, rows, columns, block.id, block.type, onInteraction]
5980
+ );
5981
+ const totals = computeWeightedTotals(rows, columns, values);
5982
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { id: baseId, role: "region", "aria-label": title ?? "Decision Matrix", style: containerStyle7, children: [
5983
+ title && /* @__PURE__ */ jsxRuntime.jsx("div", { style: headerStyle6, children: title }),
5984
+ /* @__PURE__ */ jsxRuntime.jsxs("table", { role: "grid", style: tableStyle, children: [
5985
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
5986
+ /* @__PURE__ */ jsxRuntime.jsx("th", { style: thStyle }),
5987
+ columns.map((col) => /* @__PURE__ */ jsxRuntime.jsxs("th", { style: thStyle, children: [
5988
+ /* @__PURE__ */ jsxRuntime.jsx(runtime.RichText, { content: col.label }),
5989
+ (col.weight ?? 1) !== 1 && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: weightStyle, children: [
5990
+ "\xD7",
5991
+ String(col.weight)
5992
+ ] })
5993
+ ] }, col.id)),
5994
+ showTotals && /* @__PURE__ */ jsxRuntime.jsx("th", { style: totalHeaderStyle, children: "Total" })
5995
+ ] }) }),
5996
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: rows.map((row) => {
5997
+ const rowTotal = totals.find((t) => t.rowId === row.id)?.total ?? 0;
5998
+ const rowLabelText = typeof row.label === "string" ? row.label : "Row";
5999
+ return /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
6000
+ /* @__PURE__ */ jsxRuntime.jsx("th", { scope: "row", style: rowHeaderStyle, children: /* @__PURE__ */ jsxRuntime.jsx(runtime.RichText, { content: row.label }) }),
6001
+ columns.map((col) => {
6002
+ const cellValue = values[row.id]?.[col.id] ?? 0;
6003
+ const colLabelText = typeof col.label === "string" ? col.label : "Column";
6004
+ return /* @__PURE__ */ jsxRuntime.jsx("td", { style: cellStyle, children: /* @__PURE__ */ jsxRuntime.jsx(
6005
+ "input",
6006
+ {
6007
+ type: "number",
6008
+ min: 0,
6009
+ max: scale,
6010
+ value: cellValue,
6011
+ onChange: (e) => handleChange(row.id, col.id, Number(e.target.value)),
6012
+ "aria-label": `Score for ${rowLabelText} on ${colLabelText}`,
6013
+ style: inputStyle
6014
+ }
6015
+ ) }, col.id);
6016
+ }),
6017
+ showTotals && /* @__PURE__ */ jsxRuntime.jsx("td", { style: totalCellStyle, children: String(rowTotal) })
6018
+ ] }, row.id);
6019
+ }) })
6020
+ ] })
6021
+ ] });
6022
+ }
6023
+
6024
+ // src/matrix/index.ts
6025
+ var matrixDefinition = {
6026
+ type: "ui:matrix",
6027
+ schema: schemas.matrixSchema,
6028
+ render: Matrix
6029
+ };
6030
+
6031
+ // src/form/styles.ts
6032
+ var containerStyle8 = {
6033
+ fontFamily: "var(--glyph-font-body, system-ui, sans-serif)",
6034
+ color: "var(--glyph-text, #1a2035)",
6035
+ border: "1px solid var(--glyph-border, #d0d8e4)",
6036
+ borderRadius: "var(--glyph-radius-md, 0.5rem)",
6037
+ overflow: "hidden"
6038
+ };
6039
+ var headerStyle7 = {
6040
+ fontWeight: 700,
6041
+ fontSize: "1.125rem",
6042
+ padding: "var(--glyph-spacing-md, 1rem)",
6043
+ paddingBottom: "0.25rem",
6044
+ color: "var(--glyph-heading, #0a0e1a)"
6045
+ };
6046
+ var descriptionStyle = {
6047
+ fontSize: "0.875rem",
6048
+ color: "var(--glyph-text-muted, #6b7a94)",
6049
+ padding: "0 var(--glyph-spacing-md, 1rem)",
6050
+ paddingBottom: "var(--glyph-spacing-sm, 0.5rem)"
6051
+ };
6052
+ var formStyle = {
6053
+ padding: "var(--glyph-spacing-md, 1rem)"
6054
+ };
6055
+ var fieldStyle = {
6056
+ marginBottom: "var(--glyph-spacing-md, 1rem)"
6057
+ };
6058
+ var labelStyle3 = {
6059
+ display: "block",
6060
+ fontWeight: 600,
6061
+ fontSize: "0.875rem",
6062
+ marginBottom: "0.375rem"
6063
+ };
6064
+ var requiredStyle = {
6065
+ color: "var(--glyph-form-error, #dc2626)",
6066
+ marginLeft: "0.25rem"
6067
+ };
6068
+ var textInputStyle = {
6069
+ width: "100%",
6070
+ padding: "0.5rem 0.75rem",
6071
+ border: "1px solid var(--glyph-border, #d0d8e4)",
6072
+ borderRadius: "var(--glyph-radius-sm, 0.375rem)",
6073
+ background: "transparent",
6074
+ color: "var(--glyph-text, #1a2035)",
6075
+ fontSize: "0.875rem",
6076
+ fontFamily: "inherit",
6077
+ boxSizing: "border-box"
6078
+ };
6079
+ var selectInputStyle = {
6080
+ ...textInputStyle,
6081
+ appearance: "auto"
6082
+ };
6083
+ var checkboxLabelStyle = {
6084
+ display: "flex",
6085
+ alignItems: "center",
6086
+ gap: "0.5rem",
6087
+ fontSize: "0.875rem",
6088
+ cursor: "pointer"
6089
+ };
6090
+ var rangeValueStyle = {
6091
+ fontSize: "0.875rem",
6092
+ fontWeight: 600,
6093
+ color: "var(--glyph-accent, #0a9d7c)",
6094
+ marginLeft: "0.5rem",
6095
+ fontVariantNumeric: "tabular-nums"
6096
+ };
6097
+ var submitButtonStyle = {
6098
+ padding: "0.625rem 1.5rem",
6099
+ borderRadius: "var(--glyph-radius-md, 0.5rem)",
6100
+ border: "1px solid var(--glyph-accent, #0a9d7c)",
6101
+ background: "var(--glyph-accent, #0a9d7c)",
6102
+ color: "#fff",
6103
+ cursor: "pointer",
6104
+ fontWeight: 600,
6105
+ fontSize: "0.875rem",
6106
+ marginTop: "var(--glyph-spacing-sm, 0.5rem)"
6107
+ };
6108
+ function invalidStyle(isInvalid) {
6109
+ if (!isInvalid) return {};
6110
+ return {
6111
+ borderColor: "var(--glyph-form-error, #dc2626)"
6112
+ };
6113
+ }
6114
+ function renderField({
6115
+ field,
6116
+ baseId,
6117
+ values,
6118
+ validation,
6119
+ submitted,
6120
+ updateValue
6121
+ }) {
6122
+ const isInvalid = validation[field.id] === true;
6123
+ const fieldId = `${baseId}-${field.id}`;
6124
+ switch (field.type) {
6125
+ case "text":
6126
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: fieldStyle, children: [
6127
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { htmlFor: fieldId, style: labelStyle3, children: [
6128
+ field.label,
6129
+ field.required && /* @__PURE__ */ jsxRuntime.jsx("span", { style: requiredStyle, "aria-hidden": "true", children: "*" })
6130
+ ] }),
6131
+ /* @__PURE__ */ jsxRuntime.jsx(
6132
+ "input",
6133
+ {
6134
+ id: fieldId,
6135
+ type: "text",
6136
+ value: values[field.id] ?? "",
6137
+ onChange: (e) => updateValue(field.id, e.target.value),
6138
+ placeholder: field.placeholder,
6139
+ disabled: submitted,
6140
+ "aria-required": field.required,
6141
+ "aria-invalid": isInvalid,
6142
+ style: { ...textInputStyle, ...invalidStyle(isInvalid) }
6143
+ }
6144
+ )
6145
+ ] }, field.id);
6146
+ case "textarea":
6147
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: fieldStyle, children: [
6148
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { htmlFor: fieldId, style: labelStyle3, children: [
6149
+ field.label,
6150
+ field.required && /* @__PURE__ */ jsxRuntime.jsx("span", { style: requiredStyle, "aria-hidden": "true", children: "*" })
6151
+ ] }),
6152
+ /* @__PURE__ */ jsxRuntime.jsx(
6153
+ "textarea",
6154
+ {
6155
+ id: fieldId,
6156
+ value: values[field.id] ?? "",
6157
+ onChange: (e) => updateValue(field.id, e.target.value),
6158
+ placeholder: field.placeholder,
6159
+ rows: field.rows ?? 4,
6160
+ disabled: submitted,
6161
+ "aria-required": field.required,
6162
+ "aria-invalid": isInvalid,
6163
+ style: {
6164
+ ...textInputStyle,
6165
+ ...invalidStyle(isInvalid),
6166
+ resize: "vertical",
6167
+ fontFamily: "inherit"
6168
+ }
6169
+ }
6170
+ )
6171
+ ] }, field.id);
6172
+ case "select":
6173
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: fieldStyle, children: [
6174
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { htmlFor: fieldId, style: labelStyle3, children: [
6175
+ field.label,
6176
+ field.required && /* @__PURE__ */ jsxRuntime.jsx("span", { style: requiredStyle, "aria-hidden": "true", children: "*" })
6177
+ ] }),
6178
+ /* @__PURE__ */ jsxRuntime.jsx(
6179
+ "select",
6180
+ {
6181
+ id: fieldId,
6182
+ value: values[field.id] ?? "",
6183
+ onChange: (e) => updateValue(field.id, e.target.value),
6184
+ disabled: submitted,
6185
+ "aria-required": field.required,
6186
+ style: selectInputStyle,
6187
+ children: field.options.map((opt) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: opt, children: opt }, opt))
6188
+ }
6189
+ )
6190
+ ] }, field.id);
6191
+ case "checkbox":
6192
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: fieldStyle, children: /* @__PURE__ */ jsxRuntime.jsxs("label", { style: checkboxLabelStyle, children: [
6193
+ /* @__PURE__ */ jsxRuntime.jsx(
6194
+ "input",
6195
+ {
6196
+ id: fieldId,
6197
+ type: "checkbox",
6198
+ checked: values[field.id] ?? false,
6199
+ onChange: (e) => updateValue(field.id, e.target.checked),
6200
+ disabled: submitted
6201
+ }
6202
+ ),
6203
+ field.label
6204
+ ] }) }, field.id);
6205
+ case "range": {
6206
+ const min2 = field.min ?? 0;
6207
+ const max2 = field.max ?? 100;
6208
+ const step = field.step ?? 1;
6209
+ const currentValue = values[field.id] ?? min2;
6210
+ const displayValue = field.unit ? `${String(currentValue)}${field.unit}` : String(currentValue);
6211
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: fieldStyle, children: [
6212
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { htmlFor: fieldId, style: labelStyle3, children: [
6213
+ field.label,
6214
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: rangeValueStyle, children: displayValue })
6215
+ ] }),
6216
+ /* @__PURE__ */ jsxRuntime.jsx(
6217
+ "input",
6218
+ {
6219
+ id: fieldId,
6220
+ type: "range",
6221
+ min: min2,
6222
+ max: max2,
6223
+ step,
6224
+ value: currentValue,
6225
+ onChange: (e) => updateValue(field.id, Number(e.target.value)),
6226
+ disabled: submitted,
6227
+ "aria-valuemin": min2,
6228
+ "aria-valuemax": max2,
6229
+ "aria-valuenow": currentValue,
6230
+ "aria-valuetext": displayValue,
6231
+ style: { width: "100%", accentColor: "var(--glyph-accent, #0a9d7c)" }
6232
+ }
6233
+ )
6234
+ ] }, field.id);
6235
+ }
6236
+ }
6237
+ }
6238
+ function Form({ data, block, onInteraction }) {
6239
+ const { title, description, submitLabel = "Submit", fields } = data;
6240
+ const baseId = `glyph-form-${block.id}`;
6241
+ const [values, setValues] = react.useState(() => {
6242
+ const init = {};
6243
+ for (const field of fields) {
6244
+ switch (field.type) {
6245
+ case "text":
6246
+ case "textarea":
6247
+ init[field.id] = field.default ?? "";
6248
+ break;
6249
+ case "select":
6250
+ init[field.id] = field.default ?? field.options[0] ?? "";
6251
+ break;
6252
+ case "checkbox":
6253
+ init[field.id] = field.default ?? false;
6254
+ break;
6255
+ case "range":
6256
+ init[field.id] = field.default ?? field.min ?? 0;
6257
+ break;
6258
+ }
6259
+ }
6260
+ return init;
6261
+ });
6262
+ const [submitted, setSubmitted] = react.useState(false);
6263
+ const [validation, setValidation] = react.useState({});
6264
+ const updateValue = (fieldId, value) => {
6265
+ setValues((prev) => ({ ...prev, [fieldId]: value }));
6266
+ if (validation[fieldId]) {
6267
+ setValidation((prev) => ({ ...prev, [fieldId]: false }));
6268
+ }
6269
+ };
6270
+ const handleSubmit = (e) => {
6271
+ e.preventDefault();
6272
+ const errors = {};
6273
+ for (const field of fields) {
6274
+ if ("required" in field && field.required) {
6275
+ const val = values[field.id];
6276
+ if (val === "" || val === void 0) {
6277
+ errors[field.id] = true;
6278
+ }
6279
+ }
6280
+ }
6281
+ if (Object.keys(errors).length > 0) {
6282
+ setValidation(errors);
6283
+ return;
6284
+ }
6285
+ setSubmitted(true);
6286
+ if (onInteraction) {
6287
+ onInteraction({
6288
+ kind: "form-submit",
6289
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
6290
+ blockId: block.id,
6291
+ blockType: block.type,
6292
+ payload: {
6293
+ values: { ...values },
6294
+ fields: fields.map((f) => ({
6295
+ id: f.id,
6296
+ label: f.label,
6297
+ type: f.type,
6298
+ value: values[f.id] !== void 0 ? values[f.id] : ""
6299
+ }))
6300
+ }
6301
+ });
6302
+ }
6303
+ };
6304
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { id: baseId, role: "region", "aria-label": title ?? "Form", style: containerStyle8, children: [
6305
+ title && /* @__PURE__ */ jsxRuntime.jsx("div", { style: headerStyle7, children: title }),
6306
+ description && /* @__PURE__ */ jsxRuntime.jsx("div", { style: descriptionStyle, children: /* @__PURE__ */ jsxRuntime.jsx(runtime.RichText, { content: description }) }),
6307
+ /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: handleSubmit, style: formStyle, noValidate: true, children: [
6308
+ fields.map(
6309
+ (field) => renderField({ field, baseId, values, validation, submitted, updateValue })
6310
+ ),
6311
+ /* @__PURE__ */ jsxRuntime.jsx(
6312
+ "button",
6313
+ {
6314
+ type: "submit",
6315
+ disabled: submitted,
6316
+ style: {
6317
+ ...submitButtonStyle,
6318
+ opacity: submitted ? 0.5 : 1,
6319
+ cursor: submitted ? "default" : "pointer"
6320
+ },
6321
+ children: submitted ? "Submitted" : submitLabel
6322
+ }
6323
+ )
6324
+ ] })
6325
+ ] });
6326
+ }
6327
+
6328
+ // src/form/index.ts
6329
+ var formDefinition = {
6330
+ type: "ui:form",
6331
+ schema: schemas.formSchema,
6332
+ render: Form
6333
+ };
6334
+
6335
+ // src/kanban/styles.ts
6336
+ var containerStyle9 = {
6337
+ fontFamily: "var(--glyph-font-body, system-ui, sans-serif)",
6338
+ color: "var(--glyph-text, #1a2035)",
6339
+ border: "1px solid var(--glyph-border, #d0d8e4)",
6340
+ borderRadius: "var(--glyph-radius-md, 0.5rem)",
6341
+ overflow: "hidden"
6342
+ };
6343
+ var headerStyle8 = {
6344
+ fontWeight: 700,
6345
+ fontSize: "1.125rem",
6346
+ padding: "var(--glyph-spacing-md, 1rem)",
6347
+ borderBottom: "1px solid var(--glyph-border, #d0d8e4)",
6348
+ color: "var(--glyph-heading, #0a0e1a)"
6349
+ };
6350
+ var boardStyle = {
6351
+ display: "flex",
6352
+ gap: "var(--glyph-spacing-sm, 0.5rem)",
6353
+ padding: "var(--glyph-spacing-md, 1rem)",
6354
+ overflowX: "auto",
6355
+ minHeight: "200px"
6356
+ };
6357
+ function columnStyle(isOver) {
6358
+ return {
6359
+ flex: "1 1 0",
6360
+ minWidth: "180px",
6361
+ background: isOver ? "var(--glyph-accent-subtle, #e6f6f2)" : "var(--glyph-kanban-column-bg, var(--glyph-surface, #e8ecf3))",
6362
+ borderRadius: "var(--glyph-radius-md, 0.5rem)",
6363
+ padding: "var(--glyph-spacing-sm, 0.5rem)",
6364
+ display: "flex",
6365
+ flexDirection: "column",
6366
+ transition: "background 0.15s ease"
6367
+ };
6368
+ }
6369
+ var columnHeaderStyle = {
6370
+ fontWeight: 700,
6371
+ fontSize: "0.8125rem",
6372
+ textTransform: "uppercase",
6373
+ letterSpacing: "0.5px",
6374
+ padding: "0.375rem 0.5rem",
6375
+ marginBottom: "0.375rem",
6376
+ display: "flex",
6377
+ justifyContent: "space-between",
6378
+ alignItems: "center"
6379
+ };
6380
+ var columnCountStyle = {
6381
+ fontSize: "0.6875rem",
6382
+ fontWeight: 400,
6383
+ color: "var(--glyph-text-muted, #6b7a94)"
6384
+ };
6385
+ function cardStyle(isGrabbed, priority) {
6386
+ const priorityColors = {
6387
+ high: "var(--glyph-kanban-priority-high, #dc2626)",
6388
+ medium: "var(--glyph-kanban-priority-medium, #f59e0b)",
6389
+ low: "var(--glyph-kanban-priority-low, #22c55e)"
6390
+ };
6391
+ return {
6392
+ background: "var(--glyph-kanban-card-bg, var(--glyph-surface-raised, #f4f6fa))",
6393
+ border: `1px solid var(--glyph-kanban-card-border, var(--glyph-border, #d0d8e4))`,
6394
+ borderRadius: "var(--glyph-radius-sm, 0.375rem)",
6395
+ padding: "0.625rem 0.75rem",
6396
+ marginBottom: "0.375rem",
6397
+ cursor: isGrabbed ? "grabbing" : "grab",
6398
+ userSelect: "none",
6399
+ boxShadow: isGrabbed ? "var(--glyph-kanban-drag-shadow, var(--glyph-shadow-md, 0 4px 12px rgba(0,0,0,0.15)))" : "none",
6400
+ borderLeft: priority && priorityColors[priority] ? `3px solid ${priorityColors[priority]}` : void 0,
6401
+ outline: isGrabbed ? "2px solid var(--glyph-accent, #0a9d7c)" : "none",
6402
+ outlineOffset: "-2px"
6403
+ };
6404
+ }
6405
+ var cardTitleStyle = {
6406
+ fontWeight: 600,
6407
+ fontSize: "0.875rem",
6408
+ marginBottom: "0.25rem"
6409
+ };
6410
+ var cardDescStyle = {
6411
+ fontSize: "0.75rem",
6412
+ color: "var(--glyph-text-muted, #6b7a94)",
6413
+ lineHeight: 1.4
6414
+ };
6415
+ var tagContainerStyle = {
6416
+ display: "flex",
6417
+ flexWrap: "wrap",
6418
+ gap: "0.25rem",
6419
+ marginTop: "0.375rem"
6420
+ };
6421
+ var tagStyle = {
6422
+ fontSize: "0.625rem",
6423
+ fontWeight: 600,
6424
+ padding: "0.125rem 0.375rem",
6425
+ borderRadius: "9999px",
6426
+ background: "var(--glyph-accent-subtle, #e6f6f2)",
6427
+ color: "var(--glyph-accent, #0a9d7c)"
6428
+ };
6429
+ var limitStyle = {
6430
+ fontSize: "0.625rem",
6431
+ color: "var(--glyph-text-muted, #6b7a94)"
6432
+ };
6433
+ function Kanban({
6434
+ data,
6435
+ block,
6436
+ onInteraction
6437
+ }) {
6438
+ const { title } = data;
6439
+ const baseId = `glyph-kanban-${block.id}`;
6440
+ const [columns, setColumns] = react.useState(
6441
+ () => data.columns.map((col) => ({ ...col, cards: [...col.cards] }))
6442
+ );
6443
+ const [grabbed, setGrabbed] = react.useState(null);
6444
+ const moveCard = (cardId, sourceColId, destColId, destIndex) => {
6445
+ const newColumns = columns.map((col) => ({
6446
+ ...col,
6447
+ cards: [...col.cards]
6448
+ }));
6449
+ const sourceCol = newColumns.find((c) => c.id === sourceColId);
6450
+ const destCol = newColumns.find((c) => c.id === destColId);
6451
+ if (!sourceCol || !destCol) return;
6452
+ const cardIndex = sourceCol.cards.findIndex((c) => c.id === cardId);
6453
+ if (cardIndex === -1) return;
6454
+ const [card] = sourceCol.cards.splice(cardIndex, 1);
6455
+ if (!card) return;
6456
+ destCol.cards.splice(destIndex, 0, card);
6457
+ setColumns(newColumns);
6458
+ if (onInteraction) {
6459
+ onInteraction({
6460
+ kind: "kanban-move",
6461
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
6462
+ blockId: block.id,
6463
+ blockType: block.type,
6464
+ payload: {
6465
+ cardId: card.id,
6466
+ cardTitle: card.title,
6467
+ sourceColumnId: sourceColId,
6468
+ sourceColumnTitle: sourceCol.title,
6469
+ destinationColumnId: destColId,
6470
+ destinationColumnTitle: destCol.title,
6471
+ position: destIndex,
6472
+ allColumns: newColumns.map((c) => ({
6473
+ id: c.id,
6474
+ title: c.title,
6475
+ cardIds: c.cards.map((card2) => card2.id)
6476
+ }))
6477
+ }
6478
+ });
6479
+ }
6480
+ };
6481
+ const handleCardKeyDown = (e, cardId, columnId, cardIndex) => {
6482
+ if (e.key === " " || e.key === "Enter") {
6483
+ e.preventDefault();
6484
+ if (grabbed === null) {
6485
+ setGrabbed({ cardId, columnId, cardIndex });
6486
+ } else {
6487
+ setGrabbed(null);
6488
+ }
6489
+ } else if (e.key === "Escape") {
6490
+ setGrabbed(null);
6491
+ } else if (grabbed && grabbed.cardId === cardId) {
6492
+ const colIndex = columns.findIndex((c) => c.id === grabbed.columnId);
6493
+ const col = columns[colIndex];
6494
+ if (!col) return;
6495
+ if (e.key === "ArrowUp") {
6496
+ e.preventDefault();
6497
+ if (grabbed.cardIndex > 0) {
6498
+ moveCard(cardId, grabbed.columnId, grabbed.columnId, grabbed.cardIndex - 1);
6499
+ setGrabbed({ ...grabbed, cardIndex: grabbed.cardIndex - 1 });
6500
+ }
6501
+ } else if (e.key === "ArrowDown") {
6502
+ e.preventDefault();
6503
+ if (grabbed.cardIndex < col.cards.length - 1) {
6504
+ moveCard(cardId, grabbed.columnId, grabbed.columnId, grabbed.cardIndex + 1);
6505
+ setGrabbed({ ...grabbed, cardIndex: grabbed.cardIndex + 1 });
6506
+ }
6507
+ } else if (e.key === "ArrowLeft") {
6508
+ e.preventDefault();
6509
+ if (colIndex > 0) {
6510
+ const prevCol = columns[colIndex - 1];
6511
+ if (!prevCol) return;
6512
+ const newIndex = prevCol.cards.length;
6513
+ moveCard(cardId, grabbed.columnId, prevCol.id, newIndex);
6514
+ setGrabbed({ cardId, columnId: prevCol.id, cardIndex: newIndex });
6515
+ }
6516
+ } else if (e.key === "ArrowRight") {
6517
+ e.preventDefault();
6518
+ if (colIndex < columns.length - 1) {
6519
+ const nextCol = columns[colIndex + 1];
6520
+ if (!nextCol) return;
6521
+ const newIndex = nextCol.cards.length;
6522
+ moveCard(cardId, grabbed.columnId, nextCol.id, newIndex);
6523
+ setGrabbed({ cardId, columnId: nextCol.id, cardIndex: newIndex });
6524
+ }
6525
+ }
6526
+ }
6527
+ };
6528
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { id: baseId, role: "region", "aria-label": title ?? "Kanban Board", style: containerStyle9, children: [
6529
+ title && /* @__PURE__ */ jsxRuntime.jsx("div", { style: headerStyle8, children: title }),
6530
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: boardStyle, children: columns.map((col) => /* @__PURE__ */ jsxRuntime.jsxs("div", { style: columnStyle(false), children: [
6531
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: columnHeaderStyle, children: [
6532
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: col.title }),
6533
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: columnCountStyle, children: [
6534
+ String(col.cards.length),
6535
+ col.limit !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { style: limitStyle, children: [
6536
+ " / ",
6537
+ String(col.limit)
6538
+ ] })
6539
+ ] })
6540
+ ] }),
6541
+ /* @__PURE__ */ jsxRuntime.jsx("div", { role: "list", "aria-label": col.title, children: col.cards.map((card, cardIndex) => {
6542
+ const isGrabbed = grabbed !== null && grabbed.cardId === card.id;
6543
+ return /* @__PURE__ */ jsxRuntime.jsxs(
6544
+ "div",
6545
+ {
6546
+ role: "listitem",
6547
+ "aria-grabbed": isGrabbed,
6548
+ "aria-label": `${card.title}${card.priority ? `, ${card.priority} priority` : ""}`,
6549
+ tabIndex: 0,
6550
+ style: cardStyle(isGrabbed, card.priority),
6551
+ onKeyDown: (e) => handleCardKeyDown(e, card.id, col.id, cardIndex),
6552
+ children: [
6553
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: cardTitleStyle, children: card.title }),
6554
+ card.description && /* @__PURE__ */ jsxRuntime.jsx("div", { style: cardDescStyle, children: card.description }),
6555
+ card.tags && card.tags.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: tagContainerStyle, children: card.tags.map((tag) => /* @__PURE__ */ jsxRuntime.jsx("span", { style: tagStyle, children: tag }, tag)) })
6556
+ ]
6557
+ },
6558
+ card.id
6559
+ );
6560
+ }) })
6561
+ ] }, col.id)) }),
6562
+ /* @__PURE__ */ jsxRuntime.jsx(
6563
+ "div",
6564
+ {
6565
+ "aria-live": "assertive",
6566
+ style: {
6567
+ position: "absolute",
6568
+ width: "1px",
6569
+ height: "1px",
6570
+ padding: 0,
6571
+ margin: "-1px",
6572
+ overflow: "hidden",
6573
+ clip: "rect(0,0,0,0)",
6574
+ whiteSpace: "nowrap",
6575
+ border: 0
6576
+ },
6577
+ 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.`
6578
+ }
6579
+ )
6580
+ ] });
6581
+ }
6582
+
6583
+ // src/kanban/index.ts
6584
+ var kanbanDefinition = {
6585
+ type: "ui:kanban",
6586
+ schema: schemas.kanbanSchema,
6587
+ render: Kanban
6588
+ };
6589
+
6590
+ // src/annotate/styles.ts
6591
+ var containerStyle10 = {
6592
+ fontFamily: "var(--glyph-font-body, system-ui, sans-serif)",
6593
+ color: "var(--glyph-text, #1a2035)",
6594
+ border: "1px solid var(--glyph-border, #d0d8e4)",
6595
+ borderRadius: "var(--glyph-radius-md, 0.5rem)",
6596
+ overflow: "hidden"
6597
+ };
6598
+ var headerStyle9 = {
6599
+ fontWeight: 700,
6600
+ fontSize: "1.125rem",
6601
+ padding: "var(--glyph-spacing-md, 1rem)",
6602
+ borderBottom: "1px solid var(--glyph-border, #d0d8e4)",
6603
+ color: "var(--glyph-heading, #0a0e1a)"
6604
+ };
6605
+ var bodyStyle2 = {
6606
+ display: "flex",
6607
+ minHeight: "200px"
6608
+ };
6609
+ var textPaneStyle = {
6610
+ flex: 1,
6611
+ padding: "var(--glyph-spacing-md, 1rem)",
6612
+ fontFamily: "var(--glyph-font-mono, ui-monospace, monospace)",
6613
+ fontSize: "0.8125rem",
6614
+ lineHeight: 1.8,
6615
+ whiteSpace: "pre-wrap",
6616
+ wordBreak: "break-word",
6617
+ position: "relative",
6618
+ cursor: "text"
6619
+ };
6620
+ var labelPickerStyle = {
6621
+ position: "absolute",
6622
+ zIndex: 10,
6623
+ background: "var(--glyph-surface-raised, #f4f6fa)",
6624
+ border: "1px solid var(--glyph-border, #d0d8e4)",
6625
+ borderRadius: "var(--glyph-radius-sm, 0.375rem)",
6626
+ boxShadow: "var(--glyph-shadow-md, 0 4px 12px rgba(0,0,0,0.15))",
6627
+ padding: "0.25rem 0",
6628
+ minWidth: "120px"
6629
+ };
6630
+ function labelOptionStyle() {
6631
+ return {
6632
+ display: "flex",
6633
+ alignItems: "center",
6634
+ gap: "0.5rem",
6635
+ padding: "0.375rem 0.75rem",
6636
+ cursor: "pointer",
6637
+ fontSize: "0.8125rem",
6638
+ background: "transparent",
6639
+ border: "none",
6640
+ width: "100%",
6641
+ textAlign: "left",
6642
+ color: "var(--glyph-text, #1a2035)"
6643
+ };
6644
+ }
6645
+ function colorDotStyle(color3) {
6646
+ return {
6647
+ width: "0.625rem",
6648
+ height: "0.625rem",
6649
+ borderRadius: "50%",
6650
+ background: color3,
6651
+ flexShrink: 0
6652
+ };
6653
+ }
6654
+ var sidebarStyle = {
6655
+ width: "220px",
6656
+ borderLeft: "1px solid var(--glyph-border, #d0d8e4)",
6657
+ background: "var(--glyph-annotate-sidebar-bg, var(--glyph-surface, #e8ecf3))",
6658
+ overflow: "auto"
6659
+ };
6660
+ var sidebarHeaderStyle = {
6661
+ fontWeight: 700,
6662
+ fontSize: "0.75rem",
6663
+ textTransform: "uppercase",
6664
+ letterSpacing: "0.5px",
6665
+ padding: "0.75rem",
6666
+ borderBottom: "1px solid var(--glyph-border, #d0d8e4)",
6667
+ color: "var(--glyph-text-muted, #6b7a94)"
6668
+ };
6669
+ function annotationItemStyle(color3) {
6670
+ return {
6671
+ padding: "0.5rem 0.75rem",
6672
+ borderBottom: "1px solid var(--glyph-border, #d0d8e4)",
6673
+ borderLeft: `3px solid ${color3}`,
6674
+ fontSize: "0.75rem"
6675
+ };
6676
+ }
6677
+ var annotationTextStyle = {
6678
+ fontFamily: "var(--glyph-font-mono, ui-monospace, monospace)",
6679
+ fontSize: "0.6875rem",
6680
+ color: "var(--glyph-text-muted, #6b7a94)",
6681
+ marginTop: "0.25rem",
6682
+ overflow: "hidden",
6683
+ textOverflow: "ellipsis",
6684
+ whiteSpace: "nowrap"
6685
+ };
6686
+ var annotationNoteStyle = {
6687
+ fontSize: "0.6875rem",
6688
+ fontStyle: "italic",
6689
+ color: "var(--glyph-text-muted, #6b7a94)",
6690
+ marginTop: "0.125rem"
6691
+ };
6692
+ function computeSegments(text, annotations) {
6693
+ if (typeof text !== "string") {
6694
+ return [{ text: "", start: 0, annotation: null }];
6695
+ }
6696
+ if (annotations.length === 0) {
6697
+ return [{ text, start: 0, annotation: null }];
6698
+ }
6699
+ const sorted = [...annotations].sort((a, b) => a.start - b.start);
6700
+ const segments = [];
6701
+ let cursor = 0;
6702
+ for (const ann of sorted) {
6703
+ if (ann.start > cursor) {
6704
+ segments.push({ text: text.slice(cursor, ann.start), start: cursor, annotation: null });
6705
+ }
6706
+ segments.push({
6707
+ text: text.slice(ann.start, ann.end),
6708
+ start: ann.start,
6709
+ annotation: ann
6710
+ });
6711
+ cursor = ann.end;
6712
+ }
6713
+ if (cursor < text.length) {
6714
+ segments.push({ text: text.slice(cursor), start: cursor, annotation: null });
6715
+ }
6716
+ return segments;
6717
+ }
6718
+ function Annotate({
6719
+ data,
6720
+ block,
6721
+ onInteraction
6722
+ }) {
6723
+ const { title, labels, text } = data;
6724
+ const baseId = `glyph-annotate-${block.id}`;
6725
+ const [annotations, setAnnotations] = react.useState(data.annotations ?? []);
6726
+ const [pickerPos, setPickerPos] = react.useState(null);
6727
+ const [pendingSelection, setPendingSelection] = react.useState(null);
6728
+ const textRef = react.useRef(null);
6729
+ const handleMouseUp = react.useCallback(() => {
6730
+ const selection = window.getSelection();
6731
+ if (!selection || selection.isCollapsed || !textRef.current) return;
6732
+ const range = selection.getRangeAt(0);
6733
+ if (!range || !textRef.current.contains(range.commonAncestorContainer)) return;
6734
+ const selectedText = selection.toString();
6735
+ if (!selectedText.trim()) return;
6736
+ const preCaretRange = document.createRange();
6737
+ preCaretRange.selectNodeContents(textRef.current);
6738
+ preCaretRange.setEnd(range.startContainer, range.startOffset);
6739
+ const startOffset = preCaretRange.toString().length;
6740
+ const endOffset = startOffset + selectedText.length;
6741
+ const rect = range.getBoundingClientRect();
6742
+ const containerRect = textRef.current.getBoundingClientRect();
6743
+ setPendingSelection({ start: startOffset, end: endOffset, text: selectedText });
6744
+ setPickerPos({
6745
+ x: rect.left - containerRect.left,
6746
+ y: rect.bottom - containerRect.top + 4
6747
+ });
6748
+ }, []);
6749
+ const selectLabel = (labelName) => {
6750
+ if (!pendingSelection) return;
6751
+ const newAnnotation = {
6752
+ start: pendingSelection.start,
6753
+ end: pendingSelection.end,
6754
+ label: labelName
6755
+ };
6756
+ const newAnnotations = [...annotations, newAnnotation];
6757
+ setAnnotations(newAnnotations);
6758
+ setPickerPos(null);
6759
+ setPendingSelection(null);
6760
+ window.getSelection()?.removeAllRanges();
6761
+ if (onInteraction) {
6762
+ onInteraction({
6763
+ kind: "annotate-create",
6764
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
6765
+ blockId: block.id,
6766
+ blockType: block.type,
6767
+ payload: {
6768
+ start: newAnnotation.start,
6769
+ end: newAnnotation.end,
6770
+ selectedText: pendingSelection.text,
6771
+ label: labelName,
6772
+ allAnnotations: newAnnotations.map((a) => ({
6773
+ start: a.start,
6774
+ end: a.end,
6775
+ text: typeof text === "string" ? text.slice(a.start, a.end) : "",
6776
+ label: a.label
6777
+ }))
6778
+ }
6779
+ });
6780
+ }
6781
+ };
6782
+ const closePicker = (e) => {
6783
+ if (e.target.closest("[data-label-picker]")) return;
6784
+ setPickerPos(null);
6785
+ setPendingSelection(null);
6786
+ };
6787
+ const segments = computeSegments(text, annotations);
6788
+ const labelColorMap = new Map(labels.map((l) => [l.name, l.color]));
6789
+ return /* @__PURE__ */ jsxRuntime.jsxs(
6790
+ "div",
6791
+ {
6792
+ id: baseId,
6793
+ role: "region",
6794
+ "aria-label": title ?? "Annotate",
6795
+ style: containerStyle10,
6796
+ onClick: closePicker,
6797
+ children: [
6798
+ title && /* @__PURE__ */ jsxRuntime.jsx("div", { style: headerStyle9, children: title }),
6799
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: bodyStyle2, children: [
6800
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: textRef, role: "document", style: textPaneStyle, onMouseUp: handleMouseUp, children: [
6801
+ typeof text === "string" ? segments.map((seg, i) => {
6802
+ if (seg.annotation) {
6803
+ const color3 = labelColorMap.get(seg.annotation.label) ?? "#888";
6804
+ return /* @__PURE__ */ jsxRuntime.jsx(
6805
+ "mark",
6806
+ {
6807
+ style: {
6808
+ backgroundColor: `${color3}33`,
6809
+ borderBottom: `2px solid ${color3}`,
6810
+ padding: "0 1px"
6811
+ },
6812
+ title: `${seg.annotation.label}${seg.annotation.note ? `: ${seg.annotation.note}` : ""}`,
6813
+ children: seg.text
6814
+ },
6815
+ i
6816
+ );
6817
+ }
6818
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { children: seg.text }, i);
6819
+ }) : /* @__PURE__ */ jsxRuntime.jsx(runtime.RichText, { content: text }),
6820
+ pickerPos && /* @__PURE__ */ jsxRuntime.jsx(
6821
+ "div",
6822
+ {
6823
+ role: "menu",
6824
+ "data-label-picker": true,
6825
+ style: {
6826
+ ...labelPickerStyle,
6827
+ left: `${String(pickerPos.x)}px`,
6828
+ top: `${String(pickerPos.y)}px`
6829
+ },
6830
+ children: labels.map((label) => /* @__PURE__ */ jsxRuntime.jsxs(
6831
+ "button",
6832
+ {
6833
+ role: "menuitem",
6834
+ style: labelOptionStyle(),
6835
+ onClick: (e) => {
6836
+ e.stopPropagation();
6837
+ selectLabel(label.name);
6838
+ },
6839
+ children: [
6840
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: colorDotStyle(label.color) }),
6841
+ label.name
6842
+ ]
6843
+ },
6844
+ label.name
6845
+ ))
6846
+ }
6847
+ )
6848
+ ] }),
6849
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: sidebarStyle, role: "complementary", "aria-label": "Annotations", children: [
6850
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: sidebarHeaderStyle, children: [
6851
+ "Annotations (",
6852
+ String(annotations.length),
6853
+ ")"
6854
+ ] }),
6855
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "list", children: [
6856
+ annotations.map((ann, i) => {
6857
+ const color3 = labelColorMap.get(ann.label) ?? "#888";
6858
+ const annotatedText = typeof text === "string" ? text.slice(ann.start, ann.end) : "";
6859
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "listitem", style: annotationItemStyle(color3), children: [
6860
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: "0.375rem" }, children: [
6861
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: colorDotStyle(color3) }),
6862
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { style: { fontSize: "0.75rem" }, children: ann.label })
6863
+ ] }),
6864
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: annotationTextStyle, children: annotatedText }),
6865
+ ann.note && /* @__PURE__ */ jsxRuntime.jsx("div", { style: annotationNoteStyle, children: ann.note })
6866
+ ] }, i);
6867
+ }),
6868
+ annotations.length === 0 && /* @__PURE__ */ jsxRuntime.jsx(
6869
+ "div",
6870
+ {
6871
+ style: {
6872
+ padding: "0.75rem",
6873
+ fontSize: "0.75rem",
6874
+ color: "var(--glyph-text-muted, #6b7a94)"
6875
+ },
6876
+ children: "Select text to add annotations."
6877
+ }
6878
+ )
6879
+ ] })
6880
+ ] })
6881
+ ] })
6882
+ ]
6883
+ }
6884
+ );
6885
+ }
6886
+
6887
+ // src/annotate/index.ts
6888
+ var annotateDefinition = {
6889
+ type: "ui:annotate",
6890
+ schema: schemas.annotateSchema,
6891
+ render: Annotate
6892
+ };
6893
+
4878
6894
  exports.Accordion = Accordion;
6895
+ exports.Annotate = Annotate;
4879
6896
  exports.Architecture = Architecture;
4880
6897
  exports.Callout = Callout;
4881
6898
  exports.Card = Card;
@@ -4885,18 +6902,26 @@ exports.Comparison = Comparison;
4885
6902
  exports.Equation = Equation;
4886
6903
  exports.FileTree = FileTree;
4887
6904
  exports.Flowchart = Flowchart;
6905
+ exports.Form = Form;
4888
6906
  exports.Graph = Graph;
4889
6907
  exports.Infographic = Infographic;
6908
+ exports.Kanban = Kanban;
4890
6909
  exports.Kpi = Kpi;
6910
+ exports.Matrix = Matrix;
4891
6911
  exports.MindMap = MindMap;
6912
+ exports.Poll = Poll;
4892
6913
  exports.Quiz = Quiz;
6914
+ exports.Ranker = Ranker;
6915
+ exports.Rating = Rating;
4893
6916
  exports.Relation = Relation;
4894
6917
  exports.Sequence = Sequence;
6918
+ exports.Slider = Slider;
4895
6919
  exports.Steps = Steps;
4896
6920
  exports.Table = Table;
4897
6921
  exports.Tabs = Tabs;
4898
6922
  exports.Timeline = Timeline;
4899
6923
  exports.accordionDefinition = accordionDefinition;
6924
+ exports.annotateDefinition = annotateDefinition;
4900
6925
  exports.architectureDefinition = architectureDefinition;
4901
6926
  exports.calloutDefinition = calloutDefinition;
4902
6927
  exports.cardDefinition = cardDefinition;
@@ -4910,13 +6935,20 @@ exports.computeForceLayout = computeForceLayout;
4910
6935
  exports.equationDefinition = equationDefinition;
4911
6936
  exports.fileTreeDefinition = fileTreeDefinition;
4912
6937
  exports.flowchartDefinition = flowchartDefinition;
6938
+ exports.formDefinition = formDefinition;
4913
6939
  exports.graphDefinition = graphDefinition;
4914
6940
  exports.infographicDefinition = infographicDefinition;
6941
+ exports.kanbanDefinition = kanbanDefinition;
4915
6942
  exports.kpiDefinition = kpiDefinition;
6943
+ exports.matrixDefinition = matrixDefinition;
4916
6944
  exports.mindMapDefinition = mindMapDefinition;
6945
+ exports.pollDefinition = pollDefinition;
4917
6946
  exports.quizDefinition = quizDefinition;
6947
+ exports.rankerDefinition = rankerDefinition;
6948
+ exports.ratingDefinition = ratingDefinition;
4918
6949
  exports.relationDefinition = relationDefinition;
4919
6950
  exports.sequenceDefinition = sequenceDefinition;
6951
+ exports.sliderDefinition = sliderDefinition;
4920
6952
  exports.stepsDefinition = stepsDefinition;
4921
6953
  exports.tableDefinition = tableDefinition;
4922
6954
  exports.tabsDefinition = tabsDefinition;