drizzle-cube 0.9.0 → 0.9.2

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 (287) hide show
  1. package/dist/adapters/ActivityGridChart.config-BIHNNXe8.cjs +1 -0
  2. package/dist/adapters/ActivityGridChart.config-D8brFtkU.js +2 -0
  3. package/dist/adapters/AreaChart.config-BjBnqhrD.js +2 -0
  4. package/dist/adapters/AreaChart.config-hCYHwAaC.cjs +1 -0
  5. package/dist/adapters/BarChart.config-JWskLg7a.js +2 -0
  6. package/dist/adapters/BarChart.config-ldUoUV-V.cjs +1 -0
  7. package/dist/adapters/BoxPlotChart.config-DXgi-pWk.js +2 -0
  8. package/dist/adapters/BoxPlotChart.config-t7CU_SEB.cjs +1 -0
  9. package/dist/adapters/BubbleChart.config-BqNbgcs4.js +2 -0
  10. package/dist/adapters/BubbleChart.config-SRbrJWj2.cjs +1 -0
  11. package/dist/adapters/CandlestickChart.config-CTqj07at.cjs +1 -0
  12. package/dist/adapters/CandlestickChart.config-D-KaQpN1.js +2 -0
  13. package/dist/adapters/DataTable.config-4QtzEm2K.js +2 -0
  14. package/dist/adapters/DataTable.config-C4OZyZKL.cjs +1 -0
  15. package/dist/adapters/DotStripChart.config-CrwF5K67.cjs +1 -0
  16. package/dist/adapters/DotStripChart.config-tmdykLsT.js +2 -0
  17. package/dist/adapters/FunnelChart.config-Dk7bb3KU.cjs +1 -0
  18. package/dist/adapters/FunnelChart.config-Uah6h6t6.js +2 -0
  19. package/dist/adapters/GaugeChart.config-BGi82n3v.cjs +1 -0
  20. package/dist/adapters/GaugeChart.config-DIWl6xVG.js +2 -0
  21. package/dist/adapters/HeatMapChart.config-C1Wl6SHf.js +2 -0
  22. package/dist/adapters/HeatMapChart.config-CsA67rfc.cjs +1 -0
  23. package/dist/adapters/KpiDelta.config-CBo61Kp9.cjs +1 -0
  24. package/dist/adapters/KpiDelta.config-CgYK9zIF.js +2 -0
  25. package/dist/adapters/KpiNumber.config-BqQjoY4A.cjs +1 -0
  26. package/dist/adapters/KpiNumber.config-Dt8kOvIA.js +2 -0
  27. package/dist/adapters/KpiText.config-BE8HbXQ0.js +2 -0
  28. package/dist/adapters/KpiText.config-DaSbcfKE.cjs +1 -0
  29. package/dist/adapters/LineChart.config-CUPtbHNz.cjs +1 -0
  30. package/dist/adapters/LineChart.config-DHlctcYT.js +2 -0
  31. package/dist/adapters/MarkdownChart.config-B0T5A7dw.js +2 -0
  32. package/dist/adapters/MarkdownChart.config-iV60Icd3.cjs +1 -0
  33. package/dist/adapters/MeasureProfileChart.config-CBCezRPr.cjs +1 -0
  34. package/dist/adapters/MeasureProfileChart.config-CO9Cc3Ha.js +2 -0
  35. package/dist/adapters/PieChart.config-D6Ofc2h-.js +2 -0
  36. package/dist/adapters/PieChart.config-TOCX24yj.cjs +1 -0
  37. package/dist/adapters/ProportionBarChart.config-BfiCmXI7.js +2 -0
  38. package/dist/adapters/ProportionBarChart.config-Bh8_aieC.cjs +1 -0
  39. package/dist/adapters/RadarChart.config-BfWKUYv4.cjs +1 -0
  40. package/dist/adapters/RadarChart.config-x87UPfQH.js +2 -0
  41. package/dist/adapters/RadialBarChart.config-BGA-fVmo.js +2 -0
  42. package/dist/adapters/RadialBarChart.config-Bo3E1IWc.cjs +1 -0
  43. package/dist/adapters/RecordsTable.config-CKb4xY0c.cjs +1 -0
  44. package/dist/adapters/RecordsTable.config-KsPOo8YB.js +2 -0
  45. package/dist/adapters/RetentionCombinedChart.config-DVDQBMW8.js +2 -0
  46. package/dist/adapters/RetentionCombinedChart.config-DqfOBn6k.cjs +1 -0
  47. package/dist/adapters/RetentionHeatmap.config-B7NVTM5_.cjs +1 -0
  48. package/dist/adapters/RetentionHeatmap.config-tnd7N7g-.js +2 -0
  49. package/dist/adapters/SankeyChart.config-B1K4e0RA.js +2 -0
  50. package/dist/adapters/SankeyChart.config-I26Q_e5f.cjs +1 -0
  51. package/dist/adapters/ScatterChart.config-CMta6GGl.cjs +1 -0
  52. package/dist/adapters/ScatterChart.config-WwER0vgC.js +2 -0
  53. package/dist/adapters/SunburstChart.config-CBYye6Ez.js +2 -0
  54. package/dist/adapters/SunburstChart.config-DbC1AZCI.cjs +1 -0
  55. package/dist/adapters/TreeMapChart.config-EcJf0Dzm.cjs +1 -0
  56. package/dist/adapters/TreeMapChart.config-wNJWipDf.js +2 -0
  57. package/dist/adapters/WaterfallChart.config--d2EuVF-.cjs +1 -0
  58. package/dist/adapters/WaterfallChart.config-CPJLjH4K.js +2 -0
  59. package/dist/adapters/{compiler-SBDjxyQ3.js → compiler-B4tLaYye.js} +10 -10
  60. package/dist/adapters/{compiler-BdVMQ3Xj.cjs → compiler-BYm93X_G.cjs} +5 -5
  61. package/dist/adapters/core/index.cjs +1 -1
  62. package/dist/adapters/core/index.js +1 -1
  63. package/dist/adapters/{core-CSt8MGbw.cjs → core-BQEJzwTJ.cjs} +1 -1
  64. package/dist/adapters/{core-CXrxqh7l.js → core-CVDf8WNs.js} +2 -2
  65. package/dist/adapters/express/index.cjs +1 -1
  66. package/dist/adapters/express/index.js +5 -5
  67. package/dist/adapters/fastify/index.cjs +1 -1
  68. package/dist/adapters/fastify/index.js +5 -5
  69. package/dist/adapters/{handler-Cppe-aqH.js → handler-Ck2X85KS.js} +170 -151
  70. package/dist/adapters/handler-MFn_eVcJ.cjs +16 -0
  71. package/dist/adapters/hono/index.cjs +1 -1
  72. package/dist/adapters/hono/index.js +4 -4
  73. package/dist/adapters/mcp-app-html-V1nZOil3.cjs +1 -0
  74. package/dist/adapters/mcp-app-html-sD7j035f.js +5 -0
  75. package/dist/adapters/mcp-app-html.cjs +1 -0
  76. package/dist/adapters/mcp-app-html.d.ts +25 -0
  77. package/dist/adapters/mcp-app-html.js +2 -0
  78. package/dist/adapters/mcp-tools.cjs +1 -1
  79. package/dist/adapters/mcp-tools.js +21 -20
  80. package/dist/adapters/mcp-transport-BQxL1h2h.cjs +42 -0
  81. package/dist/adapters/mcp-transport-yJDy3T6W.js +758 -0
  82. package/dist/adapters/mcp-transport.d.ts +19 -2
  83. package/dist/adapters/nextjs/index.cjs +1 -1
  84. package/dist/adapters/nextjs/index.js +5 -5
  85. package/dist/adapters/{openai-BIoIunc7.js → openai-B7EVH3cl.js} +2578 -1797
  86. package/dist/adapters/openai-BkD4iuG8.cjs +19 -0
  87. package/dist/adapters/{openai-62sEF0Ev.js → openai-DRFOtBUU.js} +1 -1
  88. package/dist/adapters/{openai-CaxNY6TC.cjs → openai-FjvP4Mek.cjs} +1 -1
  89. package/dist/adapters/{utils-C_RKaP0n.js → utils-BY3FDOmN.js} +8 -1
  90. package/dist/adapters/utils-BkdzL4m9.cjs +132 -0
  91. package/dist/adapters/utils.cjs +1 -1
  92. package/dist/adapters/utils.js +1 -1
  93. package/dist/cjs/adapters/mcp-app-html.d.ts +25 -0
  94. package/dist/cjs/adapters/mcp-transport.d.ts +19 -2
  95. package/dist/cjs/client/charts/chartConfigs.d.ts +34 -0
  96. package/dist/cjs/client/components/AnalysisBuilder/AnalysisChartConfigPanel.d.ts +10 -2
  97. package/dist/cjs/client/components/AnalysisBuilder/TemplateEditor.d.ts +17 -0
  98. package/dist/cjs/client/components/AnalysisBuilder/templateSyntax.d.ts +26 -0
  99. package/dist/cjs/client/components/analyticsPortlet/parsePortletQuery.d.ts +12 -0
  100. package/dist/cjs/client/components/analyticsPortlet/portletRenderState.d.ts +7 -0
  101. package/dist/cjs/client/components/charts/MarkdownChart.d.ts +1 -1
  102. package/dist/cjs/client/components/charts/MarkdownChart.helpers.d.ts +156 -0
  103. package/dist/cjs/client/components/charts/markdownTemplate.d.ts +92 -0
  104. package/dist/client/charts/chartConfigs.d.ts +34 -0
  105. package/dist/client/charts.js +12 -12
  106. package/dist/client/chunks/{DashboardEditModal-DjMGNTUa.js → DashboardEditModal-B8zIH0ja.js} +2529 -2368
  107. package/dist/client/chunks/DashboardEditModal-B8zIH0ja.js.map +1 -0
  108. package/dist/client/chunks/{DotStripChart-EEtJyV1v.js → DotStripChart-BCgHGY0s.js} +4 -4
  109. package/dist/client/chunks/{DotStripChart-EEtJyV1v.js.map → DotStripChart-BCgHGY0s.js.map} +1 -1
  110. package/dist/client/chunks/{ProportionBarChart-3r7ZPX-V.js → ProportionBarChart-DzdKxzr4.js} +3 -3
  111. package/dist/client/chunks/{ProportionBarChart-3r7ZPX-V.js.map → ProportionBarChart-DzdKxzr4.js.map} +1 -1
  112. package/dist/client/chunks/{RecordsTable-DvMXV6F6.js → RecordsTable-kQfZ25Pz.js} +4 -4
  113. package/dist/client/chunks/{RecordsTable-DvMXV6F6.js.map → RecordsTable-kQfZ25Pz.js.map} +1 -1
  114. package/dist/client/chunks/{RetentionCombinedChart-DbxB86fj.js → RetentionCombinedChart-CvddCEOk.js} +3 -3
  115. package/dist/client/chunks/{RetentionCombinedChart-DbxB86fj.js.map → RetentionCombinedChart-CvddCEOk.js.map} +1 -1
  116. package/dist/client/chunks/{RetentionHeatmap-CE3rxhAU.js → RetentionHeatmap-BURt-mfm.js} +2 -2
  117. package/dist/client/chunks/{RetentionHeatmap-CE3rxhAU.js.map → RetentionHeatmap-BURt-mfm.js.map} +1 -1
  118. package/dist/client/chunks/{analysis-builder-lQk_qJBQ.js → analysis-builder-C0hzThmQ.js} +1688 -1675
  119. package/dist/client/chunks/{analysis-builder-lQk_qJBQ.js.map → analysis-builder-C0hzThmQ.js.map} +1 -1
  120. package/dist/client/chunks/{analysis-builder-shared-C_pLSXJR.js → analysis-builder-shared-CQFcY6vA.js} +476 -476
  121. package/dist/client/chunks/{analysis-builder-shared-C_pLSXJR.js.map → analysis-builder-shared-CQFcY6vA.js.map} +1 -1
  122. package/dist/client/chunks/{chart-activity-grid-UZIp9_2E.js → chart-activity-grid-aIyjUpbJ.js} +9 -2
  123. package/dist/client/chunks/chart-activity-grid-aIyjUpbJ.js.map +1 -0
  124. package/dist/client/chunks/{chart-area-CEBcvCcn.js → chart-area-DCCvp06H.js} +3 -3
  125. package/dist/client/chunks/{chart-area-CEBcvCcn.js.map → chart-area-DCCvp06H.js.map} +1 -1
  126. package/dist/client/chunks/{chart-bar-DGh7cpU-.js → chart-bar-C8xQ15Aa.js} +3 -3
  127. package/dist/client/chunks/{chart-bar-DGh7cpU-.js.map → chart-bar-C8xQ15Aa.js.map} +1 -1
  128. package/dist/client/chunks/{chart-box-plot-CeqoKPFa.js → chart-box-plot-DFMjBoL1.js} +2 -2
  129. package/dist/client/chunks/{chart-box-plot-CeqoKPFa.js.map → chart-box-plot-DFMjBoL1.js.map} +1 -1
  130. package/dist/client/chunks/{chart-bubble-8uH_JKNt.js → chart-bubble-CavhSOT8.js} +2 -2
  131. package/dist/client/chunks/{chart-bubble-8uH_JKNt.js.map → chart-bubble-CavhSOT8.js.map} +1 -1
  132. package/dist/client/chunks/{chart-candlestick-DogklMg1.js → chart-candlestick-BlJ6l-0-.js} +2 -2
  133. package/dist/client/chunks/{chart-candlestick-DogklMg1.js.map → chart-candlestick-BlJ6l-0-.js.map} +1 -1
  134. package/dist/client/chunks/{chart-config-markdown-C5kBQOjP.js → chart-config-markdown-CK8PAWUg.js} +8 -3
  135. package/dist/client/chunks/chart-config-markdown-CK8PAWUg.js.map +1 -0
  136. package/dist/client/chunks/{chart-data-table-CCvvgeG_.js → chart-data-table-Dfup1JpL.js} +426 -423
  137. package/dist/client/chunks/chart-data-table-Dfup1JpL.js.map +1 -0
  138. package/dist/client/chunks/{chart-funnel-D2vGQhaz.js → chart-funnel-DqBD-oth.js} +2 -2
  139. package/dist/client/chunks/{chart-funnel-D2vGQhaz.js.map → chart-funnel-DqBD-oth.js.map} +1 -1
  140. package/dist/client/chunks/{chart-gauge-DbU0rxqv.js → chart-gauge-Tr9BRoTo.js} +3 -3
  141. package/dist/client/chunks/{chart-gauge-DbU0rxqv.js.map → chart-gauge-Tr9BRoTo.js.map} +1 -1
  142. package/dist/client/chunks/{chart-heat-map-x6aQq91x.js → chart-heat-map-V200X1EP.js} +3 -3
  143. package/dist/client/chunks/{chart-heat-map-x6aQq91x.js.map → chart-heat-map-V200X1EP.js.map} +1 -1
  144. package/dist/client/chunks/{chart-kpi-delta-B2-m16QC.js → chart-kpi-delta-DuXkAxKV.js} +3 -3
  145. package/dist/client/chunks/{chart-kpi-delta-B2-m16QC.js.map → chart-kpi-delta-DuXkAxKV.js.map} +1 -1
  146. package/dist/client/chunks/{chart-kpi-number-DggvlYT_.js → chart-kpi-number-CzRaT5KF.js} +5 -5
  147. package/dist/client/chunks/{chart-kpi-number-DggvlYT_.js.map → chart-kpi-number-CzRaT5KF.js.map} +1 -1
  148. package/dist/client/chunks/{chart-kpi-text-BFy-m1NP.js → chart-kpi-text-DxMl_C7M.js} +3 -3
  149. package/dist/client/chunks/{chart-kpi-text-BFy-m1NP.js.map → chart-kpi-text-DxMl_C7M.js.map} +1 -1
  150. package/dist/client/chunks/{chart-line-C4r44HWi.js → chart-line-rl9q-vom.js} +3 -3
  151. package/dist/client/chunks/{chart-line-C4r44HWi.js.map → chart-line-rl9q-vom.js.map} +1 -1
  152. package/dist/client/chunks/chart-markdown-DJq7fXKF.js +8622 -0
  153. package/dist/client/chunks/chart-markdown-DJq7fXKF.js.map +1 -0
  154. package/dist/client/chunks/{chart-measure-profile-Dc-d82kF.js → chart-measure-profile-BDpkr55p.js} +3 -3
  155. package/dist/client/chunks/{chart-measure-profile-Dc-d82kF.js.map → chart-measure-profile-BDpkr55p.js.map} +1 -1
  156. package/dist/client/chunks/{chart-pie-BHrPxga2.js → chart-pie-W4Mk0WFB.js} +3 -3
  157. package/dist/client/chunks/{chart-pie-BHrPxga2.js.map → chart-pie-W4Mk0WFB.js.map} +1 -1
  158. package/dist/client/chunks/{chart-radar-I9sOFbG5.js → chart-radar-BXkMOUVC.js} +3 -3
  159. package/dist/client/chunks/{chart-radar-I9sOFbG5.js.map → chart-radar-BXkMOUVC.js.map} +1 -1
  160. package/dist/client/chunks/{chart-radial-bar-DeDMMf9P.js → chart-radial-bar-CSw1hCK2.js} +3 -3
  161. package/dist/client/chunks/{chart-radial-bar-DeDMMf9P.js.map → chart-radial-bar-CSw1hCK2.js.map} +1 -1
  162. package/dist/client/chunks/{chart-sankey-1qB7yWHq.js → chart-sankey-CXG8SeW3.js} +2 -2
  163. package/dist/client/chunks/{chart-sankey-1qB7yWHq.js.map → chart-sankey-CXG8SeW3.js.map} +1 -1
  164. package/dist/client/chunks/{chart-scatter-BHttSxcc.js → chart-scatter-BHp1yLvR.js} +3 -3
  165. package/dist/client/chunks/{chart-scatter-BHttSxcc.js.map → chart-scatter-BHp1yLvR.js.map} +1 -1
  166. package/dist/client/chunks/{chart-sunburst-Cmzol8kR.js → chart-sunburst-CHgQCRCz.js} +3 -3
  167. package/dist/client/chunks/{chart-sunburst-Cmzol8kR.js.map → chart-sunburst-CHgQCRCz.js.map} +1 -1
  168. package/dist/client/chunks/{chart-tree-map-CdSwr_zI.js → chart-tree-map-YBAOj00L.js} +3 -3
  169. package/dist/client/chunks/{chart-tree-map-CdSwr_zI.js.map → chart-tree-map-YBAOj00L.js.map} +1 -1
  170. package/dist/client/chunks/{chart-waterfall-BN3B5luW.js → chart-waterfall-CSQ3xvUJ.js} +3 -3
  171. package/dist/client/chunks/{chart-waterfall-BN3B5luW.js.map → chart-waterfall-CSQ3xvUJ.js.map} +1 -1
  172. package/dist/client/chunks/{charts-core-DIneMirc.js → charts-core-ghjDtt0_.js} +2 -2
  173. package/dist/client/chunks/{charts-core-DIneMirc.js.map → charts-core-ghjDtt0_.js.map} +1 -1
  174. package/dist/client/chunks/charts-loader-B_ye_C5_.js +87 -0
  175. package/dist/client/chunks/{charts-loader-OHpTBq8c.js.map → charts-loader-B_ye_C5_.js.map} +1 -1
  176. package/dist/client/chunks/{dist-BPmPx7kz.js → dist-COqN0-Yj.js} +4 -4
  177. package/dist/client/chunks/{dist-BPmPx7kz.js.map → dist-COqN0-Yj.js.map} +1 -1
  178. package/dist/client/chunks/{exceljs.min-CiByP8VQ.js → exceljs.min-CdNVdwM3.js} +8 -11
  179. package/dist/client/chunks/{exceljs.min-CiByP8VQ.js.map → exceljs.min-CdNVdwM3.js.map} +1 -1
  180. package/dist/client/chunks/{nl-NL-dk-kC0sL.js → nl-NL-CKdVNqSp.js} +8 -1
  181. package/dist/client/chunks/nl-NL-CKdVNqSp.js.map +1 -0
  182. package/dist/client/chunks/{schema-visualization-PYa578pV.js → schema-visualization-txYCG4v5.js} +4 -4
  183. package/dist/client/chunks/{schema-visualization-PYa578pV.js.map → schema-visualization-txYCG4v5.js.map} +1 -1
  184. package/dist/client/chunks/{useDirtyStateTracking-BQupC3wJ.js → useDirtyStateTracking-BUk23Wbh.js} +6 -6
  185. package/dist/client/chunks/{useDirtyStateTracking-BQupC3wJ.js.map → useDirtyStateTracking-BUk23Wbh.js.map} +1 -1
  186. package/dist/client/chunks/{useExplainAI-DORnTynt.js → useExplainAI-CGLroDBD.js} +5 -5
  187. package/dist/client/chunks/{useExplainAI-DORnTynt.js.map → useExplainAI-CGLroDBD.js.map} +1 -1
  188. package/dist/client/chunks/{vendor-DDo8nSGP.js → vendor-CApRxi77.js} +5 -6
  189. package/dist/client/chunks/{vendor-DDo8nSGP.js.map → vendor-CApRxi77.js.map} +1 -1
  190. package/dist/client/components/AnalysisBuilder/AnalysisChartConfigPanel.d.ts +10 -2
  191. package/dist/client/components/AnalysisBuilder/TemplateEditor.d.ts +17 -0
  192. package/dist/client/components/AnalysisBuilder/templateSyntax.d.ts +26 -0
  193. package/dist/client/components/analyticsPortlet/parsePortletQuery.d.ts +12 -0
  194. package/dist/client/components/analyticsPortlet/portletRenderState.d.ts +7 -0
  195. package/dist/client/components/charts/MarkdownChart.d.ts +1 -1
  196. package/dist/client/components/charts/MarkdownChart.helpers.d.ts +156 -0
  197. package/dist/client/components/charts/markdownTemplate.d.ts +92 -0
  198. package/dist/client/components.js +2 -2
  199. package/dist/client/hooks.js +3 -3
  200. package/dist/client/icons.js +1 -1
  201. package/dist/client/index.js +409 -409
  202. package/dist/client/index.js.map +1 -1
  203. package/dist/client/providers.js +1 -1
  204. package/dist/client/schema.js +1 -1
  205. package/dist/client/styles.css +1 -1
  206. package/dist/client/utils.js +4 -4
  207. package/dist/client-bundle-stats.html +1 -1
  208. package/dist/mcp-app/mcp-app.html +190 -132
  209. package/dist/server/index.cjs +17 -23
  210. package/dist/server/index.js +176 -147
  211. package/dist/server/openai-BWGYfr6Z.cjs +19 -0
  212. package/dist/server/{openai-nBhuSFd3.js → openai-Cap1nBuK.js} +1 -1
  213. package/dist/server/{openai-C1gLuR-0.js → openai-DW5TRMXE.js} +2577 -1796
  214. package/dist/server/{openai-rF50ZEiR.cjs → openai-G2EivP-n.cjs} +1 -1
  215. package/package.json +14 -3
  216. package/dist/adapters/ActivityGridChart.config-DfyZh3He.js +0 -2
  217. package/dist/adapters/ActivityGridChart.config-DgOwl51m.cjs +0 -1
  218. package/dist/adapters/AreaChart.config-BL_ZpMsg.js +0 -2
  219. package/dist/adapters/AreaChart.config-rc1phjmI.cjs +0 -1
  220. package/dist/adapters/BarChart.config-C6k4BxhM.cjs +0 -1
  221. package/dist/adapters/BarChart.config-CBACR7Z-.js +0 -2
  222. package/dist/adapters/BoxPlotChart.config-Bvg6yFn2.js +0 -2
  223. package/dist/adapters/BoxPlotChart.config-CAlLRM0v.cjs +0 -1
  224. package/dist/adapters/BubbleChart.config-22qXNskY.js +0 -2
  225. package/dist/adapters/BubbleChart.config-BkB3P6Bi.cjs +0 -1
  226. package/dist/adapters/CandlestickChart.config-DuZb-y6d.cjs +0 -1
  227. package/dist/adapters/CandlestickChart.config-G78UbuwS.js +0 -2
  228. package/dist/adapters/DataTable.config-BXkpzOC2.js +0 -2
  229. package/dist/adapters/DataTable.config-CwPlSht6.cjs +0 -1
  230. package/dist/adapters/DotStripChart.config-Bw9Ww5zY.cjs +0 -1
  231. package/dist/adapters/DotStripChart.config-D5sbLAqA.js +0 -2
  232. package/dist/adapters/FunnelChart.config-CUs1rqN2.cjs +0 -1
  233. package/dist/adapters/FunnelChart.config-Cw9qrC2C.js +0 -2
  234. package/dist/adapters/GaugeChart.config-CjGtJVoO.cjs +0 -1
  235. package/dist/adapters/GaugeChart.config-F5SA5Oi3.js +0 -2
  236. package/dist/adapters/HeatMapChart.config-CvgmSiuY.cjs +0 -1
  237. package/dist/adapters/HeatMapChart.config-DrvQrPHT.js +0 -2
  238. package/dist/adapters/KpiDelta.config-B-UJWdJ5.cjs +0 -1
  239. package/dist/adapters/KpiDelta.config-C00mTZYy.js +0 -2
  240. package/dist/adapters/KpiNumber.config-CB0kTeoV.cjs +0 -1
  241. package/dist/adapters/KpiNumber.config-D3CogH8j.js +0 -2
  242. package/dist/adapters/KpiText.config-B0LbKRCf.js +0 -2
  243. package/dist/adapters/KpiText.config-Dv4TNprC.cjs +0 -1
  244. package/dist/adapters/LineChart.config-CdONxSLL.js +0 -2
  245. package/dist/adapters/LineChart.config-DBHsfIYG.cjs +0 -1
  246. package/dist/adapters/MarkdownChart.config-AYKXdpuw.cjs +0 -1
  247. package/dist/adapters/MarkdownChart.config-CngxD_Lc.js +0 -2
  248. package/dist/adapters/MeasureProfileChart.config-B0a5sUk6.cjs +0 -1
  249. package/dist/adapters/MeasureProfileChart.config-ByW5F1Ey.js +0 -2
  250. package/dist/adapters/PieChart.config-Csx1ctk4.js +0 -2
  251. package/dist/adapters/PieChart.config-Dfw66Xab.cjs +0 -1
  252. package/dist/adapters/ProportionBarChart.config-BaeYznc1.cjs +0 -1
  253. package/dist/adapters/ProportionBarChart.config-DCOizSVs.js +0 -2
  254. package/dist/adapters/RadarChart.config-AxQh97rB.js +0 -2
  255. package/dist/adapters/RadarChart.config-BjP-zEGW.cjs +0 -1
  256. package/dist/adapters/RadialBarChart.config-D5EwyJ0G.cjs +0 -1
  257. package/dist/adapters/RadialBarChart.config-DMaEJYM_.js +0 -2
  258. package/dist/adapters/RecordsTable.config-BdN0kZ8b.js +0 -2
  259. package/dist/adapters/RecordsTable.config-CWRvGDbi.cjs +0 -1
  260. package/dist/adapters/RetentionCombinedChart.config-BpciOTeX.cjs +0 -1
  261. package/dist/adapters/RetentionCombinedChart.config-CY8TDzuy.js +0 -2
  262. package/dist/adapters/RetentionHeatmap.config-BZPRoVpF.cjs +0 -1
  263. package/dist/adapters/RetentionHeatmap.config-YMZt-u-T.js +0 -2
  264. package/dist/adapters/SankeyChart.config-BdSA-Jlp.cjs +0 -1
  265. package/dist/adapters/SankeyChart.config-Dlgi5_DA.js +0 -2
  266. package/dist/adapters/ScatterChart.config-VJ0xG0Ak.cjs +0 -1
  267. package/dist/adapters/ScatterChart.config-oOd1Jplx.js +0 -2
  268. package/dist/adapters/SunburstChart.config-Cllhyjy4.js +0 -2
  269. package/dist/adapters/SunburstChart.config-DU11BAei.cjs +0 -1
  270. package/dist/adapters/TreeMapChart.config--EB8bQTo.js +0 -2
  271. package/dist/adapters/TreeMapChart.config-BPmMLBH1.cjs +0 -1
  272. package/dist/adapters/WaterfallChart.config-E99Fl6uM.cjs +0 -1
  273. package/dist/adapters/WaterfallChart.config-xz3DaoMQ.js +0 -2
  274. package/dist/adapters/handler-_u5krbOj.cjs +0 -26
  275. package/dist/adapters/mcp-transport-CpF7usmR.js +0 -754
  276. package/dist/adapters/mcp-transport-DXIfb0p-.cjs +0 -42
  277. package/dist/adapters/openai-EBTQKJnV.cjs +0 -19
  278. package/dist/adapters/utils-DXWQUJbD.cjs +0 -128
  279. package/dist/client/chunks/DashboardEditModal-DjMGNTUa.js.map +0 -1
  280. package/dist/client/chunks/chart-activity-grid-UZIp9_2E.js.map +0 -1
  281. package/dist/client/chunks/chart-config-markdown-C5kBQOjP.js.map +0 -1
  282. package/dist/client/chunks/chart-data-table-CCvvgeG_.js.map +0 -1
  283. package/dist/client/chunks/chart-markdown-b0qPZ617.js +0 -3926
  284. package/dist/client/chunks/chart-markdown-b0qPZ617.js.map +0 -1
  285. package/dist/client/chunks/charts-loader-OHpTBq8c.js +0 -87
  286. package/dist/client/chunks/nl-NL-dk-kC0sL.js.map +0 -1
  287. package/dist/server/openai-BmADmD1e.cjs +0 -19
@@ -1,5 +1,5 @@
1
- import { F as e, S as t, o as n, s as r, w as i } from "./chart-activity-grid-UZIp9_2E.js";
2
- import { D as a, O as o, k as s } from "./chart-area-CEBcvCcn.js";
1
+ import { F as e, S as t, o as n, s as r, w as i } from "./chart-activity-grid-aIyjUpbJ.js";
2
+ import { D as a, O as o, k as s } from "./chart-area-DCCvp06H.js";
3
3
  import c, { useMemo as l, useState as u } from "react";
4
4
  import { jsx as d, jsxs as f } from "react/jsx-runtime";
5
5
  var p = {
@@ -235,7 +235,7 @@ var D = c.memo(function({ data: m, chartConfig: h, displayConfig: _ = {}, height
235
235
  z,
236
236
  q
237
237
  ]), Z = O?.colors ?? r, ce = Z[0], le = Z[+(Z.length > 1)], Q = (e) => i(e, V), $ = (e, t, n) => {
238
- !k || !A || !H || k({
238
+ k && A && H && k({
239
239
  dataPoint: t.row,
240
240
  clickedField: H,
241
241
  xValue: e.label,
@@ -405,4 +405,4 @@ var D = c.memo(function({ data: m, chartConfig: h, displayConfig: _ = {}, height
405
405
  //#endregion
406
406
  export { D as default };
407
407
 
408
- //# sourceMappingURL=DotStripChart-EEtJyV1v.js.map
408
+ //# sourceMappingURL=DotStripChart-BCgHGY0s.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"DotStripChart-EEtJyV1v.js","names":[],"sources":["../../../src/client/components/charts/DotStripChart.helpers.ts","../../../src/client/components/charts/DotStripChart.tsx"],"sourcesContent":["/**\n * Layout maths for the dot strip (beeswarm) chart.\n *\n * Kept out of the component — like `ScatterChart.helpers.ts` and\n * `BubbleChart.render.ts` — so the swarm packing can be unit-tested without a\n * DOM. Everything here is pure: same input, same output, every time.\n */\n\n/** Hard caps so one pathological band cannot blow up the layout. */\nexport const MAX_BANDS = 50\nexport const MAX_DOTS_PER_BAND = 500\nexport const MAX_LANES = 8\n\nexport const MIN_ROW_HEIGHT = 44\nexport const MAX_ROW_HEIGHT = 220\n/** Vertical breathing room added above and below the outermost lanes. */\nexport const ROW_VERTICAL_PADDING = 12\n\nexport const DOT_RADIUS: Record<'small' | 'medium' | 'large', number> = {\n small: 3.5,\n medium: 5,\n large: 7,\n}\n\n/** Gap between the edges of two adjacent dots in the same lane. */\nexport const DOT_GAP = 1.5\n\nexport interface DotDatum {\n /** Raw numeric value of the measure. */\n value: number\n /** Identity label for the dot (from the optional series dimension). */\n label: string\n /** The source row, handed back on click. */\n row: Record<string, unknown>\n}\n\nexport interface PlacedDot extends DotDatum {\n /** Pixel x within the plot area. */\n x: number\n /** Lane index: 0 is the centre line, then +1, -1, +2, -2, … */\n lane: number\n /** Pixel y offset from the band's centre line. */\n y: number\n}\n\nexport interface BandStats {\n /** The band's own label (the value of the band dimension). */\n label: string\n dots: PlacedDot[]\n count: number\n median: number | null\n min: number | null\n max: number | null\n /**\n * max / min. `null` when it cannot be stated meaningfully — fewer than two\n * dots, or a non-positive minimum (the ratio is infinite or sign-flipped).\n */\n spread: number | null\n /** True when the band has rows but not one usable numeric value. */\n noData: boolean\n /** Rendered height of this band's row, in px. */\n height: number\n /** True when the band's dots were truncated at MAX_DOTS_PER_BAND. */\n truncated: boolean\n}\n\nexport function parseNumeric(v: unknown): number | null {\n if (v === undefined || v === null || v === '') return null\n const n = typeof v === 'number' ? v : parseFloat(String(v))\n return Number.isFinite(n) ? n : null\n}\n\nexport function median(sortedValues: number[]): number | null {\n const n = sortedValues.length\n if (n === 0) return null\n const mid = Math.floor(n / 2)\n return n % 2 === 0 ? (sortedValues[mid - 1] + sortedValues[mid]) / 2 : sortedValues[mid]\n}\n\n/**\n * Walk lanes outward from the centre: 0, +1, -1, +2, -2, …\n * Returns the lane index for the nth step.\n */\nfunction laneAtStep(step: number): number {\n if (step === 0) return 0\n const magnitude = Math.ceil(step / 2)\n return step % 2 === 1 ? magnitude : -magnitude\n}\n\n/**\n * Greedy deterministic beeswarm packing.\n *\n * Dots are sorted by x (ties broken by label, so the output is stable for a\n * given input rather than dependent on row order) and each is dropped into the\n * first lane — searched outward from the centre — where it clears every dot\n * already in that lane by at least `2r + gap`. Beyond `MAX_LANES` the dot is\n * forced onto the outermost lane and simply overlaps; the surface-coloured\n * stroke keeps that readable.\n *\n * A force simulation would do this too, but it settles asynchronously and\n * non-deterministically, which makes it untestable and jittery on re-render.\n */\nexport function packSwarm(dots: DotDatum[], scale: (v: number) => number, radius: number): PlacedDot[] {\n const minSeparation = 2 * radius + DOT_GAP\n const laneHeight = 2 * radius + DOT_GAP\n\n const sorted = [...dots].sort((a, b) => (a.value - b.value) || a.label.localeCompare(b.label))\n\n // lane index -> x positions already placed in that lane\n const lanes = new Map<number, number[]>()\n const placed: PlacedDot[] = []\n\n for (const dot of sorted) {\n const x = scale(dot.value)\n\n let chosenLane = laneAtStep(2 * MAX_LANES)\n for (let step = 0; step <= 2 * MAX_LANES; step++) {\n const lane = laneAtStep(step)\n const occupied = lanes.get(lane)\n if (!occupied || occupied.every((other) => Math.abs(x - other) >= minSeparation)) {\n chosenLane = lane\n break\n }\n }\n\n const occupied = lanes.get(chosenLane)\n if (occupied) occupied.push(x)\n else lanes.set(chosenLane, [x])\n\n placed.push({ ...dot, x, lane: chosenLane, y: chosenLane * laneHeight })\n }\n\n return placed\n}\n\n/** Row height needed to show every lane the swarm used. */\nexport function swarmHeight(placed: PlacedDot[], radius: number): number {\n const maxAbsLane = placed.reduce((acc, d) => Math.max(acc, Math.abs(d.lane)), 0)\n const laneHeight = 2 * radius + DOT_GAP\n const needed = (2 * maxAbsLane + 1) * laneHeight + 2 * ROW_VERTICAL_PADDING\n return Math.min(Math.max(needed, MIN_ROW_HEIGHT), MAX_ROW_HEIGHT)\n}\n\n/** max / min, or null when the ratio would be meaningless. */\nexport function computeSpread(min: number | null, max: number | null, count: number): number | null {\n if (min === null || max === null || count < 2) return null\n if (min <= 0) return null\n return max / min\n}\n\nexport type BandSort = 'none' | 'valueDesc' | 'valueAsc' | 'count'\n\n/**\n * Group rows into bands, in the order requested. Sorting happens before the\n * swarm is packed so row heights follow the rows the user actually sees.\n */\nexport function groupIntoBands(\n rows: Record<string, unknown>[],\n bandField: string,\n valueField: string,\n labelField: string | undefined,\n sort: BandSort\n): Array<{ label: string; values: DotDatum[]; truncated: boolean }> {\n const order: string[] = []\n const grouped = new Map<string, DotDatum[]>()\n\n for (const row of rows) {\n const bandLabel = String(row[bandField] ?? '')\n if (!grouped.has(bandLabel)) {\n grouped.set(bandLabel, [])\n order.push(bandLabel)\n }\n const value = parseNumeric(row[valueField])\n if (value === null) continue\n grouped.get(bandLabel)!.push({\n value,\n label: labelField ? String(row[labelField] ?? '') : bandLabel,\n row,\n })\n }\n\n let bands = order.map((label) => {\n const all = grouped.get(label)!\n return {\n label,\n values: all.slice(0, MAX_DOTS_PER_BAND),\n truncated: all.length > MAX_DOTS_PER_BAND,\n }\n })\n\n if (sort !== 'none') {\n const medianOf = (values: DotDatum[]) => median(values.map((d) => d.value).sort((a, b) => a - b))\n bands = [...bands].sort((a, b) => {\n if (sort === 'count') return b.values.length - a.values.length\n const ma = medianOf(a.values)\n const mb = medianOf(b.values)\n if (ma === null && mb === null) return 0\n if (ma === null) return 1\n if (mb === null) return -1\n return sort === 'valueDesc' ? mb - ma : ma - mb\n })\n }\n\n return bands.slice(0, MAX_BANDS)\n}\n\n/** Nicely padded numeric domain across every dot in every band. */\nexport function computeDomain(values: number[]): { min: number; max: number } {\n if (values.length === 0) return { min: 0, max: 1 }\n const rawMin = Math.min(...values)\n const rawMax = Math.max(...values)\n if (rawMin === rawMax) {\n const pad = Math.abs(rawMin) * 0.1 || 1\n return { min: rawMin - pad, max: rawMax + pad }\n }\n const pad = (rawMax - rawMin) * 0.1\n return { min: rawMin - pad, max: rawMax + pad }\n}\n\n/** Evenly spaced axis ticks across the domain. */\nexport function computeTicks(min: number, max: number, count = 5): number[] {\n if (max === min) return [min]\n const step = (max - min) / (count - 1)\n return Array.from({ length: count }, (_, i) => min + i * step)\n}\n","import React, { useMemo, useState } from 'react'\nimport { useTranslation } from '../../hooks/useTranslation.js'\nimport { useCubeFieldLabel } from '../../hooks/useCubeFieldLabel.js'\nimport { CHART_COLORS } from '../../utils/chartConstants.js'\nimport { formatAxisValue } from '../../utils/chartUtils.js'\nimport { ChartConfigError, ChartEmptyState, ChartRenderError } from './ChartStates.js'\nimport { useChartDimensions } from './useChartDimensions.js'\nimport {\n DOT_RADIUS,\n MAX_BANDS,\n computeDomain,\n computeSpread,\n computeTicks,\n groupIntoBands,\n median,\n packSwarm,\n swarmHeight,\n type BandSort,\n type BandStats,\n type PlacedDot,\n} from './DotStripChart.helpers.js'\nimport type { ChartProps } from '../../types.js'\n\nconst GUTTER_WIDTH = 190\nconst AXIS_HEIGHT = 44\nconst PLOT_PADDING_X = 24\n\n/**\n * Height the left gutter needs for its own content, so a band with a flat swarm\n * still gets a row tall enough for the name and the badges. Folded into the\n * band height rather than left to the grid, so the strip SVG always fills the\n * whole row and its gridlines stay continuous between bands.\n *\n * `dc:text-sm` line (20) + `dc:gap-1` (4) + badge line (20) + `dc:py-2` (16).\n */\nconst GUTTER_HEIGHT_WITH_STATS = 60\nconst GUTTER_HEIGHT_NAME_ONLY = 36\n\n/**\n * Read a drop zone's field, tolerating both shapes it is stored in: the\n * AnalysisBuilder writes a bare string into any zone with `maxItems: 1`\n * (`addFieldToAxis` in `AnalysisBuilder/utils/axisConfigUtils.ts`), while the\n * agent's `inferChartConfig` always writes an array.\n */\nfunction firstField(value: unknown): string | undefined {\n if (Array.isArray(value)) return typeof value[0] === 'string' ? value[0] : undefined\n return typeof value === 'string' && value !== '' ? value : undefined\n}\n\ninterface HoveredDot {\n band: string\n dot: PlacedDot\n clientX: number\n clientY: number\n}\n\n/**\n * One band's strip: gridlines, the baseline, the median tick and the swarm.\n * The x-scale is passed in so every band and the axis share one domain.\n */\nfunction BandStrip({\n band,\n width,\n radius,\n ticks,\n scale,\n showGrid,\n showMedianMarker,\n showExtremeLabels,\n color,\n accentColor,\n drillEnabled,\n onDotClick,\n onDotHover,\n onDotLeave,\n}: {\n band: BandStats\n width: number\n radius: number\n ticks: number[]\n scale: (v: number) => number\n showGrid: boolean\n showMedianMarker: boolean\n showExtremeLabels: boolean\n color: string\n accentColor: string\n drillEnabled: boolean\n onDotClick: (band: BandStats, dot: PlacedDot, event: React.MouseEvent) => void\n onDotHover: (band: BandStats, dot: PlacedDot, event: React.MouseEvent) => void\n onDotLeave: () => void\n}) {\n const centreY = band.height / 2\n const extremes = useMemo(() => {\n if (!showExtremeLabels || band.dots.length < 2) return new Set<PlacedDot>()\n const sorted = [...band.dots].sort((a, b) => a.value - b.value)\n return new Set<PlacedDot>([sorted[0], sorted[sorted.length - 1]])\n }, [band.dots, showExtremeLabels])\n\n return (\n <svg\n width={width}\n height={band.height}\n viewBox={`0 0 ${width} ${band.height}`}\n data-testid={`dot-strip-band-${band.label}`}\n >\n {showGrid &&\n ticks.map((tick, i) => (\n <line\n key={i}\n x1={scale(tick)}\n x2={scale(tick)}\n y1={0}\n y2={band.height}\n stroke=\"currentColor\"\n strokeOpacity={0.08}\n strokeWidth={1}\n className=\"text-dc-text-secondary\"\n />\n ))}\n\n {band.dots.length > 0 && (\n <line\n x1={scale(band.min!)}\n x2={scale(band.max!)}\n y1={centreY}\n y2={centreY}\n stroke=\"currentColor\"\n strokeOpacity={0.25}\n strokeWidth={1}\n className=\"text-dc-text-secondary\"\n />\n )}\n\n {showMedianMarker && band.median !== null && (\n <line\n x1={scale(band.median)}\n x2={scale(band.median)}\n y1={centreY - Math.min(centreY - 2, 16)}\n y2={centreY + Math.min(centreY - 2, 16)}\n stroke=\"currentColor\"\n strokeOpacity={0.55}\n strokeWidth={2}\n className=\"text-dc-text-secondary\"\n data-testid={`dot-strip-median-${band.label}`}\n />\n )}\n\n {band.dots.map((dot, i) => {\n const isExtreme = extremes.has(dot)\n return (\n <g key={`${dot.label}-${i}`}>\n <circle\n cx={dot.x}\n cy={centreY + dot.y}\n r={radius}\n fill={isExtreme ? accentColor : color}\n stroke=\"var(--dc-surface, #ffffff)\"\n strokeWidth={1.25}\n cursor={drillEnabled ? 'pointer' : undefined}\n data-testid=\"dot-strip-dot\"\n onClick={(event) => onDotClick(band, dot, event)}\n onMouseEnter={(event) => onDotHover(band, dot, event)}\n onMouseLeave={onDotLeave}\n />\n {isExtreme && (\n <text\n x={dot.x}\n y={centreY + dot.y + (dot.y <= 0 ? -radius - 6 : radius + 14)}\n textAnchor=\"middle\"\n fontSize={11}\n fill={accentColor}\n data-testid={`dot-strip-extreme-${dot.label}`}\n >\n {dot.label}\n </text>\n )}\n </g>\n )\n })}\n </svg>\n )\n}\n\nconst DotStripChart = React.memo(function DotStripChart({\n data,\n chartConfig,\n displayConfig = {},\n height = '100%',\n colorPalette,\n onDataPointClick,\n drillEnabled,\n}: ChartProps) {\n const { t } = useTranslation()\n const getFieldLabel = useCubeFieldLabel()\n const { containerRef, dimensions } = useChartDimensions()\n const [hovered, setHovered] = useState<HoveredDot | null>(null)\n\n const showGrid = displayConfig.showGrid !== false\n const showTooltip = displayConfig.showTooltip !== false\n const showMedianMarker = displayConfig.showMedianMarker !== false\n const showBandStats = displayConfig.showBandStats !== false\n const showExtremeLabels = displayConfig.showExtremeLabels === true\n const radius = DOT_RADIUS[displayConfig.dotSize ?? 'medium'] ?? DOT_RADIUS.medium\n const bandSort: BandSort = displayConfig.bandSort ?? 'none'\n const xAxisFormat = displayConfig.xAxisFormat\n\n const { bandField, valueField, labelField, configError } = useMemo(() => {\n const bandField = firstField(chartConfig?.xAxis) ?? firstField(chartConfig?.x)\n const valueField = firstField(chartConfig?.yAxis) ?? firstField(chartConfig?.y)\n const labelField = firstField(chartConfig?.series)\n\n if (!bandField || !valueField) {\n return {\n bandField,\n valueField,\n labelField,\n configError: t('chart.runtime.configErrorHint.dotStrip'),\n }\n }\n return { bandField, valueField, labelField, configError: null as string | null }\n }, [chartConfig, t])\n\n const plotWidth = Math.max((dimensions.width || 720) - GUTTER_WIDTH - PLOT_PADDING_X, 80)\n\n const gutterHeight = showBandStats ? GUTTER_HEIGHT_WITH_STATS : GUTTER_HEIGHT_NAME_ONLY\n\n const { bands, ticks, scale, truncated } = useMemo(() => {\n const empty = { bands: [] as BandStats[], ticks: [] as number[], scale: (_: number) => 0, truncated: false }\n if (configError || !bandField || !valueField || !data || data.length === 0) return empty\n\n const rows = data as Record<string, unknown>[]\n const grouped = groupIntoBands(rows, bandField, valueField, labelField, bandSort)\n\n const allValues = grouped.flatMap((b) => b.values.map((d) => d.value))\n const domain = computeDomain(allValues)\n const span = domain.max - domain.min\n const inner = Math.max(plotWidth - 2 * radius - 2, 10)\n const scale = (v: number) => radius + 1 + (span === 0 ? inner / 2 : ((v - domain.min) / span) * inner)\n\n const bands: BandStats[] = grouped.map((group) => {\n const placed = packSwarm(group.values, scale, radius)\n const sortedValues = group.values.map((d) => d.value).sort((a, b) => a - b)\n const min = sortedValues.length > 0 ? sortedValues[0] : null\n const max = sortedValues.length > 0 ? sortedValues[sortedValues.length - 1] : null\n return {\n label: group.label,\n dots: placed,\n count: placed.length,\n median: median(sortedValues),\n min,\n max,\n spread: computeSpread(min, max, placed.length),\n noData: placed.length === 0,\n height: Math.max(swarmHeight(placed, radius), gutterHeight),\n truncated: group.truncated,\n }\n })\n\n return {\n bands,\n ticks: computeTicks(domain.min, domain.max, 5),\n scale,\n truncated: new Set(rows.map((r) => String(r[bandField] ?? ''))).size > MAX_BANDS,\n }\n }, [data, bandField, valueField, labelField, bandSort, configError, plotWidth, radius, gutterHeight])\n\n const palette = colorPalette?.colors ?? CHART_COLORS\n const color = palette[0]\n const accentColor = palette[palette.length > 1 ? 1 : 0]\n\n const formatValue = (v: number) => formatAxisValue(v, xAxisFormat)\n\n const handleDotClick = (band: BandStats, dot: PlacedDot, event: React.MouseEvent) => {\n if (!onDataPointClick || !drillEnabled || !bandField) return\n onDataPointClick({\n dataPoint: dot.row,\n clickedField: bandField,\n xValue: band.label,\n position: { x: event.clientX, y: event.clientY },\n nativeEvent: event,\n })\n }\n\n const handleDotHover = (band: BandStats, dot: PlacedDot, event: React.MouseEvent) => {\n if (!showTooltip) return\n setHovered({ band: band.label, dot, clientX: event.clientX, clientY: event.clientY })\n }\n\n if (configError) {\n return <ChartConfigError height={height} hint={configError} />\n }\n\n if (!data || data.length === 0) {\n return <ChartEmptyState height={height} hint={t('chart.runtime.noDataHint.dotStrip')} />\n }\n\n if (bands.length === 0) {\n return (\n <ChartEmptyState\n height={height}\n titleKey=\"chart.runtime.noValidData\"\n hint={t('chart.runtime.noValidDataHint.dotStrip')}\n />\n )\n }\n\n try {\n return (\n <div ref={containerRef} className=\"dc:relative dc:w-full dc:overflow-y-auto\" style={{ height }}>\n <div\n className=\"dc:grid dc:items-stretch\"\n style={{ gridTemplateColumns: `${GUTTER_WIDTH}px 1fr` }}\n data-testid=\"dot-strip-grid\"\n >\n {bands.map((band) => (\n <React.Fragment key={band.label}>\n <div\n className=\"dc:flex dc:flex-col dc:justify-center dc:gap-1 dc:pr-3 dc:py-2 dc:border-b border-dc-border\"\n style={{ minHeight: band.height }}\n >\n <div className=\"dc:text-sm dc:font-semibold dc:truncate text-dc-text\" title={band.label}>\n {band.label || t('chart.runtime.dotStrip.unlabelledBand')}\n </div>\n {showBandStats && (\n <div className=\"dc:flex dc:flex-wrap dc:gap-1 dc:text-xs\">\n <span className=\"dc:rounded-sm dc:px-1.5 dc:py-0.5 bg-dc-surface-secondary text-dc-text-secondary\">\n {t(\n band.truncated\n ? 'chart.runtime.dotStrip.countTruncated'\n : 'chart.runtime.dotStrip.count',\n { count: band.count }\n )}\n </span>\n {band.noData ? (\n <span className=\"dc:rounded-sm dc:px-1.5 dc:py-0.5 bg-dc-surface-secondary text-dc-text-muted\">\n {t('chart.runtime.dotStrip.noDataRecorded')}\n </span>\n ) : (\n band.spread !== null && (\n <span className=\"dc:rounded-sm dc:px-1.5 dc:py-0.5 bg-dc-surface-secondary text-dc-text-secondary\">\n {t('chart.runtime.dotStrip.spread', { spread: band.spread.toFixed(2) })}\n </span>\n )\n )}\n </div>\n )}\n </div>\n <div\n className=\"dc:flex dc:items-center dc:border-b border-dc-border\"\n style={{ minHeight: band.height, paddingRight: PLOT_PADDING_X }}\n >\n <BandStrip\n band={band}\n width={plotWidth}\n radius={radius}\n ticks={ticks}\n scale={scale}\n showGrid={showGrid}\n showMedianMarker={showMedianMarker}\n showExtremeLabels={showExtremeLabels}\n color={color}\n accentColor={accentColor}\n drillEnabled={drillEnabled === true}\n onDotClick={handleDotClick}\n onDotHover={handleDotHover}\n onDotLeave={() => setHovered(null)}\n />\n </div>\n </React.Fragment>\n ))}\n\n <div />\n <div style={{ paddingRight: PLOT_PADDING_X }}>\n <svg\n width={plotWidth}\n height={AXIS_HEIGHT}\n viewBox={`0 0 ${plotWidth} ${AXIS_HEIGHT}`}\n data-testid=\"dot-strip-axis\"\n >\n <line x1={0} x2={plotWidth} y1={1} y2={1} stroke=\"currentColor\" strokeOpacity={0.2} className=\"text-dc-text-secondary\" />\n {ticks.map((tick, i) => (\n <g key={i} transform={`translate(${scale(tick)}, 0)`}>\n <line y1={1} y2={7} stroke=\"currentColor\" strokeOpacity={0.4} className=\"text-dc-text-secondary\" />\n <text\n y={22}\n textAnchor=\"middle\"\n fontSize={11}\n fill=\"currentColor\"\n className=\"text-dc-text-secondary\"\n >\n {formatValue(tick)}\n </text>\n </g>\n ))}\n {valueField && (\n <text\n x={plotWidth / 2}\n y={39}\n textAnchor=\"middle\"\n fontSize={11}\n fill=\"currentColor\"\n className=\"text-dc-text-muted\"\n >\n {getFieldLabel(valueField)}\n </text>\n )}\n </svg>\n </div>\n </div>\n\n {truncated && (\n <div className=\"dc:text-xs text-dc-warning dc:text-center dc:mt-1\">\n {t('chart.runtime.dotStrip.truncated', { max: MAX_BANDS })}\n </div>\n )}\n\n {showTooltip && hovered && (\n <div\n className=\"dc:fixed dc:z-50 dc:pointer-events-none dc:rounded-sm dc:px-2 dc:py-1 dc:text-xs dc:shadow-lg bg-dc-surface text-dc-text dc:border border-dc-border\"\n style={{ left: hovered.clientX + 12, top: hovered.clientY + 12 }}\n data-testid=\"dot-strip-tooltip\"\n >\n <div className=\"dc:font-semibold\">{hovered.dot.label}</div>\n <div className=\"text-dc-text-secondary\">{hovered.band}</div>\n <div>{formatValue(hovered.dot.value)}</div>\n </div>\n )}\n </div>\n )\n } catch (error) {\n return <ChartRenderError height={height} chartType=\"Dot Strip Chart\" error={error} />\n }\n})\n\nexport default DotStripChart\n"],"mappings":"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"}
1
+ {"version":3,"file":"DotStripChart-BCgHGY0s.js","names":[],"sources":["../../../src/client/components/charts/DotStripChart.helpers.ts","../../../src/client/components/charts/DotStripChart.tsx"],"sourcesContent":["/**\n * Layout maths for the dot strip (beeswarm) chart.\n *\n * Kept out of the component — like `ScatterChart.helpers.ts` and\n * `BubbleChart.render.ts` — so the swarm packing can be unit-tested without a\n * DOM. Everything here is pure: same input, same output, every time.\n */\n\n/** Hard caps so one pathological band cannot blow up the layout. */\nexport const MAX_BANDS = 50\nexport const MAX_DOTS_PER_BAND = 500\nexport const MAX_LANES = 8\n\nexport const MIN_ROW_HEIGHT = 44\nexport const MAX_ROW_HEIGHT = 220\n/** Vertical breathing room added above and below the outermost lanes. */\nexport const ROW_VERTICAL_PADDING = 12\n\nexport const DOT_RADIUS: Record<'small' | 'medium' | 'large', number> = {\n small: 3.5,\n medium: 5,\n large: 7,\n}\n\n/** Gap between the edges of two adjacent dots in the same lane. */\nexport const DOT_GAP = 1.5\n\nexport interface DotDatum {\n /** Raw numeric value of the measure. */\n value: number\n /** Identity label for the dot (from the optional series dimension). */\n label: string\n /** The source row, handed back on click. */\n row: Record<string, unknown>\n}\n\nexport interface PlacedDot extends DotDatum {\n /** Pixel x within the plot area. */\n x: number\n /** Lane index: 0 is the centre line, then +1, -1, +2, -2, … */\n lane: number\n /** Pixel y offset from the band's centre line. */\n y: number\n}\n\nexport interface BandStats {\n /** The band's own label (the value of the band dimension). */\n label: string\n dots: PlacedDot[]\n count: number\n median: number | null\n min: number | null\n max: number | null\n /**\n * max / min. `null` when it cannot be stated meaningfully — fewer than two\n * dots, or a non-positive minimum (the ratio is infinite or sign-flipped).\n */\n spread: number | null\n /** True when the band has rows but not one usable numeric value. */\n noData: boolean\n /** Rendered height of this band's row, in px. */\n height: number\n /** True when the band's dots were truncated at MAX_DOTS_PER_BAND. */\n truncated: boolean\n}\n\nexport function parseNumeric(v: unknown): number | null {\n if (v === undefined || v === null || v === '') return null\n const n = typeof v === 'number' ? v : parseFloat(String(v))\n return Number.isFinite(n) ? n : null\n}\n\nexport function median(sortedValues: number[]): number | null {\n const n = sortedValues.length\n if (n === 0) return null\n const mid = Math.floor(n / 2)\n return n % 2 === 0 ? (sortedValues[mid - 1] + sortedValues[mid]) / 2 : sortedValues[mid]\n}\n\n/**\n * Walk lanes outward from the centre: 0, +1, -1, +2, -2, …\n * Returns the lane index for the nth step.\n */\nfunction laneAtStep(step: number): number {\n if (step === 0) return 0\n const magnitude = Math.ceil(step / 2)\n return step % 2 === 1 ? magnitude : -magnitude\n}\n\n/**\n * Greedy deterministic beeswarm packing.\n *\n * Dots are sorted by x (ties broken by label, so the output is stable for a\n * given input rather than dependent on row order) and each is dropped into the\n * first lane — searched outward from the centre — where it clears every dot\n * already in that lane by at least `2r + gap`. Beyond `MAX_LANES` the dot is\n * forced onto the outermost lane and simply overlaps; the surface-coloured\n * stroke keeps that readable.\n *\n * A force simulation would do this too, but it settles asynchronously and\n * non-deterministically, which makes it untestable and jittery on re-render.\n */\nexport function packSwarm(dots: DotDatum[], scale: (v: number) => number, radius: number): PlacedDot[] {\n const minSeparation = 2 * radius + DOT_GAP\n const laneHeight = 2 * radius + DOT_GAP\n\n const sorted = [...dots].sort((a, b) => (a.value - b.value) || a.label.localeCompare(b.label))\n\n // lane index -> x positions already placed in that lane\n const lanes = new Map<number, number[]>()\n const placed: PlacedDot[] = []\n\n for (const dot of sorted) {\n const x = scale(dot.value)\n\n let chosenLane = laneAtStep(2 * MAX_LANES)\n for (let step = 0; step <= 2 * MAX_LANES; step++) {\n const lane = laneAtStep(step)\n const occupied = lanes.get(lane)\n if (!occupied || occupied.every((other) => Math.abs(x - other) >= minSeparation)) {\n chosenLane = lane\n break\n }\n }\n\n const occupied = lanes.get(chosenLane)\n if (occupied) occupied.push(x)\n else lanes.set(chosenLane, [x])\n\n placed.push({ ...dot, x, lane: chosenLane, y: chosenLane * laneHeight })\n }\n\n return placed\n}\n\n/** Row height needed to show every lane the swarm used. */\nexport function swarmHeight(placed: PlacedDot[], radius: number): number {\n const maxAbsLane = placed.reduce((acc, d) => Math.max(acc, Math.abs(d.lane)), 0)\n const laneHeight = 2 * radius + DOT_GAP\n const needed = (2 * maxAbsLane + 1) * laneHeight + 2 * ROW_VERTICAL_PADDING\n return Math.min(Math.max(needed, MIN_ROW_HEIGHT), MAX_ROW_HEIGHT)\n}\n\n/** max / min, or null when the ratio would be meaningless. */\nexport function computeSpread(min: number | null, max: number | null, count: number): number | null {\n if (min === null || max === null || count < 2) return null\n if (min <= 0) return null\n return max / min\n}\n\nexport type BandSort = 'none' | 'valueDesc' | 'valueAsc' | 'count'\n\n/**\n * Group rows into bands, in the order requested. Sorting happens before the\n * swarm is packed so row heights follow the rows the user actually sees.\n */\nexport function groupIntoBands(\n rows: Record<string, unknown>[],\n bandField: string,\n valueField: string,\n labelField: string | undefined,\n sort: BandSort\n): Array<{ label: string; values: DotDatum[]; truncated: boolean }> {\n const order: string[] = []\n const grouped = new Map<string, DotDatum[]>()\n\n for (const row of rows) {\n const bandLabel = String(row[bandField] ?? '')\n if (!grouped.has(bandLabel)) {\n grouped.set(bandLabel, [])\n order.push(bandLabel)\n }\n const value = parseNumeric(row[valueField])\n if (value === null) continue\n grouped.get(bandLabel)!.push({\n value,\n label: labelField ? String(row[labelField] ?? '') : bandLabel,\n row,\n })\n }\n\n let bands = order.map((label) => {\n const all = grouped.get(label)!\n return {\n label,\n values: all.slice(0, MAX_DOTS_PER_BAND),\n truncated: all.length > MAX_DOTS_PER_BAND,\n }\n })\n\n if (sort !== 'none') {\n const medianOf = (values: DotDatum[]) => median(values.map((d) => d.value).sort((a, b) => a - b))\n bands = [...bands].sort((a, b) => {\n if (sort === 'count') return b.values.length - a.values.length\n const ma = medianOf(a.values)\n const mb = medianOf(b.values)\n if (ma === null && mb === null) return 0\n if (ma === null) return 1\n if (mb === null) return -1\n return sort === 'valueDesc' ? mb - ma : ma - mb\n })\n }\n\n return bands.slice(0, MAX_BANDS)\n}\n\n/** Nicely padded numeric domain across every dot in every band. */\nexport function computeDomain(values: number[]): { min: number; max: number } {\n if (values.length === 0) return { min: 0, max: 1 }\n const rawMin = Math.min(...values)\n const rawMax = Math.max(...values)\n if (rawMin === rawMax) {\n const pad = Math.abs(rawMin) * 0.1 || 1\n return { min: rawMin - pad, max: rawMax + pad }\n }\n const pad = (rawMax - rawMin) * 0.1\n return { min: rawMin - pad, max: rawMax + pad }\n}\n\n/** Evenly spaced axis ticks across the domain. */\nexport function computeTicks(min: number, max: number, count = 5): number[] {\n if (max === min) return [min]\n const step = (max - min) / (count - 1)\n return Array.from({ length: count }, (_, i) => min + i * step)\n}\n","import React, { useMemo, useState } from 'react'\nimport { useTranslation } from '../../hooks/useTranslation.js'\nimport { useCubeFieldLabel } from '../../hooks/useCubeFieldLabel.js'\nimport { CHART_COLORS } from '../../utils/chartConstants.js'\nimport { formatAxisValue } from '../../utils/chartUtils.js'\nimport { ChartConfigError, ChartEmptyState, ChartRenderError } from './ChartStates.js'\nimport { useChartDimensions } from './useChartDimensions.js'\nimport {\n DOT_RADIUS,\n MAX_BANDS,\n computeDomain,\n computeSpread,\n computeTicks,\n groupIntoBands,\n median,\n packSwarm,\n swarmHeight,\n type BandSort,\n type BandStats,\n type PlacedDot,\n} from './DotStripChart.helpers.js'\nimport type { ChartProps } from '../../types.js'\n\nconst GUTTER_WIDTH = 190\nconst AXIS_HEIGHT = 44\nconst PLOT_PADDING_X = 24\n\n/**\n * Height the left gutter needs for its own content, so a band with a flat swarm\n * still gets a row tall enough for the name and the badges. Folded into the\n * band height rather than left to the grid, so the strip SVG always fills the\n * whole row and its gridlines stay continuous between bands.\n *\n * `dc:text-sm` line (20) + `dc:gap-1` (4) + badge line (20) + `dc:py-2` (16).\n */\nconst GUTTER_HEIGHT_WITH_STATS = 60\nconst GUTTER_HEIGHT_NAME_ONLY = 36\n\n/**\n * Read a drop zone's field, tolerating both shapes it is stored in: the\n * AnalysisBuilder writes a bare string into any zone with `maxItems: 1`\n * (`addFieldToAxis` in `AnalysisBuilder/utils/axisConfigUtils.ts`), while the\n * agent's `inferChartConfig` always writes an array.\n */\nfunction firstField(value: unknown): string | undefined {\n if (Array.isArray(value)) return typeof value[0] === 'string' ? value[0] : undefined\n return typeof value === 'string' && value !== '' ? value : undefined\n}\n\ninterface HoveredDot {\n band: string\n dot: PlacedDot\n clientX: number\n clientY: number\n}\n\n/**\n * One band's strip: gridlines, the baseline, the median tick and the swarm.\n * The x-scale is passed in so every band and the axis share one domain.\n */\nfunction BandStrip({\n band,\n width,\n radius,\n ticks,\n scale,\n showGrid,\n showMedianMarker,\n showExtremeLabels,\n color,\n accentColor,\n drillEnabled,\n onDotClick,\n onDotHover,\n onDotLeave,\n}: {\n band: BandStats\n width: number\n radius: number\n ticks: number[]\n scale: (v: number) => number\n showGrid: boolean\n showMedianMarker: boolean\n showExtremeLabels: boolean\n color: string\n accentColor: string\n drillEnabled: boolean\n onDotClick: (band: BandStats, dot: PlacedDot, event: React.MouseEvent) => void\n onDotHover: (band: BandStats, dot: PlacedDot, event: React.MouseEvent) => void\n onDotLeave: () => void\n}) {\n const centreY = band.height / 2\n const extremes = useMemo(() => {\n if (!showExtremeLabels || band.dots.length < 2) return new Set<PlacedDot>()\n const sorted = [...band.dots].sort((a, b) => a.value - b.value)\n return new Set<PlacedDot>([sorted[0], sorted[sorted.length - 1]])\n }, [band.dots, showExtremeLabels])\n\n return (\n <svg\n width={width}\n height={band.height}\n viewBox={`0 0 ${width} ${band.height}`}\n data-testid={`dot-strip-band-${band.label}`}\n >\n {showGrid &&\n ticks.map((tick, i) => (\n <line\n key={i}\n x1={scale(tick)}\n x2={scale(tick)}\n y1={0}\n y2={band.height}\n stroke=\"currentColor\"\n strokeOpacity={0.08}\n strokeWidth={1}\n className=\"text-dc-text-secondary\"\n />\n ))}\n\n {band.dots.length > 0 && (\n <line\n x1={scale(band.min!)}\n x2={scale(band.max!)}\n y1={centreY}\n y2={centreY}\n stroke=\"currentColor\"\n strokeOpacity={0.25}\n strokeWidth={1}\n className=\"text-dc-text-secondary\"\n />\n )}\n\n {showMedianMarker && band.median !== null && (\n <line\n x1={scale(band.median)}\n x2={scale(band.median)}\n y1={centreY - Math.min(centreY - 2, 16)}\n y2={centreY + Math.min(centreY - 2, 16)}\n stroke=\"currentColor\"\n strokeOpacity={0.55}\n strokeWidth={2}\n className=\"text-dc-text-secondary\"\n data-testid={`dot-strip-median-${band.label}`}\n />\n )}\n\n {band.dots.map((dot, i) => {\n const isExtreme = extremes.has(dot)\n return (\n <g key={`${dot.label}-${i}`}>\n <circle\n cx={dot.x}\n cy={centreY + dot.y}\n r={radius}\n fill={isExtreme ? accentColor : color}\n stroke=\"var(--dc-surface, #ffffff)\"\n strokeWidth={1.25}\n cursor={drillEnabled ? 'pointer' : undefined}\n data-testid=\"dot-strip-dot\"\n onClick={(event) => onDotClick(band, dot, event)}\n onMouseEnter={(event) => onDotHover(band, dot, event)}\n onMouseLeave={onDotLeave}\n />\n {isExtreme && (\n <text\n x={dot.x}\n y={centreY + dot.y + (dot.y <= 0 ? -radius - 6 : radius + 14)}\n textAnchor=\"middle\"\n fontSize={11}\n fill={accentColor}\n data-testid={`dot-strip-extreme-${dot.label}`}\n >\n {dot.label}\n </text>\n )}\n </g>\n )\n })}\n </svg>\n )\n}\n\nconst DotStripChart = React.memo(function DotStripChart({\n data,\n chartConfig,\n displayConfig = {},\n height = '100%',\n colorPalette,\n onDataPointClick,\n drillEnabled,\n}: ChartProps) {\n const { t } = useTranslation()\n const getFieldLabel = useCubeFieldLabel()\n const { containerRef, dimensions } = useChartDimensions()\n const [hovered, setHovered] = useState<HoveredDot | null>(null)\n\n const showGrid = displayConfig.showGrid !== false\n const showTooltip = displayConfig.showTooltip !== false\n const showMedianMarker = displayConfig.showMedianMarker !== false\n const showBandStats = displayConfig.showBandStats !== false\n const showExtremeLabels = displayConfig.showExtremeLabels === true\n const radius = DOT_RADIUS[displayConfig.dotSize ?? 'medium'] ?? DOT_RADIUS.medium\n const bandSort: BandSort = displayConfig.bandSort ?? 'none'\n const xAxisFormat = displayConfig.xAxisFormat\n\n const { bandField, valueField, labelField, configError } = useMemo(() => {\n const bandField = firstField(chartConfig?.xAxis) ?? firstField(chartConfig?.x)\n const valueField = firstField(chartConfig?.yAxis) ?? firstField(chartConfig?.y)\n const labelField = firstField(chartConfig?.series)\n\n if (!bandField || !valueField) {\n return {\n bandField,\n valueField,\n labelField,\n configError: t('chart.runtime.configErrorHint.dotStrip'),\n }\n }\n return { bandField, valueField, labelField, configError: null as string | null }\n }, [chartConfig, t])\n\n const plotWidth = Math.max((dimensions.width || 720) - GUTTER_WIDTH - PLOT_PADDING_X, 80)\n\n const gutterHeight = showBandStats ? GUTTER_HEIGHT_WITH_STATS : GUTTER_HEIGHT_NAME_ONLY\n\n const { bands, ticks, scale, truncated } = useMemo(() => {\n const empty = { bands: [] as BandStats[], ticks: [] as number[], scale: (_: number) => 0, truncated: false }\n if (configError || !bandField || !valueField || !data || data.length === 0) return empty\n\n const rows = data as Record<string, unknown>[]\n const grouped = groupIntoBands(rows, bandField, valueField, labelField, bandSort)\n\n const allValues = grouped.flatMap((b) => b.values.map((d) => d.value))\n const domain = computeDomain(allValues)\n const span = domain.max - domain.min\n const inner = Math.max(plotWidth - 2 * radius - 2, 10)\n const scale = (v: number) => radius + 1 + (span === 0 ? inner / 2 : ((v - domain.min) / span) * inner)\n\n const bands: BandStats[] = grouped.map((group) => {\n const placed = packSwarm(group.values, scale, radius)\n const sortedValues = group.values.map((d) => d.value).sort((a, b) => a - b)\n const min = sortedValues.length > 0 ? sortedValues[0] : null\n const max = sortedValues.length > 0 ? sortedValues[sortedValues.length - 1] : null\n return {\n label: group.label,\n dots: placed,\n count: placed.length,\n median: median(sortedValues),\n min,\n max,\n spread: computeSpread(min, max, placed.length),\n noData: placed.length === 0,\n height: Math.max(swarmHeight(placed, radius), gutterHeight),\n truncated: group.truncated,\n }\n })\n\n return {\n bands,\n ticks: computeTicks(domain.min, domain.max, 5),\n scale,\n truncated: new Set(rows.map((r) => String(r[bandField] ?? ''))).size > MAX_BANDS,\n }\n }, [data, bandField, valueField, labelField, bandSort, configError, plotWidth, radius, gutterHeight])\n\n const palette = colorPalette?.colors ?? CHART_COLORS\n const color = palette[0]\n const accentColor = palette[palette.length > 1 ? 1 : 0]\n\n const formatValue = (v: number) => formatAxisValue(v, xAxisFormat)\n\n const handleDotClick = (band: BandStats, dot: PlacedDot, event: React.MouseEvent) => {\n if (!onDataPointClick || !drillEnabled || !bandField) return\n onDataPointClick({\n dataPoint: dot.row,\n clickedField: bandField,\n xValue: band.label,\n position: { x: event.clientX, y: event.clientY },\n nativeEvent: event,\n })\n }\n\n const handleDotHover = (band: BandStats, dot: PlacedDot, event: React.MouseEvent) => {\n if (!showTooltip) return\n setHovered({ band: band.label, dot, clientX: event.clientX, clientY: event.clientY })\n }\n\n if (configError) {\n return <ChartConfigError height={height} hint={configError} />\n }\n\n if (!data || data.length === 0) {\n return <ChartEmptyState height={height} hint={t('chart.runtime.noDataHint.dotStrip')} />\n }\n\n if (bands.length === 0) {\n return (\n <ChartEmptyState\n height={height}\n titleKey=\"chart.runtime.noValidData\"\n hint={t('chart.runtime.noValidDataHint.dotStrip')}\n />\n )\n }\n\n try {\n return (\n <div ref={containerRef} className=\"dc:relative dc:w-full dc:overflow-y-auto\" style={{ height }}>\n <div\n className=\"dc:grid dc:items-stretch\"\n style={{ gridTemplateColumns: `${GUTTER_WIDTH}px 1fr` }}\n data-testid=\"dot-strip-grid\"\n >\n {bands.map((band) => (\n <React.Fragment key={band.label}>\n <div\n className=\"dc:flex dc:flex-col dc:justify-center dc:gap-1 dc:pr-3 dc:py-2 dc:border-b border-dc-border\"\n style={{ minHeight: band.height }}\n >\n <div className=\"dc:text-sm dc:font-semibold dc:truncate text-dc-text\" title={band.label}>\n {band.label || t('chart.runtime.dotStrip.unlabelledBand')}\n </div>\n {showBandStats && (\n <div className=\"dc:flex dc:flex-wrap dc:gap-1 dc:text-xs\">\n <span className=\"dc:rounded-sm dc:px-1.5 dc:py-0.5 bg-dc-surface-secondary text-dc-text-secondary\">\n {t(\n band.truncated\n ? 'chart.runtime.dotStrip.countTruncated'\n : 'chart.runtime.dotStrip.count',\n { count: band.count }\n )}\n </span>\n {band.noData ? (\n <span className=\"dc:rounded-sm dc:px-1.5 dc:py-0.5 bg-dc-surface-secondary text-dc-text-muted\">\n {t('chart.runtime.dotStrip.noDataRecorded')}\n </span>\n ) : (\n band.spread !== null && (\n <span className=\"dc:rounded-sm dc:px-1.5 dc:py-0.5 bg-dc-surface-secondary text-dc-text-secondary\">\n {t('chart.runtime.dotStrip.spread', { spread: band.spread.toFixed(2) })}\n </span>\n )\n )}\n </div>\n )}\n </div>\n <div\n className=\"dc:flex dc:items-center dc:border-b border-dc-border\"\n style={{ minHeight: band.height, paddingRight: PLOT_PADDING_X }}\n >\n <BandStrip\n band={band}\n width={plotWidth}\n radius={radius}\n ticks={ticks}\n scale={scale}\n showGrid={showGrid}\n showMedianMarker={showMedianMarker}\n showExtremeLabels={showExtremeLabels}\n color={color}\n accentColor={accentColor}\n drillEnabled={drillEnabled === true}\n onDotClick={handleDotClick}\n onDotHover={handleDotHover}\n onDotLeave={() => setHovered(null)}\n />\n </div>\n </React.Fragment>\n ))}\n\n <div />\n <div style={{ paddingRight: PLOT_PADDING_X }}>\n <svg\n width={plotWidth}\n height={AXIS_HEIGHT}\n viewBox={`0 0 ${plotWidth} ${AXIS_HEIGHT}`}\n data-testid=\"dot-strip-axis\"\n >\n <line x1={0} x2={plotWidth} y1={1} y2={1} stroke=\"currentColor\" strokeOpacity={0.2} className=\"text-dc-text-secondary\" />\n {ticks.map((tick, i) => (\n <g key={i} transform={`translate(${scale(tick)}, 0)`}>\n <line y1={1} y2={7} stroke=\"currentColor\" strokeOpacity={0.4} className=\"text-dc-text-secondary\" />\n <text\n y={22}\n textAnchor=\"middle\"\n fontSize={11}\n fill=\"currentColor\"\n className=\"text-dc-text-secondary\"\n >\n {formatValue(tick)}\n </text>\n </g>\n ))}\n {valueField && (\n <text\n x={plotWidth / 2}\n y={39}\n textAnchor=\"middle\"\n fontSize={11}\n fill=\"currentColor\"\n className=\"text-dc-text-muted\"\n >\n {getFieldLabel(valueField)}\n </text>\n )}\n </svg>\n </div>\n </div>\n\n {truncated && (\n <div className=\"dc:text-xs text-dc-warning dc:text-center dc:mt-1\">\n {t('chart.runtime.dotStrip.truncated', { max: MAX_BANDS })}\n </div>\n )}\n\n {showTooltip && hovered && (\n <div\n className=\"dc:fixed dc:z-50 dc:pointer-events-none dc:rounded-sm dc:px-2 dc:py-1 dc:text-xs dc:shadow-lg bg-dc-surface text-dc-text dc:border border-dc-border\"\n style={{ left: hovered.clientX + 12, top: hovered.clientY + 12 }}\n data-testid=\"dot-strip-tooltip\"\n >\n <div className=\"dc:font-semibold\">{hovered.dot.label}</div>\n <div className=\"text-dc-text-secondary\">{hovered.band}</div>\n <div>{formatValue(hovered.dot.value)}</div>\n </div>\n )}\n </div>\n )\n } catch (error) {\n return <ChartRenderError height={height} chartType=\"Dot Strip Chart\" error={error} />\n }\n})\n\nexport default DotStripChart\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- import { F as e, S as t, w as n } from "./chart-activity-grid-UZIp9_2E.js";
2
- import { D as r, O as i, k as a, s as o } from "./chart-area-CEBcvCcn.js";
1
+ import { F as e, S as t, w as n } from "./chart-activity-grid-aIyjUpbJ.js";
2
+ import { D as r, O as i, k as a, s as o } from "./chart-area-DCCvp06H.js";
3
3
  import s, { useMemo as c } from "react";
4
4
  import { jsx as l, jsxs as u } from "react/jsx-runtime";
5
5
  //#region src/client/components/charts/ProportionBarChart.tsx
@@ -106,4 +106,4 @@ var d = s.memo(function({ data: s, chartConfig: d, displayConfig: f = {}, height
106
106
  //#endregion
107
107
  export { d as default };
108
108
 
109
- //# sourceMappingURL=ProportionBarChart-3r7ZPX-V.js.map
109
+ //# sourceMappingURL=ProportionBarChart-DzdKxzr4.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ProportionBarChart-3r7ZPX-V.js","names":[],"sources":["../../../src/client/components/charts/ProportionBarChart.tsx"],"sourcesContent":["import React, { useMemo } from 'react'\nimport { useTranslation } from '../../hooks/useTranslation.js'\nimport { useCubeFieldLabel } from '../../hooks/useCubeFieldLabel.js'\nimport { ChartEmptyState, ChartConfigError, ChartRenderError } from './ChartStates.js'\nimport { getSeriesColor } from './cartesianChartHelpers.js'\nimport { formatAxisValue } from '../../utils/chartUtils.js'\nimport type { ChartProps } from '../../types.js'\n\ninterface Segment {\n /**\n * React key. Category labels are not reliably unique - a query can return\n * duplicates, and every null collapses to the same empty string - so the key\n * is tied to the source row instead. Stable across sorting, which reorders\n * the segments under the same identity.\n */\n id: string\n label: string\n value: number\n share: number\n color: string\n}\n\n/**\n * A single 100%-wide stacked bar plus a labelled percentage legend.\n *\n * A flatter alternative to a pie chart for part-to-whole breakdowns: shares are\n * far easier to compare along one axis than as angles, and it costs a fraction\n * of the vertical space. Deliberately plain HTML rather than Recharts — there is\n * no axis, scale or interaction to justify an SVG chart.\n */\nconst ProportionBarChart = React.memo(function ProportionBarChart({\n data,\n chartConfig,\n displayConfig = {},\n height = '100%',\n colorPalette\n}: ChartProps) {\n const { t } = useTranslation()\n const getFieldLabel = useCubeFieldLabel()\n\n const categoryField = useMemo(() => {\n const x = chartConfig?.xAxis\n return (Array.isArray(x) ? x[0] : x) || ''\n }, [chartConfig?.xAxis])\n\n const valueField = useMemo(() => {\n const y = chartConfig?.yAxis\n return (Array.isArray(y) ? y[0] : y) || ''\n }, [chartConfig?.yAxis])\n\n const sortSegments = displayConfig.sortSegments ?? false\n\n const { segments, total } = useMemo(() => {\n if (!data || !categoryField || !valueField) return { segments: [] as Segment[], total: 0 }\n\n const rows = data\n .map((row: any, sourceIndex: number) => {\n const raw = row?.[valueField]\n const num = typeof raw === 'number' ? raw : parseFloat(String(raw))\n const label = String(row?.[categoryField] ?? '')\n return {\n id: `${sourceIndex}:${label}`,\n label,\n // Negative shares cannot be drawn in a part-to-whole bar.\n value: !isNaN(num) && isFinite(num) && num > 0 ? num : 0\n }\n })\n .filter((row) => row.value > 0)\n\n if (sortSegments) rows.sort((a, b) => b.value - a.value)\n\n const sum = rows.reduce((acc, row) => acc + row.value, 0)\n return {\n total: sum,\n segments: rows.map((row, index) => ({\n ...row,\n share: sum > 0 ? (row.value / sum) * 100 : 0,\n color: getSeriesColor(colorPalette, index)\n }))\n }\n }, [data, categoryField, valueField, colorPalette, sortSegments])\n\n try {\n if (!data || data.length === 0) {\n return <ChartEmptyState height={height} hint={t('chart.runtime.noDataHint.proportionBar')} />\n }\n\n if (!categoryField || !valueField) {\n return <ChartConfigError height={height} hint={t('chart.runtime.configErrorHint.proportionBar')} />\n }\n\n if (segments.length === 0 || total === 0) {\n return (\n <ChartEmptyState\n height={height}\n titleKey=\"chart.runtime.noValidData\"\n hint={t('chart.runtime.noValidDataHint.proportionBar')}\n />\n )\n }\n\n const showLabels = displayConfig.showLabels ?? true\n const showPercentages = displayConfig.showPercentages ?? true\n const decimals = displayConfig.decimals ?? 0\n\n return (\n <div\n className=\"dc:w-full dc:h-full dc:flex dc:flex-col dc:justify-center dc:gap-3 dc:px-3 dc:py-2 dc:overflow-hidden\"\n // Fill the parent rather than the `height` prop, which is a pixel value\n // that can exceed the visible panel and push this short content low.\n style={{ height: '100%', minHeight: '80px' }}\n data-testid=\"proportion-bar\"\n >\n <div className=\"dc:flex dc:w-full dc:overflow-hidden dc:rounded-sm\" style={{ height: 10 }}>\n {segments.map((segment) => (\n <div\n key={segment.id}\n data-testid=\"proportion-bar-segment\"\n title={`${segment.label}: ${formatAxisValue(segment.value, displayConfig.leftYAxisFormat)}`}\n style={{ width: `${segment.share}%`, backgroundColor: segment.color }}\n />\n ))}\n </div>\n\n {(showLabels || showPercentages) && (\n <div className=\"dc:flex dc:flex-wrap dc:gap-x-6 dc:gap-y-2\">\n {segments.map((segment) => (\n <div key={segment.id} className=\"dc:flex dc:flex-col dc:gap-0.5 dc:min-w-0\">\n {showLabels && (\n <span\n className=\"text-dc-text-muted dc:uppercase dc:truncate dc:font-semibold\"\n style={{ fontSize: '11px', letterSpacing: '0.06em' }}\n >\n {segment.label}\n </span>\n )}\n {showPercentages && (\n <span\n className=\"dc:font-semibold dc:leading-none\"\n style={{ fontSize: '18px', color: segment.color }}\n >\n {segment.share.toFixed(decimals)}%\n </span>\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n )\n } catch (error) {\n return <ChartRenderError height={height} chartType={getFieldLabel(valueField)} error={error} />\n }\n})\n\nexport default ProportionBarChart\n"],"mappings":"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"}
1
+ {"version":3,"file":"ProportionBarChart-DzdKxzr4.js","names":[],"sources":["../../../src/client/components/charts/ProportionBarChart.tsx"],"sourcesContent":["import React, { useMemo } from 'react'\nimport { useTranslation } from '../../hooks/useTranslation.js'\nimport { useCubeFieldLabel } from '../../hooks/useCubeFieldLabel.js'\nimport { ChartEmptyState, ChartConfigError, ChartRenderError } from './ChartStates.js'\nimport { getSeriesColor } from './cartesianChartHelpers.js'\nimport { formatAxisValue } from '../../utils/chartUtils.js'\nimport type { ChartProps } from '../../types.js'\n\ninterface Segment {\n /**\n * React key. Category labels are not reliably unique - a query can return\n * duplicates, and every null collapses to the same empty string - so the key\n * is tied to the source row instead. Stable across sorting, which reorders\n * the segments under the same identity.\n */\n id: string\n label: string\n value: number\n share: number\n color: string\n}\n\n/**\n * A single 100%-wide stacked bar plus a labelled percentage legend.\n *\n * A flatter alternative to a pie chart for part-to-whole breakdowns: shares are\n * far easier to compare along one axis than as angles, and it costs a fraction\n * of the vertical space. Deliberately plain HTML rather than Recharts — there is\n * no axis, scale or interaction to justify an SVG chart.\n */\nconst ProportionBarChart = React.memo(function ProportionBarChart({\n data,\n chartConfig,\n displayConfig = {},\n height = '100%',\n colorPalette\n}: ChartProps) {\n const { t } = useTranslation()\n const getFieldLabel = useCubeFieldLabel()\n\n const categoryField = useMemo(() => {\n const x = chartConfig?.xAxis\n return (Array.isArray(x) ? x[0] : x) || ''\n }, [chartConfig?.xAxis])\n\n const valueField = useMemo(() => {\n const y = chartConfig?.yAxis\n return (Array.isArray(y) ? y[0] : y) || ''\n }, [chartConfig?.yAxis])\n\n const sortSegments = displayConfig.sortSegments ?? false\n\n const { segments, total } = useMemo(() => {\n if (!data || !categoryField || !valueField) return { segments: [] as Segment[], total: 0 }\n\n const rows = data\n .map((row: any, sourceIndex: number) => {\n const raw = row?.[valueField]\n const num = typeof raw === 'number' ? raw : parseFloat(String(raw))\n const label = String(row?.[categoryField] ?? '')\n return {\n id: `${sourceIndex}:${label}`,\n label,\n // Negative shares cannot be drawn in a part-to-whole bar.\n value: !isNaN(num) && isFinite(num) && num > 0 ? num : 0\n }\n })\n .filter((row) => row.value > 0)\n\n if (sortSegments) rows.sort((a, b) => b.value - a.value)\n\n const sum = rows.reduce((acc, row) => acc + row.value, 0)\n return {\n total: sum,\n segments: rows.map((row, index) => ({\n ...row,\n share: sum > 0 ? (row.value / sum) * 100 : 0,\n color: getSeriesColor(colorPalette, index)\n }))\n }\n }, [data, categoryField, valueField, colorPalette, sortSegments])\n\n try {\n if (!data || data.length === 0) {\n return <ChartEmptyState height={height} hint={t('chart.runtime.noDataHint.proportionBar')} />\n }\n\n if (!categoryField || !valueField) {\n return <ChartConfigError height={height} hint={t('chart.runtime.configErrorHint.proportionBar')} />\n }\n\n if (segments.length === 0 || total === 0) {\n return (\n <ChartEmptyState\n height={height}\n titleKey=\"chart.runtime.noValidData\"\n hint={t('chart.runtime.noValidDataHint.proportionBar')}\n />\n )\n }\n\n const showLabels = displayConfig.showLabels ?? true\n const showPercentages = displayConfig.showPercentages ?? true\n const decimals = displayConfig.decimals ?? 0\n\n return (\n <div\n className=\"dc:w-full dc:h-full dc:flex dc:flex-col dc:justify-center dc:gap-3 dc:px-3 dc:py-2 dc:overflow-hidden\"\n // Fill the parent rather than the `height` prop, which is a pixel value\n // that can exceed the visible panel and push this short content low.\n style={{ height: '100%', minHeight: '80px' }}\n data-testid=\"proportion-bar\"\n >\n <div className=\"dc:flex dc:w-full dc:overflow-hidden dc:rounded-sm\" style={{ height: 10 }}>\n {segments.map((segment) => (\n <div\n key={segment.id}\n data-testid=\"proportion-bar-segment\"\n title={`${segment.label}: ${formatAxisValue(segment.value, displayConfig.leftYAxisFormat)}`}\n style={{ width: `${segment.share}%`, backgroundColor: segment.color }}\n />\n ))}\n </div>\n\n {(showLabels || showPercentages) && (\n <div className=\"dc:flex dc:flex-wrap dc:gap-x-6 dc:gap-y-2\">\n {segments.map((segment) => (\n <div key={segment.id} className=\"dc:flex dc:flex-col dc:gap-0.5 dc:min-w-0\">\n {showLabels && (\n <span\n className=\"text-dc-text-muted dc:uppercase dc:truncate dc:font-semibold\"\n style={{ fontSize: '11px', letterSpacing: '0.06em' }}\n >\n {segment.label}\n </span>\n )}\n {showPercentages && (\n <span\n className=\"dc:font-semibold dc:leading-none\"\n style={{ fontSize: '18px', color: segment.color }}\n >\n {segment.share.toFixed(decimals)}%\n </span>\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n )\n } catch (error) {\n return <ChartRenderError height={height} chartType={getFieldLabel(valueField)} error={error} />\n }\n})\n\nexport default ProportionBarChart\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
- import { i as e, y as t } from "./chart-data-table-CCvvgeG_.js";
2
- import { B as n, E as r, F as i, w as a } from "./chart-activity-grid-UZIp9_2E.js";
3
- import { O as o } from "./chart-area-CEBcvCcn.js";
1
+ import { i as e, y as t } from "./chart-data-table-Dfup1JpL.js";
2
+ import { B as n, E as r, F as i, w as a } from "./chart-activity-grid-aIyjUpbJ.js";
3
+ import { O as o } from "./chart-area-DCCvp06H.js";
4
4
  import s, { useCallback as c, useEffect as l, useMemo as u, useRef as d, useState as f } from "react";
5
5
  import { jsx as p, jsxs as m } from "react/jsx-runtime";
6
6
  //#region src/client/utils/rowLinkUtils.ts
@@ -553,4 +553,4 @@ function $({ label: e, disabled: t, onClick: n, children: r }) {
553
553
  //#endregion
554
554
  export { ie as default };
555
555
 
556
- //# sourceMappingURL=RecordsTable-DvMXV6F6.js.map
556
+ //# sourceMappingURL=RecordsTable-kQfZ25Pz.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"RecordsTable-DvMXV6F6.js","names":[],"sources":["../../../src/client/utils/rowLinkUtils.ts","../../../src/client/utils/recordsTableUtils.ts","../../../src/client/components/charts/recordsTableHooks.ts","../../../src/client/components/charts/RecordsTable.tsx"],"sourcesContent":["/**\n * Row click-through URLs for the records table.\n *\n * A dashboard author writes a template such as `/employees/{Employees.id}`,\n * and each row fills its own tokens. The template is author-controlled but the\n * *values* are data, so they are percent-encoded before substitution — an\n * encoded value cannot introduce a scheme, a path segment or a query separator.\n *\n * The resulting URL is then checked rather than trusted: only same-origin\n * relative paths and absolute http(s) URLs are allowed through.\n */\n\nconst TOKEN_PATTERN = /\\{([A-Za-z0-9_]+\\.[A-Za-z0-9_]+)\\}/g\n\n/** Schemes that must never survive validation, even spelled oddly. */\nconst ALLOWED_PROTOCOLS = new Set(['http:', 'https:'])\n\n/**\n * Resolve a row link template against one row.\n *\n * Returns `null` — meaning \"render no link\" — when the template references a\n * field the row does not carry, or when the result is not a URL we are willing\n * to navigate to. Never throws: a malformed template must not take the table\n * down with it.\n *\n * @param template - e.g. `/employees/{Employees.id}?tab=profile`\n * @param row - the row's values, including hidden columns\n * @param origin - the origin relative URLs are resolved against\n */\nexport function buildRowUrl(\n template: string | undefined,\n row: Record<string, unknown>,\n origin: string = defaultOrigin()\n): string | null {\n if (!template) return null\n\n let unresolved = false\n const substituted = template.replace(TOKEN_PATTERN, (_match, field: string) => {\n const value = row[field]\n if (value === null || value === undefined) {\n unresolved = true\n return ''\n }\n return encodeURIComponent(String(value))\n })\n\n if (unresolved) return null\n\n return isSafeUrl(substituted, origin) ? substituted : null\n}\n\n/**\n * Whether a URL is one we will navigate to: an absolute `http(s)` URL, or a\n * path that stays on this origin.\n *\n * Rejected, deliberately: `javascript:`, `data:`, `vbscript:` and every other\n * scheme; protocol-relative `//evil.example`, which inherits the current scheme\n * and silently leaves the origin; and backslash forms such as `\\\\evil.example`\n * or `/\\evil.example`, which several browsers normalise into that same\n * protocol-relative shape.\n */\nexport function isSafeUrl(url: string, origin: string = defaultOrigin()): boolean {\n const trimmed = url.trim()\n if (trimmed === '') return false\n\n // Control characters, including the tab/newline a browser strips out of a\n // scheme — which turns `java<TAB>script:` back into `javascript:`.\n // eslint-disable-next-line no-control-regex\n if (/[\\u0000-\\u001F\\u007F]/.test(trimmed)) return false\n\n if (trimmed.includes('\\\\')) return false\n if (trimmed.startsWith('//')) return false\n\n try {\n const parsed = new URL(trimmed, origin)\n if (!ALLOWED_PROTOCOLS.has(parsed.protocol)) return false\n\n // A relative path is only allowed while it stays here. An absolute http(s)\n // URL to another host is fine — that is the \"external link\" case — so the\n // origin check applies to inputs that did not declare a scheme.\n const declaresScheme = /^[A-Za-z][A-Za-z0-9+.-]*:/.test(trimmed)\n if (!declaresScheme && parsed.origin !== new URL(origin).origin) return false\n\n return true\n } catch {\n return false\n }\n}\n\n/**\n * The origin to resolve relative links against. Falls back to a placeholder\n * outside a browser (SSR, tests) so validation still runs rather than throwing.\n */\nfunction defaultOrigin(): string {\n return typeof window !== 'undefined' && window.location?.origin\n ? window.location.origin\n : 'http://localhost'\n}\n","/**\n * Cell formatting and column-width persistence for the records table.\n *\n * Kept out of the component so each piece is unit-testable without a DOM, and\n * so the format switch stays a pure value → display transform.\n */\n\nimport type { ColumnFormatConfig, ColumnFormatKind } from '../types.js'\nimport { formatAxisValue, formatTimeValue } from './chartUtils.js'\n\n/** Fallback when an author has not sized a column. */\nexport const DEFAULT_COLUMN_WIDTH = 160\n/** Narrower than this and the resize handle overlaps the header text. */\nexport const MIN_COLUMN_WIDTH = 60\n\nconst COLUMN_WIDTHS_STORAGE_KEY = 'dc-records-table-column-widths'\nconst COLUMN_ORDER_STORAGE_KEY = 'dc-records-table-column-order'\n\n/**\n * Identity under which a viewer's column widths are remembered.\n *\n * A chart component has no portlet id, so widths key on the columns themselves:\n * two records tables showing the same fields share widths, which is the\n * behaviour an author would expect from two views of the same records.\n */\nexport function columnWidthStorageKey(columns: string[]): string {\n return [...columns].sort().join('|')\n}\n\nexport function loadColumnWidths(key: string): Record<string, number> {\n try {\n const raw = localStorage.getItem(COLUMN_WIDTHS_STORAGE_KEY)\n if (!raw) return {}\n return JSON.parse(raw)[key] ?? {}\n } catch {\n return {}\n }\n}\n\nexport function saveColumnWidths(key: string, widths: Record<string, number>): void {\n try {\n const raw = localStorage.getItem(COLUMN_WIDTHS_STORAGE_KEY)\n const all = raw ? JSON.parse(raw) : {}\n all[key] = widths\n localStorage.setItem(COLUMN_WIDTHS_STORAGE_KEY, JSON.stringify(all))\n } catch {\n // localStorage unavailable (private mode, SSR) — widths stay session-local\n }\n}\n\n/**\n * A viewer's own column order, remembered under the same identity as widths.\n *\n * Stored as a list rather than a map so it survives the author adding or\n * removing a column: {@link applyColumnOrder} reconciles it against the\n * columns that actually exist.\n */\nexport function loadColumnOrder(key: string): string[] {\n try {\n const raw = localStorage.getItem(COLUMN_ORDER_STORAGE_KEY)\n if (!raw) return []\n const stored = JSON.parse(raw)[key]\n return Array.isArray(stored) ? stored : []\n } catch {\n return []\n }\n}\n\nexport function saveColumnOrder(key: string, order: string[]): void {\n try {\n const raw = localStorage.getItem(COLUMN_ORDER_STORAGE_KEY)\n const all = raw ? JSON.parse(raw) : {}\n all[key] = order\n localStorage.setItem(COLUMN_ORDER_STORAGE_KEY, JSON.stringify(all))\n } catch {\n // localStorage unavailable — the order stays session-local\n }\n}\n\n/**\n * Lay a remembered order over the columns the chart actually has.\n *\n * Columns the order does not mention keep their authored position relative to\n * the end, so an author adding a column still sees it rather than having it\n * silently swallowed by a stale local order.\n */\nexport function applyColumnOrder(columns: string[], order: string[]): string[] {\n if (order.length === 0) return columns\n const present = new Set(columns)\n const ordered = order.filter(column => present.has(column))\n const remaining = columns.filter(column => !ordered.includes(column))\n return [...ordered, ...remaining]\n}\n\n/** Move one column to another's position, returning a new order. */\nexport function moveColumn(columns: string[], from: string, to: string): string[] {\n const fromIndex = columns.indexOf(from)\n const toIndex = columns.indexOf(to)\n if (fromIndex === -1 || toIndex === -1 || fromIndex === toIndex) return columns\n\n const next = [...columns]\n next.splice(fromIndex, 1)\n next.splice(toIndex, 0, from)\n return next\n}\n\n/**\n * A cell resolved to something renderable. `progress` carries its own shape\n * because the component draws a bar or a ring rather than a string; every\n * other kind reduces to text plus, for badges, a colour.\n *\n * The component only ever sees a `RenderedCell`, so the progress `style` is\n * resolved here rather than being read back out of the column's config.\n */\nexport type RenderedCell =\n | { kind: 'text'; text: string }\n | { kind: 'badge'; text: string; colorIndex?: number }\n | { kind: 'progress'; text: string; fraction: number; style: 'bar' | 'circle' }\n\nconst EMPTY: RenderedCell = { kind: 'text', text: '' }\n\n/**\n * Turn a raw cell value into its display form under the column's format.\n *\n * Every kind degrades to text rather than erroring: a non-numeric value under\n * `number`/`progress` renders as-is (EAV columns legitimately contain 'n/a'),\n * and a badge value with no colour mapping renders neutral rather than being\n * given a guessed colour.\n */\nexport function renderCellValue(\n value: unknown,\n format: ColumnFormatConfig | undefined\n): RenderedCell {\n if (isEmpty(value)) return EMPTY\n const render = RENDERERS[format?.kind ?? 'text'] ?? renderText\n return render(value, format)\n}\n\ntype CellRenderer = (value: unknown, format: ColumnFormatConfig | undefined) => RenderedCell\n\nconst RENDERERS: Record<ColumnFormatKind, CellRenderer> = {\n text: renderText,\n number: renderNumber,\n date: renderDate,\n badge: renderBadge,\n progress: renderProgress\n}\n\nfunction renderText(value: unknown): RenderedCell {\n return { kind: 'text', text: stringify(value) }\n}\n\nfunction renderNumber(value: unknown, format: ColumnFormatConfig | undefined): RenderedCell {\n const num = toNumber(value)\n if (num === null) return { kind: 'text', text: String(value) }\n return { kind: 'text', text: formatAxisValue(num, format?.numberFormat ?? {}) }\n}\n\nfunction renderDate(value: unknown, format: ColumnFormatConfig | undefined): RenderedCell {\n return { kind: 'text', text: formatTimeValue(value, format?.dateGranularity) }\n}\n\n/**\n * Colour mappings that are actually usable.\n *\n * A `columnFormats` entry can arrive from an agent tool call or a hand-edited\n * portlet, where `badgeColors` may not be the array of `{ value, colorIndex }`\n * the type promises — an object map and colour names instead of palette indices\n * are the common shapes. Anything unusable is discarded so the badge renders\n * neutral, which is what an unmapped value does anyway.\n */\nfunction usableBadgeColors(format: ColumnFormatConfig | undefined): ColumnFormatConfig['badgeColors'] {\n if (!Array.isArray(format?.badgeColors)) return []\n return format.badgeColors.filter(entry => entry && typeof entry.colorIndex === 'number')\n}\n\nfunction renderBadge(value: unknown, format: ColumnFormatConfig | undefined): RenderedCell {\n const text = stringify(value)\n return { kind: 'badge', text, colorIndex: usableBadgeColors(format)?.find(e => e.value === text)?.colorIndex }\n}\n\nconst PROGRESS_NUMBER_FORMAT = { unit: 'number', abbreviate: false, decimals: 0 } as const\n\nfunction renderProgress(value: unknown, format: ColumnFormatConfig | undefined): RenderedCell {\n const num = toNumber(value)\n if (num === null) return { kind: 'text', text: String(value) }\n\n return {\n kind: 'progress',\n fraction: progressFraction(num, format),\n style: format?.progressStyle ?? 'bar',\n text: formatAxisValue(num, format?.numberFormat ?? PROGRESS_NUMBER_FORMAT)\n }\n}\n\n/** Where a value sits within the column's bounds, as 0-1. */\nfunction progressFraction(value: number, format: ColumnFormatConfig | undefined): number {\n const min = format?.progressMin ?? 0\n const max = format?.progressMax ?? 100\n const span = max - min\n // A zero-width range would divide by zero; treat it as \"full\".\n return span === 0 ? 1 : clamp((value - min) / span, 0, 1)\n}\n\nfunction stringify(value: unknown): string {\n if (typeof value === 'boolean') return value ? 'true' : 'false'\n if (typeof value === 'number') return value.toLocaleString()\n if (value instanceof Date) return value.toISOString()\n return String(value)\n}\n\nfunction toNumber(value: unknown): number | null {\n if (typeof value === 'number') return Number.isFinite(value) ? value : null\n if (typeof value === 'string' && value.trim() !== '') {\n const parsed = Number(value)\n return Number.isFinite(parsed) ? parsed : null\n }\n return null\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, value))\n}\n\n/**\n * Sort rows by one column, client-side.\n *\n * Only used when the host provides no server-side pagination — with paging on,\n * sorting the loaded page alone would put the wrong rows on page 1.\n */\nexport function sortRows(\n rows: Record<string, unknown>[],\n column: string,\n direction: 'asc' | 'desc'\n): Record<string, unknown>[] {\n const factor = direction === 'asc' ? 1 : -1\n return [...rows].sort((a, b) => {\n // Empty values sort last in *both* directions — reversing the sort should\n // not push a column's blanks to the top of the table.\n const aEmpty = isEmpty(a[column])\n const bEmpty = isEmpty(b[column])\n if (aEmpty || bEmpty) return aEmpty && bEmpty ? 0 : aEmpty ? 1 : -1\n return factor * compareValues(a[column], b[column])\n })\n}\n\nfunction isEmpty(value: unknown): boolean {\n return value === null || value === undefined || value === ''\n}\n\nfunction compareValues(a: unknown, b: unknown): number {\n const aNum = toNumber(a)\n const bNum = toNumber(b)\n if (aNum !== null && bNum !== null) return aNum - bNum\n\n return String(a).localeCompare(String(b))\n}\n","/**\n * State for the records table, split out of the component so each concern —\n * which columns and in what order, how wide they are, which page and sort —\n * reads on its own.\n *\n * All three are viewer-side. The *author's* choices (assigned columns, page\n * size, widths) arrive through `chartConfig`/`displayConfig`; these hooks layer\n * the viewer's own adjustments over them.\n */\n\nimport React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { getOrderedColumnsFromQuery } from '../../utils/pivotUtils.js'\nimport {\n DEFAULT_COLUMN_WIDTH,\n MIN_COLUMN_WIDTH,\n applyColumnOrder,\n columnWidthStorageKey,\n loadColumnOrder,\n loadColumnWidths,\n moveColumn,\n saveColumnOrder,\n saveColumnWidths,\n sortRows\n} from '../../utils/recordsTableUtils.js'\nimport type { ChartAxisConfig, ChartPagination, CubeQuery } from '../../types.js'\n\nexport const DEFAULT_PAGE_SIZE = 25\n\ntype SortState = { column: string; direction: 'asc' | 'desc' }\n\n/**\n * Which columns to render, in which order.\n *\n * The author's arrangement comes from the Columns drop zone; a viewer can drag\n * a header on top of it, and that order is remembered per column set. It is\n * *reconciled* rather than substituted, so a column the author adds later still\n * appears instead of being swallowed by a stale local order.\n */\nexport function useRecordsColumns(params: {\n rows: Record<string, unknown>[]\n chartConfig?: ChartAxisConfig\n queryObject?: CubeQuery\n}) {\n const { rows, chartConfig, queryObject } = params\n\n const baseColumns = useMemo(\n () => resolveColumns(rows, chartConfig, queryObject),\n [rows, chartConfig, queryObject]\n )\n\n const storageKey = useMemo(() => columnWidthStorageKey(baseColumns), [baseColumns])\n const [columnOrder, setColumnOrder] = useState<string[]>([])\n\n useEffect(() => {\n setColumnOrder(loadColumnOrder(storageKey))\n }, [storageKey])\n\n const columns = useMemo(\n () => applyColumnOrder(baseColumns, columnOrder),\n [baseColumns, columnOrder]\n )\n\n const [draggedColumn, setDraggedColumn] = useState<string | null>(null)\n // A drag ending on the header it started from can still produce a click;\n // without this, reordering would also toggle the sort.\n const didDragRef = useRef(false)\n\n const startColumnDrag = useCallback((column: string) => {\n didDragRef.current = true\n setDraggedColumn(column)\n }, [])\n\n const endColumnDrag = useCallback(() => {\n setDraggedColumn(null)\n requestAnimationFrame(() => { didDragRef.current = false })\n }, [])\n\n const dropColumn = useCallback((target: string) => {\n const source = draggedColumn\n endColumnDrag()\n if (!source || source === target) return\n const next = moveColumn(columns, source, target)\n setColumnOrder(next)\n saveColumnOrder(storageKey, next)\n }, [columns, draggedColumn, endColumnDrag, storageKey])\n\n return { columns, storageKey, draggedColumn, didDragRef, startColumnDrag, endColumnDrag, dropColumn }\n}\n\nfunction resolveColumns(\n rows: Record<string, unknown>[],\n chartConfig: ChartAxisConfig | undefined,\n queryObject: CubeQuery | undefined\n): string[] {\n const available = Object.keys(rows[0] ?? {})\n const hidden = new Set(chartConfig?.hiddenColumns ?? [])\n\n const configured = chartConfig?.columns?.filter(column => available.includes(column))\n if (configured && configured.length > 0) {\n return configured.filter(column => !hidden.has(column))\n }\n\n // Nothing assigned: follow the order the query asked for, then anything else\n // the rows happen to carry.\n const fromQuery = getOrderedColumnsFromQuery(queryObject).filter(column => available.includes(column))\n const ordered = fromQuery.length > 0\n ? [...fromQuery, ...available.filter(column => !fromQuery.includes(column))]\n : available\n\n return ordered.filter(column => !hidden.has(column))\n}\n\n/**\n * Drag-resizable column widths, remembered per column set.\n *\n * A chart has no portlet id and no write path back to `displayConfig`, so an\n * authored `columnWidths` acts as the default a viewer's own drags layer over —\n * the same arrangement the data browser uses.\n */\nexport function useColumnWidths(params: {\n columns: string[]\n storageKey: string\n authored?: Record<string, number>\n}) {\n const { columns, storageKey, authored } = params\n\n const [columnWidths, setColumnWidths] = useState<Record<string, number>>({})\n const tableRef = useRef<HTMLTableElement>(null)\n const didResizeRef = useRef(false)\n\n useEffect(() => {\n setColumnWidths({ ...(authored ?? {}), ...loadColumnWidths(storageKey) })\n }, [storageKey, authored])\n\n const startResize = useCallback((event: React.MouseEvent, column: string) => {\n event.preventDefault()\n event.stopPropagation()\n\n const startX = event.clientX\n didResizeRef.current = false\n\n // Freeze every column at its rendered width first, so the ones either side\n // of the handle do not jump when this one changes.\n const snapshot: Record<string, number> = {}\n tableRef.current?.querySelectorAll('thead th').forEach((th, index) => {\n const name = columns[index]\n if (name) snapshot[name] = th.getBoundingClientRect().width\n })\n\n const startWidth = snapshot[column] ?? DEFAULT_COLUMN_WIDTH\n let latest = snapshot\n\n const onMove = (moveEvent: MouseEvent) => {\n const diff = moveEvent.clientX - startX\n if (Math.abs(diff) > 2) didResizeRef.current = true\n latest = { ...latest, [column]: Math.max(MIN_COLUMN_WIDTH, startWidth + diff) }\n setColumnWidths(latest)\n }\n\n const onUp = () => {\n document.removeEventListener('mousemove', onMove)\n document.removeEventListener('mouseup', onUp)\n document.body.style.cursor = ''\n document.body.style.userSelect = ''\n saveColumnWidths(storageKey, latest)\n // Held one frame so the header's click handler can see it and not sort.\n requestAnimationFrame(() => { didResizeRef.current = false })\n }\n\n document.addEventListener('mousemove', onMove)\n document.addEventListener('mouseup', onUp)\n document.body.style.cursor = 'col-resize'\n document.body.style.userSelect = 'none'\n }, [columns, storageKey])\n\n const totalWidth = Object.keys(columnWidths).length > 0\n ? columns.reduce((sum, column) => sum + (columnWidths[column] ?? DEFAULT_COLUMN_WIDTH), 0)\n : undefined\n\n return { columnWidths, totalWidth, tableRef, didResizeRef, startResize }\n}\n\n/**\n * Sorting and paging, server-side when the host offers it.\n *\n * With a `pagination` prop the host re-queries, so both must go through it:\n * sorting only the loaded page would put the wrong rows on page 1. Without one\n * — the AnalysisBuilder preview, the notebook, plugin hosts — the table sorts\n * and pages over the rows it already has.\n */\nexport function useRecordsPaging(params: {\n rows: Record<string, unknown>[]\n pagination?: ChartPagination\n authoredPageSize?: number\n}) {\n const { rows, pagination, authoredPageSize } = params\n\n const [localSort, setLocalSort] = useState<SortState | null>(null)\n const [localPage, setLocalPage] = useState(0)\n\n const isServerPaged = Boolean(pagination)\n const sort = pagination ? pagination.sort ?? null : localSort\n\n const toggleSort = useCallback((column: string) => {\n if (pagination) {\n pagination.toggleSort(column)\n return\n }\n setLocalSort(current => {\n if (current?.column !== column) return { column, direction: 'asc' }\n return current.direction === 'asc' ? { column, direction: 'desc' } : null\n })\n }, [pagination])\n\n const sortedRows = useMemo(\n () => (!isServerPaged && localSort ? sortRows(rows, localSort.column, localSort.direction) : rows),\n [rows, isServerPaged, localSort]\n )\n\n const pageSize = pagination?.pageSize ?? authoredPageSize ?? DEFAULT_PAGE_SIZE\n const page = pagination?.page ?? localPage\n // Server-side the count is whatever the server reported; falling back to the\n // loaded page keeps the pager honest before the first total arrives.\n const rowCount = isServerPaged\n ? pagination?.total ?? page * pageSize + sortedRows.length\n : sortedRows.length\n const pageCount = Math.max(1, Math.ceil(rowCount / pageSize))\n\n const goToPage = useCallback((next: number) => {\n if (pagination) pagination.setPage(next)\n else setLocalPage(next)\n }, [pagination])\n\n // A shrinking result set can leave the viewer past the last page.\n useEffect(() => {\n if (isServerPaged) return\n setLocalPage(current => Math.min(current, pageCount - 1))\n }, [pageCount, isServerPaged])\n\n const visibleRows = useMemo(\n () => (isServerPaged ? sortedRows : sortedRows.slice(page * pageSize, page * pageSize + pageSize)),\n [sortedRows, isServerPaged, page, pageSize]\n )\n\n return {\n sort,\n toggleSort,\n visibleRows,\n page,\n pageSize,\n pageCount,\n rowCount,\n goToPage,\n showPager: rowCount > pageSize\n }\n}\n","/**\n * Records Table\n *\n * A record-grain listing (as opposed to DataTable's aggregate view): ordered\n * columns with per-column formats, hidden columns fetched for row context,\n * click-to-sort headers, drag-resizable widths and pagination.\n *\n * Hand-rolled rather than built on a table library: the sort/resize/<colgroup>\n * patterns already exist in `DataBrowser/DataBrowserTable.tsx` (copied, not\n * imported — that component reads the data-browser store), and badge/progress\n * cells are bespoke either way.\n */\n\nimport React, { useCallback, useMemo } from 'react'\nimport { useTranslation } from '../../hooks/useTranslation.js'\nimport { useCubeMeta } from '../../providers/CubeProvider.js'\nimport { ChartEmptyState } from './ChartStates.js'\nimport { getIcon } from '../../icons/index.js'\nimport { buildRowUrl } from '../../utils/rowLinkUtils.js'\nimport { DEFAULT_COLUMN_WIDTH, renderCellValue, type RenderedCell } from '../../utils/recordsTableUtils.js'\nimport { useColumnWidths, useRecordsColumns, useRecordsPaging } from './recordsTableHooks.js'\nimport type { ChartProps, ColumnFormatConfig } from '../../types.js'\n\nconst SortAscIcon = getIcon('chevronUp')\nconst SortDescIcon = getIcon('chevronDown')\n\nconst RecordsTable = React.memo(function RecordsTable({\n data,\n chartConfig,\n displayConfig = {},\n queryObject,\n colorPalette,\n height = 300,\n onDataPointClick,\n drillEnabled,\n pagination\n}: ChartProps) {\n const { t } = useTranslation()\n const { getFieldLabel } = useCubeMeta()\n\n const rows = useMemo(() => (Array.isArray(data) ? (data as Record<string, unknown>[]) : []), [data])\n const columnFormats = displayConfig.columnFormats ?? {}\n\n const {\n columns,\n storageKey,\n draggedColumn,\n didDragRef,\n startColumnDrag,\n endColumnDrag,\n dropColumn\n } = useRecordsColumns({ rows, chartConfig, queryObject })\n\n const { columnWidths, totalWidth, tableRef, didResizeRef, startResize } = useColumnWidths({\n columns,\n storageKey,\n authored: displayConfig.columnWidths\n })\n\n const {\n sort,\n toggleSort,\n visibleRows,\n page,\n pageSize,\n pageCount,\n rowCount,\n goToPage,\n showPager\n } = useRecordsPaging({ rows, pagination, authoredPageSize: displayConfig.pageSize })\n\n const handleHeaderClick = useCallback((column: string) => {\n // Suppressed after a resize or reorder drag, which can still emit a click.\n if (didResizeRef.current || didDragRef.current) return\n toggleSort(column)\n }, [didDragRef, didResizeRef, toggleSort])\n\n // A configured row link wins over drill: the row becomes a real anchor, so\n // modifier-clicks and \"open in new tab\" behave as they do anywhere else.\n const rowLink = displayConfig.rowLink\n const rowClickable = Boolean(!rowLink && drillEnabled && onDataPointClick)\n\n const handleRowClick = useCallback((row: Record<string, unknown>, column: string, event: React.MouseEvent) => {\n if (!rowClickable) return\n onDataPointClick?.({\n dataPoint: row,\n clickedField: column,\n xValue: row[column],\n position: { x: event.clientX, y: event.clientY },\n nativeEvent: event\n })\n }, [onDataPointClick, rowClickable])\n\n if (rows.length === 0) {\n return <ChartEmptyState height={height} hint={t('chart.runtime.noDataHint.table')} />\n }\n\n if (columns.length === 0) {\n return (\n <ChartEmptyState\n height={height}\n titleKey=\"chart.runtime.recordsTable.noColumns\"\n hint={t('chart.runtime.table.invalidStructure')}\n />\n )\n }\n\n const firstRow = page * pageSize + 1\n const lastRow = Math.min(rowCount, page * pageSize + visibleRows.length)\n\n return (\n <div className=\"dc:flex dc:flex-col dc:w-full\" style={{ height }}>\n <div className=\"dc:flex-1 dc:overflow-auto\">\n <table\n ref={tableRef}\n className=\"dc:border-collapse\"\n style={{ tableLayout: 'fixed', width: totalWidth, minWidth: '100%' }}\n >\n <colgroup>\n {columns.map(column => (\n <col key={column} style={{ width: columnWidths[column] ?? DEFAULT_COLUMN_WIDTH }} />\n ))}\n </colgroup>\n <thead className=\"bg-dc-surface-secondary dc:sticky dc:top-0 dc:z-20\">\n <tr>\n {columns.map(column => (\n <HeaderCell\n key={column}\n column={column}\n label={columnFormats[column]?.label || getFieldLabel(column)}\n align={columnFormats[column]?.align ?? defaultAlign(columnFormats[column])}\n sortDirection={sort?.column === column ? sort.direction : undefined}\n isDropTarget={draggedColumn !== null && draggedColumn !== column}\n onClick={handleHeaderClick}\n onResizeStart={startResize}\n onDragStart={startColumnDrag}\n onDragEnd={endColumnDrag}\n onDrop={dropColumn}\n />\n ))}\n </tr>\n </thead>\n <tbody className=\"bg-dc-surface\">\n {visibleRows.map((row, index) => (\n <Row\n key={index}\n row={row}\n columns={columns}\n columnFormats={columnFormats}\n colorPalette={colorPalette}\n href={rowLink ? buildRowUrl(rowLink.urlTemplate, row) : null}\n target={rowLink?.target}\n clickable={rowClickable}\n onCellClick={handleRowClick}\n />\n ))}\n </tbody>\n </table>\n </div>\n\n {showPager && (\n <div className=\"dc:flex dc:items-center dc:justify-between dc:gap-2 dc:px-3 dc:py-1.5 dc:border-t border-dc-border dc:text-xs text-dc-text-secondary\">\n <span>{t('chart.runtime.recordsTable.rowRange', { from: firstRow, to: lastRow, total: rowCount })}</span>\n <div className=\"dc:flex dc:items-center dc:gap-1\">\n <PagerButton\n label={t('chart.runtime.recordsTable.previousPage')}\n disabled={page === 0}\n onClick={() => goToPage(Math.max(0, page - 1))}\n >\n ‹\n </PagerButton>\n <span>{t('chart.runtime.recordsTable.pageOf', { page: page + 1, pages: pageCount })}</span>\n <PagerButton\n label={t('chart.runtime.recordsTable.nextPage')}\n disabled={page >= pageCount - 1}\n onClick={() => goToPage(Math.min(pageCount - 1, page + 1))}\n >\n ›\n </PagerButton>\n </div>\n </div>\n )}\n </div>\n )\n})\n\nexport default RecordsTable\n\n/** Numeric-ish formats read better right-aligned unless the author says otherwise. */\nfunction defaultAlign(format: ColumnFormatConfig | undefined): 'left' | 'right' {\n return format?.kind === 'number' ? 'right' : 'left'\n}\n\ninterface HeaderCellProps {\n column: string\n label: string\n align: 'left' | 'right'\n sortDirection?: 'asc' | 'desc'\n isDropTarget: boolean\n onClick: (column: string) => void\n onResizeStart: (event: React.MouseEvent, column: string) => void\n onDragStart: (column: string) => void\n onDragEnd: () => void\n onDrop: (column: string) => void\n}\n\nfunction HeaderCell({\n column,\n label,\n align,\n sortDirection,\n isDropTarget,\n onClick,\n onResizeStart,\n onDragStart,\n onDragEnd,\n onDrop\n}: HeaderCellProps) {\n return (\n <th\n draggable\n onDragStart={(event) => {\n // Required for Firefox to start the drag at all.\n event.dataTransfer.setData('text/plain', column)\n event.dataTransfer.effectAllowed = 'move'\n onDragStart(column)\n }}\n onDragOver={(event) => { if (isDropTarget) event.preventDefault() }}\n onDrop={(event) => { event.preventDefault(); onDrop(column) }}\n onDragEnd={onDragEnd}\n onClick={() => onClick(column)}\n className={`dc:relative dc:px-3 dc:py-2 dc:text-xs dc:font-medium text-dc-text-muted dc:uppercase dc:tracking-wider dc:cursor-pointer dc:select-none dc:border-b border-dc-border ${\n align === 'right' ? 'dc:text-right' : 'dc:text-left'\n }${isDropTarget ? ' dc:border-l-2 border-dc-accent' : ''}`}\n >\n <div className={`dc:flex dc:items-center dc:gap-1.5 dc:overflow-hidden${align === 'right' ? ' dc:justify-end' : ''}`}>\n <span className=\"dc:truncate\">{label}</span>\n {sortDirection === 'asc' && <SortAscIcon className=\"dc:w-3 dc:h-3 text-dc-accent dc:shrink-0\" />}\n {sortDirection === 'desc' && <SortDescIcon className=\"dc:w-3 dc:h-3 text-dc-accent dc:shrink-0\" />}\n </div>\n <div\n onMouseDown={(event) => onResizeStart(event, column)}\n className=\"dc:absolute dc:top-0 dc:right-0 dc:w-1.5 dc:h-full dc:cursor-col-resize dc:hover:bg-dc-accent dc:opacity-0 dc:hover:opacity-100 dc:transition-opacity dc:z-30\"\n />\n </th>\n )\n}\n\ninterface RowProps {\n row: Record<string, unknown>\n columns: string[]\n columnFormats: Record<string, ColumnFormatConfig>\n colorPalette?: ChartProps['colorPalette']\n href: string | null\n target?: 'self' | 'blank'\n clickable: boolean\n onCellClick: (row: Record<string, unknown>, column: string, event: React.MouseEvent) => void\n}\n\nfunction Row({ row, columns, columnFormats, colorPalette, href, target, clickable, onCellClick }: RowProps) {\n return (\n <tr className={`dc:border-b border-dc-border hover:bg-dc-surface-secondary${clickable ? ' dc:cursor-pointer' : ''}`}>\n {columns.map(column => {\n const format = columnFormats[column]\n const align = format?.align ?? defaultAlign(format)\n return (\n <td\n key={column}\n onClick={(event) => onCellClick(row, column, event)}\n className={`dc:px-3 dc:py-1.5 dc:text-sm text-dc-text dc:overflow-hidden${\n align === 'right' ? ' dc:text-right dc:tabular-nums' : ''\n }`}\n >\n <CellContent\n cell={renderCellValue(row[column], format)}\n colorPalette={colorPalette}\n href={href}\n target={target}\n />\n </td>\n )\n })}\n </tr>\n )\n}\n\n/**\n * A cell, wrapped in the row's link when one resolves. An unresolvable or\n * unsafe template yields no anchor rather than a dead one.\n */\nfunction CellContent({\n cell,\n colorPalette,\n href,\n target\n}: {\n cell: RenderedCell\n colorPalette?: ChartProps['colorPalette']\n href: string | null\n target?: 'self' | 'blank'\n}) {\n const content = <Cell cell={cell} colorPalette={colorPalette} />\n if (!href) return content\n\n return (\n <a\n href={href}\n target={target === 'blank' ? '_blank' : undefined}\n rel={target === 'blank' ? 'noopener noreferrer' : undefined}\n className=\"dc:block dc:no-underline dc:text-inherit hover:dc:underline\"\n >\n {content}\n </a>\n )\n}\n\nfunction Cell({ cell, colorPalette }: { cell: RenderedCell; colorPalette?: ChartProps['colorPalette'] }) {\n if (cell.kind === 'badge') {\n const color = cell.colorIndex !== undefined ? colorPalette?.colors[cell.colorIndex] : undefined\n return (\n <span\n className=\"dc:inline-block dc:px-2 dc:py-0.5 dc:rounded-full dc:text-xs dc:font-medium dc:truncate dc:max-w-full\"\n style={color\n ? { backgroundColor: `${color}22`, color, border: `1px solid ${color}55` }\n : { backgroundColor: 'var(--dc-surface-secondary)', color: 'var(--dc-text-secondary)', border: '1px solid var(--dc-border)' }}\n >\n {cell.text}\n </span>\n )\n }\n\n if (cell.kind === 'progress') {\n return (\n <div className=\"dc:flex dc:items-center dc:gap-2\">\n {cell.style === 'circle'\n ? <ProgressRing fraction={cell.fraction} label={cell.text} />\n : (\n // Outlined track, so the full 0-100% extent is visible and a\n // near-complete bar is distinguishable from a complete one.\n <div className=\"dc:flex-1 dc:h-2 dc:rounded-full dc:overflow-hidden dc:border border-dc-border bg-dc-surface-secondary dc:min-w-[2rem]\">\n <div\n className=\"dc:h-full dc:rounded-full bg-dc-primary\"\n style={{ width: `${cell.fraction * 100}%` }}\n />\n </div>\n )}\n {/*\n Fixed width, not shrink-to-fit: the track is flexible, so a wider\n label (\"£117K\") would shorten its row's bar and two equal values\n would draw different lengths. Right-aligned and tabular so the\n numbers line up as a column.\n */}\n <span className=\"dc:w-16 dc:shrink-0 dc:text-right dc:text-xs dc:tabular-nums dc:truncate\">\n {cell.text}\n </span>\n </div>\n )\n }\n\n return <span className=\"dc:truncate dc:block\">{cell.text}</span>\n}\n\nconst RING_SIZE = 18\nconst RING_STROKE = 3\nconst RING_CENTRE = RING_SIZE / 2\nconst RING_RADIUS = RING_CENTRE - RING_STROKE / 2\n\n/**\n * The compact alternative to the progress bar: a ring that fills 0→100%, so a\n * progress column can be as narrow as its value label.\n *\n * `pathLength` normalises the circumference to 100, which lets the dash offset\n * be the percentage remaining without any 2πr arithmetic. The ring alone\n * carries the value, hence the `img` role and label.\n */\nfunction ProgressRing({ fraction, label }: { fraction: number; label: string }) {\n return (\n <svg\n role=\"img\"\n aria-label={label}\n width={RING_SIZE}\n height={RING_SIZE}\n viewBox={`0 0 ${RING_SIZE} ${RING_SIZE}`}\n className=\"dc:shrink-0\"\n >\n <circle\n cx={RING_CENTRE}\n cy={RING_CENTRE}\n r={RING_RADIUS}\n fill=\"none\"\n stroke=\"var(--dc-surface-secondary)\"\n strokeWidth={RING_STROKE}\n />\n {fraction > 0 && (\n <circle\n cx={RING_CENTRE}\n cy={RING_CENTRE}\n r={RING_RADIUS}\n fill=\"none\"\n stroke=\"var(--dc-primary)\"\n strokeWidth={RING_STROKE}\n strokeLinecap=\"round\"\n pathLength={100}\n strokeDasharray={100}\n strokeDashoffset={100 - fraction * 100}\n transform={`rotate(-90 ${RING_CENTRE} ${RING_CENTRE})`}\n />\n )}\n </svg>\n )\n}\n\nfunction PagerButton({\n label,\n disabled,\n onClick,\n children\n}: {\n label: string\n disabled: boolean\n onClick: () => void\n children: React.ReactNode\n}) {\n return (\n <button\n type=\"button\"\n aria-label={label}\n title={label}\n disabled={disabled}\n onClick={onClick}\n className=\"dc:px-2 dc:py-0.5 dc:border border-dc-border dc:rounded-sm bg-dc-surface text-dc-text hover:bg-dc-surface-secondary dc:disabled:opacity-40 dc:disabled:cursor-not-allowed\"\n >\n {children}\n </button>\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"RecordsTable-kQfZ25Pz.js","names":[],"sources":["../../../src/client/utils/rowLinkUtils.ts","../../../src/client/utils/recordsTableUtils.ts","../../../src/client/components/charts/recordsTableHooks.ts","../../../src/client/components/charts/RecordsTable.tsx"],"sourcesContent":["/**\n * Row click-through URLs for the records table.\n *\n * A dashboard author writes a template such as `/employees/{Employees.id}`,\n * and each row fills its own tokens. The template is author-controlled but the\n * *values* are data, so they are percent-encoded before substitution — an\n * encoded value cannot introduce a scheme, a path segment or a query separator.\n *\n * The resulting URL is then checked rather than trusted: only same-origin\n * relative paths and absolute http(s) URLs are allowed through.\n */\n\nconst TOKEN_PATTERN = /\\{([A-Za-z0-9_]+\\.[A-Za-z0-9_]+)\\}/g\n\n/** Schemes that must never survive validation, even spelled oddly. */\nconst ALLOWED_PROTOCOLS = new Set(['http:', 'https:'])\n\n/**\n * Resolve a row link template against one row.\n *\n * Returns `null` — meaning \"render no link\" — when the template references a\n * field the row does not carry, or when the result is not a URL we are willing\n * to navigate to. Never throws: a malformed template must not take the table\n * down with it.\n *\n * @param template - e.g. `/employees/{Employees.id}?tab=profile`\n * @param row - the row's values, including hidden columns\n * @param origin - the origin relative URLs are resolved against\n */\nexport function buildRowUrl(\n template: string | undefined,\n row: Record<string, unknown>,\n origin: string = defaultOrigin()\n): string | null {\n if (!template) return null\n\n let unresolved = false\n const substituted = template.replace(TOKEN_PATTERN, (_match, field: string) => {\n const value = row[field]\n if (value === null || value === undefined) {\n unresolved = true\n return ''\n }\n return encodeURIComponent(String(value))\n })\n\n if (unresolved) return null\n\n return isSafeUrl(substituted, origin) ? substituted : null\n}\n\n/**\n * Whether a URL is one we will navigate to: an absolute `http(s)` URL, or a\n * path that stays on this origin.\n *\n * Rejected, deliberately: `javascript:`, `data:`, `vbscript:` and every other\n * scheme; protocol-relative `//evil.example`, which inherits the current scheme\n * and silently leaves the origin; and backslash forms such as `\\\\evil.example`\n * or `/\\evil.example`, which several browsers normalise into that same\n * protocol-relative shape.\n */\nexport function isSafeUrl(url: string, origin: string = defaultOrigin()): boolean {\n const trimmed = url.trim()\n if (trimmed === '') return false\n\n // Control characters, including the tab/newline a browser strips out of a\n // scheme — which turns `java<TAB>script:` back into `javascript:`.\n // eslint-disable-next-line no-control-regex\n if (/[\\u0000-\\u001F\\u007F]/.test(trimmed)) return false\n\n if (trimmed.includes('\\\\')) return false\n if (trimmed.startsWith('//')) return false\n\n try {\n const parsed = new URL(trimmed, origin)\n if (!ALLOWED_PROTOCOLS.has(parsed.protocol)) return false\n\n // A relative path is only allowed while it stays here. An absolute http(s)\n // URL to another host is fine — that is the \"external link\" case — so the\n // origin check applies to inputs that did not declare a scheme.\n const declaresScheme = /^[A-Za-z][A-Za-z0-9+.-]*:/.test(trimmed)\n if (!declaresScheme && parsed.origin !== new URL(origin).origin) return false\n\n return true\n } catch {\n return false\n }\n}\n\n/**\n * The origin to resolve relative links against. Falls back to a placeholder\n * outside a browser (SSR, tests) so validation still runs rather than throwing.\n */\nfunction defaultOrigin(): string {\n return typeof window !== 'undefined' && window.location?.origin\n ? window.location.origin\n : 'http://localhost'\n}\n","/**\n * Cell formatting and column-width persistence for the records table.\n *\n * Kept out of the component so each piece is unit-testable without a DOM, and\n * so the format switch stays a pure value → display transform.\n */\n\nimport type { ColumnFormatConfig, ColumnFormatKind } from '../types.js'\nimport { formatAxisValue, formatTimeValue } from './chartUtils.js'\n\n/** Fallback when an author has not sized a column. */\nexport const DEFAULT_COLUMN_WIDTH = 160\n/** Narrower than this and the resize handle overlaps the header text. */\nexport const MIN_COLUMN_WIDTH = 60\n\nconst COLUMN_WIDTHS_STORAGE_KEY = 'dc-records-table-column-widths'\nconst COLUMN_ORDER_STORAGE_KEY = 'dc-records-table-column-order'\n\n/**\n * Identity under which a viewer's column widths are remembered.\n *\n * A chart component has no portlet id, so widths key on the columns themselves:\n * two records tables showing the same fields share widths, which is the\n * behaviour an author would expect from two views of the same records.\n */\nexport function columnWidthStorageKey(columns: string[]): string {\n return [...columns].sort().join('|')\n}\n\nexport function loadColumnWidths(key: string): Record<string, number> {\n try {\n const raw = localStorage.getItem(COLUMN_WIDTHS_STORAGE_KEY)\n if (!raw) return {}\n return JSON.parse(raw)[key] ?? {}\n } catch {\n return {}\n }\n}\n\nexport function saveColumnWidths(key: string, widths: Record<string, number>): void {\n try {\n const raw = localStorage.getItem(COLUMN_WIDTHS_STORAGE_KEY)\n const all = raw ? JSON.parse(raw) : {}\n all[key] = widths\n localStorage.setItem(COLUMN_WIDTHS_STORAGE_KEY, JSON.stringify(all))\n } catch {\n // localStorage unavailable (private mode, SSR) — widths stay session-local\n }\n}\n\n/**\n * A viewer's own column order, remembered under the same identity as widths.\n *\n * Stored as a list rather than a map so it survives the author adding or\n * removing a column: {@link applyColumnOrder} reconciles it against the\n * columns that actually exist.\n */\nexport function loadColumnOrder(key: string): string[] {\n try {\n const raw = localStorage.getItem(COLUMN_ORDER_STORAGE_KEY)\n if (!raw) return []\n const stored = JSON.parse(raw)[key]\n return Array.isArray(stored) ? stored : []\n } catch {\n return []\n }\n}\n\nexport function saveColumnOrder(key: string, order: string[]): void {\n try {\n const raw = localStorage.getItem(COLUMN_ORDER_STORAGE_KEY)\n const all = raw ? JSON.parse(raw) : {}\n all[key] = order\n localStorage.setItem(COLUMN_ORDER_STORAGE_KEY, JSON.stringify(all))\n } catch {\n // localStorage unavailable — the order stays session-local\n }\n}\n\n/**\n * Lay a remembered order over the columns the chart actually has.\n *\n * Columns the order does not mention keep their authored position relative to\n * the end, so an author adding a column still sees it rather than having it\n * silently swallowed by a stale local order.\n */\nexport function applyColumnOrder(columns: string[], order: string[]): string[] {\n if (order.length === 0) return columns\n const present = new Set(columns)\n const ordered = order.filter(column => present.has(column))\n const remaining = columns.filter(column => !ordered.includes(column))\n return [...ordered, ...remaining]\n}\n\n/** Move one column to another's position, returning a new order. */\nexport function moveColumn(columns: string[], from: string, to: string): string[] {\n const fromIndex = columns.indexOf(from)\n const toIndex = columns.indexOf(to)\n if (fromIndex === -1 || toIndex === -1 || fromIndex === toIndex) return columns\n\n const next = [...columns]\n next.splice(fromIndex, 1)\n next.splice(toIndex, 0, from)\n return next\n}\n\n/**\n * A cell resolved to something renderable. `progress` carries its own shape\n * because the component draws a bar or a ring rather than a string; every\n * other kind reduces to text plus, for badges, a colour.\n *\n * The component only ever sees a `RenderedCell`, so the progress `style` is\n * resolved here rather than being read back out of the column's config.\n */\nexport type RenderedCell =\n | { kind: 'text'; text: string }\n | { kind: 'badge'; text: string; colorIndex?: number }\n | { kind: 'progress'; text: string; fraction: number; style: 'bar' | 'circle' }\n\nconst EMPTY: RenderedCell = { kind: 'text', text: '' }\n\n/**\n * Turn a raw cell value into its display form under the column's format.\n *\n * Every kind degrades to text rather than erroring: a non-numeric value under\n * `number`/`progress` renders as-is (EAV columns legitimately contain 'n/a'),\n * and a badge value with no colour mapping renders neutral rather than being\n * given a guessed colour.\n */\nexport function renderCellValue(\n value: unknown,\n format: ColumnFormatConfig | undefined\n): RenderedCell {\n if (isEmpty(value)) return EMPTY\n const render = RENDERERS[format?.kind ?? 'text'] ?? renderText\n return render(value, format)\n}\n\ntype CellRenderer = (value: unknown, format: ColumnFormatConfig | undefined) => RenderedCell\n\nconst RENDERERS: Record<ColumnFormatKind, CellRenderer> = {\n text: renderText,\n number: renderNumber,\n date: renderDate,\n badge: renderBadge,\n progress: renderProgress\n}\n\nfunction renderText(value: unknown): RenderedCell {\n return { kind: 'text', text: stringify(value) }\n}\n\nfunction renderNumber(value: unknown, format: ColumnFormatConfig | undefined): RenderedCell {\n const num = toNumber(value)\n if (num === null) return { kind: 'text', text: String(value) }\n return { kind: 'text', text: formatAxisValue(num, format?.numberFormat ?? {}) }\n}\n\nfunction renderDate(value: unknown, format: ColumnFormatConfig | undefined): RenderedCell {\n return { kind: 'text', text: formatTimeValue(value, format?.dateGranularity) }\n}\n\n/**\n * Colour mappings that are actually usable.\n *\n * A `columnFormats` entry can arrive from an agent tool call or a hand-edited\n * portlet, where `badgeColors` may not be the array of `{ value, colorIndex }`\n * the type promises — an object map and colour names instead of palette indices\n * are the common shapes. Anything unusable is discarded so the badge renders\n * neutral, which is what an unmapped value does anyway.\n */\nfunction usableBadgeColors(format: ColumnFormatConfig | undefined): ColumnFormatConfig['badgeColors'] {\n if (!Array.isArray(format?.badgeColors)) return []\n return format.badgeColors.filter(entry => entry && typeof entry.colorIndex === 'number')\n}\n\nfunction renderBadge(value: unknown, format: ColumnFormatConfig | undefined): RenderedCell {\n const text = stringify(value)\n return { kind: 'badge', text, colorIndex: usableBadgeColors(format)?.find(e => e.value === text)?.colorIndex }\n}\n\nconst PROGRESS_NUMBER_FORMAT = { unit: 'number', abbreviate: false, decimals: 0 } as const\n\nfunction renderProgress(value: unknown, format: ColumnFormatConfig | undefined): RenderedCell {\n const num = toNumber(value)\n if (num === null) return { kind: 'text', text: String(value) }\n\n return {\n kind: 'progress',\n fraction: progressFraction(num, format),\n style: format?.progressStyle ?? 'bar',\n text: formatAxisValue(num, format?.numberFormat ?? PROGRESS_NUMBER_FORMAT)\n }\n}\n\n/** Where a value sits within the column's bounds, as 0-1. */\nfunction progressFraction(value: number, format: ColumnFormatConfig | undefined): number {\n const min = format?.progressMin ?? 0\n const max = format?.progressMax ?? 100\n const span = max - min\n // A zero-width range would divide by zero; treat it as \"full\".\n return span === 0 ? 1 : clamp((value - min) / span, 0, 1)\n}\n\nfunction stringify(value: unknown): string {\n if (typeof value === 'boolean') return value ? 'true' : 'false'\n if (typeof value === 'number') return value.toLocaleString()\n if (value instanceof Date) return value.toISOString()\n return String(value)\n}\n\nfunction toNumber(value: unknown): number | null {\n if (typeof value === 'number') return Number.isFinite(value) ? value : null\n if (typeof value === 'string' && value.trim() !== '') {\n const parsed = Number(value)\n return Number.isFinite(parsed) ? parsed : null\n }\n return null\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, value))\n}\n\n/**\n * Sort rows by one column, client-side.\n *\n * Only used when the host provides no server-side pagination — with paging on,\n * sorting the loaded page alone would put the wrong rows on page 1.\n */\nexport function sortRows(\n rows: Record<string, unknown>[],\n column: string,\n direction: 'asc' | 'desc'\n): Record<string, unknown>[] {\n const factor = direction === 'asc' ? 1 : -1\n return [...rows].sort((a, b) => {\n // Empty values sort last in *both* directions — reversing the sort should\n // not push a column's blanks to the top of the table.\n const aEmpty = isEmpty(a[column])\n const bEmpty = isEmpty(b[column])\n if (aEmpty || bEmpty) return aEmpty && bEmpty ? 0 : aEmpty ? 1 : -1\n return factor * compareValues(a[column], b[column])\n })\n}\n\nfunction isEmpty(value: unknown): boolean {\n return value === null || value === undefined || value === ''\n}\n\nfunction compareValues(a: unknown, b: unknown): number {\n const aNum = toNumber(a)\n const bNum = toNumber(b)\n if (aNum !== null && bNum !== null) return aNum - bNum\n\n return String(a).localeCompare(String(b))\n}\n","/**\n * State for the records table, split out of the component so each concern —\n * which columns and in what order, how wide they are, which page and sort —\n * reads on its own.\n *\n * All three are viewer-side. The *author's* choices (assigned columns, page\n * size, widths) arrive through `chartConfig`/`displayConfig`; these hooks layer\n * the viewer's own adjustments over them.\n */\n\nimport React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { getOrderedColumnsFromQuery } from '../../utils/pivotUtils.js'\nimport {\n DEFAULT_COLUMN_WIDTH,\n MIN_COLUMN_WIDTH,\n applyColumnOrder,\n columnWidthStorageKey,\n loadColumnOrder,\n loadColumnWidths,\n moveColumn,\n saveColumnOrder,\n saveColumnWidths,\n sortRows\n} from '../../utils/recordsTableUtils.js'\nimport type { ChartAxisConfig, ChartPagination, CubeQuery } from '../../types.js'\n\nexport const DEFAULT_PAGE_SIZE = 25\n\ntype SortState = { column: string; direction: 'asc' | 'desc' }\n\n/**\n * Which columns to render, in which order.\n *\n * The author's arrangement comes from the Columns drop zone; a viewer can drag\n * a header on top of it, and that order is remembered per column set. It is\n * *reconciled* rather than substituted, so a column the author adds later still\n * appears instead of being swallowed by a stale local order.\n */\nexport function useRecordsColumns(params: {\n rows: Record<string, unknown>[]\n chartConfig?: ChartAxisConfig\n queryObject?: CubeQuery\n}) {\n const { rows, chartConfig, queryObject } = params\n\n const baseColumns = useMemo(\n () => resolveColumns(rows, chartConfig, queryObject),\n [rows, chartConfig, queryObject]\n )\n\n const storageKey = useMemo(() => columnWidthStorageKey(baseColumns), [baseColumns])\n const [columnOrder, setColumnOrder] = useState<string[]>([])\n\n useEffect(() => {\n setColumnOrder(loadColumnOrder(storageKey))\n }, [storageKey])\n\n const columns = useMemo(\n () => applyColumnOrder(baseColumns, columnOrder),\n [baseColumns, columnOrder]\n )\n\n const [draggedColumn, setDraggedColumn] = useState<string | null>(null)\n // A drag ending on the header it started from can still produce a click;\n // without this, reordering would also toggle the sort.\n const didDragRef = useRef(false)\n\n const startColumnDrag = useCallback((column: string) => {\n didDragRef.current = true\n setDraggedColumn(column)\n }, [])\n\n const endColumnDrag = useCallback(() => {\n setDraggedColumn(null)\n requestAnimationFrame(() => { didDragRef.current = false })\n }, [])\n\n const dropColumn = useCallback((target: string) => {\n const source = draggedColumn\n endColumnDrag()\n if (!source || source === target) return\n const next = moveColumn(columns, source, target)\n setColumnOrder(next)\n saveColumnOrder(storageKey, next)\n }, [columns, draggedColumn, endColumnDrag, storageKey])\n\n return { columns, storageKey, draggedColumn, didDragRef, startColumnDrag, endColumnDrag, dropColumn }\n}\n\nfunction resolveColumns(\n rows: Record<string, unknown>[],\n chartConfig: ChartAxisConfig | undefined,\n queryObject: CubeQuery | undefined\n): string[] {\n const available = Object.keys(rows[0] ?? {})\n const hidden = new Set(chartConfig?.hiddenColumns ?? [])\n\n const configured = chartConfig?.columns?.filter(column => available.includes(column))\n if (configured && configured.length > 0) {\n return configured.filter(column => !hidden.has(column))\n }\n\n // Nothing assigned: follow the order the query asked for, then anything else\n // the rows happen to carry.\n const fromQuery = getOrderedColumnsFromQuery(queryObject).filter(column => available.includes(column))\n const ordered = fromQuery.length > 0\n ? [...fromQuery, ...available.filter(column => !fromQuery.includes(column))]\n : available\n\n return ordered.filter(column => !hidden.has(column))\n}\n\n/**\n * Drag-resizable column widths, remembered per column set.\n *\n * A chart has no portlet id and no write path back to `displayConfig`, so an\n * authored `columnWidths` acts as the default a viewer's own drags layer over —\n * the same arrangement the data browser uses.\n */\nexport function useColumnWidths(params: {\n columns: string[]\n storageKey: string\n authored?: Record<string, number>\n}) {\n const { columns, storageKey, authored } = params\n\n const [columnWidths, setColumnWidths] = useState<Record<string, number>>({})\n const tableRef = useRef<HTMLTableElement>(null)\n const didResizeRef = useRef(false)\n\n useEffect(() => {\n setColumnWidths({ ...(authored ?? {}), ...loadColumnWidths(storageKey) })\n }, [storageKey, authored])\n\n const startResize = useCallback((event: React.MouseEvent, column: string) => {\n event.preventDefault()\n event.stopPropagation()\n\n const startX = event.clientX\n didResizeRef.current = false\n\n // Freeze every column at its rendered width first, so the ones either side\n // of the handle do not jump when this one changes.\n const snapshot: Record<string, number> = {}\n tableRef.current?.querySelectorAll('thead th').forEach((th, index) => {\n const name = columns[index]\n if (name) snapshot[name] = th.getBoundingClientRect().width\n })\n\n const startWidth = snapshot[column] ?? DEFAULT_COLUMN_WIDTH\n let latest = snapshot\n\n const onMove = (moveEvent: MouseEvent) => {\n const diff = moveEvent.clientX - startX\n if (Math.abs(diff) > 2) didResizeRef.current = true\n latest = { ...latest, [column]: Math.max(MIN_COLUMN_WIDTH, startWidth + diff) }\n setColumnWidths(latest)\n }\n\n const onUp = () => {\n document.removeEventListener('mousemove', onMove)\n document.removeEventListener('mouseup', onUp)\n document.body.style.cursor = ''\n document.body.style.userSelect = ''\n saveColumnWidths(storageKey, latest)\n // Held one frame so the header's click handler can see it and not sort.\n requestAnimationFrame(() => { didResizeRef.current = false })\n }\n\n document.addEventListener('mousemove', onMove)\n document.addEventListener('mouseup', onUp)\n document.body.style.cursor = 'col-resize'\n document.body.style.userSelect = 'none'\n }, [columns, storageKey])\n\n const totalWidth = Object.keys(columnWidths).length > 0\n ? columns.reduce((sum, column) => sum + (columnWidths[column] ?? DEFAULT_COLUMN_WIDTH), 0)\n : undefined\n\n return { columnWidths, totalWidth, tableRef, didResizeRef, startResize }\n}\n\n/**\n * Sorting and paging, server-side when the host offers it.\n *\n * With a `pagination` prop the host re-queries, so both must go through it:\n * sorting only the loaded page would put the wrong rows on page 1. Without one\n * — the AnalysisBuilder preview, the notebook, plugin hosts — the table sorts\n * and pages over the rows it already has.\n */\nexport function useRecordsPaging(params: {\n rows: Record<string, unknown>[]\n pagination?: ChartPagination\n authoredPageSize?: number\n}) {\n const { rows, pagination, authoredPageSize } = params\n\n const [localSort, setLocalSort] = useState<SortState | null>(null)\n const [localPage, setLocalPage] = useState(0)\n\n const isServerPaged = Boolean(pagination)\n const sort = pagination ? pagination.sort ?? null : localSort\n\n const toggleSort = useCallback((column: string) => {\n if (pagination) {\n pagination.toggleSort(column)\n return\n }\n setLocalSort(current => {\n if (current?.column !== column) return { column, direction: 'asc' }\n return current.direction === 'asc' ? { column, direction: 'desc' } : null\n })\n }, [pagination])\n\n const sortedRows = useMemo(\n () => (!isServerPaged && localSort ? sortRows(rows, localSort.column, localSort.direction) : rows),\n [rows, isServerPaged, localSort]\n )\n\n const pageSize = pagination?.pageSize ?? authoredPageSize ?? DEFAULT_PAGE_SIZE\n const page = pagination?.page ?? localPage\n // Server-side the count is whatever the server reported; falling back to the\n // loaded page keeps the pager honest before the first total arrives.\n const rowCount = isServerPaged\n ? pagination?.total ?? page * pageSize + sortedRows.length\n : sortedRows.length\n const pageCount = Math.max(1, Math.ceil(rowCount / pageSize))\n\n const goToPage = useCallback((next: number) => {\n if (pagination) pagination.setPage(next)\n else setLocalPage(next)\n }, [pagination])\n\n // A shrinking result set can leave the viewer past the last page.\n useEffect(() => {\n if (isServerPaged) return\n setLocalPage(current => Math.min(current, pageCount - 1))\n }, [pageCount, isServerPaged])\n\n const visibleRows = useMemo(\n () => (isServerPaged ? sortedRows : sortedRows.slice(page * pageSize, page * pageSize + pageSize)),\n [sortedRows, isServerPaged, page, pageSize]\n )\n\n return {\n sort,\n toggleSort,\n visibleRows,\n page,\n pageSize,\n pageCount,\n rowCount,\n goToPage,\n showPager: rowCount > pageSize\n }\n}\n","/**\n * Records Table\n *\n * A record-grain listing (as opposed to DataTable's aggregate view): ordered\n * columns with per-column formats, hidden columns fetched for row context,\n * click-to-sort headers, drag-resizable widths and pagination.\n *\n * Hand-rolled rather than built on a table library: the sort/resize/<colgroup>\n * patterns already exist in `DataBrowser/DataBrowserTable.tsx` (copied, not\n * imported — that component reads the data-browser store), and badge/progress\n * cells are bespoke either way.\n */\n\nimport React, { useCallback, useMemo } from 'react'\nimport { useTranslation } from '../../hooks/useTranslation.js'\nimport { useCubeMeta } from '../../providers/CubeProvider.js'\nimport { ChartEmptyState } from './ChartStates.js'\nimport { getIcon } from '../../icons/index.js'\nimport { buildRowUrl } from '../../utils/rowLinkUtils.js'\nimport { DEFAULT_COLUMN_WIDTH, renderCellValue, type RenderedCell } from '../../utils/recordsTableUtils.js'\nimport { useColumnWidths, useRecordsColumns, useRecordsPaging } from './recordsTableHooks.js'\nimport type { ChartProps, ColumnFormatConfig } from '../../types.js'\n\nconst SortAscIcon = getIcon('chevronUp')\nconst SortDescIcon = getIcon('chevronDown')\n\nconst RecordsTable = React.memo(function RecordsTable({\n data,\n chartConfig,\n displayConfig = {},\n queryObject,\n colorPalette,\n height = 300,\n onDataPointClick,\n drillEnabled,\n pagination\n}: ChartProps) {\n const { t } = useTranslation()\n const { getFieldLabel } = useCubeMeta()\n\n const rows = useMemo(() => (Array.isArray(data) ? (data as Record<string, unknown>[]) : []), [data])\n const columnFormats = displayConfig.columnFormats ?? {}\n\n const {\n columns,\n storageKey,\n draggedColumn,\n didDragRef,\n startColumnDrag,\n endColumnDrag,\n dropColumn\n } = useRecordsColumns({ rows, chartConfig, queryObject })\n\n const { columnWidths, totalWidth, tableRef, didResizeRef, startResize } = useColumnWidths({\n columns,\n storageKey,\n authored: displayConfig.columnWidths\n })\n\n const {\n sort,\n toggleSort,\n visibleRows,\n page,\n pageSize,\n pageCount,\n rowCount,\n goToPage,\n showPager\n } = useRecordsPaging({ rows, pagination, authoredPageSize: displayConfig.pageSize })\n\n const handleHeaderClick = useCallback((column: string) => {\n // Suppressed after a resize or reorder drag, which can still emit a click.\n if (didResizeRef.current || didDragRef.current) return\n toggleSort(column)\n }, [didDragRef, didResizeRef, toggleSort])\n\n // A configured row link wins over drill: the row becomes a real anchor, so\n // modifier-clicks and \"open in new tab\" behave as they do anywhere else.\n const rowLink = displayConfig.rowLink\n const rowClickable = Boolean(!rowLink && drillEnabled && onDataPointClick)\n\n const handleRowClick = useCallback((row: Record<string, unknown>, column: string, event: React.MouseEvent) => {\n if (!rowClickable) return\n onDataPointClick?.({\n dataPoint: row,\n clickedField: column,\n xValue: row[column],\n position: { x: event.clientX, y: event.clientY },\n nativeEvent: event\n })\n }, [onDataPointClick, rowClickable])\n\n if (rows.length === 0) {\n return <ChartEmptyState height={height} hint={t('chart.runtime.noDataHint.table')} />\n }\n\n if (columns.length === 0) {\n return (\n <ChartEmptyState\n height={height}\n titleKey=\"chart.runtime.recordsTable.noColumns\"\n hint={t('chart.runtime.table.invalidStructure')}\n />\n )\n }\n\n const firstRow = page * pageSize + 1\n const lastRow = Math.min(rowCount, page * pageSize + visibleRows.length)\n\n return (\n <div className=\"dc:flex dc:flex-col dc:w-full\" style={{ height }}>\n <div className=\"dc:flex-1 dc:overflow-auto\">\n <table\n ref={tableRef}\n className=\"dc:border-collapse\"\n style={{ tableLayout: 'fixed', width: totalWidth, minWidth: '100%' }}\n >\n <colgroup>\n {columns.map(column => (\n <col key={column} style={{ width: columnWidths[column] ?? DEFAULT_COLUMN_WIDTH }} />\n ))}\n </colgroup>\n <thead className=\"bg-dc-surface-secondary dc:sticky dc:top-0 dc:z-20\">\n <tr>\n {columns.map(column => (\n <HeaderCell\n key={column}\n column={column}\n label={columnFormats[column]?.label || getFieldLabel(column)}\n align={columnFormats[column]?.align ?? defaultAlign(columnFormats[column])}\n sortDirection={sort?.column === column ? sort.direction : undefined}\n isDropTarget={draggedColumn !== null && draggedColumn !== column}\n onClick={handleHeaderClick}\n onResizeStart={startResize}\n onDragStart={startColumnDrag}\n onDragEnd={endColumnDrag}\n onDrop={dropColumn}\n />\n ))}\n </tr>\n </thead>\n <tbody className=\"bg-dc-surface\">\n {visibleRows.map((row, index) => (\n <Row\n key={index}\n row={row}\n columns={columns}\n columnFormats={columnFormats}\n colorPalette={colorPalette}\n href={rowLink ? buildRowUrl(rowLink.urlTemplate, row) : null}\n target={rowLink?.target}\n clickable={rowClickable}\n onCellClick={handleRowClick}\n />\n ))}\n </tbody>\n </table>\n </div>\n\n {showPager && (\n <div className=\"dc:flex dc:items-center dc:justify-between dc:gap-2 dc:px-3 dc:py-1.5 dc:border-t border-dc-border dc:text-xs text-dc-text-secondary\">\n <span>{t('chart.runtime.recordsTable.rowRange', { from: firstRow, to: lastRow, total: rowCount })}</span>\n <div className=\"dc:flex dc:items-center dc:gap-1\">\n <PagerButton\n label={t('chart.runtime.recordsTable.previousPage')}\n disabled={page === 0}\n onClick={() => goToPage(Math.max(0, page - 1))}\n >\n ‹\n </PagerButton>\n <span>{t('chart.runtime.recordsTable.pageOf', { page: page + 1, pages: pageCount })}</span>\n <PagerButton\n label={t('chart.runtime.recordsTable.nextPage')}\n disabled={page >= pageCount - 1}\n onClick={() => goToPage(Math.min(pageCount - 1, page + 1))}\n >\n ›\n </PagerButton>\n </div>\n </div>\n )}\n </div>\n )\n})\n\nexport default RecordsTable\n\n/** Numeric-ish formats read better right-aligned unless the author says otherwise. */\nfunction defaultAlign(format: ColumnFormatConfig | undefined): 'left' | 'right' {\n return format?.kind === 'number' ? 'right' : 'left'\n}\n\ninterface HeaderCellProps {\n column: string\n label: string\n align: 'left' | 'right'\n sortDirection?: 'asc' | 'desc'\n isDropTarget: boolean\n onClick: (column: string) => void\n onResizeStart: (event: React.MouseEvent, column: string) => void\n onDragStart: (column: string) => void\n onDragEnd: () => void\n onDrop: (column: string) => void\n}\n\nfunction HeaderCell({\n column,\n label,\n align,\n sortDirection,\n isDropTarget,\n onClick,\n onResizeStart,\n onDragStart,\n onDragEnd,\n onDrop\n}: HeaderCellProps) {\n return (\n <th\n draggable\n onDragStart={(event) => {\n // Required for Firefox to start the drag at all.\n event.dataTransfer.setData('text/plain', column)\n event.dataTransfer.effectAllowed = 'move'\n onDragStart(column)\n }}\n onDragOver={(event) => { if (isDropTarget) event.preventDefault() }}\n onDrop={(event) => { event.preventDefault(); onDrop(column) }}\n onDragEnd={onDragEnd}\n onClick={() => onClick(column)}\n className={`dc:relative dc:px-3 dc:py-2 dc:text-xs dc:font-medium text-dc-text-muted dc:uppercase dc:tracking-wider dc:cursor-pointer dc:select-none dc:border-b border-dc-border ${\n align === 'right' ? 'dc:text-right' : 'dc:text-left'\n }${isDropTarget ? ' dc:border-l-2 border-dc-accent' : ''}`}\n >\n <div className={`dc:flex dc:items-center dc:gap-1.5 dc:overflow-hidden${align === 'right' ? ' dc:justify-end' : ''}`}>\n <span className=\"dc:truncate\">{label}</span>\n {sortDirection === 'asc' && <SortAscIcon className=\"dc:w-3 dc:h-3 text-dc-accent dc:shrink-0\" />}\n {sortDirection === 'desc' && <SortDescIcon className=\"dc:w-3 dc:h-3 text-dc-accent dc:shrink-0\" />}\n </div>\n <div\n onMouseDown={(event) => onResizeStart(event, column)}\n className=\"dc:absolute dc:top-0 dc:right-0 dc:w-1.5 dc:h-full dc:cursor-col-resize dc:hover:bg-dc-accent dc:opacity-0 dc:hover:opacity-100 dc:transition-opacity dc:z-30\"\n />\n </th>\n )\n}\n\ninterface RowProps {\n row: Record<string, unknown>\n columns: string[]\n columnFormats: Record<string, ColumnFormatConfig>\n colorPalette?: ChartProps['colorPalette']\n href: string | null\n target?: 'self' | 'blank'\n clickable: boolean\n onCellClick: (row: Record<string, unknown>, column: string, event: React.MouseEvent) => void\n}\n\nfunction Row({ row, columns, columnFormats, colorPalette, href, target, clickable, onCellClick }: RowProps) {\n return (\n <tr className={`dc:border-b border-dc-border hover:bg-dc-surface-secondary${clickable ? ' dc:cursor-pointer' : ''}`}>\n {columns.map(column => {\n const format = columnFormats[column]\n const align = format?.align ?? defaultAlign(format)\n return (\n <td\n key={column}\n onClick={(event) => onCellClick(row, column, event)}\n className={`dc:px-3 dc:py-1.5 dc:text-sm text-dc-text dc:overflow-hidden${\n align === 'right' ? ' dc:text-right dc:tabular-nums' : ''\n }`}\n >\n <CellContent\n cell={renderCellValue(row[column], format)}\n colorPalette={colorPalette}\n href={href}\n target={target}\n />\n </td>\n )\n })}\n </tr>\n )\n}\n\n/**\n * A cell, wrapped in the row's link when one resolves. An unresolvable or\n * unsafe template yields no anchor rather than a dead one.\n */\nfunction CellContent({\n cell,\n colorPalette,\n href,\n target\n}: {\n cell: RenderedCell\n colorPalette?: ChartProps['colorPalette']\n href: string | null\n target?: 'self' | 'blank'\n}) {\n const content = <Cell cell={cell} colorPalette={colorPalette} />\n if (!href) return content\n\n return (\n <a\n href={href}\n target={target === 'blank' ? '_blank' : undefined}\n rel={target === 'blank' ? 'noopener noreferrer' : undefined}\n className=\"dc:block dc:no-underline dc:text-inherit hover:dc:underline\"\n >\n {content}\n </a>\n )\n}\n\nfunction Cell({ cell, colorPalette }: { cell: RenderedCell; colorPalette?: ChartProps['colorPalette'] }) {\n if (cell.kind === 'badge') {\n const color = cell.colorIndex !== undefined ? colorPalette?.colors[cell.colorIndex] : undefined\n return (\n <span\n className=\"dc:inline-block dc:px-2 dc:py-0.5 dc:rounded-full dc:text-xs dc:font-medium dc:truncate dc:max-w-full\"\n style={color\n ? { backgroundColor: `${color}22`, color, border: `1px solid ${color}55` }\n : { backgroundColor: 'var(--dc-surface-secondary)', color: 'var(--dc-text-secondary)', border: '1px solid var(--dc-border)' }}\n >\n {cell.text}\n </span>\n )\n }\n\n if (cell.kind === 'progress') {\n return (\n <div className=\"dc:flex dc:items-center dc:gap-2\">\n {cell.style === 'circle'\n ? <ProgressRing fraction={cell.fraction} label={cell.text} />\n : (\n // Outlined track, so the full 0-100% extent is visible and a\n // near-complete bar is distinguishable from a complete one.\n <div className=\"dc:flex-1 dc:h-2 dc:rounded-full dc:overflow-hidden dc:border border-dc-border bg-dc-surface-secondary dc:min-w-[2rem]\">\n <div\n className=\"dc:h-full dc:rounded-full bg-dc-primary\"\n style={{ width: `${cell.fraction * 100}%` }}\n />\n </div>\n )}\n {/*\n Fixed width, not shrink-to-fit: the track is flexible, so a wider\n label (\"£117K\") would shorten its row's bar and two equal values\n would draw different lengths. Right-aligned and tabular so the\n numbers line up as a column.\n */}\n <span className=\"dc:w-16 dc:shrink-0 dc:text-right dc:text-xs dc:tabular-nums dc:truncate\">\n {cell.text}\n </span>\n </div>\n )\n }\n\n return <span className=\"dc:truncate dc:block\">{cell.text}</span>\n}\n\nconst RING_SIZE = 18\nconst RING_STROKE = 3\nconst RING_CENTRE = RING_SIZE / 2\nconst RING_RADIUS = RING_CENTRE - RING_STROKE / 2\n\n/**\n * The compact alternative to the progress bar: a ring that fills 0→100%, so a\n * progress column can be as narrow as its value label.\n *\n * `pathLength` normalises the circumference to 100, which lets the dash offset\n * be the percentage remaining without any 2πr arithmetic. The ring alone\n * carries the value, hence the `img` role and label.\n */\nfunction ProgressRing({ fraction, label }: { fraction: number; label: string }) {\n return (\n <svg\n role=\"img\"\n aria-label={label}\n width={RING_SIZE}\n height={RING_SIZE}\n viewBox={`0 0 ${RING_SIZE} ${RING_SIZE}`}\n className=\"dc:shrink-0\"\n >\n <circle\n cx={RING_CENTRE}\n cy={RING_CENTRE}\n r={RING_RADIUS}\n fill=\"none\"\n stroke=\"var(--dc-surface-secondary)\"\n strokeWidth={RING_STROKE}\n />\n {fraction > 0 && (\n <circle\n cx={RING_CENTRE}\n cy={RING_CENTRE}\n r={RING_RADIUS}\n fill=\"none\"\n stroke=\"var(--dc-primary)\"\n strokeWidth={RING_STROKE}\n strokeLinecap=\"round\"\n pathLength={100}\n strokeDasharray={100}\n strokeDashoffset={100 - fraction * 100}\n transform={`rotate(-90 ${RING_CENTRE} ${RING_CENTRE})`}\n />\n )}\n </svg>\n )\n}\n\nfunction PagerButton({\n label,\n disabled,\n onClick,\n children\n}: {\n label: string\n disabled: boolean\n onClick: () => void\n children: React.ReactNode\n}) {\n return (\n <button\n type=\"button\"\n aria-label={label}\n title={label}\n disabled={disabled}\n onClick={onClick}\n className=\"dc:px-2 dc:py-0.5 dc:border border-dc-border dc:rounded-sm bg-dc-surface text-dc-text hover:bg-dc-surface-secondary dc:disabled:opacity-40 dc:disabled:cursor-not-allowed\"\n >\n {children}\n </button>\n )\n}\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { c as e } from "./retention-ChW9jYdy.js";
2
- import { F as t, l as n, s as r } from "./chart-activity-grid-UZIp9_2E.js";
3
- import { M as i, j as a } from "./chart-area-CEBcvCcn.js";
2
+ import { F as t, l as n, s as r } from "./chart-activity-grid-aIyjUpbJ.js";
3
+ import { M as i, j as a } from "./chart-area-DCCvp06H.js";
4
4
  import o, { useMemo as s, useState as c } from "react";
5
5
  import { jsx as l, jsxs as u } from "react/jsx-runtime";
6
6
  import { CartesianGrid as d, ComposedChart as f, Legend as p, Line as m, XAxis as h, YAxis as g } from "recharts";
@@ -267,4 +267,4 @@ var C = o.memo(function({ data: o, height: x = "100%", displayConfig: C, colorPa
267
267
  //#endregion
268
268
  export { C as default };
269
269
 
270
- //# sourceMappingURL=RetentionCombinedChart-DbxB86fj.js.map
270
+ //# sourceMappingURL=RetentionCombinedChart-CvddCEOk.js.map