@altertable/data-app 0.66.0 → 0.68.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/CONTRIBUTING.md +22 -16
  2. package/dist/chunks/{contract-19ckn3n7.js → index-23dktynd.js} +8 -6
  3. package/dist/chunks/index-23dktynd.js.map +10 -0
  4. package/dist/chunks/{contract-rfbakka4.js → index-ag0cgnvq.js} +169 -35
  5. package/dist/chunks/index-ag0cgnvq.js.map +13 -0
  6. package/dist/chunks/index-dq7b35fn.js +307 -0
  7. package/dist/chunks/index-dq7b35fn.js.map +13 -0
  8. package/dist/chunks/{contract-e11m4y7f.js → index-ev2b5aaf.js} +11 -6
  9. package/dist/chunks/{contract-e11m4y7f.js.map → index-ev2b5aaf.js.map} +3 -3
  10. package/dist/chunks/{contract-awa2d5b1.js → index-f5cnbrjz.js} +19 -2
  11. package/dist/chunks/{contract-awa2d5b1.js.map → index-f5cnbrjz.js.map} +4 -3
  12. package/dist/chunks/{contract-havnjmdr.js → index-gvers8jw.js} +2429 -1110
  13. package/dist/chunks/index-gvers8jw.js.map +109 -0
  14. package/dist/chunks/{contract-13j5zb3c.js → index-ktmvs916.js} +8 -7
  15. package/dist/chunks/index-ktmvs916.js.map +13 -0
  16. package/dist/chunks/index-mt93ff2b.js +28 -0
  17. package/dist/chunks/index-mt93ff2b.js.map +10 -0
  18. package/dist/chunks/{contract-tf8c3qpv.js → index-qd5xgx6y.js} +46 -5
  19. package/dist/chunks/index-qd5xgx6y.js.map +13 -0
  20. package/dist/chunks/{contract-pk603qj7.js → index-x69p7cv7.js} +12 -9
  21. package/dist/chunks/index-x69p7cv7.js.map +10 -0
  22. package/dist/client/index.js +6 -6
  23. package/dist/core/appearance.js +1 -1
  24. package/dist/core/config.js +7 -3
  25. package/dist/core/config.js.map +1 -1
  26. package/dist/core/contract.js +14 -6
  27. package/dist/core/contract.js.map +1 -1
  28. package/dist/core/format.js +1 -1
  29. package/dist/embed/index.js +12 -10
  30. package/dist/embed/index.js.map +4 -4
  31. package/dist/index.js +12 -0
  32. package/dist/index.js.map +9 -0
  33. package/dist/local.js +20 -30
  34. package/dist/local.js.map +7 -8
  35. package/dist/react/embed/index.js +1 -1
  36. package/dist/react/embed/index.js.map +2 -2
  37. package/dist/react/index.js +79 -29
  38. package/dist/react/index.js.map +5 -6
  39. package/dist/react/ui/index.js +1702 -177
  40. package/dist/react/ui/index.js.map +20 -9
  41. package/dist/server.js +13 -26
  42. package/dist/server.js.map +6 -7
  43. package/dist/types/client/annotations.d.ts +4 -0
  44. package/dist/types/client/data-client.d.ts +1 -2
  45. package/dist/types/client/iframe.d.ts +4 -2
  46. package/dist/types/core/annotations.d.ts +2 -0
  47. package/dist/types/core/config.d.ts +48 -4
  48. package/dist/types/core/contract.d.ts +20 -12
  49. package/dist/types/core/dimension.d.ts +6 -4
  50. package/dist/types/core/filters.d.ts +44 -0
  51. package/dist/types/core/messages.d.ts +2 -0
  52. package/dist/types/core/operation-types.d.ts +5 -2
  53. package/dist/types/core/operation.d.ts +3 -4
  54. package/dist/types/core/queries.d.ts +16 -0
  55. package/dist/types/core/uuid.d.ts +2 -0
  56. package/dist/types/core/variables.d.ts +32 -13
  57. package/dist/types/embed/runtime-html.d.ts +2 -0
  58. package/dist/types/embed/source.d.ts +2 -1
  59. package/dist/types/index.d.ts +3 -0
  60. package/dist/types/react/annotations/AnnotationBar.d.ts +7 -1
  61. package/dist/types/react/annotations/AnnotationEditor.d.ts +2 -1
  62. package/dist/types/react/annotations/useDataAppAnnotations.d.ts +3 -0
  63. package/dist/types/react/app-context.d.ts +3 -0
  64. package/dist/types/react/index.d.ts +3 -3
  65. package/dist/types/react/mount.d.ts +6 -4
  66. package/dist/types/react/style-contract.d.ts +34 -2
  67. package/dist/types/react/ui/Button.d.ts +3 -3
  68. package/dist/types/react/ui/ChartLegend.d.ts +31 -0
  69. package/dist/types/react/ui/Checkbox.d.ts +15 -4
  70. package/dist/types/react/ui/CheckboxGroup.d.ts +7 -0
  71. package/dist/types/react/ui/{Combobox.d.ts → ChoicePicker.d.ts} +10 -15
  72. package/dist/types/react/ui/ComposedChart.d.ts +41 -0
  73. package/dist/types/react/ui/ComposedChartLegend.d.ts +8 -0
  74. package/dist/types/react/ui/DataAppFrame.d.ts +1 -2
  75. package/dist/types/react/ui/DimensionPicker.d.ts +1 -1
  76. package/dist/types/react/ui/FilterActions.d.ts +10 -0
  77. package/dist/types/react/ui/FilterBar.d.ts +3 -0
  78. package/dist/types/react/ui/FunnelChart.d.ts +20 -0
  79. package/dist/types/react/ui/GettingStarted.d.ts +2 -4
  80. package/dist/types/react/ui/JourneyChart.d.ts +10 -0
  81. package/dist/types/react/ui/Menu.d.ts +12 -0
  82. package/dist/types/react/ui/NumberField.d.ts +27 -0
  83. package/dist/types/react/ui/NumberFilterPicker.d.ts +9 -0
  84. package/dist/types/react/ui/PieChart.d.ts +5 -2
  85. package/dist/types/react/ui/RadioGroup.d.ts +18 -0
  86. package/dist/types/react/ui/RetentionChart.d.ts +27 -0
  87. package/dist/types/react/ui/SearchInput.d.ts +2 -2
  88. package/dist/types/react/ui/Select.d.ts +14 -0
  89. package/dist/types/react/ui/SelectionMark.d.ts +2 -1
  90. package/dist/types/react/ui/TooltipSurface.d.ts +3 -0
  91. package/dist/types/react/ui/chart-data.d.ts +10 -1
  92. package/dist/types/react/ui/chart-primitives.d.ts +22 -0
  93. package/dist/types/react/ui/chartColor.d.ts +1 -0
  94. package/dist/types/react/ui/icons.d.ts +1 -0
  95. package/dist/types/react/ui/index.d.ts +33 -2
  96. package/dist/types/react/ui/journey-flow.d.ts +49 -0
  97. package/dist/types/react/ui/keyboard.d.ts +6 -0
  98. package/dist/types/react/ui/shortcuts.d.ts +7 -0
  99. package/dist/types/react/ui/useAppAppearance.d.ts +1 -1
  100. package/dist/types/react/ui/useSvgId.d.ts +2 -0
  101. package/dist/types/react/ui/variables.d.ts +4 -2
  102. package/dist/types/react/view-controls.d.ts +3 -1
  103. package/dist/types/server/handler.d.ts +4 -4
  104. package/dist/worker.js +21 -738
  105. package/docs/app-authoring.md +15 -1
  106. package/docs/client.md +8 -8
  107. package/docs/contract.md +43 -20
  108. package/docs/data-context.md +8 -6
  109. package/docs/embed.md +10 -2
  110. package/docs/formatting-and-appearance.md +23 -7
  111. package/docs/hosted-apps.md +9 -2
  112. package/docs/layout.md +1 -1
  113. package/docs/react-embed.md +3 -2
  114. package/docs/react.md +4 -3
  115. package/docs/server-bun.md +1 -1
  116. package/docs/server.md +4 -4
  117. package/docs/styling.md +10 -3
  118. package/docs/ui.md +176 -3
  119. package/docs/variables.md +80 -7
  120. package/docs/views.md +4 -8
  121. package/docs/widgets.md +19 -13
  122. package/docs/worker.md +2 -1
  123. package/examples/starter-data-app/index.tsx +34 -32
  124. package/package.json +16 -11
  125. package/dist/chunks/contract-13j5zb3c.js.map +0 -13
  126. package/dist/chunks/contract-19ckn3n7.js.map +0 -10
  127. package/dist/chunks/contract-h8a6f559.js +0 -286
  128. package/dist/chunks/contract-h8a6f559.js.map +0 -12
  129. package/dist/chunks/contract-havnjmdr.js.map +0 -99
  130. package/dist/chunks/contract-pk603qj7.js.map +0 -10
  131. package/dist/chunks/contract-rfbakka4.js.map +0 -13
  132. package/dist/chunks/contract-tf8c3qpv.js.map +0 -11
  133. package/dist/chunks/contract-wz59z8pq.js +0 -8
  134. package/dist/chunks/contract-wz59z8pq.js.map +0 -10
  135. /package/dist/chunks/{contract-m6n8ctfc.js → index-m6n8ctfc.js} +0 -0
  136. /package/dist/chunks/{contract-m6n8ctfc.js.map → index-m6n8ctfc.js.map} +0 -0
  137. /package/dist/chunks/{contract-mev09s5v.js → index-mev09s5v.js} +0 -0
  138. /package/dist/chunks/{contract-mev09s5v.js.map → index-mev09s5v.js.map} +0 -0
@@ -4,6 +4,8 @@ import {
4
4
  AppIcon2,
5
5
  classNames,
6
6
  Button2,
7
+ PressButton,
8
+ TooltipSurface,
7
9
  TooltipProvider2,
8
10
  Tooltip2,
9
11
  GradientScroll2,
@@ -17,18 +19,34 @@ import {
17
19
  AboutData2,
18
20
  DataAppProvider2,
19
21
  useDeclaredResult,
22
+ validateChoiceOptions,
20
23
  useAppVariables2,
24
+ Select2,
25
+ FilterActions2,
26
+ NumberField2,
27
+ NumberRangeField2,
28
+ NumberFilterPicker2,
21
29
  SearchField2,
22
30
  Skeleton2,
23
31
  SearchMatch2,
24
32
  searchItems2,
25
- Combobox2,
33
+ ChoicePicker2,
34
+ CheckboxGroupContext,
26
35
  Checkbox2,
27
36
  DateRangePicker2,
37
+ FilterBar2,
28
38
  DimensionPicker2,
29
39
  createDataHooks2,
40
+ isPlainKeyEvent2,
41
+ MenuTrigger2,
42
+ MenuSection2,
43
+ MenuPopover2,
44
+ Menu2,
45
+ MenuItem2,
46
+ MenuSeparator2,
30
47
  LiveControl2,
31
48
  PresentStory2,
49
+ VariableBar2,
32
50
  DataAppFrame,
33
51
  Grid2,
34
52
  DataWidget2,
@@ -42,19 +60,24 @@ import {
42
60
  TableWidget2,
43
61
  TextWidget2,
44
62
  DataValue2,
63
+ chartBarFill,
45
64
  createDataContext2
46
- } from "../../chunks/contract-havnjmdr.js";
47
- import"../../chunks/contract-wz59z8pq.js";
48
- import"../../chunks/contract-mev09s5v.js";
65
+ } from "../../chunks/index-gvers8jw.js";
49
66
  import {
50
- invariant
51
- } from "../../chunks/contract-rfbakka4.js";
52
- import"../../chunks/contract-m6n8ctfc.js";
67
+ formatNumber2,
68
+ formatPercent2
69
+ } from "../../chunks/index-mev09s5v.js";
70
+ import"../../chunks/index-ag0cgnvq.js";
71
+ import"../../chunks/index-mt93ff2b.js";
72
+ import"../../chunks/index-m6n8ctfc.js";
53
73
  import {
54
74
  createDataClient2
55
- } from "../../chunks/contract-19ckn3n7.js";
56
- import"../../chunks/contract-pk603qj7.js";
57
- import"../../chunks/contract-e11m4y7f.js";
75
+ } from "../../chunks/index-23dktynd.js";
76
+ import"../../chunks/index-x69p7cv7.js";
77
+ import"../../chunks/index-ev2b5aaf.js";
78
+ import {
79
+ invariant
80
+ } from "../../chunks/index-qd5xgx6y.js";
58
81
 
59
82
  // src/react/shellStyles.ts
60
83
  function injectDataAppShellStyles(options = {}) {
@@ -64,6 +87,7 @@ function injectDataAppShellStyles(options = {}) {
64
87
  css: `/* src/react/tokens.css */
65
88
  @layer atbl.tokens {
66
89
  :root {
90
+ --atbl-control-text-size: var(--atbl-type-body-size);
67
91
  --atbl-background: var(--atbl-palette-background);
68
92
  --atbl-surface: var(--atbl-palette-surface);
69
93
  --atbl-subtle: var(--atbl-palette-subtle);
@@ -812,14 +836,10 @@ var connectionView = defineDataView({
812
836
  isEmpty: () => false,
813
837
  emptyFallback: { title: "No connection" }
814
838
  });
815
- function GettingStarted({
816
- config,
817
- dataContext
818
- }) {
839
+ function GettingStarted({ dataContext }) {
819
840
  const connection = useDeclaredResult(connectionView);
820
841
  const state = connection.view.kind === "error" || connection.view.kind === "stale-error" ? "error" : connection.refreshing || !connection.snapshot ? "checking" : "connected";
821
842
  return /* @__PURE__ */ jsxs(DataApp, {
822
- config,
823
843
  dataContext,
824
844
  description: "Check your lakehouse connection, then build a view around a real question.",
825
845
  queries: connection.queries,
@@ -1064,7 +1084,376 @@ function GlossaryDefinition({
1064
1084
  });
1065
1085
  }
1066
1086
  // src/react/ui/TrendChart.tsx
1067
- import { useLayoutEffect, useRef } from "react";
1087
+ import { useLayoutEffect, useRef, useState as useState2 } from "react";
1088
+ import {
1089
+ AreaChart as RechartsAreaChart,
1090
+ LineChart as RechartsLineChart,
1091
+ ReferenceLine
1092
+ } from "recharts";
1093
+
1094
+ // src/react/ui/chart-primitives.tsx
1095
+ import {
1096
+ Area,
1097
+ Dot,
1098
+ Bar,
1099
+ Line,
1100
+ Scatter,
1101
+ XAxis,
1102
+ YAxis,
1103
+ CartesianGrid,
1104
+ Tooltip,
1105
+ DefaultTooltipContent
1106
+ } from "recharts";
1107
+
1108
+ // src/react/ui/ChartLegend.tsx
1109
+ import {
1110
+ Children,
1111
+ useId,
1112
+ useState
1113
+ } from "react";
1114
+ import { jsx as jsx6, jsxs as jsxs4, Fragment } from "react/jsx-runtime";
1115
+ function ChartLegendRoot({
1116
+ children,
1117
+ layout = "horizontal",
1118
+ align = "start",
1119
+ maxVisibleItems = 6,
1120
+ className,
1121
+ "aria-label": ariaLabel = "Chart legend",
1122
+ ...props
1123
+ }) {
1124
+ const [expanded, setExpanded] = useState(false);
1125
+ const listId = useId();
1126
+ const items = Children.toArray(children);
1127
+ const limit = Number.isFinite(maxVisibleItems) ? Math.max(1, Math.floor(maxVisibleItems)) : 6;
1128
+ const visibleCount = items.length > limit ? limit - 1 : limit;
1129
+ const hiddenCount = Math.max(0, items.length - visibleCount);
1130
+ return /* @__PURE__ */ jsx6("div", {
1131
+ className: "altertable-chart-legend-container",
1132
+ children: /* @__PURE__ */ jsxs4("ul", {
1133
+ id: listId,
1134
+ ...props,
1135
+ "aria-label": ariaLabel,
1136
+ "data-layout": layout,
1137
+ "data-align": align,
1138
+ className: classNames("altertable-chart-legend", className),
1139
+ children: [
1140
+ expanded ? items : items.slice(0, visibleCount),
1141
+ hiddenCount > 0 && /* @__PURE__ */ jsx6("li", {
1142
+ className: "altertable-chart-legend-overflow",
1143
+ children: /* @__PURE__ */ jsx6("button", {
1144
+ type: "button",
1145
+ "data-atbl-control": "action",
1146
+ "data-atbl-focus": "ring",
1147
+ className: "altertable-chart-legend-action",
1148
+ "aria-expanded": expanded,
1149
+ "aria-controls": props.id ?? listId,
1150
+ onClick: () => setExpanded((value) => !value),
1151
+ children: expanded ? "Show fewer" : `+${hiddenCount} more`
1152
+ })
1153
+ })
1154
+ ]
1155
+ })
1156
+ });
1157
+ }
1158
+ function ChartLegendItem({
1159
+ inactive,
1160
+ className,
1161
+ ...props
1162
+ }) {
1163
+ return /* @__PURE__ */ jsx6("li", {
1164
+ ...props,
1165
+ "data-inactive": inactive || undefined,
1166
+ className: classNames("altertable-chart-legend-item", className)
1167
+ });
1168
+ }
1169
+ function ChartLegendMarker({
1170
+ kind,
1171
+ color = kind === "bar" ? chartBarFill : "var(--atbl-accent)",
1172
+ className,
1173
+ style,
1174
+ ...props
1175
+ }) {
1176
+ return /* @__PURE__ */ jsxs4("svg", {
1177
+ ...props,
1178
+ "aria-hidden": "true",
1179
+ focusable: "false",
1180
+ viewBox: kind === "square" ? "0 0 8 8" : "0 0 20 12",
1181
+ "data-kind": kind,
1182
+ className: classNames("altertable-chart-legend-marker", className),
1183
+ style: { color, ...style },
1184
+ children: [
1185
+ kind === "square" && /* @__PURE__ */ jsx6("rect", {
1186
+ width: "8",
1187
+ height: "8",
1188
+ fill: "currentColor"
1189
+ }),
1190
+ kind === "bar" && /* @__PURE__ */ jsx6("path", {
1191
+ d: "M6 11V3Q6 1 8 1H12Q14 1 14 3V11Z",
1192
+ fill: "currentColor"
1193
+ }),
1194
+ kind === "slice" && /* @__PURE__ */ jsx6("rect", {
1195
+ x: "5",
1196
+ y: "1",
1197
+ width: "10",
1198
+ height: "10",
1199
+ rx: "2",
1200
+ fill: "currentColor"
1201
+ }),
1202
+ kind === "point" && /* @__PURE__ */ jsx6("circle", {
1203
+ cx: "10",
1204
+ cy: "6",
1205
+ r: "3",
1206
+ fill: "currentColor"
1207
+ }),
1208
+ kind === "line" && /* @__PURE__ */ jsxs4(Fragment, {
1209
+ children: [
1210
+ /* @__PURE__ */ jsx6("path", {
1211
+ d: "M1 6H19",
1212
+ fill: "none",
1213
+ stroke: "currentColor",
1214
+ strokeWidth: "2",
1215
+ strokeLinecap: "round"
1216
+ }),
1217
+ /* @__PURE__ */ jsx6("circle", {
1218
+ cx: "10",
1219
+ cy: "6",
1220
+ r: "2.5",
1221
+ fill: "var(--atbl-surface)",
1222
+ stroke: "currentColor",
1223
+ strokeWidth: "2"
1224
+ })
1225
+ ]
1226
+ }),
1227
+ kind === "area" && /* @__PURE__ */ jsxs4(Fragment, {
1228
+ children: [
1229
+ /* @__PURE__ */ jsx6("path", {
1230
+ d: "M1 10L7 5L13 7L19 2V11H1Z",
1231
+ fill: "currentColor",
1232
+ fillOpacity: "0.14"
1233
+ }),
1234
+ /* @__PURE__ */ jsx6("path", {
1235
+ d: "M1 10L7 5L13 7L19 2",
1236
+ fill: "none",
1237
+ stroke: "currentColor",
1238
+ strokeWidth: "2",
1239
+ strokeLinejoin: "round",
1240
+ strokeLinecap: "round"
1241
+ })
1242
+ ]
1243
+ })
1244
+ ]
1245
+ });
1246
+ }
1247
+ function ChartLegendLabel({
1248
+ className,
1249
+ children,
1250
+ title,
1251
+ ...props
1252
+ }) {
1253
+ return /* @__PURE__ */ jsx6("span", {
1254
+ ...props,
1255
+ title: title ?? (typeof children === "string" ? children : undefined),
1256
+ className: classNames("altertable-chart-legend-label", className),
1257
+ children
1258
+ });
1259
+ }
1260
+ var ChartLegend = Object.assign(ChartLegendRoot, {
1261
+ Item: ChartLegendItem,
1262
+ Marker: ChartLegendMarker,
1263
+ Label: ChartLegendLabel
1264
+ });
1265
+
1266
+ // src/react/ui/chart-primitives.tsx
1267
+ import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
1268
+ var accent = "var(--atbl-accent)";
1269
+ function ChartDot({
1270
+ active = false,
1271
+ stroke = accent,
1272
+ fill = active ? stroke : "var(--atbl-surface)",
1273
+ r = active ? 4.5 : 3,
1274
+ ...props
1275
+ }) {
1276
+ return /* @__PURE__ */ jsx7(Dot, {
1277
+ stroke,
1278
+ fill,
1279
+ r,
1280
+ strokeWidth: 2,
1281
+ ...props
1282
+ });
1283
+ }
1284
+ function ChartBar(props) {
1285
+ return /* @__PURE__ */ jsx7(Bar, {
1286
+ fill: chartBarFill,
1287
+ radius: [5, 5, 0, 0],
1288
+ maxBarSize: 30,
1289
+ activeBar: { fill: accent },
1290
+ isAnimationActive: false,
1291
+ ...props
1292
+ });
1293
+ }
1294
+ function ChartLine({
1295
+ stroke = accent,
1296
+ dot,
1297
+ activeDot,
1298
+ ...props
1299
+ }) {
1300
+ return /* @__PURE__ */ jsx7(Line, {
1301
+ type: "linear",
1302
+ stroke,
1303
+ strokeWidth: 2,
1304
+ strokeLinecap: "round",
1305
+ strokeLinejoin: "round",
1306
+ dot: dot === undefined || dot === true ? ({ cx, cy }) => /* @__PURE__ */ jsx7(ChartDot, {
1307
+ cx,
1308
+ cy,
1309
+ stroke
1310
+ }) : dot,
1311
+ activeDot: activeDot === undefined || activeDot === true ? ({ cx, cy }) => /* @__PURE__ */ jsx7(ChartDot, {
1312
+ cx,
1313
+ cy,
1314
+ stroke,
1315
+ active: true
1316
+ }) : activeDot,
1317
+ isAnimationActive: false,
1318
+ ...props
1319
+ });
1320
+ }
1321
+ function ChartArea({
1322
+ stroke = accent,
1323
+ dot,
1324
+ activeDot,
1325
+ ...props
1326
+ }) {
1327
+ return /* @__PURE__ */ jsx7(Area, {
1328
+ legendType: "square",
1329
+ type: "linear",
1330
+ stroke,
1331
+ strokeWidth: 2,
1332
+ strokeLinecap: "round",
1333
+ strokeLinejoin: "round",
1334
+ fill: stroke,
1335
+ fillOpacity: 0.14,
1336
+ dot: dot === undefined || dot === true ? ({ cx, cy }) => /* @__PURE__ */ jsx7(ChartDot, {
1337
+ cx,
1338
+ cy,
1339
+ stroke
1340
+ }) : dot,
1341
+ activeDot: activeDot === undefined || activeDot === true ? ({ cx, cy }) => /* @__PURE__ */ jsx7(ChartDot, {
1342
+ cx,
1343
+ cy,
1344
+ stroke,
1345
+ active: true
1346
+ }) : activeDot,
1347
+ isAnimationActive: false,
1348
+ ...props
1349
+ });
1350
+ }
1351
+ function ChartScatter(props) {
1352
+ return /* @__PURE__ */ jsx7(Scatter, {
1353
+ fill: accent,
1354
+ isAnimationActive: false,
1355
+ ...props
1356
+ });
1357
+ }
1358
+ function ChartXAxis(props) {
1359
+ return /* @__PURE__ */ jsx7(XAxis, {
1360
+ stroke: "var(--atbl-muted)",
1361
+ tickLine: false,
1362
+ axisLine: { stroke: "var(--atbl-border)" },
1363
+ tick: { fontSize: "var(--atbl-type-meta-size)" },
1364
+ ...props
1365
+ });
1366
+ }
1367
+ function ChartYAxis(props) {
1368
+ return /* @__PURE__ */ jsx7(YAxis, {
1369
+ stroke: "var(--atbl-muted)",
1370
+ tickLine: false,
1371
+ axisLine: false,
1372
+ tick: { fontSize: "var(--atbl-type-meta-size)" },
1373
+ ...props
1374
+ });
1375
+ }
1376
+ function ChartGrid(props) {
1377
+ return /* @__PURE__ */ jsx7(CartesianGrid, {
1378
+ stroke: "var(--atbl-border)",
1379
+ vertical: false,
1380
+ ...props
1381
+ });
1382
+ }
1383
+ function ChartTooltipContent(props) {
1384
+ if (!props.active)
1385
+ return null;
1386
+ return /* @__PURE__ */ jsx7(TooltipSurface, {
1387
+ "data-variant": "chart",
1388
+ role: "tooltip",
1389
+ children: /* @__PURE__ */ jsx7(DefaultTooltipContent, {
1390
+ ...props,
1391
+ separator: "",
1392
+ payload: props.payload?.map((entry) => ({
1393
+ ...entry,
1394
+ unit: undefined,
1395
+ formatter: (value, name, _item, index, payload) => {
1396
+ const formatter = entry.formatter ?? props.formatter;
1397
+ const formatted = formatter ? formatter(value, name, entry, index, props.payload ?? payload) : Array.isArray(value) ? value.join(" ~ ") : value;
1398
+ if (formatted == null)
1399
+ return null;
1400
+ const [displayValue, displayName] = Array.isArray(formatted) ? formatted : [formatted, name];
1401
+ return [
1402
+ /* @__PURE__ */ jsxs5("span", {
1403
+ className: "altertable-chart-tooltip-row",
1404
+ children: [
1405
+ /* @__PURE__ */ jsx7(ChartLegend.Marker, {
1406
+ kind: "square",
1407
+ className: "altertable-chart-tooltip-marker",
1408
+ color: entry.color ?? entry.stroke ?? entry.fill ?? "var(--atbl-accent)"
1409
+ }),
1410
+ /* @__PURE__ */ jsxs5("span", {
1411
+ className: "altertable-chart-tooltip-reading",
1412
+ children: [
1413
+ displayName,
1414
+ displayName !== undefined && displayName !== "" ? props.separator ?? " : " : "",
1415
+ displayValue,
1416
+ entry.unit
1417
+ ]
1418
+ })
1419
+ ]
1420
+ }, entry.graphicalItemId),
1421
+ ""
1422
+ ];
1423
+ }
1424
+ })),
1425
+ contentStyle: {
1426
+ margin: 0,
1427
+ padding: 0,
1428
+ background: "transparent",
1429
+ border: 0,
1430
+ whiteSpace: "normal",
1431
+ ...props.contentStyle
1432
+ },
1433
+ labelStyle: {
1434
+ color: "var(--atbl-text)",
1435
+ fontSize: "var(--atbl-type-body-size)",
1436
+ fontWeight: 650,
1437
+ lineHeight: 1.2,
1438
+ ...props.labelStyle
1439
+ },
1440
+ itemStyle: {
1441
+ color: "var(--atbl-text)",
1442
+ paddingTop: 2,
1443
+ paddingBottom: 0,
1444
+ ...props.itemStyle
1445
+ }
1446
+ })
1447
+ });
1448
+ }
1449
+ function ChartTooltip(props) {
1450
+ return /* @__PURE__ */ jsx7(Tooltip, {
1451
+ content: /* @__PURE__ */ jsx7(ChartTooltipContent, {}),
1452
+ cursor: false,
1453
+ isAnimationActive: false,
1454
+ ...props
1455
+ });
1456
+ }
1068
1457
 
1069
1458
  // src/react/ui/chart-data.ts
1070
1459
  function validateChartItems(kind, items) {
@@ -1085,7 +1474,7 @@ function validateChartItems(kind, items) {
1085
1474
  }
1086
1475
 
1087
1476
  // src/react/ui/TrendChart.tsx
1088
- import { jsx as jsx6, jsxs as jsxs4, Fragment } from "react/jsx-runtime";
1477
+ import { jsx as jsx8, jsxs as jsxs6, Fragment as Fragment2 } from "react/jsx-runtime";
1089
1478
  function TrendChart({
1090
1479
  items,
1091
1480
  unit,
@@ -1094,6 +1483,8 @@ function TrendChart({
1094
1483
  area = false
1095
1484
  }) {
1096
1485
  validateChartItems(area ? "area" : "line", items);
1486
+ const Plot = area ? RechartsAreaChart : RechartsLineChart;
1487
+ const [activeIndex, setActiveIndex] = useState2();
1097
1488
  const scrollport = useRef(null);
1098
1489
  useLayoutEffect(() => {
1099
1490
  const element = scrollport.current;
@@ -1103,67 +1494,80 @@ function TrendChart({
1103
1494
  const magnitude = items.reduce((max, item) => Math.max(max, Math.abs(item.value)), 1);
1104
1495
  const low = items.reduce((min, item) => Math.min(min, item.value / magnitude), 0);
1105
1496
  const high = items.reduce((max, item) => Math.max(max, item.value / magnitude), 0);
1106
- const span = high - low || 1;
1107
- function y(value) {
1108
- return 208 - (value / magnitude - low) / span * 196;
1109
- }
1110
- const points = items.map((item, index) => ({
1111
- x: (index + 0.5) / items.length * 1000,
1112
- y: y(item.value)
1113
- }));
1114
- const path = points.map((point, index) => `${index ? "L" : "M"}${point.x},${point.y}`).join(" ");
1115
- const first = points[0];
1116
- const last = points.at(-1);
1117
- return /* @__PURE__ */ jsxs4("section", {
1497
+ return /* @__PURE__ */ jsxs6("section", {
1118
1498
  className: "altertable-trend-chart",
1119
1499
  "aria-label": ariaLabel,
1120
1500
  children: [
1121
- items.length === 0 && /* @__PURE__ */ jsx6("span", {
1501
+ items.length === 0 && /* @__PURE__ */ jsx8("span", {
1122
1502
  children: "No data"
1123
1503
  }),
1124
- items.length > 0 && /* @__PURE__ */ jsx6(Fragment, {
1125
- children: /* @__PURE__ */ jsx6("div", {
1504
+ items.length > 0 && /* @__PURE__ */ jsx8(Fragment2, {
1505
+ children: /* @__PURE__ */ jsx8("div", {
1126
1506
  className: "altertable-trend-scroll",
1127
1507
  ref: scrollport,
1128
- children: /* @__PURE__ */ jsxs4("div", {
1508
+ children: /* @__PURE__ */ jsxs6("div", {
1129
1509
  className: "altertable-trend-plot",
1130
1510
  style: { minWidth: items.length * 40 },
1131
1511
  children: [
1132
- /* @__PURE__ */ jsxs4("svg", {
1133
- viewBox: "0 0 1000 220",
1134
- preserveAspectRatio: "none",
1512
+ /* @__PURE__ */ jsx8("div", {
1513
+ className: "altertable-chart-canvas",
1135
1514
  "aria-hidden": "true",
1136
- focusable: "false",
1137
- children: [
1138
- /* @__PURE__ */ jsx6("line", {
1139
- className: "altertable-trend-baseline",
1140
- x1: "0",
1141
- x2: "1000",
1142
- y1: y(0),
1143
- y2: y(0),
1144
- vectorEffect: "non-scaling-stroke"
1145
- }),
1146
- area && first && last && /* @__PURE__ */ jsx6("path", {
1147
- className: "altertable-trend-fill",
1148
- d: `${path} L${last.x},${y(0)} L${first.x},${y(0)} Z`
1149
- }),
1150
- /* @__PURE__ */ jsx6("path", {
1151
- className: "altertable-trend-line",
1152
- d: path,
1153
- vectorEffect: "non-scaling-stroke"
1154
- })
1155
- ]
1515
+ children: /* @__PURE__ */ jsxs6(Plot, {
1516
+ data: items.map((item) => ({
1517
+ ...item,
1518
+ plotValue: item.value / magnitude
1519
+ })),
1520
+ title: ariaLabel,
1521
+ responsive: true,
1522
+ width: "100%",
1523
+ height: 220,
1524
+ margin: { top: 12, right: 0, bottom: 12, left: 0 },
1525
+ accessibilityLayer: false,
1526
+ tabIndex: -1,
1527
+ children: [
1528
+ /* @__PURE__ */ jsx8(ChartXAxis, {
1529
+ hide: true,
1530
+ dataKey: "id",
1531
+ scale: "band"
1532
+ }),
1533
+ /* @__PURE__ */ jsx8(ChartYAxis, {
1534
+ hide: true,
1535
+ domain: [low, high === low ? low + 1 : high]
1536
+ }),
1537
+ /* @__PURE__ */ jsx8(ReferenceLine, {
1538
+ y: 0,
1539
+ stroke: "var(--atbl-border)"
1540
+ }),
1541
+ area && /* @__PURE__ */ jsx8(ChartArea, {
1542
+ dataKey: "plotValue",
1543
+ stroke: "none",
1544
+ fill: "var(--atbl-accent)",
1545
+ dot: false,
1546
+ activeDot: false
1547
+ }),
1548
+ /* @__PURE__ */ jsx8(ChartLine, {
1549
+ dataKey: "plotValue",
1550
+ dot: ({ cx, cy, index }) => /* @__PURE__ */ jsx8(ChartDot, {
1551
+ cx,
1552
+ cy,
1553
+ active: index === activeIndex
1554
+ }),
1555
+ activeDot: false
1556
+ })
1557
+ ]
1558
+ })
1156
1559
  }),
1157
- /* @__PURE__ */ jsx6("div", {
1560
+ /* @__PURE__ */ jsx8("div", {
1158
1561
  className: "altertable-trend-points",
1159
- children: items.map((item, index) => /* @__PURE__ */ jsx6(Tooltip2, {
1562
+ children: items.map((item, index) => /* @__PURE__ */ jsx8(Tooltip2, {
1160
1563
  variant: "chart",
1161
- content: /* @__PURE__ */ jsxs4(Fragment, {
1564
+ onOpenChange: (open) => setActiveIndex((current) => open ? index : current === index ? undefined : current),
1565
+ content: /* @__PURE__ */ jsxs6(Fragment2, {
1162
1566
  children: [
1163
- /* @__PURE__ */ jsx6("strong", {
1567
+ /* @__PURE__ */ jsx8("strong", {
1164
1568
  children: item.label
1165
1569
  }),
1166
- /* @__PURE__ */ jsxs4("span", {
1570
+ /* @__PURE__ */ jsxs6("span", {
1167
1571
  children: [
1168
1572
  formatValue(item.value),
1169
1573
  " ",
@@ -1173,22 +1577,18 @@ function TrendChart({
1173
1577
  ]
1174
1578
  }),
1175
1579
  className: "altertable-chart-column",
1176
- children: /* @__PURE__ */ jsxs4("button", {
1580
+ children: /* @__PURE__ */ jsxs6("button", {
1177
1581
  "data-atbl-focus": "ring",
1178
- "data-atbl-control": "action",
1582
+ "data-atbl-control": "default",
1179
1583
  type: "button",
1180
1584
  tabIndex: -1,
1181
1585
  className: "altertable-trend-point",
1182
1586
  "aria-label": `${item.label}: ${formatValue(item.value)} ${unit}`,
1183
1587
  children: [
1184
- /* @__PURE__ */ jsx6("span", {
1185
- className: "altertable-trend-hit",
1186
- children: /* @__PURE__ */ jsx6("span", {
1187
- className: "altertable-trend-dot",
1188
- style: { top: `${points[index].y / 220 * 100}%` }
1189
- })
1588
+ /* @__PURE__ */ jsx8("span", {
1589
+ className: "altertable-trend-hit"
1190
1590
  }),
1191
- /* @__PURE__ */ jsx6("span", {
1591
+ /* @__PURE__ */ jsx8("span", {
1192
1592
  className: "altertable-trend-label",
1193
1593
  children: item.label
1194
1594
  })
@@ -1205,33 +1605,34 @@ function TrendChart({
1205
1605
  }
1206
1606
 
1207
1607
  // src/react/ui/LineChart.tsx
1208
- import { jsx as jsx7 } from "react/jsx-runtime";
1608
+ import { jsx as jsx9 } from "react/jsx-runtime";
1209
1609
  function LineChart(props) {
1210
- return /* @__PURE__ */ jsx7(TrendChart, {
1610
+ return /* @__PURE__ */ jsx9(TrendChart, {
1211
1611
  ...props
1212
1612
  });
1213
1613
  }
1214
1614
  // src/react/ui/AreaChart.tsx
1215
- import { jsx as jsx8 } from "react/jsx-runtime";
1615
+ import { jsx as jsx10 } from "react/jsx-runtime";
1216
1616
  function AreaChart(props) {
1217
- return /* @__PURE__ */ jsx8(TrendChart, {
1617
+ return /* @__PURE__ */ jsx10(TrendChart, {
1218
1618
  ...props,
1219
1619
  area: true
1220
1620
  });
1221
1621
  }
1222
1622
  // src/react/ui/PieChart.tsx
1223
- import { useState } from "react";
1224
- import { jsx as jsx9, jsxs as jsxs5, Fragment as Fragment2 } from "react/jsx-runtime";
1623
+ import { useState as useState3 } from "react";
1624
+ import { jsx as jsx11, jsxs as jsxs7, Fragment as Fragment3 } from "react/jsx-runtime";
1225
1625
  function PieChart({
1226
1626
  items,
1227
1627
  unit,
1228
1628
  ariaLabel,
1629
+ showLegend = true,
1229
1630
  formatValue = (value) => new Intl.NumberFormat().format(value)
1230
1631
  }) {
1231
1632
  validateChartItems("pie", items);
1232
- const [previewId, setPreviewId] = useState(null);
1233
- const [plotOpen, setPlotOpen] = useState(false);
1234
- const [legendId, setLegendId] = useState(null);
1633
+ const [previewId, setPreviewId] = useState3(null);
1634
+ const [plotOpen, setPlotOpen] = useState3(false);
1635
+ const [legendId, setLegendId] = useState3(null);
1235
1636
  const maximum = items.reduce((max, item) => Math.max(max, item.value), 0);
1236
1637
  const total = maximum ? items.reduce((sum, item) => sum + item.value / maximum, 0) : 0;
1237
1638
  let cursor = -Math.PI / 2;
@@ -1258,12 +1659,12 @@ function PieChart({
1258
1659
  }).format(share);
1259
1660
  }
1260
1661
  function detail(item) {
1261
- return /* @__PURE__ */ jsxs5(Fragment2, {
1662
+ return /* @__PURE__ */ jsxs7(Fragment3, {
1262
1663
  children: [
1263
- /* @__PURE__ */ jsx9("strong", {
1664
+ /* @__PURE__ */ jsx11("strong", {
1264
1665
  children: item.label
1265
1666
  }),
1266
- /* @__PURE__ */ jsxs5("span", {
1667
+ /* @__PURE__ */ jsxs7("span", {
1267
1668
  children: [
1268
1669
  formatValue(item.value),
1269
1670
  " ",
@@ -1276,37 +1677,37 @@ function PieChart({
1276
1677
  });
1277
1678
  }
1278
1679
  const preview = slices.find((item) => item.id === previewId);
1279
- return /* @__PURE__ */ jsxs5("section", {
1680
+ return /* @__PURE__ */ jsxs7("section", {
1280
1681
  className: "altertable-pie-chart",
1281
1682
  "aria-label": ariaLabel,
1282
1683
  children: [
1283
- items.length === 0 && /* @__PURE__ */ jsx9("span", {
1684
+ items.length === 0 && /* @__PURE__ */ jsx11("span", {
1284
1685
  children: "No data"
1285
1686
  }),
1286
- items.length > 0 && /* @__PURE__ */ jsxs5(Fragment2, {
1687
+ items.length > 0 && /* @__PURE__ */ jsxs7(Fragment3, {
1287
1688
  children: [
1288
- /* @__PURE__ */ jsxs5("div", {
1689
+ /* @__PURE__ */ jsxs7("div", {
1289
1690
  className: "altertable-pie-plot",
1290
1691
  children: [
1291
- /* @__PURE__ */ jsx9(Tooltip2, {
1692
+ /* @__PURE__ */ jsx11(Tooltip2, {
1292
1693
  variant: "chart",
1293
1694
  className: "altertable-pie-tooltip",
1294
1695
  onOpenChange: setPlotOpen,
1295
1696
  content: preview ? detail(preview) : null,
1296
- children: /* @__PURE__ */ jsx9("svg", {
1697
+ children: /* @__PURE__ */ jsx11("svg", {
1297
1698
  viewBox: "0 0 220 220",
1298
1699
  "aria-hidden": "true",
1299
1700
  focusable: "false",
1300
- children: total === 0 ? /* @__PURE__ */ jsx9("circle", {
1701
+ children: total === 0 ? /* @__PURE__ */ jsx11("circle", {
1301
1702
  cx: "110",
1302
1703
  cy: "110",
1303
1704
  r: "100",
1304
1705
  className: "altertable-pie-empty"
1305
- }) : slices.filter((item) => item.share > 0).map((item) => /* @__PURE__ */ jsx9("path", {
1706
+ }) : slices.filter((item) => item.share > 0).map((item) => /* @__PURE__ */ jsx11("path", {
1306
1707
  d: item.path,
1307
1708
  fill: item.color,
1308
1709
  className: "altertable-pie-slice",
1309
- "data-active": item.id === (plotOpen && previewId ? previewId : legendId) || undefined,
1710
+ "data-active": item.id === (plotOpen && previewId ? previewId : showLegend ? legendId : null) || undefined,
1310
1711
  onPointerDown: (event) => {
1311
1712
  if (plotOpen && previewId !== item.id)
1312
1713
  event.preventDefault();
@@ -1323,40 +1724,44 @@ function PieChart({
1323
1724
  }, item.id))
1324
1725
  })
1325
1726
  }),
1326
- total === 0 && /* @__PURE__ */ jsx9("span", {
1727
+ total === 0 && /* @__PURE__ */ jsx11("span", {
1327
1728
  className: "altertable-pie-empty-label",
1328
1729
  children: "No nonzero values"
1329
1730
  })
1330
1731
  ]
1331
1732
  }),
1332
- /* @__PURE__ */ jsx9("div", {
1733
+ showLegend && /* @__PURE__ */ jsx11(ChartLegend, {
1734
+ layout: "vertical",
1735
+ "aria-label": `${ariaLabel} legend`,
1333
1736
  className: "altertable-pie-legend",
1334
- children: slices.map((item) => /* @__PURE__ */ jsx9(Tooltip2, {
1335
- variant: "chart",
1336
- content: detail(item),
1337
- onOpenChange: (open) => setLegendId((current) => open ? item.id : current === item.id ? null : current),
1338
- children: /* @__PURE__ */ jsxs5("button", {
1339
- "data-atbl-focus": "ring",
1340
- "data-atbl-control": "action",
1341
- type: "button",
1342
- tabIndex: -1,
1343
- "aria-label": `${item.label}: ${formatValue(item.value)} ${unit}, ${percent(item.share)}`,
1344
- children: [
1345
- /* @__PURE__ */ jsx9("span", {
1346
- className: "altertable-pie-key",
1347
- style: { background: item.color }
1348
- }),
1349
- /* @__PURE__ */ jsx9("span", {
1350
- className: "altertable-pie-label",
1351
- children: item.label
1352
- }),
1353
- /* @__PURE__ */ jsx9("strong", {
1354
- children: formatValue(item.value)
1355
- }),
1356
- /* @__PURE__ */ jsx9("span", {
1357
- children: percent(item.share)
1358
- })
1359
- ]
1737
+ children: slices.map((item) => /* @__PURE__ */ jsx11(ChartLegend.Item, {
1738
+ children: /* @__PURE__ */ jsx11(Tooltip2, {
1739
+ variant: "chart",
1740
+ content: detail(item),
1741
+ onOpenChange: (open) => setLegendId((current) => open ? item.id : current === item.id ? null : current),
1742
+ children: /* @__PURE__ */ jsxs7("button", {
1743
+ "data-atbl-focus": "ring",
1744
+ "data-atbl-control": "default",
1745
+ type: "button",
1746
+ tabIndex: -1,
1747
+ "aria-label": `${item.label}: ${formatValue(item.value)} ${unit}, ${percent(item.share)}`,
1748
+ children: [
1749
+ /* @__PURE__ */ jsx11(ChartLegend.Marker, {
1750
+ kind: "square",
1751
+ color: item.color
1752
+ }),
1753
+ /* @__PURE__ */ jsx11(ChartLegend.Label, {
1754
+ className: "altertable-pie-label",
1755
+ children: item.label
1756
+ }),
1757
+ /* @__PURE__ */ jsx11("strong", {
1758
+ children: formatValue(item.value)
1759
+ }),
1760
+ /* @__PURE__ */ jsx11("span", {
1761
+ children: percent(item.share)
1762
+ })
1763
+ ]
1764
+ })
1360
1765
  })
1361
1766
  }, item.id))
1362
1767
  })
@@ -1366,7 +1771,7 @@ function PieChart({
1366
1771
  });
1367
1772
  }
1368
1773
  // src/react/ui/ScatterChart.tsx
1369
- import { jsx as jsx10, jsxs as jsxs6, Fragment as Fragment3 } from "react/jsx-runtime";
1774
+ import { jsx as jsx12, jsxs as jsxs8, Fragment as Fragment4 } from "react/jsx-runtime";
1370
1775
  function formatNumber(value) {
1371
1776
  return new Intl.NumberFormat(undefined, { maximumFractionDigits: 1 }).format(value);
1372
1777
  }
@@ -1395,37 +1800,37 @@ function ScatterChart({
1395
1800
  validateChartItems("scatter", items);
1396
1801
  const xScale = scale(items.map((item) => item.x));
1397
1802
  const yScale = scale(items.map((item) => item.y));
1398
- return /* @__PURE__ */ jsxs6("section", {
1803
+ return /* @__PURE__ */ jsxs8("section", {
1399
1804
  className: "altertable-scatter-chart",
1400
1805
  "aria-label": ariaLabel,
1401
1806
  children: [
1402
- items.length === 0 && /* @__PURE__ */ jsx10("span", {
1807
+ items.length === 0 && /* @__PURE__ */ jsx12("span", {
1403
1808
  children: "No data"
1404
1809
  }),
1405
- items.length > 0 && /* @__PURE__ */ jsxs6(Fragment3, {
1810
+ items.length > 0 && /* @__PURE__ */ jsxs8(Fragment4, {
1406
1811
  children: [
1407
- /* @__PURE__ */ jsxs6("p", {
1812
+ /* @__PURE__ */ jsxs8("p", {
1408
1813
  className: "altertable-scatter-axis-title",
1409
1814
  children: [
1410
1815
  yLabel,
1411
1816
  yUnit && ` (${yUnit})`
1412
1817
  ]
1413
1818
  }),
1414
- /* @__PURE__ */ jsx10("div", {
1819
+ /* @__PURE__ */ jsx12("div", {
1415
1820
  className: "altertable-scatter-axes",
1416
- children: /* @__PURE__ */ jsxs6("div", {
1821
+ children: /* @__PURE__ */ jsxs8("div", {
1417
1822
  className: "altertable-scatter-plot",
1418
1823
  children: [
1419
- yScale.ticks.map((value, index) => /* @__PURE__ */ jsx10("div", {
1824
+ yScale.ticks.map((value, index) => /* @__PURE__ */ jsx12("div", {
1420
1825
  className: "altertable-scatter-gridline",
1421
1826
  style: { bottom: `${index * 50}%` },
1422
1827
  "aria-hidden": "true",
1423
- children: /* @__PURE__ */ jsx10("span", {
1828
+ children: /* @__PURE__ */ jsx12("span", {
1424
1829
  title: formatY(value),
1425
1830
  children: formatY(value)
1426
1831
  })
1427
1832
  }, index)),
1428
- xScale.ticks.map((value, index) => /* @__PURE__ */ jsx10("span", {
1833
+ xScale.ticks.map((value, index) => /* @__PURE__ */ jsx12("span", {
1429
1834
  className: "altertable-scatter-x-tick",
1430
1835
  "data-edge": index,
1431
1836
  style: { left: `${index * 50}%` },
@@ -1433,14 +1838,14 @@ function ScatterChart({
1433
1838
  title: formatX(value),
1434
1839
  children: formatX(value)
1435
1840
  }, index)),
1436
- items.map((item) => /* @__PURE__ */ jsx10(Tooltip2, {
1841
+ items.map((item) => /* @__PURE__ */ jsx12(Tooltip2, {
1437
1842
  variant: "chart",
1438
- content: /* @__PURE__ */ jsxs6(Fragment3, {
1843
+ content: /* @__PURE__ */ jsxs8(Fragment4, {
1439
1844
  children: [
1440
- /* @__PURE__ */ jsx10("strong", {
1845
+ /* @__PURE__ */ jsx12("strong", {
1441
1846
  children: item.label
1442
1847
  }),
1443
- /* @__PURE__ */ jsxs6("span", {
1848
+ /* @__PURE__ */ jsxs8("span", {
1444
1849
  children: [
1445
1850
  xLabel,
1446
1851
  ": ",
@@ -1449,7 +1854,7 @@ function ScatterChart({
1449
1854
  xUnit
1450
1855
  ]
1451
1856
  }),
1452
- /* @__PURE__ */ jsxs6("span", {
1857
+ /* @__PURE__ */ jsxs8("span", {
1453
1858
  children: [
1454
1859
  yLabel,
1455
1860
  ": ",
@@ -1465,20 +1870,20 @@ function ScatterChart({
1465
1870
  left: `${xScale.position(item.x)}%`,
1466
1871
  bottom: `${yScale.position(item.y)}%`
1467
1872
  },
1468
- children: /* @__PURE__ */ jsx10("button", {
1873
+ children: /* @__PURE__ */ jsx12("button", {
1469
1874
  "data-atbl-focus": "ring",
1470
- "data-atbl-control": "action",
1875
+ "data-atbl-control": "default",
1471
1876
  type: "button",
1472
1877
  tabIndex: -1,
1473
1878
  className: "altertable-scatter-point",
1474
1879
  "aria-label": `${item.label}: ${xLabel} ${formatX(item.x)} ${xUnit}, ${yLabel} ${formatY(item.y)} ${yUnit}`,
1475
- children: /* @__PURE__ */ jsx10("span", {})
1880
+ children: /* @__PURE__ */ jsx12("span", {})
1476
1881
  })
1477
1882
  }, item.id))
1478
1883
  ]
1479
1884
  })
1480
1885
  }),
1481
- /* @__PURE__ */ jsxs6("p", {
1886
+ /* @__PURE__ */ jsxs8("p", {
1482
1887
  className: "altertable-scatter-axis-title altertable-scatter-x-title",
1483
1888
  children: [
1484
1889
  xLabel,
@@ -1491,8 +1896,9 @@ function ScatterChart({
1491
1896
  });
1492
1897
  }
1493
1898
  // src/react/ui/BarChart.tsx
1494
- import { useLayoutEffect as useLayoutEffect2, useRef as useRef2 } from "react";
1495
- import { jsx as jsx11, jsxs as jsxs7, Fragment as Fragment4 } from "react/jsx-runtime";
1899
+ import { useLayoutEffect as useLayoutEffect2, useRef as useRef2, useState as useState4 } from "react";
1900
+ import { BarChart as RechartsBarChart, Cell } from "recharts";
1901
+ import { jsx as jsx13, jsxs as jsxs9, Fragment as Fragment5 } from "react/jsx-runtime";
1496
1902
  function BarChart({
1497
1903
  items,
1498
1904
  unit,
@@ -1500,6 +1906,7 @@ function BarChart({
1500
1906
  formatValue = (value) => new Intl.NumberFormat().format(value)
1501
1907
  }) {
1502
1908
  validateChartItems("bar", items);
1909
+ const [activeId, setActiveId] = useState4();
1503
1910
  const scrollport = useRef2(null);
1504
1911
  useLayoutEffect2(() => {
1505
1912
  const element = scrollport.current;
@@ -1507,59 +1914,1152 @@ function BarChart({
1507
1914
  element.scrollLeft = element.scrollWidth - element.clientWidth;
1508
1915
  }, [items]);
1509
1916
  const maximum = items.reduce((max, item) => Math.max(max, item.value), 1);
1510
- return /* @__PURE__ */ jsxs7("section", {
1917
+ return /* @__PURE__ */ jsxs9("section", {
1511
1918
  className: "altertable-selectable-bars",
1512
1919
  "aria-label": ariaLabel,
1513
1920
  children: [
1514
- items.length === 0 && /* @__PURE__ */ jsx11("span", {
1921
+ items.length === 0 && /* @__PURE__ */ jsx13("span", {
1515
1922
  children: "No data"
1516
1923
  }),
1517
- /* @__PURE__ */ jsx11("div", {
1924
+ /* @__PURE__ */ jsx13("div", {
1518
1925
  className: "altertable-selectable-bars-scroll",
1519
1926
  ref: scrollport,
1520
- children: items.map((item) => /* @__PURE__ */ jsx11(Tooltip2, {
1521
- variant: "chart",
1522
- content: /* @__PURE__ */ jsxs7(Fragment4, {
1927
+ children: /* @__PURE__ */ jsxs9("div", {
1928
+ className: "altertable-selectable-bars-plot",
1929
+ style: { minWidth: items.length * 45 },
1930
+ children: [
1931
+ /* @__PURE__ */ jsx13("div", {
1932
+ className: "altertable-chart-canvas",
1933
+ "aria-hidden": "true",
1934
+ children: /* @__PURE__ */ jsxs9(RechartsBarChart, {
1935
+ data: items.map((item) => ({
1936
+ ...item,
1937
+ plotValue: item.value / maximum
1938
+ })),
1939
+ title: ariaLabel,
1940
+ responsive: true,
1941
+ width: "100%",
1942
+ height: 220,
1943
+ margin: { top: 0, right: 0, bottom: 0, left: 0 },
1944
+ accessibilityLayer: false,
1945
+ tabIndex: -1,
1946
+ barCategoryGap: "24%",
1947
+ children: [
1948
+ /* @__PURE__ */ jsx13(ChartXAxis, {
1949
+ hide: true,
1950
+ dataKey: "id",
1951
+ scale: "band"
1952
+ }),
1953
+ /* @__PURE__ */ jsx13(ChartYAxis, {
1954
+ hide: true,
1955
+ domain: [0, 1],
1956
+ allowDataOverflow: true
1957
+ }),
1958
+ /* @__PURE__ */ jsx13(ChartBar, {
1959
+ dataKey: "plotValue",
1960
+ minPointSize: (value) => (value ?? 0) > 0 ? 7 : 0,
1961
+ children: items.map((item) => /* @__PURE__ */ jsx13(Cell, {
1962
+ fill: activeId === item.id ? "var(--atbl-accent)" : chartBarFill
1963
+ }, item.id))
1964
+ })
1965
+ ]
1966
+ })
1967
+ }),
1968
+ /* @__PURE__ */ jsx13("div", {
1969
+ className: "altertable-selectable-bars-items",
1970
+ children: items.map((item) => /* @__PURE__ */ jsx13(Tooltip2, {
1971
+ variant: "chart",
1972
+ onOpenChange: (open) => setActiveId((current) => open ? item.id : current === item.id ? undefined : current),
1973
+ content: /* @__PURE__ */ jsxs9(Fragment5, {
1974
+ children: [
1975
+ /* @__PURE__ */ jsx13("strong", {
1976
+ children: item.label
1977
+ }),
1978
+ /* @__PURE__ */ jsxs9("span", {
1979
+ children: [
1980
+ formatValue(item.value),
1981
+ " ",
1982
+ unit
1983
+ ]
1984
+ })
1985
+ ]
1986
+ }),
1987
+ className: "altertable-chart-column",
1988
+ children: /* @__PURE__ */ jsxs9("button", {
1989
+ "data-atbl-focus": "ring",
1990
+ "data-atbl-control": "default",
1991
+ type: "button",
1992
+ tabIndex: -1,
1993
+ className: "altertable-selectable-bars-item",
1994
+ "aria-label": `${item.label}: ${formatValue(item.value)} ${unit}`,
1995
+ children: [
1996
+ /* @__PURE__ */ jsx13("span", {
1997
+ className: "altertable-selectable-bars-area"
1998
+ }),
1999
+ /* @__PURE__ */ jsx13("span", {
2000
+ className: "altertable-selectable-bars-label",
2001
+ children: item.label
2002
+ })
2003
+ ]
2004
+ })
2005
+ }, item.id))
2006
+ })
2007
+ ]
2008
+ })
2009
+ })
2010
+ ]
2011
+ });
2012
+ }
2013
+ // src/react/ui/RadioGroup.tsx
2014
+ import {
2015
+ RadioGroup as AriaRadioGroup,
2016
+ Radio as AriaRadio,
2017
+ Label
2018
+ } from "react-aria-components";
2019
+ import { jsx as jsx14, jsxs as jsxs10, Fragment as Fragment6 } from "react/jsx-runtime";
2020
+ function RadioGroup({
2021
+ label,
2022
+ className,
2023
+ children,
2024
+ ...props
2025
+ }) {
2026
+ return /* @__PURE__ */ jsxs10(AriaRadioGroup, {
2027
+ ...props,
2028
+ className: classNames("altertable-radio-group", className),
2029
+ children: [
2030
+ /* @__PURE__ */ jsx14(Label, {
2031
+ className: "altertable-control-label",
2032
+ children: label
2033
+ }),
2034
+ children
2035
+ ]
2036
+ });
2037
+ }
2038
+ function Radio({ className, children, ...props }) {
2039
+ return /* @__PURE__ */ jsx14(AriaRadio, {
2040
+ ...props,
2041
+ "data-atbl-control": "action",
2042
+ "data-atbl-focus": "ring",
2043
+ className: classNames("altertable-radio", className),
2044
+ children: (state) => /* @__PURE__ */ jsxs10(Fragment6, {
2045
+ children: [
2046
+ /* @__PURE__ */ jsx14("span", {
2047
+ className: "altertable-radio-mark",
2048
+ "aria-hidden": "true"
2049
+ }),
2050
+ typeof children === "function" ? children(state) : children
2051
+ ]
2052
+ })
2053
+ });
2054
+ }
2055
+ function SegmentedControl({
2056
+ options,
2057
+ className,
2058
+ ...props
2059
+ }) {
2060
+ validateChoiceOptions(options);
2061
+ return /* @__PURE__ */ jsx14(RadioGroup, {
2062
+ ...props,
2063
+ orientation: "horizontal",
2064
+ className: classNames("altertable-segmented-control", className),
2065
+ children: /* @__PURE__ */ jsx14("div", {
2066
+ className: "altertable-segmented-options",
2067
+ children: options.map((option) => /* @__PURE__ */ jsx14(Radio, {
2068
+ value: option.id,
2069
+ "data-atbl-internal-surface": "option",
2070
+ children: option.label
2071
+ }, option.id))
2072
+ })
2073
+ });
2074
+ }
2075
+ // src/react/ui/CheckboxGroup.tsx
2076
+ import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
2077
+ function CheckboxGroup({
2078
+ label,
2079
+ values,
2080
+ onChange,
2081
+ className,
2082
+ children,
2083
+ disabled,
2084
+ ...props
2085
+ }) {
2086
+ invariant(new Set(values).size === values.length, "Checkbox group values must be unique.");
2087
+ return /* @__PURE__ */ jsxs11("fieldset", {
2088
+ ...props,
2089
+ disabled,
2090
+ className: classNames("altertable-checkbox-group", className),
2091
+ children: [
2092
+ /* @__PURE__ */ jsx15("legend", {
2093
+ children: label
2094
+ }),
2095
+ /* @__PURE__ */ jsx15(CheckboxGroupContext, {
2096
+ value: {
2097
+ values,
2098
+ disabled,
2099
+ onChange(value, checked) {
2100
+ onChange(checked ? [...values.filter((item) => item !== value), value] : values.filter((item) => item !== value));
2101
+ }
2102
+ },
2103
+ children
2104
+ })
2105
+ ]
2106
+ });
2107
+ }
2108
+ // src/react/ui/ComposedChart.tsx
2109
+ import { useState as useState5 } from "react";
2110
+
2111
+ // src/react/ui/ComposedChartLegend.tsx
2112
+ import { Legend } from "recharts";
2113
+ import { jsx as jsx16, jsxs as jsxs12, Fragment as Fragment7 } from "react/jsx-runtime";
2114
+ var legendMarkerKinds = {
2115
+ square: "area",
2116
+ rect: "bar",
2117
+ line: "line",
2118
+ plainline: "line"
2119
+ };
2120
+ function LegendContent({
2121
+ payload = [],
2122
+ maxVisibleItems,
2123
+ formatter,
2124
+ layout,
2125
+ align,
2126
+ labelStyle,
2127
+ iconType,
2128
+ iconSize = 8,
2129
+ inactiveColor = "var(--atbl-muted)",
2130
+ position,
2131
+ verticalAlign,
2132
+ onClick,
2133
+ onMouseEnter,
2134
+ onMouseLeave,
2135
+ "aria-label": ariaLabel
2136
+ }) {
2137
+ if (!payload.some((entry) => entry.type !== "none"))
2138
+ return null;
2139
+ const vertical = layout === "vertical" || layout === "auto" && (position === "left" || position === "right");
2140
+ return /* @__PURE__ */ jsx16(ChartLegend, {
2141
+ "aria-label": ariaLabel,
2142
+ className: position === "top" || position == null && verticalAlign === "top" ? "altertable-composed-chart-legend-above" : position === "bottom" || position == null && verticalAlign !== "middle" && !vertical ? "altertable-composed-chart-legend-below" : undefined,
2143
+ maxVisibleItems,
2144
+ layout: vertical ? "vertical" : "horizontal",
2145
+ align: align === "right" ? "end" : align === "left" ? "start" : "center",
2146
+ children: payload.map((entry, index) => {
2147
+ if (entry.type === "none")
2148
+ return null;
2149
+ const type = iconType ?? entry.type;
2150
+ const kind = iconType ? legendMarkerKinds[type ?? "none"] ?? "point" : "square";
2151
+ const formatLabel = entry.formatter ?? formatter;
2152
+ const label = formatLabel ? formatLabel(entry.value, entry, index) : entry.value;
2153
+ const content = /* @__PURE__ */ jsxs12(Fragment7, {
2154
+ children: [
2155
+ type !== "none" && (entry.legendIcon ?? /* @__PURE__ */ jsx16(ChartLegend.Marker, {
2156
+ kind,
2157
+ color: entry.inactive ? inactiveColor : entry.color,
2158
+ style: {
2159
+ width: iconSize,
2160
+ height: kind === "square" ? iconSize : iconSize * 0.6
2161
+ }
2162
+ })),
2163
+ /* @__PURE__ */ jsx16(ChartLegend.Label, {
2164
+ style: labelStyle,
2165
+ children: label
2166
+ })
2167
+ ]
2168
+ });
2169
+ return /* @__PURE__ */ jsx16(ChartLegend.Item, {
2170
+ inactive: entry.inactive,
2171
+ onMouseEnter: onMouseEnter ? (event) => onMouseEnter(entry, index, event) : undefined,
2172
+ onMouseLeave: onMouseLeave ? (event) => onMouseLeave(entry, index, event) : undefined,
2173
+ children: onClick ? /* @__PURE__ */ jsx16("button", {
2174
+ type: "button",
2175
+ "data-atbl-control": "action",
2176
+ "data-atbl-focus": "ring",
2177
+ className: "altertable-chart-legend-action",
2178
+ onClick: (event) => onClick(entry, index, event),
2179
+ children: content
2180
+ }) : content
2181
+ }, index);
2182
+ })
2183
+ });
2184
+ }
2185
+ function ComposedChartLegend({
2186
+ maxVisibleItems,
2187
+ ...props
2188
+ }) {
2189
+ return /* @__PURE__ */ jsx16(Legend, {
2190
+ iconSize: 8,
2191
+ inactiveColor: "var(--atbl-muted)",
2192
+ content: /* @__PURE__ */ jsx16(LegendContent, {
2193
+ maxVisibleItems
2194
+ }),
2195
+ ...props
2196
+ });
2197
+ }
2198
+
2199
+ // src/react/ui/ComposedChart.tsx
2200
+ import {
2201
+ ComposedChart as RechartsComposedChart,
2202
+ ZAxis,
2203
+ ReferenceLine as ReferenceLine2,
2204
+ ReferenceArea,
2205
+ ReferenceDot,
2206
+ Brush,
2207
+ Cell as Cell2,
2208
+ Label as Label2,
2209
+ LabelList,
2210
+ ErrorBar
2211
+ } from "recharts";
2212
+ import { jsx as jsx17 } from "react/jsx-runtime";
2213
+ function ComposedChartRoot({
2214
+ data,
2215
+ ariaLabel,
2216
+ width = "100%",
2217
+ height = 300,
2218
+ responsive = true,
2219
+ className,
2220
+ ...props
2221
+ }) {
2222
+ const [pointerFocus, setPointerFocus] = useState5(false);
2223
+ return /* @__PURE__ */ jsx17("section", {
2224
+ className: ["altertable-composed-chart", className].filter(Boolean).join(" "),
2225
+ "aria-label": ariaLabel,
2226
+ "data-atbl-internal-pointer-focus": pointerFocus || undefined,
2227
+ onPointerDownCapture: () => setPointerFocus(true),
2228
+ onKeyDownCapture: () => setPointerFocus(false),
2229
+ onBlurCapture: (event) => {
2230
+ if (!event.currentTarget.contains(event.relatedTarget))
2231
+ setPointerFocus(false);
2232
+ },
2233
+ children: /* @__PURE__ */ jsx17(RechartsComposedChart, {
2234
+ "data-atbl-focus": "inset",
2235
+ ...props,
2236
+ data,
2237
+ width,
2238
+ height,
2239
+ responsive,
2240
+ title: ariaLabel
2241
+ })
2242
+ });
2243
+ }
2244
+ var ComposedChart = Object.assign(ComposedChartRoot, {
2245
+ Dot: ChartDot,
2246
+ Area: ChartArea,
2247
+ Bar: ChartBar,
2248
+ Line: ChartLine,
2249
+ Scatter: ChartScatter,
2250
+ XAxis: ChartXAxis,
2251
+ YAxis: ChartYAxis,
2252
+ ZAxis,
2253
+ CartesianGrid: ChartGrid,
2254
+ Tooltip: ChartTooltip,
2255
+ Legend: ComposedChartLegend,
2256
+ ReferenceLine: ReferenceLine2,
2257
+ ReferenceArea,
2258
+ ReferenceDot,
2259
+ Brush,
2260
+ Cell: Cell2,
2261
+ Label: Label2,
2262
+ LabelList,
2263
+ ErrorBar
2264
+ });
2265
+ // src/react/ui/FunnelChart.tsx
2266
+ import { Fragment as Fragment8, useEffect, useRef as useRef3, useState as useState6 } from "react";
2267
+
2268
+ // src/react/ui/useSvgId.ts
2269
+ import { useId as useId2 } from "react";
2270
+ function useSvgId() {
2271
+ const id = useId2();
2272
+ return `svg-${Array.from(id, (character) => /[a-zA-Z0-9-]/.test(character) ? character : `_${character.codePointAt(0).toString(16)}_`).join("")}`;
2273
+ }
2274
+
2275
+ // src/react/ui/FunnelChart.tsx
2276
+ import { jsx as jsx18, jsxs as jsxs13, Fragment as Fragment9 } from "react/jsx-runtime";
2277
+ function FunnelChart({
2278
+ steps,
2279
+ series,
2280
+ unit,
2281
+ ariaLabel,
2282
+ className,
2283
+ formatValue = formatNumber2
2284
+ }) {
2285
+ const chartId = useSvgId();
2286
+ const [highlighted, setHighlighted] = useState6(null);
2287
+ const clearTimer = useRef3(null);
2288
+ function cancelClear() {
2289
+ if (clearTimer.current !== null)
2290
+ clearTimeout(clearTimer.current);
2291
+ clearTimer.current = null;
2292
+ }
2293
+ useEffect(() => cancelClear, []);
2294
+ function highlight(id, conversion, step) {
2295
+ cancelClear();
2296
+ setHighlighted({ id, conversion, step });
2297
+ }
2298
+ function clearHighlight() {
2299
+ cancelClear();
2300
+ setHighlighted(null);
2301
+ }
2302
+ function scheduleClear() {
2303
+ cancelClear();
2304
+ clearTimer.current = setTimeout(() => setHighlighted(null), 100);
2305
+ }
2306
+ for (const [kind, items] of [
2307
+ ["funnel step", steps],
2308
+ ["funnel series", series]
2309
+ ]) {
2310
+ const ids = new Set;
2311
+ for (const item of items) {
2312
+ invariant(item.id.trim().length > 0 && !ids.has(item.id), `${kind} chart IDs must be nonblank and unique.`);
2313
+ ids.add(item.id);
2314
+ }
2315
+ }
2316
+ for (const item of series) {
2317
+ invariant(item.values.length === steps.length, "funnel chart counts must align with steps.");
2318
+ item.values.forEach((value, index) => {
2319
+ invariant(Number.isFinite(value) && value >= 0, "funnel chart counts must be finite and nonnegative.");
2320
+ invariant(index === 0 || value <= item.values[index - 1], "funnel chart counts must be nonincreasing.");
2321
+ });
2322
+ }
2323
+ function reading(item, index) {
2324
+ const first = item.values[0] ?? 0;
2325
+ const current = item.values[index];
2326
+ const previous = item.values[Math.max(0, index - 1)];
2327
+ return {
2328
+ current,
2329
+ previous,
2330
+ dropped: previous - current,
2331
+ conversion: first > 0 ? current / first : null,
2332
+ dropOff: first > 0 ? (previous - current) / first : null
2333
+ };
2334
+ }
2335
+ const data = steps.map((step, index) => ({
2336
+ label: step.label,
2337
+ index,
2338
+ ...Object.fromEntries(series.flatMap((item, seriesIndex) => {
2339
+ const value = reading(item, index);
2340
+ return [
2341
+ [`conversion${seriesIndex}`, (value.conversion ?? 0) * 100],
2342
+ [
2343
+ `drop${seriesIndex}`,
2344
+ (value.dropOff ?? (index === 0 ? 1 : 0)) * 100
2345
+ ]
2346
+ ];
2347
+ }))
2348
+ }));
2349
+ return /* @__PURE__ */ jsx18("section", {
2350
+ className: classNames("altertable-funnel-chart", className),
2351
+ "aria-label": ariaLabel,
2352
+ onKeyDownCapture: clearHighlight,
2353
+ children: steps.length === 0 || series.length === 0 ? /* @__PURE__ */ jsx18("span", {
2354
+ children: "No data"
2355
+ }) : /* @__PURE__ */ jsxs13(Fragment9, {
2356
+ children: [
2357
+ /* @__PURE__ */ jsx18("div", {
2358
+ className: "altertable-funnel-summary",
2359
+ children: series.map((item, index) => /* @__PURE__ */ jsxs13("div", {
1523
2360
  children: [
1524
- /* @__PURE__ */ jsx11("strong", {
1525
- children: item.label
1526
- }),
1527
- /* @__PURE__ */ jsxs7("span", {
2361
+ /* @__PURE__ */ jsxs13("span", {
1528
2362
  children: [
1529
- formatValue(item.value),
1530
- " ",
1531
- unit
2363
+ /* @__PURE__ */ jsx18(ChartLegend.Marker, {
2364
+ kind: "square",
2365
+ color: `var(--atbl-chart-${index % 8 + 1})`
2366
+ }),
2367
+ item.label
1532
2368
  ]
2369
+ }),
2370
+ /* @__PURE__ */ jsx18("strong", {
2371
+ children: formatPercent2(reading(item, steps.length - 1).conversion)
1533
2372
  })
1534
2373
  ]
1535
- }),
1536
- className: "altertable-chart-column",
1537
- children: /* @__PURE__ */ jsxs7("button", {
1538
- "data-atbl-focus": "ring",
1539
- "data-atbl-control": "action",
1540
- type: "button",
1541
- tabIndex: -1,
1542
- className: "altertable-selectable-bars-item",
1543
- "aria-label": `${item.label}: ${formatValue(item.value)} ${unit}`,
1544
- children: [
1545
- /* @__PURE__ */ jsx11("span", {
1546
- className: "altertable-selectable-bars-area",
1547
- children: /* @__PURE__ */ jsx11("span", {
1548
- className: "altertable-selectable-bars-bar",
1549
- style: {
1550
- height: `${Math.max(item.value > 0 ? 3 : 0, item.value / maximum * 100)}%`
2374
+ }, item.id))
2375
+ }),
2376
+ /* @__PURE__ */ jsxs13("p", {
2377
+ className: "altertable-analytics-note",
2378
+ children: [
2379
+ steps[0].label,
2380
+ " → ",
2381
+ steps.at(-1).label
2382
+ ]
2383
+ }),
2384
+ /* @__PURE__ */ jsx18("div", {
2385
+ className: "altertable-funnel-scroll",
2386
+ children: /* @__PURE__ */ jsx18("div", {
2387
+ style: {
2388
+ minWidth: steps.length * Math.max(96, series.length * 40)
2389
+ },
2390
+ children: /* @__PURE__ */ jsxs13(ComposedChart, {
2391
+ data,
2392
+ ariaLabel: `${ariaLabel} plot`,
2393
+ height: 300,
2394
+ barCategoryGap: "4%",
2395
+ barGap: 1,
2396
+ onMouseLeave: clearHighlight,
2397
+ children: [
2398
+ /* @__PURE__ */ jsx18(ComposedChart.CartesianGrid, {}),
2399
+ /* @__PURE__ */ jsx18(ComposedChart.XAxis, {
2400
+ dataKey: "label",
2401
+ interval: 0,
2402
+ height: 48
2403
+ }),
2404
+ /* @__PURE__ */ jsx18(ComposedChart.YAxis, {
2405
+ domain: [0, 100],
2406
+ width: 42,
2407
+ tickFormatter: (value) => `${value}%`
2408
+ }),
2409
+ /* @__PURE__ */ jsx18("defs", {
2410
+ children: series.map((item, index) => /* @__PURE__ */ jsxs13(Fragment8, {
2411
+ children: [
2412
+ /* @__PURE__ */ jsxs13("linearGradient", {
2413
+ id: `${chartId}-gradient-${index}`,
2414
+ x1: "0",
2415
+ x2: "0",
2416
+ y1: "1",
2417
+ y2: "0",
2418
+ children: [
2419
+ /* @__PURE__ */ jsx18("stop", {
2420
+ offset: "0%",
2421
+ stopColor: `var(--atbl-chart-${index % 8 + 1})`,
2422
+ stopOpacity: 0.5
2423
+ }),
2424
+ /* @__PURE__ */ jsx18("stop", {
2425
+ offset: "100%",
2426
+ stopColor: `var(--atbl-chart-${index % 8 + 1})`,
2427
+ stopOpacity: 0.7
2428
+ })
2429
+ ]
2430
+ }),
2431
+ /* @__PURE__ */ jsxs13("pattern", {
2432
+ id: `${chartId}-hatch-${index}`,
2433
+ patternUnits: "userSpaceOnUse",
2434
+ width: "6",
2435
+ height: "6",
2436
+ patternTransform: "rotate(45)",
2437
+ children: [
2438
+ /* @__PURE__ */ jsx18("rect", {
2439
+ width: "8",
2440
+ height: "8",
2441
+ fill: `var(--atbl-chart-${index % 8 + 1})`,
2442
+ fillOpacity: 0.16
2443
+ }),
2444
+ /* @__PURE__ */ jsx18("line", {
2445
+ x1: "0",
2446
+ y1: "0",
2447
+ x2: "0",
2448
+ y2: "8",
2449
+ stroke: `var(--atbl-chart-${index % 8 + 1})`,
2450
+ strokeOpacity: 0.4,
2451
+ strokeWidth: "2"
2452
+ })
2453
+ ]
2454
+ })
2455
+ ]
2456
+ }, item.id))
2457
+ }),
2458
+ series.map((item, index) => /* @__PURE__ */ jsxs13(Fragment8, {
2459
+ children: [
2460
+ /* @__PURE__ */ jsx18(ComposedChart.Bar, {
2461
+ dataKey: `conversion${index}`,
2462
+ name: item.label,
2463
+ stackId: item.id,
2464
+ fill: `url(#${chartId}-gradient-${index})`,
2465
+ radius: 0,
2466
+ maxBarSize: undefined,
2467
+ activeBar: false,
2468
+ minPointSize: 0,
2469
+ opacity: !highlighted || highlighted.id === item.id ? 1 : 0.2,
2470
+ onMouseEnter: (bar) => highlight(item.id, true, bar.payload.index),
2471
+ onMouseLeave: scheduleClear,
2472
+ onClick: (bar) => highlight(item.id, true, bar.payload.index)
2473
+ }),
2474
+ /* @__PURE__ */ jsx18(ComposedChart.Bar, {
2475
+ dataKey: `drop${index}`,
2476
+ name: `${item.label} drop-off`,
2477
+ stackId: item.id,
2478
+ fill: `url(#${chartId}-hatch-${index})`,
2479
+ opacity: !highlighted || highlighted.id === item.id ? 1 : 0.2,
2480
+ onMouseEnter: (bar) => highlight(item.id, false, bar.payload.index),
2481
+ onMouseLeave: scheduleClear,
2482
+ onClick: (bar) => highlight(item.id, false, bar.payload.index),
2483
+ radius: 0,
2484
+ maxBarSize: undefined,
2485
+ activeBar: false
2486
+ })
2487
+ ]
2488
+ }, item.id)),
2489
+ /* @__PURE__ */ jsx18(ComposedChart.Tooltip, {
2490
+ content: ({ active, payload }) => {
2491
+ const index = payload?.[0]?.payload.index;
2492
+ if (!active || typeof index !== "number")
2493
+ return null;
2494
+ const tooltipItems = series.filter((item) => !highlighted || highlighted.step !== index || item.id === highlighted.id).flatMap((item) => {
2495
+ const value = reading(item, index);
2496
+ const selected = highlighted?.step === index;
2497
+ const readings = selected ? [highlighted.conversion] : [true, false];
2498
+ const seriesIndex = series.indexOf(item);
2499
+ return readings.map((conversion) => ({
2500
+ name: item.label,
2501
+ graphicalItemId: `${item.id}-${conversion ? "conversion" : "drop-off"}`,
2502
+ dataKey: `${item.id}-${conversion ? "conversion" : "drop-off"}`,
2503
+ color: `var(--atbl-chart-${seriesIndex % 8 + 1})`,
2504
+ value: (conversion ? value.conversion : value.dropOff) ?? 0,
2505
+ formatter: () => /* @__PURE__ */ jsxs13(Fragment9, {
2506
+ children: [
2507
+ formatPercent2(conversion ? value.conversion : value.dropOff),
2508
+ " ",
2509
+ conversion ? "conversion" : "drop-off",
2510
+ /* @__PURE__ */ jsxs13("span", {
2511
+ className: "altertable-funnel-tooltip-detail",
2512
+ children: [
2513
+ formatValue(conversion ? value.current : value.dropped),
2514
+ " ",
2515
+ "of ",
2516
+ formatValue(value.previous),
2517
+ " ",
2518
+ unit
2519
+ ]
2520
+ })
2521
+ ]
2522
+ })
2523
+ }));
2524
+ });
2525
+ return /* @__PURE__ */ jsx18(ChartTooltipContent, {
2526
+ active: true,
2527
+ label: steps[index].label,
2528
+ payload: tooltipItems
2529
+ });
1551
2530
  }
1552
2531
  })
2532
+ ]
2533
+ })
2534
+ })
2535
+ }),
2536
+ /* @__PURE__ */ jsx18("ul", {
2537
+ className: "altertable-sr-only",
2538
+ children: steps.map((step, index) => /* @__PURE__ */ jsxs13("li", {
2539
+ children: [
2540
+ step.label,
2541
+ series.map((item) => {
2542
+ const value = reading(item, index);
2543
+ return /* @__PURE__ */ jsxs13("span", {
2544
+ children: [
2545
+ " ",
2546
+ "· ",
2547
+ item.label,
2548
+ ": ",
2549
+ formatValue(value.current),
2550
+ " ",
2551
+ unit,
2552
+ ",",
2553
+ " ",
2554
+ formatPercent2(value.conversion),
2555
+ " conversion,",
2556
+ " ",
2557
+ formatValue(value.dropped),
2558
+ " dropped off"
2559
+ ]
2560
+ }, item.id);
2561
+ })
2562
+ ]
2563
+ }, step.id))
2564
+ })
2565
+ ]
2566
+ })
2567
+ });
2568
+ }
2569
+ // src/react/ui/RetentionChart.tsx
2570
+ import { Fragment as Fragment10 } from "react";
2571
+ import { jsx as jsx19, jsxs as jsxs14, Fragment as Fragment11 } from "react/jsx-runtime";
2572
+ function RetentionChart({
2573
+ series,
2574
+ unit,
2575
+ ariaLabel,
2576
+ className,
2577
+ formatValue = formatNumber2
2578
+ }) {
2579
+ const ids = new Set;
2580
+ const offsets = new Map;
2581
+ for (const item of series) {
2582
+ invariant(item.id.trim().length > 0 && !ids.has(item.id), "retention series chart IDs must be nonblank and unique.");
2583
+ ids.add(item.id);
2584
+ invariant(Number.isFinite(item.cohortSize) && item.cohortSize >= 0, "retention chart counts must be finite and nonnegative.");
2585
+ const seen = new Set;
2586
+ for (const point of item.points) {
2587
+ invariant(Number.isSafeInteger(point.offset) && point.offset >= 0 && !seen.has(point.offset), "retention chart offsets must be unique nonnegative integers within each series.");
2588
+ seen.add(point.offset);
2589
+ invariant(point.rate === null || Number.isFinite(point.rate) && point.rate >= 0 && point.rate <= 1, "retention chart rates must be finite fractions between 0 and 1, or null.");
2590
+ invariant(point.rate === null === (point.retainedCount === null), "retention chart unobserved rates and counts must both be null.");
2591
+ if (point.retainedCount !== null) {
2592
+ invariant(Number.isFinite(point.retainedCount) && point.retainedCount >= 0, "retention chart counts must be finite and nonnegative.");
2593
+ invariant(point.retainedCount <= item.cohortSize, "retention chart retained counts cannot exceed cohort size.");
2594
+ }
2595
+ invariant(!offsets.has(point.offset) || offsets.get(point.offset) === point.label, "retention chart labels must agree for the same offset.");
2596
+ offsets.set(point.offset, point.label);
2597
+ }
2598
+ }
2599
+ const data = [...offsets].sort(([a], [b]) => a - b).map(([offset, label]) => ({
2600
+ offset,
2601
+ label,
2602
+ ...Object.fromEntries(series.flatMap((_, index) => [
2603
+ [`rate${index}`, null],
2604
+ [`tail${index}`, null]
2605
+ ]))
2606
+ }));
2607
+ series.forEach((item, index) => {
2608
+ const points = [...item.points].sort((a, b) => a.offset - b.offset);
2609
+ points.forEach((point, pointIndex) => {
2610
+ const row = data.find((row) => row.offset === point.offset);
2611
+ row[point.incomplete ? `tail${index}` : `rate${index}`] = point.rate === null ? null : point.rate * 100;
2612
+ if (point.incomplete && pointIndex > 0) {
2613
+ const previous = points[pointIndex - 1];
2614
+ data.find((row) => row.offset === previous.offset)[`tail${index}`] = previous.rate === null ? null : previous.rate * 100;
2615
+ }
2616
+ });
2617
+ });
2618
+ return /* @__PURE__ */ jsx19("section", {
2619
+ className: classNames("altertable-retention-chart", className),
2620
+ "aria-label": ariaLabel,
2621
+ children: data.length === 0 ? /* @__PURE__ */ jsx19("span", {
2622
+ children: "No data"
2623
+ }) : /* @__PURE__ */ jsxs14(Fragment11, {
2624
+ children: [
2625
+ /* @__PURE__ */ jsxs14(ComposedChart, {
2626
+ data,
2627
+ ariaLabel: `${ariaLabel} plot`,
2628
+ children: [
2629
+ /* @__PURE__ */ jsx19(ComposedChart.CartesianGrid, {}),
2630
+ /* @__PURE__ */ jsx19(ComposedChart.XAxis, {
2631
+ dataKey: "offset",
2632
+ type: "number",
2633
+ domain: [0, Math.max(1, ...offsets.keys())],
2634
+ tickFormatter: (value) => offsets.get(Number(value)) ?? (data[0]?.label.replace(/\d+(?:\.\d+)?/, String(value)) || String(value))
2635
+ }),
2636
+ /* @__PURE__ */ jsx19(ComposedChart.YAxis, {
2637
+ domain: [0, 100],
2638
+ width: 42,
2639
+ tickFormatter: (value) => `${value}%`
2640
+ }),
2641
+ series.map((item, index) => /* @__PURE__ */ jsxs14(Fragment10, {
2642
+ children: [
2643
+ /* @__PURE__ */ jsx19(ComposedChart.Line, {
2644
+ dataKey: `rate${index}`,
2645
+ name: item.label,
2646
+ stroke: `var(--atbl-chart-${index % 8 + 1})`,
2647
+ connectNulls: false
2648
+ }),
2649
+ /* @__PURE__ */ jsx19(ComposedChart.Line, {
2650
+ dataKey: `tail${index}`,
2651
+ name: item.label,
2652
+ stroke: `var(--atbl-chart-${index % 8 + 1})`,
2653
+ strokeDasharray: "3 3",
2654
+ dot: false,
2655
+ activeDot: false,
2656
+ connectNulls: false
2657
+ })
2658
+ ]
2659
+ }, item.id)),
2660
+ /* @__PURE__ */ jsx19(ComposedChart.Tooltip, {
2661
+ filterNull: false,
2662
+ content: ({ active, payload }) => {
2663
+ const offset = payload?.[0]?.payload.offset;
2664
+ if (!active || typeof offset !== "number")
2665
+ return null;
2666
+ return /* @__PURE__ */ jsxs14(TooltipSurface, {
2667
+ "data-variant": "chart",
2668
+ role: "tooltip",
2669
+ children: [
2670
+ /* @__PURE__ */ jsx19("strong", {
2671
+ children: offsets.get(offset)
2672
+ }),
2673
+ series.map((item) => {
2674
+ const point = item.points.find((point) => point.offset === offset);
2675
+ if (!point)
2676
+ return null;
2677
+ return /* @__PURE__ */ jsxs14("span", {
2678
+ children: [
2679
+ item.label,
2680
+ ":",
2681
+ " ",
2682
+ point.rate === null ? "Not observed" : `${formatPercent2(point.rate)} · ${formatValue(point.retainedCount)} / ${formatValue(item.cohortSize)} ${unit}${point.incomplete ? " · Incomplete period" : ""}`
2683
+ ]
2684
+ }, item.id);
2685
+ })
2686
+ ]
2687
+ });
2688
+ }
2689
+ })
2690
+ ]
2691
+ }),
2692
+ /* @__PURE__ */ jsx19(ChartLegend, {
2693
+ "aria-label": `${ariaLabel} cohorts`,
2694
+ children: series.map((item, index) => /* @__PURE__ */ jsxs14(ChartLegend.Item, {
2695
+ children: [
2696
+ /* @__PURE__ */ jsx19(ChartLegend.Marker, {
2697
+ kind: "square",
2698
+ color: `var(--atbl-chart-${index % 8 + 1})`
1553
2699
  }),
1554
- /* @__PURE__ */ jsx11("span", {
1555
- className: "altertable-selectable-bars-label",
2700
+ /* @__PURE__ */ jsx19(ChartLegend.Label, {
1556
2701
  children: item.label
1557
2702
  })
1558
2703
  ]
1559
- })
1560
- }, item.id))
1561
- })
1562
- ]
2704
+ }, item.id))
2705
+ }),
2706
+ /* @__PURE__ */ jsx19("ul", {
2707
+ className: "altertable-sr-only",
2708
+ children: series.map((item) => /* @__PURE__ */ jsxs14("li", {
2709
+ children: [
2710
+ item.label,
2711
+ item.points.map((point) => /* @__PURE__ */ jsxs14("span", {
2712
+ children: [
2713
+ " ",
2714
+ "· ",
2715
+ point.label,
2716
+ ":",
2717
+ " ",
2718
+ point.rate === null ? "Not observed" : `${formatPercent2(point.rate)} · ${formatValue(point.retainedCount)} / ${formatValue(item.cohortSize)} ${unit}${point.incomplete ? " · Incomplete period" : ""}`
2719
+ ]
2720
+ }, point.offset))
2721
+ ]
2722
+ }, item.id))
2723
+ })
2724
+ ]
2725
+ })
2726
+ });
2727
+ }
2728
+ // src/react/ui/JourneyChart.tsx
2729
+ import { useLayoutEffect as useLayoutEffect3, useRef as useRef4, useState as useState7 } from "react";
2730
+
2731
+ // src/react/ui/journey-flow.ts
2732
+ var journeyOutcomeLabels = {
2733
+ truncated: "More steps",
2734
+ "drop-off": "Drop-off",
2735
+ converted: "Converted",
2736
+ end: "End of path"
2737
+ };
2738
+ function journeyNodes(path) {
2739
+ const nodes = path.steps.map((step, depth) => ({
2740
+ key: JSON.stringify([depth, "event", step.event, step.property]),
2741
+ label: step.property === null ? step.event : `${step.event} · ${step.property}`,
2742
+ depth,
2743
+ outcome: "event"
2744
+ }));
2745
+ const outcome = path.truncated ? "truncated" : path.converted === false ? "drop-off" : path.converted === true ? "converted" : "end";
2746
+ nodes.push({
2747
+ key: JSON.stringify([nodes.length, outcome]),
2748
+ label: journeyOutcomeLabels[outcome],
2749
+ depth: nodes.length,
2750
+ outcome
2751
+ });
2752
+ return nodes;
2753
+ }
2754
+ function buildJourneyFlow(paths, expandedBranch) {
2755
+ const nodes = new Map;
2756
+ const links = new Map;
2757
+ for (const path of paths) {
2758
+ const fullSequence = journeyNodes(path);
2759
+ if (path.truncated)
2760
+ fullSequence.pop();
2761
+ const sequence = expandedBranch === undefined ? fullSequence : fullSequence.flatMap((node, index) => {
2762
+ const branchKeys = fullSequence.slice(0, index + 1).map((step) => step.key);
2763
+ const parentExpanded = index <= 1 || branchKeys.slice(0, index).every((key, depth) => expandedBranch[depth] === key);
2764
+ if (!parentExpanded)
2765
+ return [];
2766
+ return [
2767
+ {
2768
+ ...node,
2769
+ key: JSON.stringify(branchKeys),
2770
+ branchKeys,
2771
+ expandable: index >= 1 && index < fullSequence.length - 1,
2772
+ expanded: branchKeys.every((key, depth) => expandedBranch[depth] === key)
2773
+ }
2774
+ ];
2775
+ });
2776
+ sequence.forEach((node, index) => {
2777
+ const current = nodes.get(node.key) ?? {
2778
+ ...node,
2779
+ count: 0,
2780
+ pathKeys: []
2781
+ };
2782
+ current.count += path.count;
2783
+ if ("expandable" in node && node.expandable)
2784
+ current.expandable = true;
2785
+ current.pathKeys.push(path.id);
2786
+ nodes.set(node.key, current);
2787
+ const previous = sequence[index - 1];
2788
+ if (!previous)
2789
+ return;
2790
+ const key = JSON.stringify([previous.key, node.key]);
2791
+ const link = links.get(key) ?? {
2792
+ key,
2793
+ source: previous.key,
2794
+ target: node.key,
2795
+ count: 0,
2796
+ pathKeys: []
2797
+ };
2798
+ link.count += path.count;
2799
+ link.pathKeys.push(path.id);
2800
+ links.set(key, link);
2801
+ });
2802
+ }
2803
+ return { nodes: [...nodes.values()], links: [...links.values()] };
2804
+ }
2805
+ function limitJourneyFlow(graph, visibleEventsByDepth = {}) {
2806
+ const nodes = [];
2807
+ const replacements = new Map;
2808
+ for (const depth of new Set(graph.nodes.map((node) => node.depth))) {
2809
+ const column = graph.nodes.filter((node) => node.depth === depth).sort((a, b) => Number(Boolean(b.expanded)) - Number(Boolean(a.expanded)) || b.count - a.count || a.key.localeCompare(b.key));
2810
+ const events = column.filter((node) => node.outcome === "event");
2811
+ const outcomes = column.filter((node) => node.outcome !== "event");
2812
+ const limit = visibleEventsByDepth[depth] ?? 3;
2813
+ if (events.length <= limit) {
2814
+ nodes.push(...column);
2815
+ continue;
2816
+ }
2817
+ nodes.push(...events.slice(0, limit), ...outcomes);
2818
+ const hidden = events.slice(limit);
2819
+ const key = JSON.stringify(["other", depth]);
2820
+ hidden.forEach((node) => replacements.set(node.key, key));
2821
+ nodes.push({
2822
+ key,
2823
+ label: "More events",
2824
+ depth,
2825
+ outcome: "other",
2826
+ hiddenBranchCount: hidden.length,
2827
+ count: hidden.reduce((sum, node) => sum + node.count, 0),
2828
+ pathKeys: [...new Set(hidden.flatMap((node) => node.pathKeys))]
2829
+ });
2830
+ }
2831
+ const links = new Map;
2832
+ for (const link of graph.links) {
2833
+ const source = replacements.get(link.source) ?? link.source;
2834
+ const target = replacements.get(link.target) ?? link.target;
2835
+ const key = JSON.stringify([source, target]);
2836
+ const existing = links.get(key);
2837
+ if (existing) {
2838
+ existing.count += link.count;
2839
+ existing.pathKeys.push(...link.pathKeys);
2840
+ } else {
2841
+ links.set(key, {
2842
+ ...link,
2843
+ key,
2844
+ source,
2845
+ target,
2846
+ pathKeys: [...link.pathKeys]
2847
+ });
2848
+ }
2849
+ }
2850
+ return { nodes, links: [...links.values()] };
2851
+ }
2852
+
2853
+ // src/react/ui/JourneyChart.tsx
2854
+ import { jsx as jsx20, jsxs as jsxs15 } from "react/jsx-runtime";
2855
+ function JourneyChart({
2856
+ paths,
2857
+ unit,
2858
+ ariaLabel,
2859
+ className,
2860
+ formatValue = formatNumber2
2861
+ }) {
2862
+ const ids = new Set;
2863
+ let total = 0;
2864
+ for (const path of paths) {
2865
+ invariant(path.id.trim().length > 0 && !ids.has(path.id), "journey path chart IDs must be nonblank and unique.");
2866
+ ids.add(path.id);
2867
+ invariant(Number.isFinite(path.count) && path.count >= 0, "journey chart counts must be finite and nonnegative.");
2868
+ total += path.count;
2869
+ invariant(path.steps.length > 0 && path.steps.every((step) => step.event.trim().length > 0), "journey chart paths require nonblank events.");
2870
+ }
2871
+ invariant(Number.isFinite(total), "journey chart total count must be finite.");
2872
+ return /* @__PURE__ */ jsx20("section", {
2873
+ className: classNames("altertable-journey-chart", className),
2874
+ "aria-label": ariaLabel,
2875
+ children: paths.length === 0 ? /* @__PURE__ */ jsx20("span", {
2876
+ children: "No data"
2877
+ }) : /* @__PURE__ */ jsx20(JourneyFlow, {
2878
+ paths,
2879
+ unit,
2880
+ formatValue
2881
+ }, JSON.stringify(paths))
2882
+ });
2883
+ }
2884
+ function JourneyFlow({
2885
+ paths,
2886
+ unit,
2887
+ formatValue
2888
+ }) {
2889
+ const [expandedBranch, setExpandedBranch] = useState7([]);
2890
+ const [visibleEventsByDepth, setVisibleEventsByDepth] = useState7({});
2891
+ const graph = limitJourneyFlow(buildJourneyFlow(paths, expandedBranch), visibleEventsByDepth);
2892
+ const scrollRef = useRef4(null);
2893
+ const pendingDepth = useRef4(null);
2894
+ const depthCount = Math.max(0, ...graph.nodes.map((node) => node.depth)) + 1;
2895
+ const startingCount = graph.nodes.filter((node) => node.depth === 0).reduce((sum, node) => sum + node.count, 0);
2896
+ function percent(count) {
2897
+ return formatPercent2(startingCount > 0 ? count / startingCount : null);
2898
+ }
2899
+ const nodeWidth = 176;
2900
+ const nodeHeight = 96;
2901
+ const barWidth = 16;
2902
+ const rowSpacing = nodeHeight + 80;
2903
+ const columnSpacing = nodeWidth + 112;
2904
+ const width = 34 + nodeWidth + (depthCount - 1) * columnSpacing;
2905
+ function columnX(depth) {
2906
+ return 10 + depth * columnSpacing;
2907
+ }
2908
+ useLayoutEffect3(() => {
2909
+ const depth = pendingDepth.current;
2910
+ const container = scrollRef.current;
2911
+ if (depth === null || !container || depth >= depthCount)
2912
+ return;
2913
+ pendingDepth.current = null;
2914
+ const left = columnX(depth);
2915
+ const right = left + nodeWidth + 24;
2916
+ const nextLeft = left < container.scrollLeft ? left - 24 : right > container.scrollLeft + container.clientWidth ? right - container.clientWidth : container.scrollLeft;
2917
+ if (nextLeft !== container.scrollLeft)
2918
+ container.scrollTo({
2919
+ left: Math.max(0, nextLeft),
2920
+ behavior: window.matchMedia("(prefers-reduced-motion: reduce)").matches ? "instant" : "smooth"
2921
+ });
2922
+ });
2923
+ const columns = Array.from({ length: depthCount }, (_, depth) => graph.nodes.filter((node) => node.depth === depth).sort((a, b) => {
2924
+ function rank(node) {
2925
+ return node.outcome === "event" ? 0 : node.outcome === "other" ? 1 : 2;
2926
+ }
2927
+ return rank(a) - rank(b) || b.count - a.count || a.key.localeCompare(b.key);
2928
+ }));
2929
+ const positions = new Map;
2930
+ columns.forEach((column, depth) => {
2931
+ const parent = columns[depth - 1]?.find((node) => node.expanded);
2932
+ const parentY = parent ? positions.get(parent.key)?.y : undefined;
2933
+ const top = Math.max(104, (parentY ?? 104) - (column.length > 1 ? rowSpacing : 0));
2934
+ column.forEach((node, index) => positions.set(node.key, {
2935
+ node,
2936
+ x: columnX(depth),
2937
+ y: top + index * rowSpacing
2938
+ }));
2939
+ });
2940
+ const height = Math.max(280, ...[...positions.values()].map((position) => position.y + nodeHeight + 40));
2941
+ const outgoingOffsets = new Map;
2942
+ const incomingOffsets = new Map;
2943
+ const orderedLinks = [...graph.links].sort((a, b) => (positions.get(a.source)?.y ?? 0) - (positions.get(b.source)?.y ?? 0) || (positions.get(a.target)?.y ?? 0) - (positions.get(b.target)?.y ?? 0));
2944
+ const links = orderedLinks.flatMap((link) => {
2945
+ const source = positions.get(link.source);
2946
+ const target = positions.get(link.target);
2947
+ if (!source || !target || link.count <= 0 || startingCount <= 0)
2948
+ return [];
2949
+ const x0 = source.x + barWidth;
2950
+ const x1 = target.x;
2951
+ const mid = (x0 + x1) / 2;
2952
+ const sourceTop = source.y + (outgoingOffsets.get(link.source) ?? 0);
2953
+ const targetTop = target.y + (incomingOffsets.get(link.target) ?? 0);
2954
+ const thickness = link.count / startingCount * nodeHeight;
2955
+ outgoingOffsets.set(link.source, (outgoingOffsets.get(link.source) ?? 0) + thickness);
2956
+ incomingOffsets.set(link.target, (incomingOffsets.get(link.target) ?? 0) + thickness);
2957
+ return [
2958
+ {
2959
+ muted: target.node.outcome === "end" || target.node.outcome === "other",
2960
+ d: `M${x0},${sourceTop} C${mid},${sourceTop} ${mid},${targetTop} ${x1},${targetTop} L${x1},${targetTop + thickness} C${mid},${targetTop + thickness} ${mid},${sourceTop + thickness} ${x0},${sourceTop + thickness} Z`
2961
+ }
2962
+ ];
2963
+ });
2964
+ return /* @__PURE__ */ jsx20("div", {
2965
+ ref: scrollRef,
2966
+ className: "altertable-journey-scroll",
2967
+ children: /* @__PURE__ */ jsxs15("div", {
2968
+ className: "altertable-journey-plot",
2969
+ style: { minWidth: width, minHeight: height },
2970
+ children: [
2971
+ /* @__PURE__ */ jsxs15("svg", {
2972
+ width: "100%",
2973
+ height: "100%",
2974
+ "aria-hidden": "true",
2975
+ focusable: "false",
2976
+ children: [
2977
+ /* @__PURE__ */ jsx20("path", {
2978
+ d: links.filter((link) => !link.muted).map((link) => link.d).join(" "),
2979
+ fill: "color-mix(in srgb, var(--atbl-chart-5) 22%, var(--atbl-surface))"
2980
+ }),
2981
+ /* @__PURE__ */ jsx20("path", {
2982
+ d: links.filter((link) => link.muted).map((link) => link.d).join(" "),
2983
+ fill: "color-mix(in srgb, var(--atbl-muted) 22%, var(--atbl-surface))"
2984
+ })
2985
+ ]
2986
+ }),
2987
+ columns.map((column, depth) => /* @__PURE__ */ jsx20("span", {
2988
+ className: "altertable-journey-stage",
2989
+ style: { left: columnX(depth) },
2990
+ children: column.length > 0 && column.every((node) => node.outcome !== "event" && node.outcome !== "other") ? "Outcome" : `Step ${depth + 1}`
2991
+ }, depth)),
2992
+ [...positions.values()].map(({ node, x, y }) => /* @__PURE__ */ jsxs15("div", {
2993
+ className: "altertable-journey-node",
2994
+ style: {
2995
+ left: x,
2996
+ top: y,
2997
+ width: nodeWidth,
2998
+ height: startingCount > 0 ? node.count / startingCount * nodeHeight : 0
2999
+ },
3000
+ children: [
3001
+ /* @__PURE__ */ jsx20("div", {
3002
+ className: "altertable-journey-bar",
3003
+ "data-event": node.outcome === "event" || undefined,
3004
+ "aria-hidden": "true",
3005
+ style: { width: barWidth }
3006
+ }),
3007
+ /* @__PURE__ */ jsxs15("section", {
3008
+ "aria-label": `${node.label}, step ${node.depth + 1}, ${formatValue(node.count)} ${unit}`,
3009
+ className: "altertable-journey-label",
3010
+ children: [
3011
+ /* @__PURE__ */ jsx20("span", {
3012
+ className: "altertable-journey-name",
3013
+ title: node.label,
3014
+ children: node.label
3015
+ }),
3016
+ /* @__PURE__ */ jsxs15("span", {
3017
+ children: [
3018
+ formatValue(node.count),
3019
+ " ",
3020
+ unit,
3021
+ " · ",
3022
+ percent(node.count)
3023
+ ]
3024
+ }),
3025
+ node.hiddenBranchCount ? /* @__PURE__ */ jsxs15("span", {
3026
+ children: [
3027
+ node.hiddenBranchCount,
3028
+ " hidden",
3029
+ " ",
3030
+ node.hiddenBranchCount === 1 ? "event" : "events"
3031
+ ]
3032
+ }) : null
3033
+ ]
3034
+ }),
3035
+ node.expandable && /* @__PURE__ */ jsx20(Button2, {
3036
+ variant: "outline",
3037
+ size: "icon-compact",
3038
+ className: "altertable-journey-expand",
3039
+ "aria-label": `${node.expanded ? "Less" : "More"} after ${node.label}, step ${node.depth + 1}`,
3040
+ "aria-expanded": Boolean(node.expanded),
3041
+ onClick: () => {
3042
+ pendingDepth.current = node.expanded ? null : node.depth + 1;
3043
+ setVisibleEventsByDepth((limits) => Object.fromEntries(Object.entries(limits).filter(([depth]) => Number(depth) <= node.depth)));
3044
+ setExpandedBranch(node.expanded ? (node.branchKeys ?? []).slice(0, -1) : node.branchKeys ?? []);
3045
+ },
3046
+ children: node.expanded ? "−" : "›"
3047
+ }),
3048
+ node.outcome === "other" && /* @__PURE__ */ jsx20(Button2, {
3049
+ size: "compact",
3050
+ variant: "ghost",
3051
+ className: "altertable-journey-more",
3052
+ "aria-label": `Show more events at step ${node.depth + 1}`,
3053
+ onClick: () => setVisibleEventsByDepth((limits) => ({
3054
+ ...limits,
3055
+ [node.depth]: (limits[node.depth] ?? 3) + 3
3056
+ })),
3057
+ children: "Show more events"
3058
+ })
3059
+ ]
3060
+ }, node.key))
3061
+ ]
3062
+ })
1563
3063
  });
1564
3064
  }
1565
3065
  export {
@@ -1567,8 +3067,11 @@ export {
1567
3067
  AppIcon2 as AppIcon,
1568
3068
  AreaChart,
1569
3069
  BarChart,
3070
+ ChartLegend,
1570
3071
  Checkbox2 as Checkbox,
1571
- Combobox2 as Combobox,
3072
+ CheckboxGroup,
3073
+ ChoicePicker2 as ChoicePicker,
3074
+ ComposedChart,
1572
3075
  DataApp,
1573
3076
  DataAppProvider2 as DataAppProvider,
1574
3077
  DataAppSkeleton,
@@ -1580,19 +3083,38 @@ export {
1580
3083
  DataWidget2 as DataWidget,
1581
3084
  DateRangePicker2 as DateRangePicker,
1582
3085
  DimensionPicker2 as DimensionPicker,
3086
+ FilterActions2 as FilterActions,
3087
+ FilterBar2 as FilterBar,
3088
+ FunnelChart,
1583
3089
  GettingStarted,
1584
3090
  GlossaryDefinition,
1585
3091
  GlossaryExplanation,
1586
3092
  GradientScroll2 as GradientScroll,
1587
3093
  HelpPopover2 as HelpPopover,
3094
+ JourneyChart,
1588
3095
  LineChart,
1589
3096
  LiveControl2 as LiveControl,
3097
+ Menu2 as Menu,
3098
+ PressButton as MenuButton,
3099
+ MenuItem2 as MenuItem,
3100
+ MenuPopover2 as MenuPopover,
3101
+ MenuSection2 as MenuSection,
3102
+ MenuSeparator2 as MenuSeparator,
3103
+ MenuTrigger2 as MenuTrigger,
1590
3104
  MetricWidget2 as MetricWidget,
3105
+ NumberField2 as NumberField,
3106
+ NumberFilterPicker2 as NumberFilterPicker,
3107
+ NumberRangeField2 as NumberRangeField,
1591
3108
  PieChart,
1592
3109
  PresentStory2 as PresentStory,
3110
+ Radio,
3111
+ RadioGroup,
3112
+ RetentionChart,
1593
3113
  ScatterChart,
1594
3114
  SearchField2 as SearchField,
1595
3115
  SearchMatch2 as SearchMatch,
3116
+ SegmentedControl,
3117
+ Select2 as Select,
1596
3118
  Sheet2 as Sheet,
1597
3119
  Tab2 as Tab,
1598
3120
  TabList2 as TabList,
@@ -1603,12 +3125,15 @@ export {
1603
3125
  TextWidget2 as TextWidget,
1604
3126
  Tooltip2 as Tooltip,
1605
3127
  TooltipProvider2 as TooltipProvider,
3128
+ VariableBar2 as VariableBar,
1606
3129
  VisualizationWidget2 as VisualizationWidget,
1607
3130
  injectDataAppShellStyles,
1608
3131
  injectDataAppStyles2 as injectDataAppStyles,
3132
+ isPlainKeyEvent2 as isPlainKeyEvent,
1609
3133
  searchItems2 as searchItems,
1610
3134
  useAppVariables2 as useAppVariables,
3135
+ useSvgId,
1611
3136
  useViewTab2 as useViewTab
1612
3137
  };
1613
3138
 
1614
- //# debugId=44AF1347DDC5E0E164756E2164756E21
3139
+ //# debugId=6CFD84691873BB8C64756E2164756E21