@diagrammo/dgmo 0.46.0 → 0.47.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.
Files changed (232) hide show
  1. package/dist/advanced.d.cts +3 -855
  2. package/dist/advanced.d.ts +3 -855
  3. package/dist/advanced.js +541 -70107
  4. package/dist/analyzer-UYKBXAKR.js +15 -0
  5. package/dist/auto.cjs +1 -1
  6. package/dist/auto.js +1 -1
  7. package/dist/auto.mjs +1 -1
  8. package/dist/block.js +83 -69417
  9. package/dist/calculator-LDYQCDDY.js +11 -0
  10. package/dist/charts-d3-QOZHKTCT.js +25 -0
  11. package/dist/chunk-2DXMZRWJ.js +800 -0
  12. package/dist/chunk-2LACINWN.js +786 -0
  13. package/dist/chunk-33LCUH4H.js +893 -0
  14. package/dist/chunk-3CIOJ5YJ.js +35 -0
  15. package/dist/chunk-3HDBAXF5.js +86 -0
  16. package/dist/chunk-3JLKYGBQ.js +290 -0
  17. package/dist/chunk-3MQQNXPD.js +415 -0
  18. package/dist/chunk-3R5POCTZ.js +967 -0
  19. package/dist/chunk-3SMGIJDD.js +112 -0
  20. package/dist/chunk-44CM72SQ.js +409 -0
  21. package/dist/chunk-4GRI7MEB.js +1604 -0
  22. package/dist/chunk-4OHACBGX.js +606 -0
  23. package/dist/chunk-5DH3KYFH.js +21 -0
  24. package/dist/chunk-5MLYVTYM.js +76 -0
  25. package/dist/chunk-5QXL4NYS.js +553 -0
  26. package/dist/chunk-5WRI5ZAA.js +31 -0
  27. package/dist/chunk-6EK55ART.js +117 -0
  28. package/dist/chunk-6FZYCKXB.js +2065 -0
  29. package/dist/chunk-6HO5ZTXK.js +277 -0
  30. package/dist/chunk-6KFWC4CO.js +7 -0
  31. package/dist/chunk-6MTWIQVO.js +121 -0
  32. package/dist/chunk-6NYWYNBI.js +329 -0
  33. package/dist/chunk-6SM5NJQH.js +620 -0
  34. package/dist/chunk-725LDASD.js +422 -0
  35. package/dist/chunk-76OWZ3BA.js +130 -0
  36. package/dist/chunk-7JEGTRLD.js +338 -0
  37. package/dist/chunk-A44USUV7.js +923 -0
  38. package/dist/chunk-A4GOY3PE.js +25 -0
  39. package/dist/chunk-A4VFQH5G.js +251 -0
  40. package/dist/chunk-AELOBFW5.js +375 -0
  41. package/dist/chunk-AN2MOO5T.js +522 -0
  42. package/dist/chunk-BMR4CIEO.js +41 -0
  43. package/dist/chunk-BS7SV7C3.js +51 -0
  44. package/dist/chunk-BUPDIHML.js +613 -0
  45. package/dist/chunk-BUVJFRPY.js +887 -0
  46. package/dist/chunk-C3Z4DX3V.js +112 -0
  47. package/dist/chunk-CI2G3OEB.js +1818 -0
  48. package/dist/chunk-CKNVO24D.js +54 -0
  49. package/dist/chunk-CUVHQHZ7.js +391 -0
  50. package/dist/chunk-CXK4DZH3.js +114 -0
  51. package/dist/chunk-DKEPS5LH.js +315 -0
  52. package/dist/chunk-DU7W62N5.js +487 -0
  53. package/dist/chunk-DZAQBW35.js +751 -0
  54. package/dist/chunk-EB6FPQUP.js +316 -0
  55. package/dist/chunk-EMS2SK26.js +376 -0
  56. package/dist/chunk-ERLBPDGU.js +510 -0
  57. package/dist/chunk-EVCVDTXE.js +732 -0
  58. package/dist/chunk-FCIIHT24.js +384 -0
  59. package/dist/chunk-FFUN5D66.js +97 -0
  60. package/dist/chunk-FITOLXSX.js +714 -0
  61. package/dist/chunk-FMDSF3OV.js +68 -0
  62. package/dist/chunk-FNB3PNGF.js +410 -0
  63. package/dist/chunk-G2H74XJZ.js +2104 -0
  64. package/dist/chunk-GMR5MFHJ.js +339 -0
  65. package/dist/chunk-H6PI3KJT.js +305 -0
  66. package/dist/chunk-I46X5VUA.js +59 -0
  67. package/dist/chunk-I5IGB56C.js +255 -0
  68. package/dist/chunk-IH6ZMVQ5.js +3950 -0
  69. package/dist/chunk-IJZFN3HM.js +463 -0
  70. package/dist/chunk-IPWPWM6R.js +3079 -0
  71. package/dist/chunk-IYULMRBE.js +23 -0
  72. package/dist/chunk-IZMRON6C.js +167 -0
  73. package/dist/chunk-J5FIXQRT.js +813 -0
  74. package/dist/chunk-JED6ZDEE.js +73 -0
  75. package/dist/chunk-JEUJOX5X.js +136 -0
  76. package/dist/chunk-JM4LEPY3.js +436 -0
  77. package/dist/chunk-L3UHP6TD.js +101 -0
  78. package/dist/chunk-L66GEFB6.js +524 -0
  79. package/dist/chunk-LIOHW36Z.js +469 -0
  80. package/dist/chunk-LP7EC4MI.js +16 -0
  81. package/dist/chunk-MITI6NOL.js +548 -0
  82. package/dist/chunk-MO5YHHMQ.js +168 -0
  83. package/dist/chunk-MZSBFAPJ.js +885 -0
  84. package/dist/chunk-NNAAJVBU.js +9 -0
  85. package/dist/chunk-NOTA6TEI.js +47 -0
  86. package/dist/chunk-NURGB5Z4.js +335 -0
  87. package/dist/chunk-NX4EQS3U.js +1062 -0
  88. package/dist/chunk-NXMESY4J.js +1 -0
  89. package/dist/chunk-OJR3MQYV.js +1458 -0
  90. package/dist/chunk-OURHMODR.js +454 -0
  91. package/dist/chunk-P235SVAH.js +1057 -0
  92. package/dist/chunk-P4RH4OSP.js +1038 -0
  93. package/dist/chunk-P6T66CCZ.js +226 -0
  94. package/dist/chunk-PBAHEPLQ.js +223 -0
  95. package/dist/chunk-PBHE5UG6.js +1530 -0
  96. package/dist/chunk-PDKUANDZ.js +35 -0
  97. package/dist/chunk-PWIHZ6NJ.js +296 -0
  98. package/dist/chunk-QEBUY5NY.js +1685 -0
  99. package/dist/chunk-QLCCJEJI.js +253 -0
  100. package/dist/chunk-QOOUC2J5.js +52 -0
  101. package/dist/chunk-QYTPPDAO.js +402 -0
  102. package/dist/chunk-R3XI2VC7.js +801 -0
  103. package/dist/chunk-RCERWOJR.js +154 -0
  104. package/dist/chunk-RJE5JTVK.js +362 -0
  105. package/dist/chunk-RTLBSQ3G.js +1155 -0
  106. package/dist/chunk-RUNNI6VE.js +448 -0
  107. package/dist/chunk-RVMRZAPB.js +354 -0
  108. package/dist/chunk-RYEGFMUO.js +955 -0
  109. package/dist/chunk-RZCF2VVM.js +528 -0
  110. package/dist/chunk-SML67VPV.js +1025 -0
  111. package/dist/chunk-SQ7IY53Q.js +44 -0
  112. package/dist/chunk-SQAG3UXH.js +161 -0
  113. package/dist/chunk-SRTU6HMQ.js +1851 -0
  114. package/dist/chunk-T2OE3YQB.js +550 -0
  115. package/dist/chunk-T3UBLACL.js +299 -0
  116. package/dist/chunk-TLVIHFRO.js +296 -0
  117. package/dist/chunk-TYS6CSS5.js +52 -0
  118. package/dist/chunk-U6EQI3U6.js +130 -0
  119. package/dist/chunk-UJ4FJLBF.js +86 -0
  120. package/dist/chunk-ULOEATBZ.js +347 -0
  121. package/dist/chunk-UNCCZ3NU.js +127 -0
  122. package/dist/chunk-USZW2HHU.js +197 -0
  123. package/dist/chunk-UWN33VNX.js +1610 -0
  124. package/dist/chunk-UZ7EZW3B.js +40 -0
  125. package/dist/chunk-VOKGH627.js +37 -0
  126. package/dist/chunk-WBRZJOEC.js +204 -0
  127. package/dist/chunk-WCSOJFU3.js +380 -0
  128. package/dist/chunk-WCT3NYCV.js +160 -0
  129. package/dist/chunk-WVAJFAIA.js +529 -0
  130. package/dist/chunk-X5LCYCL5.js +394 -0
  131. package/dist/chunk-XAKEUMEJ.js +1256 -0
  132. package/dist/chunk-Y2ZE3MSG.js +590 -0
  133. package/dist/chunk-Y3J7VR52.js +646 -0
  134. package/dist/chunk-Y5QGVTVC.js +33 -0
  135. package/dist/chunk-YAA6IID3.js +548 -0
  136. package/dist/chunk-YGJTDXVR.js +692 -0
  137. package/dist/chunk-YRYKTYMQ.js +186 -0
  138. package/dist/chunk-YYVTBZZC.js +61 -0
  139. package/dist/chunk-Z5CGSFJB.js +647 -0
  140. package/dist/chunk-Z72XRXD4.js +787 -0
  141. package/dist/chunk-ZGR7ULFJ.js +978 -0
  142. package/dist/chunk-ZJEL4H2R.js +851 -0
  143. package/dist/chunk-ZO27FGJS.js +554 -0
  144. package/dist/collapse-CLPJD2RL.js +11 -0
  145. package/dist/collapse-SLINJQW6.js +9 -0
  146. package/dist/collapse-ZWUFFCK2.js +9 -0
  147. package/dist/compute-G55RG53N.js +8 -0
  148. package/dist/dgmo-router-DZCIDUO5.js +73 -0
  149. package/dist/dimensions-J3FHIMJ6.js +22 -0
  150. package/dist/element.cjs +1 -1
  151. package/dist/element.js +1 -1
  152. package/dist/element.mjs +1 -1
  153. package/dist/flowchart-parser-ZNSKIBVR.js +21 -0
  154. package/dist/flowchart-renderer-ZAEOL4DX.js +35 -0
  155. package/dist/index.cjs +3 -3
  156. package/dist/index.d.cts +3 -469
  157. package/dist/index.d.ts +3 -469
  158. package/dist/index.js +137 -69685
  159. package/dist/layout-46N4JJYK.js +17 -0
  160. package/dist/layout-5RA6QWR6.js +26 -0
  161. package/dist/layout-5T6RH5M6.js +14 -0
  162. package/dist/layout-7QXCNOVS.js +13 -0
  163. package/dist/layout-EE6GE46Y.js +17 -0
  164. package/dist/layout-G23F7LDH.js +9 -0
  165. package/dist/layout-HRBZPFCN.js +23 -0
  166. package/dist/layout-HWDSRX5E.js +20 -0
  167. package/dist/layout-HYZPWAK5.js +18 -0
  168. package/dist/layout-N76NR4ZO.js +19 -0
  169. package/dist/layout-OB3RI63S.js +14 -0
  170. package/dist/layout-S2QBYSK2.js +15 -0
  171. package/dist/layout-UM5F4DJB.js +15 -0
  172. package/dist/layout-search-OYV77HHS.js +1655 -0
  173. package/dist/load-data-EHDDZ5ZS.js +8 -0
  174. package/dist/palettes-PRJ2RI6D.js +56 -0
  175. package/dist/parser-2MFP2H2E.js +11 -0
  176. package/dist/parser-3STE62GB.js +21 -0
  177. package/dist/parser-4FL2CMDG.js +18 -0
  178. package/dist/parser-4OGOBJJ4.js +13 -0
  179. package/dist/parser-6BTRLKRZ.js +15 -0
  180. package/dist/parser-AQETEVRW.js +21 -0
  181. package/dist/parser-CFZD5IBL.js +12 -0
  182. package/dist/parser-FUSKFGZN.js +16 -0
  183. package/dist/parser-I6KXEYFK.js +13 -0
  184. package/dist/parser-IXSZZQHY.js +18 -0
  185. package/dist/parser-JAL2HD4I.js +13 -0
  186. package/dist/parser-KNSQSKQO.js +20 -0
  187. package/dist/parser-N5O4IHUN.js +13 -0
  188. package/dist/parser-NGU3ETO6.js +16 -0
  189. package/dist/parser-NRAI6O25.js +19 -0
  190. package/dist/parser-O5J4VBII.js +11 -0
  191. package/dist/parser-OV7NOBW6.js +21 -0
  192. package/dist/parser-PQ3POX45.js +23 -0
  193. package/dist/parser-TFZFBVWN.js +11 -0
  194. package/dist/parser-TRTHDFPP.js +18 -0
  195. package/dist/parser-VQP2UJ6W.js +11 -0
  196. package/dist/parser-XDVELHRK.js +13 -0
  197. package/dist/parser-XKRP7QKP.js +11 -0
  198. package/dist/parser-Y6WTBU76.js +14 -0
  199. package/dist/parser-ZSDPZYQP.js +12 -0
  200. package/dist/renderer-2XQQQ6QR.js +28 -0
  201. package/dist/renderer-5NDVNDAZ.js +26 -0
  202. package/dist/renderer-6EEZHMH4.js +26 -0
  203. package/dist/renderer-6W5462RB.js +43 -0
  204. package/dist/renderer-7MVLWWQD.js +22 -0
  205. package/dist/renderer-AWWAF6XD.js +33 -0
  206. package/dist/renderer-BYCNMBF7.js +34 -0
  207. package/dist/renderer-CLJHQPBJ.js +29 -0
  208. package/dist/renderer-GHPZMFU2.js +15 -0
  209. package/dist/renderer-JGBKUCZD.js +16 -0
  210. package/dist/renderer-KQB7UGDS.js +19 -0
  211. package/dist/renderer-PGX2TKVS.js +24 -0
  212. package/dist/renderer-QIWVHWZO.js +15 -0
  213. package/dist/renderer-RPOA5YPZ.js +35 -0
  214. package/dist/renderer-SMIBCRVB.js +24 -0
  215. package/dist/renderer-T7YKC5VP.js +28 -0
  216. package/dist/renderer-TOCOVLHS.js +37 -0
  217. package/dist/renderer-UWA5XS74.js +39 -0
  218. package/dist/renderer-VACVQYML.js +25 -0
  219. package/dist/renderer-W4M6QLIJ.js +37 -0
  220. package/dist/renderer-WZRREQ2P.js +23 -0
  221. package/dist/renderer-YY2OSF6E.js +27 -0
  222. package/dist/renderer-Z7I3W2PP.js +24 -0
  223. package/dist/renderer-ZGNTUWC4.js +22 -0
  224. package/dist/renderer-ZTR3DER3.js +19 -0
  225. package/dist/renderer-radial-C32GPJQ7.js +21 -0
  226. package/dist/resolver-NOAA7ID7.js +13 -0
  227. package/dist/state-collapse-HCGD6IVJ.js +8 -0
  228. package/dist/state-parser-Z2V23ATR.js +18 -0
  229. package/dist/state-renderer-RFYHSP4K.js +34 -0
  230. package/dist/themes-1-CuKpeH.d.cts +908 -0
  231. package/dist/themes-1-CuKpeH.d.ts +908 -0
  232. package/package.json +1 -1
@@ -0,0 +1,751 @@
1
+ import {
2
+ NODE_STROKE_WIDTH
3
+ } from "./chunk-3CIOJ5YJ.js";
4
+ import {
5
+ ScaleContext
6
+ } from "./chunk-YYVTBZZC.js";
7
+ import {
8
+ TITLE_FONT_SIZE,
9
+ TITLE_FONT_WEIGHT,
10
+ TITLE_Y
11
+ } from "./chunk-A4GOY3PE.js";
12
+ import {
13
+ FONT_FAMILY
14
+ } from "./chunk-6KFWC4CO.js";
15
+ import {
16
+ measureText,
17
+ truncateText,
18
+ wrapTextToWidth
19
+ } from "./chunk-WCT3NYCV.js";
20
+ import {
21
+ VARIANTS,
22
+ allTasks
23
+ } from "./chunk-RYEGFMUO.js";
24
+ import {
25
+ contrastText,
26
+ mix,
27
+ themeBaseBg
28
+ } from "./chunk-P6T66CCZ.js";
29
+
30
+ // src/raci/renderer.ts
31
+ import * as d3Selection from "d3-selection";
32
+ function groupDiagnosticsByTask(parsed) {
33
+ const map = /* @__PURE__ */ new Map();
34
+ if (parsed.diagnostics.length === 0) return map;
35
+ const tasks = [];
36
+ for (const t of parsed.tasksWithoutPhase) tasks.push(t);
37
+ for (const p of parsed.phases) for (const t of p.tasks) tasks.push(t);
38
+ for (const d of parsed.diagnostics) {
39
+ if (d.line <= 0) continue;
40
+ const owner = tasks.find(
41
+ (t) => d.line >= t.lineNumber && d.line <= t.endLineNumber
42
+ );
43
+ if (!owner) continue;
44
+ let bucket = map.get(owner.id);
45
+ if (!bucket) {
46
+ bucket = { errors: [], warnings: [], firstLine: d.line };
47
+ map.set(owner.id, bucket);
48
+ } else if (d.line < bucket.firstLine) {
49
+ bucket.firstLine = d.line;
50
+ }
51
+ const entry = { line: d.line, message: d.message };
52
+ if (d.severity === "error") bucket.errors.push(entry);
53
+ else bucket.warnings.push(entry);
54
+ }
55
+ return map;
56
+ }
57
+ function trimTaskPrefix(message) {
58
+ const prefix = message.match(/^Task '[^']*'\s+(.+)$/);
59
+ if (prefix) {
60
+ const rest = prefix[1];
61
+ return rest.charAt(0).toUpperCase() + rest.slice(1);
62
+ }
63
+ return message.replace(/\s+on task '[^']*'/, "");
64
+ }
65
+ function topRoundedRectPath(x, y, w, h, r) {
66
+ const rr = Math.min(r, w / 2, h / 2);
67
+ return `M ${x} ${y + rr} A ${rr} ${rr} 0 0 1 ${x + rr} ${y} H ${x + w - rr} A ${rr} ${rr} 0 0 1 ${x + w} ${y + rr} V ${y + h} H ${x} Z`;
68
+ }
69
+ function sortByAlphabet(markers, alphabet) {
70
+ const order = new Map(
71
+ alphabet.map((m, i) => [m, i])
72
+ );
73
+ return [...markers].sort(
74
+ (a, b) => (order.get(a) ?? 99) - (order.get(b) ?? 99)
75
+ );
76
+ }
77
+ var MARKER_LABELS = {
78
+ raci: { R: "Responsible", A: "Accountable", C: "Consulted", I: "Informed" },
79
+ rasci: {
80
+ R: "Responsible",
81
+ A: "Accountable",
82
+ S: "Support",
83
+ C: "Consulted",
84
+ I: "Informed"
85
+ },
86
+ daci: { D: "Driver", A: "Approver", C: "Contributor", I: "Informed" }
87
+ };
88
+ var TITLE_AREA_HEIGHT = 50;
89
+ var H_MARGIN = 24;
90
+ var V_MARGIN = 16;
91
+ var LEGEND_HEIGHT = 28;
92
+ var LEGEND_CHIP_GAP = 8;
93
+ var LEGEND_CHIP_LABEL_MIN = 96;
94
+ var LEGEND_LETTER_CHIP_W = 28;
95
+ var TITLE_LEGEND_GAP = 16;
96
+ var LEGEND_LABEL_FONT = 12;
97
+ var LEGEND_LETTER_FONT = 14;
98
+ var HEADER_HEIGHT = 36;
99
+ var ROW_HEIGHT = 36;
100
+ var PHASE_HEIGHT = 40;
101
+ var TASK_LABEL_MIN = 200;
102
+ var TASK_LABEL_MAX = 360;
103
+ var ROLE_COL_MIN = 64;
104
+ var ROLE_COL_MAX = 120;
105
+ var CELL_PAD = 4;
106
+ var VIOLATION_LINE_HEIGHT = 14;
107
+ var STACK_TOP_GAP = 8;
108
+ var DESC_FONT = 11;
109
+ var COLUMN_RADIUS = 8;
110
+ var COLUMN_INSET = 8;
111
+ var COLUMN_BOTTOM_PAD = 14;
112
+ var MARKER_FONT = 14;
113
+ var MARKER_FONT_WEIGHT = 600;
114
+ var LABEL_FONT = 13;
115
+ var ROLE_HEADER_FONT = 12;
116
+ var PHASE_FONT = 13;
117
+ var TINT_PCT = 25;
118
+ var NODE_RADIUS = 6;
119
+ var AUTO_ACCENTS = [
120
+ "blue",
121
+ "orange",
122
+ "green",
123
+ "purple",
124
+ "yellow",
125
+ "cyan",
126
+ "red",
127
+ "teal"
128
+ ];
129
+ function autoAccent(index, palette) {
130
+ return palette.colors[AUTO_ACCENTS[index % AUTO_ACCENTS.length]];
131
+ }
132
+ function markerColor(marker, palette) {
133
+ switch (marker) {
134
+ case "R":
135
+ return palette.colors.green;
136
+ case "A":
137
+ return palette.colors.red;
138
+ case "S":
139
+ return palette.colors.teal;
140
+ case "C":
141
+ return palette.colors.yellow;
142
+ case "I":
143
+ return palette.colors.gray;
144
+ case "D":
145
+ return palette.colors.purple;
146
+ }
147
+ }
148
+ function renderRaci(container, parsed, palette, isDark, handlers, exportDims) {
149
+ const opts = typeof handlers === "function" ? { onClickLine: handlers } : handlers ?? {};
150
+ const onClickLine = opts.onClickLine;
151
+ const onMarkerDragStart = opts.onMarkerDragStart;
152
+ const hideLegend = opts.hideLegend ?? false;
153
+ const collapsedPhases = opts.collapsedPhases ?? /* @__PURE__ */ new Set();
154
+ const hideTitle = (opts.hideTitle ?? false) || parsed.options["no-title"] === "on";
155
+ d3Selection.select(container).selectAll(":not([data-d3-tooltip])").remove();
156
+ const width = exportDims?.width ?? container.clientWidth;
157
+ const height = exportDims?.height ?? container.clientHeight;
158
+ if (width <= 0 || height <= 0) return;
159
+ const tasksAll = [...allTasks(parsed)];
160
+ if (tasksAll.length === 0 && parsed.phases.length === 0) return;
161
+ const solid = parsed.options["solid-fill"] === "on";
162
+ const surfaceBg = themeBaseBg(palette, isDark);
163
+ const roleCount = Math.max(1, parsed.roles.length);
164
+ const idealWidth = roleCount * ROLE_COL_MAX + TASK_LABEL_MAX + 2 * H_MARGIN;
165
+ const ctx = exportDims ? ScaleContext.identity() : ScaleContext.from(width, idealWidth);
166
+ const sTitleFontSize = ctx.text(TITLE_FONT_SIZE);
167
+ const sTitleY = ctx.structural(TITLE_Y);
168
+ const sHMargin = ctx.aesthetic(H_MARGIN);
169
+ const sVMargin = ctx.aesthetic(V_MARGIN);
170
+ const sTitleAreaHeight = ctx.structural(TITLE_AREA_HEIGHT);
171
+ const sHeaderHeight = ctx.structural(HEADER_HEIGHT);
172
+ const sRowHeight = ctx.structural(ROW_HEIGHT);
173
+ const sPhaseHeight = ctx.structural(PHASE_HEIGHT);
174
+ const sTaskLabelMin = ctx.structural(TASK_LABEL_MIN);
175
+ const sTaskLabelMax = ctx.structural(TASK_LABEL_MAX);
176
+ const sRoleColMin = ctx.structural(ROLE_COL_MIN);
177
+ const sRoleColMax = ctx.structural(ROLE_COL_MAX);
178
+ const sCellPad = ctx.structural(CELL_PAD);
179
+ const sLabelFont = ctx.text(LABEL_FONT);
180
+ const sRoleHeaderFont = ctx.text(ROLE_HEADER_FONT);
181
+ const sPhaseFont = ctx.text(PHASE_FONT);
182
+ const sMarkerFont = ctx.text(MARKER_FONT);
183
+ const sDescFont = ctx.text(DESC_FONT);
184
+ const sColumnRadius = ctx.structural(COLUMN_RADIUS);
185
+ const sColumnInset = ctx.structural(COLUMN_INSET);
186
+ const sColumnBottomPad = ctx.structural(COLUMN_BOTTOM_PAD);
187
+ const sLegendHeight = ctx.structural(LEGEND_HEIGHT);
188
+ const sLegendChipGap = ctx.structural(LEGEND_CHIP_GAP);
189
+ const sLegendChipLabelMin = ctx.structural(LEGEND_CHIP_LABEL_MIN);
190
+ const sLegendLetterChipW = ctx.structural(LEGEND_LETTER_CHIP_W);
191
+ const sTitleLegendGap = ctx.aesthetic(TITLE_LEGEND_GAP);
192
+ const sLegendLabelFont = ctx.text(LEGEND_LABEL_FONT);
193
+ const sLegendLetterFont = ctx.text(LEGEND_LETTER_FONT);
194
+ const sViolationLineHeight = ctx.structural(VIOLATION_LINE_HEIGHT);
195
+ const sStackTopGap = ctx.structural(STACK_TOP_GAP);
196
+ const sNodeStrokeWidth = ctx.structural(NODE_STROKE_WIDTH);
197
+ const sNodeRadius = ctx.structural(NODE_RADIUS);
198
+ const innerWidth = Math.max(0, width - 2 * sHMargin);
199
+ let roleColW = Math.max(
200
+ sRoleColMin,
201
+ Math.min(sRoleColMax, Math.floor((innerWidth - sTaskLabelMin) / roleCount))
202
+ );
203
+ let labelW = innerWidth - roleColW * roleCount;
204
+ if (labelW > sTaskLabelMax) {
205
+ labelW = sTaskLabelMax;
206
+ roleColW = Math.max(
207
+ sRoleColMin,
208
+ Math.floor((innerWidth - labelW) / roleCount)
209
+ );
210
+ }
211
+ if (labelW < sTaskLabelMin) labelW = sTaskLabelMin;
212
+ const taskDiagnostics = groupDiagnosticsByTask(parsed);
213
+ const phasedRows = [];
214
+ for (const t of parsed.tasksWithoutPhase)
215
+ phasedRows.push({ kind: "task", task: t });
216
+ for (const p of parsed.phases) {
217
+ const collapsed = collapsedPhases.has(p.id);
218
+ phasedRows.push({ kind: "phase", phase: p, collapsed });
219
+ if (!collapsed) {
220
+ for (const t of p.tasks) phasedRows.push({ kind: "task", task: t });
221
+ }
222
+ }
223
+ const hasTopRow = !hideTitle || !hideLegend;
224
+ const titleArea = hasTopRow ? sTitleAreaHeight : sVMargin;
225
+ const legendY = (sTitleAreaHeight - sLegendHeight) / 2;
226
+ const headerY = titleArea + sVMargin;
227
+ const bodyTop = headerY + sHeaderHeight;
228
+ let cursorY = bodyTop;
229
+ const rowYs = [];
230
+ const labelMaxW = labelW - 16;
231
+ const taskRowContent = /* @__PURE__ */ new Map();
232
+ for (const row of phasedRows) {
233
+ if (row.kind === "task") {
234
+ const bucket = taskDiagnostics.get(row.task.id);
235
+ taskRowContent.set(
236
+ row.task.id,
237
+ prepareRowContent(
238
+ row.task,
239
+ bucket,
240
+ labelMaxW,
241
+ sLabelFont,
242
+ sDescFont,
243
+ sRowHeight,
244
+ sStackTopGap,
245
+ sViolationLineHeight
246
+ )
247
+ );
248
+ }
249
+ }
250
+ for (const row of phasedRows) {
251
+ rowYs.push(cursorY);
252
+ if (row.kind === "phase") {
253
+ cursorY += sPhaseHeight;
254
+ } else {
255
+ const content = taskRowContent.get(row.task.id);
256
+ cursorY += content?.rowHeight ?? sRowHeight;
257
+ }
258
+ }
259
+ const colBottomY = cursorY + sColumnBottomPad;
260
+ const totalHeight = colBottomY + sVMargin;
261
+ const svgHeight = exportDims ? totalHeight : Math.max(height, totalHeight);
262
+ const svg = d3Selection.select(container).append("svg").attr("xmlns", "http://www.w3.org/2000/svg").attr("width", width).attr("height", svgHeight).attr("viewBox", `0 0 ${width} ${svgHeight}`).attr("preserveAspectRatio", "xMidYMin meet").attr("font-family", FONT_FAMILY).style("background", "transparent");
263
+ if (ctx.isBelowFloor) {
264
+ svg.attr("width", "100%");
265
+ }
266
+ const variantLabels = MARKER_LABELS[parsed.variant];
267
+ const legendMarkers = Object.keys(variantLabels).filter(
268
+ (m) => variantLabels[m] !== void 0
269
+ );
270
+ const numChips = legendMarkers.length;
271
+ const chipGapTotal = sLegendChipGap * Math.max(0, numChips - 1);
272
+ const longestLabelPx = legendMarkers.reduce(
273
+ (n, m) => Math.max(n, measureText(variantLabels[m] ?? "", sLegendLabelFont)),
274
+ 0
275
+ );
276
+ const fullChipW = Math.max(
277
+ sLegendChipLabelMin,
278
+ Math.ceil(4 + 24 + 8 + longestLabelPx + 8)
279
+ );
280
+ const fullLegendW = numChips * fullChipW + chipGapTotal;
281
+ const letterLegendW = numChips * sLegendLetterChipW + chipGapTotal;
282
+ const titleEstW = parsed.title && !hideTitle ? measureText(parsed.title, sTitleFontSize) : 0;
283
+ let legendMode = "full";
284
+ let legendTotalW = fullLegendW;
285
+ let legendChipW = fullChipW;
286
+ if (!hideLegend) {
287
+ const remaining = innerWidth - titleEstW - sTitleLegendGap;
288
+ if (remaining < fullLegendW) {
289
+ legendMode = "letters";
290
+ legendTotalW = letterLegendW;
291
+ legendChipW = sLegendLetterChipW;
292
+ }
293
+ } else {
294
+ legendTotalW = 0;
295
+ }
296
+ const legendX = sHMargin + innerWidth - legendTotalW;
297
+ const titleMaxW = hideLegend ? innerWidth : legendX - sHMargin - sTitleLegendGap;
298
+ if (parsed.title && !hideTitle) {
299
+ svg.append("text").attr("x", sHMargin).attr("y", sTitleY).attr("text-anchor", "start").attr("font-size", sTitleFontSize).attr("font-weight", TITLE_FONT_WEIGHT).attr("fill", palette.text).text(truncateText(parsed.title, sTitleFontSize, titleMaxW));
300
+ }
301
+ if (!hideLegend && numChips > 0) {
302
+ renderLegend(
303
+ svg,
304
+ legendMarkers,
305
+ parsed.variant,
306
+ legendX,
307
+ legendY,
308
+ legendMode,
309
+ legendChipW,
310
+ palette,
311
+ surfaceBg,
312
+ solid,
313
+ onMarkerDragStart,
314
+ sLegendChipGap,
315
+ sLegendHeight,
316
+ sNodeRadius,
317
+ sNodeStrokeWidth,
318
+ sLegendLetterFont,
319
+ sLegendLabelFont
320
+ );
321
+ }
322
+ const roleX = (i) => sHMargin + labelW + i * roleColW;
323
+ const colTotalHeight = colBottomY - headerY;
324
+ if (parsed.phases.length === 0) {
325
+ const bandsG = svg.append("g").attr("class", "raci-row-bands");
326
+ phasedRows.forEach((row, i) => {
327
+ if (row.kind !== "task") return;
328
+ const taskIdx = parsed.tasksWithoutPhase.indexOf(row.task);
329
+ if (taskIdx < 0) return;
330
+ const yTop = rowYs[i];
331
+ const rh = taskRowContent.get(row.task.id)?.rowHeight ?? sRowHeight;
332
+ bandsG.append("rect").attr("class", "raci-row-band").attr("data-task-id", row.task.id).attr("x", sHMargin).attr("y", yTop + 2).attr("width", innerWidth).attr("height", rh - 4).attr("rx", 6).attr("fill", mix(autoAccent(taskIdx, palette), surfaceBg, 12));
333
+ });
334
+ }
335
+ const columnsG = svg.append("g").attr("class", "raci-columns");
336
+ parsed.roles.forEach((roleId, i) => {
337
+ const cx = roleX(i) + sColumnInset;
338
+ const cw = roleColW - 2 * sColumnInset;
339
+ const roleColor = parsed.roleColors[i] ?? autoAccent(i, palette);
340
+ const bodyFill = mix(roleColor, themeBaseBg(palette, isDark), 16);
341
+ const headerFill = mix(
342
+ roleColor,
343
+ themeBaseBg(palette, isDark),
344
+ 30
345
+ );
346
+ const colG = columnsG.append("g").attr("class", "raci-column").attr("data-role-id", roleId);
347
+ colG.append("rect").attr("class", "raci-column-body").attr("x", cx).attr("y", headerY).attr("width", cw).attr("height", colTotalHeight).attr("rx", sColumnRadius).attr("fill", bodyFill);
348
+ colG.append("path").attr("class", "raci-column-header").attr(
349
+ "d",
350
+ topRoundedRectPath(cx, headerY, cw, sHeaderHeight, sColumnRadius)
351
+ ).attr("fill", headerFill);
352
+ const cxText = roleX(i) + roleColW / 2;
353
+ colG.append("text").attr("class", "raci-column-label").attr("x", cxText).attr("y", headerY + sHeaderHeight / 2).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("font-size", sRoleHeaderFont).attr("font-weight", 600).attr("fill", palette.text).text(
354
+ truncateText(
355
+ parsed.roleDisplayNames[i] ?? "",
356
+ sRoleHeaderFont,
357
+ roleColW - 2 * sCellPad
358
+ )
359
+ );
360
+ });
361
+ const hasAnyDiagnostic = taskDiagnostics.size > 0;
362
+ const SUMMARY_MIN_SLICE_W = 22;
363
+ const summaryInsetForCheck = sColumnInset + 4;
364
+ const summaryColBodyWForCheck = roleColW - 2 * summaryInsetForCheck;
365
+ let chartCanShowSummaries = true;
366
+ for (const row of phasedRows) {
367
+ if (row.kind !== "phase" || !row.collapsed) continue;
368
+ for (const roleId of parsed.roles) {
369
+ const used = /* @__PURE__ */ new Set();
370
+ for (const t of row.phase.tasks) {
371
+ const a = t.roleAssignments.find((r) => r.id === roleId);
372
+ if (!a) continue;
373
+ for (const m of a.markers) used.add(m);
374
+ }
375
+ if (used.size === 0) continue;
376
+ const totalGap = SLICE_GAP * Math.max(0, used.size - 1);
377
+ const sliceW = (summaryColBodyWForCheck - totalGap) / used.size;
378
+ if (sliceW < SUMMARY_MIN_SLICE_W) {
379
+ chartCanShowSummaries = false;
380
+ break;
381
+ }
382
+ }
383
+ if (!chartCanShowSummaries) break;
384
+ }
385
+ for (let i = 0; i < phasedRows.length; i++) {
386
+ const row = phasedRows[i];
387
+ const y = rowYs[i];
388
+ if (row.kind === "phase") {
389
+ const phaseIdx = parsed.phases.indexOf(row.phase);
390
+ renderPhaseBar(
391
+ svg,
392
+ row.phase,
393
+ sHMargin,
394
+ y,
395
+ innerWidth,
396
+ palette,
397
+ row.collapsed,
398
+ autoAccent(Math.max(0, phaseIdx), palette),
399
+ parsed.roles,
400
+ roleX,
401
+ roleColW,
402
+ parsed.variant,
403
+ surfaceBg,
404
+ solid,
405
+ chartCanShowSummaries,
406
+ sPhaseHeight,
407
+ sPhaseFont,
408
+ sColumnInset,
409
+ sNodeRadius
410
+ );
411
+ } else {
412
+ renderTaskRow(
413
+ svg,
414
+ row.task,
415
+ parsed,
416
+ sHMargin,
417
+ y,
418
+ labelW,
419
+ roleX,
420
+ roleColW,
421
+ palette,
422
+ surfaceBg,
423
+ solid,
424
+ taskDiagnostics,
425
+ hasAnyDiagnostic,
426
+ taskRowContent.get(row.task.id),
427
+ onClickLine,
428
+ onMarkerDragStart,
429
+ sLabelFont,
430
+ sDescFont,
431
+ sCellPad,
432
+ sRowHeight,
433
+ sColumnInset,
434
+ sNodeRadius,
435
+ sNodeStrokeWidth,
436
+ sMarkerFont,
437
+ sLegendLabelFont,
438
+ sStackTopGap,
439
+ sViolationLineHeight
440
+ );
441
+ }
442
+ }
443
+ const anyMarker = tasksAll.some(
444
+ (t) => t.roleAssignments.some((a) => a.markers.length > 0)
445
+ );
446
+ if (!anyMarker) {
447
+ svg.append("text").attr("x", width / 2).attr("y", totalHeight - sVMargin - 2).attr("text-anchor", "middle").attr("font-size", 12).attr("fill", palette.textMuted).text(
448
+ "Drag a marker from the legend onto any cell. Drag a marker out to remove."
449
+ );
450
+ }
451
+ setupColumnHover(svg);
452
+ }
453
+ function setupColumnHover(svg) {
454
+ const node = svg.node();
455
+ if (!node) return;
456
+ let current = null;
457
+ const findRole = (target) => {
458
+ let el = target;
459
+ while (el && el !== node) {
460
+ const r = el.getAttribute && el.getAttribute("data-role-id");
461
+ if (r) return r;
462
+ el = el.parentElement;
463
+ }
464
+ return null;
465
+ };
466
+ const set = (role) => {
467
+ if (role === current) return;
468
+ if (current) {
469
+ svg.selectAll(
470
+ `.raci-cell[data-role-id="${current}"], .raci-column[data-role-id="${current}"]`
471
+ ).classed("raci-col-active", false);
472
+ }
473
+ current = role;
474
+ if (role) {
475
+ svg.attr("data-hover-col", role);
476
+ svg.selectAll(
477
+ `.raci-cell[data-role-id="${role}"], .raci-column[data-role-id="${role}"]`
478
+ ).classed("raci-col-active", true);
479
+ } else {
480
+ svg.attr("data-hover-col", null);
481
+ }
482
+ };
483
+ svg.on("mouseover", (event) => set(findRole(event.target)));
484
+ svg.on("mouseleave", () => set(null));
485
+ }
486
+ function renderLegend(svg, markers, variant, x, y, mode, chipW, palette, surfaceBg, solid, _onMarkerDragStart, sLegendChipGap, sLegendHeight, sNodeRadius, sNodeStrokeWidth, sLegendLetterFont, sLegendLabelFont) {
487
+ if (markers.length === 0) return;
488
+ const labels = MARKER_LABELS[variant];
489
+ const legendG = svg.append("g").attr("class", "raci-legend").attr("aria-label", `${variant.toUpperCase()} marker legend`);
490
+ markers.forEach((marker, i) => {
491
+ const cx = x + i * (chipW + sLegendChipGap);
492
+ const rawColor = markerColor(marker, palette);
493
+ const labelText = labels[marker];
494
+ const chipG = legendG.append("g").attr("class", "raci-legend-chip").attr("data-marker", marker).style("cursor", "grab");
495
+ if (mode === "letters") {
496
+ const fill2 = solid ? rawColor : mix(rawColor, surfaceBg, TINT_PCT);
497
+ const stroke2 = solid ? mix(rawColor, surfaceBg, 70) : rawColor;
498
+ chipG.append("rect").attr("x", cx).attr("y", y).attr("width", chipW).attr("height", sLegendHeight).attr("rx", sNodeRadius).attr("fill", fill2).attr("stroke", stroke2).attr("stroke-width", sNodeStrokeWidth);
499
+ chipG.append("text").attr("x", cx + chipW / 2).attr("y", y + sLegendHeight / 2).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("font-size", sLegendLetterFont).attr("font-weight", 700).attr(
500
+ "fill",
501
+ solid ? contrastText(
502
+ fill2,
503
+ palette.textOnFillLight,
504
+ palette.textOnFillDark
505
+ ) : palette.text
506
+ ).text(marker);
507
+ return;
508
+ }
509
+ const fill = solid ? rawColor : mix(rawColor, surfaceBg, TINT_PCT);
510
+ const stroke = mix(rawColor, surfaceBg, 70);
511
+ chipG.append("rect").attr("x", cx).attr("y", y).attr("width", chipW).attr("height", sLegendHeight).attr("rx", sNodeRadius).attr("fill", fill).attr("stroke", stroke).attr("stroke-width", sNodeStrokeWidth);
512
+ const slabPad = 4;
513
+ const slabW = 24;
514
+ const slabFill = solid ? mix(rawColor, surfaceBg, 70) : mix(rawColor, surfaceBg, 55);
515
+ chipG.append("rect").attr("x", cx + slabPad).attr("y", y + slabPad).attr("width", slabW).attr("height", sLegendHeight - 2 * slabPad).attr("rx", 4).attr("fill", slabFill);
516
+ chipG.append("text").attr("x", cx + slabPad + slabW / 2).attr("y", y + sLegendHeight / 2).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("font-size", sLegendLetterFont).attr("font-weight", 700).attr(
517
+ "fill",
518
+ solid ? contrastText(
519
+ slabFill,
520
+ palette.textOnFillLight,
521
+ palette.textOnFillDark
522
+ ) : palette.text
523
+ ).text(marker);
524
+ chipG.append("text").attr("x", cx + slabPad + slabW + 8).attr("y", y + sLegendHeight / 2).attr("dominant-baseline", "central").attr("font-size", sLegendLabelFont).attr("font-weight", 600).attr("fill", palette.text).text(
525
+ truncateText(
526
+ labelText,
527
+ sLegendLabelFont,
528
+ chipW - slabW - slabPad * 2 - 12
529
+ )
530
+ );
531
+ });
532
+ }
533
+ function renderRaciForExport(container, parsed, palette, isDark, exportDims) {
534
+ renderRaci(container, parsed, palette, isDark, {}, exportDims);
535
+ }
536
+ function renderPhaseBar(svg, phase, x, y, width, palette, collapsed, autoColor, roles, roleX, roleColW, variant, surfaceBg, solid, showSummary, sPhaseHeight, sPhaseFont, sColumnInset, sNodeRadius) {
537
+ const phaseG = svg.append("g").attr("class", "raci-phase").attr("data-section", `phase-${phase.id}`).attr("data-section-toggle", "").attr("data-line-number", String(phase.lineNumber)).attr("data-phase-id", phase.id).attr("data-collapsed", collapsed ? "1" : "0").style("cursor", "pointer");
538
+ const phaseFill = mix(phase.color ?? autoColor, palette.bg, 30);
539
+ phaseG.append("rect").attr("x", x).attr("y", y + 4).attr("width", width).attr("height", sPhaseHeight - 8).attr("fill", phaseFill).attr("rx", 4);
540
+ const chevX = x + 12;
541
+ const chevY = y + sPhaseHeight / 2;
542
+ phaseG.append("text").attr("x", chevX).attr("y", chevY).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("font-family", FONT_FAMILY).attr("font-size", 9).attr("fill", palette.text).attr("opacity", 0.6).text(collapsed ? "\u25B6" : "\u25BC");
543
+ phaseG.append("text").attr("x", x + 26).attr("y", y + (sPhaseHeight - 4) / 2 + 2).attr("dominant-baseline", "central").attr("font-size", sPhaseFont).attr("font-weight", 600).attr("fill", palette.text).text(phase.displayName);
544
+ if (collapsed) {
545
+ const taskCount = phase.tasks.length;
546
+ if (taskCount > 0) {
547
+ const labelTextWidth = measureText(phase.displayName, sPhaseFont);
548
+ phaseG.append("text").attr("x", x + 26 + labelTextWidth + 10).attr("y", y + (sPhaseHeight - 4) / 2 + 2).attr("dominant-baseline", "central").attr("font-size", sPhaseFont - 1).attr("font-weight", 500).attr("fill", palette.textMuted).text(`${taskCount} ${taskCount === 1 ? "task" : "tasks"}`);
549
+ }
550
+ if (!showSummary) return;
551
+ const alphabet = VARIANTS[variant].alphabet;
552
+ const summaryInset = sColumnInset + 4;
553
+ const summaryColBodyW = roleColW - 2 * summaryInset;
554
+ const SUMMARY_CHIP_H = 24;
555
+ const SUMMARY_LETTER_FONT = 13;
556
+ const summaryY = y + (sPhaseHeight - SUMMARY_CHIP_H) / 2;
557
+ roles.forEach((roleId, colIdx) => {
558
+ const used = /* @__PURE__ */ new Set();
559
+ for (const t of phase.tasks) {
560
+ const a = t.roleAssignments.find((r) => r.id === roleId);
561
+ if (!a) continue;
562
+ for (const m of a.markers) used.add(m);
563
+ }
564
+ if (used.size === 0) return;
565
+ const ordered = sortByAlphabet([...used], alphabet);
566
+ const totalGap = SLICE_GAP * Math.max(0, ordered.length - 1);
567
+ const sliceW = (summaryColBodyW - totalGap) / ordered.length;
568
+ const startX = roleX(colIdx) + summaryInset;
569
+ ordered.forEach((marker, i) => {
570
+ const cx = startX + i * (sliceW + SLICE_GAP);
571
+ const rawColor = markerColor(marker, palette);
572
+ const fill = solid ? rawColor : mix(rawColor, surfaceBg, TINT_PCT);
573
+ const stroke = solid ? mix(rawColor, surfaceBg, 70) : rawColor;
574
+ const chipG = phaseG.append("g").attr("class", "raci-phase-summary");
575
+ chipG.append("rect").attr("x", cx).attr("y", summaryY).attr("width", sliceW).attr("height", SUMMARY_CHIP_H).attr("rx", sNodeRadius).attr("fill", fill).attr("stroke", stroke).attr("stroke-width", 1.25);
576
+ chipG.append("text").attr("x", cx + sliceW / 2).attr("y", summaryY + SUMMARY_CHIP_H / 2).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("font-size", SUMMARY_LETTER_FONT).attr("font-weight", 700).attr(
577
+ "fill",
578
+ solid ? contrastText(
579
+ fill,
580
+ palette.textOnFillLight,
581
+ palette.textOnFillDark
582
+ ) : palette.text
583
+ ).text(marker);
584
+ });
585
+ });
586
+ }
587
+ }
588
+ var SLICE_GAP = 4;
589
+ function renderTaskRow(svg, task, parsed, x, y, labelW, roleX, roleColW, palette, surfaceBg, solid, taskDiagnostics, _hasAnyDiagnostic, rowContent, onClickLine, _onMarkerDragStart, sLabelFont, sDescFont, sCellPad, sRowHeight, sColumnInset, sNodeRadius, sNodeStrokeWidth, sMarkerFont, sLegendLabelFont, sStackTopGap, sViolationLineHeight) {
590
+ const rowG = svg.append("g").attr("class", "raci-task-row").attr("data-task-id", task.id).attr("data-line-number", String(task.lineNumber));
591
+ const labelX = x + 8;
592
+ const labelMaxW = labelW - 16;
593
+ const content = rowContent ?? prepareRowContent(
594
+ task,
595
+ taskDiagnostics?.get(task.id),
596
+ labelMaxW,
597
+ sLabelFont,
598
+ sDescFont,
599
+ sRowHeight,
600
+ sStackTopGap,
601
+ sViolationLineHeight
602
+ );
603
+ const { nameLines, descLines, violations, rowHeight } = content;
604
+ const labelG = rowG.append("g").attr("class", "raci-task-label-area");
605
+ labelG.append("rect").attr("class", "raci-task-label-zone").attr("x", x).attr("y", y).attr("width", labelW).attr("height", rowHeight).attr("fill", "transparent").attr("pointer-events", "all");
606
+ const nameTopY = y + sLabelFont / 2 + 6;
607
+ const nameEl = labelG.append("text").attr("class", "raci-task-label").attr("x", labelX).attr("y", nameTopY).attr("dominant-baseline", "central").attr("font-size", sLabelFont).attr("fill", palette.text);
608
+ nameLines.forEach((line, i) => {
609
+ const tspan = nameEl.append("tspan").attr("x", labelX).text(line);
610
+ if (i > 0) tspan.attr("dy", NAME_LINE_HEIGHT);
611
+ });
612
+ let stackY = nameTopY + Math.max(0, nameLines.length - 1) * NAME_LINE_HEIGHT + sLabelFont / 2 + 4 + sStackTopGap;
613
+ if (descLines.length > 0) {
614
+ const descEl = labelG.append("text").attr("class", "raci-task-description").attr("x", labelX).attr("y", stackY).attr("dominant-baseline", "central").attr("font-size", sDescFont).attr("fill", palette.textMuted);
615
+ descLines.forEach((line, i) => {
616
+ const tspan = descEl.append("tspan").attr("x", labelX).text(line);
617
+ if (i > 0) tspan.attr("dy", DESC_LINE_HEIGHT);
618
+ });
619
+ stackY += descLines.length * DESC_LINE_HEIGHT;
620
+ }
621
+ for (const v of violations) {
622
+ const lineColor = v.severity === "error" ? palette.colors.red : palette.colors.orange;
623
+ const lineG = labelG.append("g").attr("class", "raci-violation-line").attr("data-severity", v.severity).style("cursor", onClickLine ? "pointer" : "default");
624
+ const violationFont = sLabelFont - 2;
625
+ const textEl = lineG.append("text").attr("x", labelX).attr("y", stackY).attr("dominant-baseline", "central").attr("font-size", violationFont).attr("fill", lineColor);
626
+ v.lines.forEach((line, i) => {
627
+ const segs = parseQuotedSegments(line);
628
+ segs.forEach((seg, segIdx) => {
629
+ const tspan = textEl.append("tspan").text(seg.text);
630
+ if (i > 0 && segIdx === 0)
631
+ tspan.attr("x", labelX).attr("dy", sViolationLineHeight);
632
+ if (seg.bold) tspan.attr("font-weight", 700);
633
+ });
634
+ });
635
+ if (onClickLine) {
636
+ const targetLine = v.sourceLine;
637
+ lineG.on("click", (e) => {
638
+ e.stopPropagation();
639
+ onClickLine(targetLine);
640
+ });
641
+ }
642
+ stackY += v.lines.length * sViolationLineHeight;
643
+ }
644
+ const cellByRole = /* @__PURE__ */ new Map();
645
+ for (const a of task.roleAssignments) {
646
+ cellByRole.set(a.id, a.markers);
647
+ }
648
+ const alphabet = VARIANTS[parsed.variant].alphabet;
649
+ parsed.roles.forEach((roleId, idx) => {
650
+ const cellInset = sColumnInset + 4;
651
+ const cx = roleX(idx) + cellInset;
652
+ const cy = y + sCellPad;
653
+ const cw = roleColW - 2 * cellInset;
654
+ const ch = sRowHeight - 2 * sCellPad;
655
+ const markersSource = cellByRole.get(roleId) ?? [];
656
+ const markers = sortByAlphabet(markersSource, alphabet);
657
+ const assignment = task.roleAssignments.find((a) => a.id === roleId);
658
+ const cellG = rowG.append("g").attr("class", "raci-cell").attr("data-task-id", task.id).attr("data-role-id", roleId);
659
+ if (assignment) {
660
+ cellG.attr("data-line-number", String(assignment.lineNumber));
661
+ }
662
+ cellG.append("rect").attr("class", "raci-cell-hit").attr("x", roleX(idx) + sColumnInset).attr("y", y).attr("width", roleColW - 2 * sColumnInset).attr("height", sRowHeight).attr("fill", "transparent").attr("pointer-events", "all");
663
+ const totalGap = SLICE_GAP * (markers.length - 1);
664
+ const sliceW = markers.length > 0 ? (cw - totalGap) / markers.length : 0;
665
+ const variantLabels = MARKER_LABELS[parsed.variant];
666
+ markers.forEach((m, i) => {
667
+ const rawColor = markerColor(m, palette);
668
+ const fill = solid ? rawColor : mix(rawColor, surfaceBg, TINT_PCT);
669
+ const stroke = rawColor;
670
+ const sliceX = cx + i * (sliceW + SLICE_GAP);
671
+ const sliceG = cellG.append("g").attr("class", "raci-marker-slice").attr("data-marker", m).style("cursor", "grab");
672
+ sliceG.append("rect").attr("x", sliceX).attr("y", cy).attr("width", sliceW).attr("height", ch).attr("rx", sNodeRadius).attr("fill", fill).attr("stroke", stroke).attr("stroke-width", sNodeStrokeWidth);
673
+ const fullLabel = variantLabels[m] ?? m;
674
+ const labelPx = measureText(fullLabel, sLegendLabelFont);
675
+ const showFullLabel = labelPx + 16 <= sliceW;
676
+ const textContent = showFullLabel ? fullLabel : m;
677
+ const textFont = showFullLabel ? sLegendLabelFont : sMarkerFont;
678
+ sliceG.append("text").attr("x", sliceX + sliceW / 2).attr("y", cy + ch / 2).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("font-size", textFont).attr("font-weight", MARKER_FONT_WEIGHT).attr(
679
+ "fill",
680
+ solid ? contrastText(
681
+ fill,
682
+ palette.textOnFillLight,
683
+ palette.textOnFillDark
684
+ ) : palette.text
685
+ ).text(textContent);
686
+ });
687
+ cellG.append("rect").attr("class", "raci-cell-drop-preview").attr("x", cx).attr("y", cy).attr("width", cw).attr("height", ch).attr("rx", sNodeRadius).attr("fill", "transparent").attr("stroke", "transparent").attr("pointer-events", "none");
688
+ if (onClickLine && assignment) {
689
+ cellG.on("click", () => onClickLine(assignment.lineNumber));
690
+ }
691
+ });
692
+ }
693
+ var NAME_LINE_HEIGHT = 16;
694
+ var DESC_LINE_HEIGHT = 16;
695
+ function prepareRowContent(task, bucket, labelMaxW, sLabelFont = LABEL_FONT, sDescFont = DESC_FONT, sRowHeight = ROW_HEIGHT, sStackTopGap = STACK_TOP_GAP, sViolationLineHeight = VIOLATION_LINE_HEIGHT) {
696
+ const nameLines = wrapTextToWidth(task.displayName, sLabelFont, labelMaxW, {
697
+ hardBreak: true
698
+ });
699
+ const description = task.description?.trim() ?? "";
700
+ const descLines = description.length > 0 ? description.split("\n").flatMap(
701
+ (line) => wrapTextToWidth(line, sDescFont, labelMaxW, { hardBreak: true })
702
+ ) : [];
703
+ const violations = [];
704
+ if (bucket) {
705
+ const collect = (arr, severity) => {
706
+ for (const e of arr) {
707
+ const text = trimTaskPrefix(e.message);
708
+ violations.push({
709
+ severity,
710
+ sourceLine: e.line,
711
+ text,
712
+ lines: wrapTextToWidth(text, sLabelFont - 2, labelMaxW, {
713
+ hardBreak: true
714
+ })
715
+ });
716
+ }
717
+ };
718
+ collect(bucket.errors, "error");
719
+ collect(bucket.warnings, "warning");
720
+ }
721
+ const totalViolationLines = violations.reduce(
722
+ (n, v) => n + v.lines.length,
723
+ 0
724
+ );
725
+ const hasStacked = descLines.length > 0 || violations.length > 0;
726
+ const labelExtra = Math.max(0, nameLines.length - 1) * NAME_LINE_HEIGHT;
727
+ const rowHeight = sRowHeight + labelExtra + (hasStacked ? sStackTopGap : 0) + descLines.length * DESC_LINE_HEIGHT + totalViolationLines * sViolationLineHeight;
728
+ return { nameLines, descLines, violations, rowHeight };
729
+ }
730
+ function parseQuotedSegments(message) {
731
+ const out = [];
732
+ const re = /'([^']+)'/g;
733
+ let last = 0;
734
+ let m;
735
+ while ((m = re.exec(message)) !== null) {
736
+ if (m.index > last)
737
+ out.push({ text: message.slice(last, m.index), bold: false });
738
+ out.push({ text: m[1], bold: true });
739
+ last = m.index + m[0].length;
740
+ }
741
+ if (last < message.length)
742
+ out.push({ text: message.slice(last), bold: false });
743
+ return out;
744
+ }
745
+
746
+ export {
747
+ MARKER_LABELS,
748
+ renderRaci,
749
+ renderRaciForExport
750
+ };
751
+ //# sourceMappingURL=chunk-DZAQBW35.js.map