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