@altertable/data-app 0.67.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.
@@ -60,9 +60,13 @@ import {
60
60
  TableWidget2,
61
61
  TextWidget2,
62
62
  DataValue2,
63
+ chartBarFill,
63
64
  createDataContext2
64
- } from "../../chunks/index-8qfxv0h5.js";
65
- import"../../chunks/index-mev09s5v.js";
65
+ } from "../../chunks/index-gvers8jw.js";
66
+ import {
67
+ formatNumber2,
68
+ formatPercent2
69
+ } from "../../chunks/index-mev09s5v.js";
66
70
  import"../../chunks/index-ag0cgnvq.js";
67
71
  import"../../chunks/index-mt93ff2b.js";
68
72
  import"../../chunks/index-m6n8ctfc.js";
@@ -1080,7 +1084,7 @@ function GlossaryDefinition({
1080
1084
  });
1081
1085
  }
1082
1086
  // src/react/ui/TrendChart.tsx
1083
- import { useLayoutEffect, useRef, useState } from "react";
1087
+ import { useLayoutEffect, useRef, useState as useState2 } from "react";
1084
1088
  import {
1085
1089
  AreaChart as RechartsAreaChart,
1086
1090
  LineChart as RechartsLineChart,
@@ -1100,8 +1104,167 @@ import {
1100
1104
  Tooltip,
1101
1105
  DefaultTooltipContent
1102
1106
  } from "recharts";
1103
- import { jsx as jsx6 } from "react/jsx-runtime";
1104
- var chartBarFill = "var(--atbl-chart-fill, color-mix(in srgb, var(--atbl-accent) 75%, var(--atbl-surface)))";
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";
1105
1268
  var accent = "var(--atbl-accent)";
1106
1269
  function ChartDot({
1107
1270
  active = false,
@@ -1110,7 +1273,7 @@ function ChartDot({
1110
1273
  r = active ? 4.5 : 3,
1111
1274
  ...props
1112
1275
  }) {
1113
- return /* @__PURE__ */ jsx6(Dot, {
1276
+ return /* @__PURE__ */ jsx7(Dot, {
1114
1277
  stroke,
1115
1278
  fill,
1116
1279
  r,
@@ -1119,7 +1282,7 @@ function ChartDot({
1119
1282
  });
1120
1283
  }
1121
1284
  function ChartBar(props) {
1122
- return /* @__PURE__ */ jsx6(Bar, {
1285
+ return /* @__PURE__ */ jsx7(Bar, {
1123
1286
  fill: chartBarFill,
1124
1287
  radius: [5, 5, 0, 0],
1125
1288
  maxBarSize: 30,
@@ -1134,18 +1297,18 @@ function ChartLine({
1134
1297
  activeDot,
1135
1298
  ...props
1136
1299
  }) {
1137
- return /* @__PURE__ */ jsx6(Line, {
1300
+ return /* @__PURE__ */ jsx7(Line, {
1138
1301
  type: "linear",
1139
1302
  stroke,
1140
1303
  strokeWidth: 2,
1141
1304
  strokeLinecap: "round",
1142
1305
  strokeLinejoin: "round",
1143
- dot: dot === undefined || dot === true ? ({ cx, cy }) => /* @__PURE__ */ jsx6(ChartDot, {
1306
+ dot: dot === undefined || dot === true ? ({ cx, cy }) => /* @__PURE__ */ jsx7(ChartDot, {
1144
1307
  cx,
1145
1308
  cy,
1146
1309
  stroke
1147
1310
  }) : dot,
1148
- activeDot: activeDot === undefined || activeDot === true ? ({ cx, cy }) => /* @__PURE__ */ jsx6(ChartDot, {
1311
+ activeDot: activeDot === undefined || activeDot === true ? ({ cx, cy }) => /* @__PURE__ */ jsx7(ChartDot, {
1149
1312
  cx,
1150
1313
  cy,
1151
1314
  stroke,
@@ -1161,7 +1324,7 @@ function ChartArea({
1161
1324
  activeDot,
1162
1325
  ...props
1163
1326
  }) {
1164
- return /* @__PURE__ */ jsx6(Area, {
1327
+ return /* @__PURE__ */ jsx7(Area, {
1165
1328
  legendType: "square",
1166
1329
  type: "linear",
1167
1330
  stroke,
@@ -1170,12 +1333,12 @@ function ChartArea({
1170
1333
  strokeLinejoin: "round",
1171
1334
  fill: stroke,
1172
1335
  fillOpacity: 0.14,
1173
- dot: dot === undefined || dot === true ? ({ cx, cy }) => /* @__PURE__ */ jsx6(ChartDot, {
1336
+ dot: dot === undefined || dot === true ? ({ cx, cy }) => /* @__PURE__ */ jsx7(ChartDot, {
1174
1337
  cx,
1175
1338
  cy,
1176
1339
  stroke
1177
1340
  }) : dot,
1178
- activeDot: activeDot === undefined || activeDot === true ? ({ cx, cy }) => /* @__PURE__ */ jsx6(ChartDot, {
1341
+ activeDot: activeDot === undefined || activeDot === true ? ({ cx, cy }) => /* @__PURE__ */ jsx7(ChartDot, {
1179
1342
  cx,
1180
1343
  cy,
1181
1344
  stroke,
@@ -1186,14 +1349,14 @@ function ChartArea({
1186
1349
  });
1187
1350
  }
1188
1351
  function ChartScatter(props) {
1189
- return /* @__PURE__ */ jsx6(Scatter, {
1352
+ return /* @__PURE__ */ jsx7(Scatter, {
1190
1353
  fill: accent,
1191
1354
  isAnimationActive: false,
1192
1355
  ...props
1193
1356
  });
1194
1357
  }
1195
1358
  function ChartXAxis(props) {
1196
- return /* @__PURE__ */ jsx6(XAxis, {
1359
+ return /* @__PURE__ */ jsx7(XAxis, {
1197
1360
  stroke: "var(--atbl-muted)",
1198
1361
  tickLine: false,
1199
1362
  axisLine: { stroke: "var(--atbl-border)" },
@@ -1202,7 +1365,7 @@ function ChartXAxis(props) {
1202
1365
  });
1203
1366
  }
1204
1367
  function ChartYAxis(props) {
1205
- return /* @__PURE__ */ jsx6(YAxis, {
1368
+ return /* @__PURE__ */ jsx7(YAxis, {
1206
1369
  stroke: "var(--atbl-muted)",
1207
1370
  tickLine: false,
1208
1371
  axisLine: false,
@@ -1211,7 +1374,7 @@ function ChartYAxis(props) {
1211
1374
  });
1212
1375
  }
1213
1376
  function ChartGrid(props) {
1214
- return /* @__PURE__ */ jsx6(CartesianGrid, {
1377
+ return /* @__PURE__ */ jsx7(CartesianGrid, {
1215
1378
  stroke: "var(--atbl-border)",
1216
1379
  vertical: false,
1217
1380
  ...props
@@ -1220,11 +1383,45 @@ function ChartGrid(props) {
1220
1383
  function ChartTooltipContent(props) {
1221
1384
  if (!props.active)
1222
1385
  return null;
1223
- return /* @__PURE__ */ jsx6(TooltipSurface, {
1386
+ return /* @__PURE__ */ jsx7(TooltipSurface, {
1224
1387
  "data-variant": "chart",
1225
1388
  role: "tooltip",
1226
- children: /* @__PURE__ */ jsx6(DefaultTooltipContent, {
1389
+ children: /* @__PURE__ */ jsx7(DefaultTooltipContent, {
1227
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
+ })),
1228
1425
  contentStyle: {
1229
1426
  margin: 0,
1230
1427
  padding: 0,
@@ -1240,13 +1437,18 @@ function ChartTooltipContent(props) {
1240
1437
  lineHeight: 1.2,
1241
1438
  ...props.labelStyle
1242
1439
  },
1243
- itemStyle: { paddingTop: 2, paddingBottom: 0, ...props.itemStyle }
1440
+ itemStyle: {
1441
+ color: "var(--atbl-text)",
1442
+ paddingTop: 2,
1443
+ paddingBottom: 0,
1444
+ ...props.itemStyle
1445
+ }
1244
1446
  })
1245
1447
  });
1246
1448
  }
1247
1449
  function ChartTooltip(props) {
1248
- return /* @__PURE__ */ jsx6(Tooltip, {
1249
- content: /* @__PURE__ */ jsx6(ChartTooltipContent, {}),
1450
+ return /* @__PURE__ */ jsx7(Tooltip, {
1451
+ content: /* @__PURE__ */ jsx7(ChartTooltipContent, {}),
1250
1452
  cursor: false,
1251
1453
  isAnimationActive: false,
1252
1454
  ...props
@@ -1272,7 +1474,7 @@ function validateChartItems(kind, items) {
1272
1474
  }
1273
1475
 
1274
1476
  // src/react/ui/TrendChart.tsx
1275
- import { jsx as jsx7, jsxs as jsxs4, Fragment } from "react/jsx-runtime";
1477
+ import { jsx as jsx8, jsxs as jsxs6, Fragment as Fragment2 } from "react/jsx-runtime";
1276
1478
  function TrendChart({
1277
1479
  items,
1278
1480
  unit,
@@ -1282,7 +1484,7 @@ function TrendChart({
1282
1484
  }) {
1283
1485
  validateChartItems(area ? "area" : "line", items);
1284
1486
  const Plot = area ? RechartsAreaChart : RechartsLineChart;
1285
- const [activeIndex, setActiveIndex] = useState();
1487
+ const [activeIndex, setActiveIndex] = useState2();
1286
1488
  const scrollport = useRef(null);
1287
1489
  useLayoutEffect(() => {
1288
1490
  const element = scrollport.current;
@@ -1292,25 +1494,25 @@ function TrendChart({
1292
1494
  const magnitude = items.reduce((max, item) => Math.max(max, Math.abs(item.value)), 1);
1293
1495
  const low = items.reduce((min, item) => Math.min(min, item.value / magnitude), 0);
1294
1496
  const high = items.reduce((max, item) => Math.max(max, item.value / magnitude), 0);
1295
- return /* @__PURE__ */ jsxs4("section", {
1497
+ return /* @__PURE__ */ jsxs6("section", {
1296
1498
  className: "altertable-trend-chart",
1297
1499
  "aria-label": ariaLabel,
1298
1500
  children: [
1299
- items.length === 0 && /* @__PURE__ */ jsx7("span", {
1501
+ items.length === 0 && /* @__PURE__ */ jsx8("span", {
1300
1502
  children: "No data"
1301
1503
  }),
1302
- items.length > 0 && /* @__PURE__ */ jsx7(Fragment, {
1303
- children: /* @__PURE__ */ jsx7("div", {
1504
+ items.length > 0 && /* @__PURE__ */ jsx8(Fragment2, {
1505
+ children: /* @__PURE__ */ jsx8("div", {
1304
1506
  className: "altertable-trend-scroll",
1305
1507
  ref: scrollport,
1306
- children: /* @__PURE__ */ jsxs4("div", {
1508
+ children: /* @__PURE__ */ jsxs6("div", {
1307
1509
  className: "altertable-trend-plot",
1308
1510
  style: { minWidth: items.length * 40 },
1309
1511
  children: [
1310
- /* @__PURE__ */ jsx7("div", {
1512
+ /* @__PURE__ */ jsx8("div", {
1311
1513
  className: "altertable-chart-canvas",
1312
1514
  "aria-hidden": "true",
1313
- children: /* @__PURE__ */ jsxs4(Plot, {
1515
+ children: /* @__PURE__ */ jsxs6(Plot, {
1314
1516
  data: items.map((item) => ({
1315
1517
  ...item,
1316
1518
  plotValue: item.value / magnitude
@@ -1323,29 +1525,29 @@ function TrendChart({
1323
1525
  accessibilityLayer: false,
1324
1526
  tabIndex: -1,
1325
1527
  children: [
1326
- /* @__PURE__ */ jsx7(ChartXAxis, {
1528
+ /* @__PURE__ */ jsx8(ChartXAxis, {
1327
1529
  hide: true,
1328
1530
  dataKey: "id",
1329
1531
  scale: "band"
1330
1532
  }),
1331
- /* @__PURE__ */ jsx7(ChartYAxis, {
1533
+ /* @__PURE__ */ jsx8(ChartYAxis, {
1332
1534
  hide: true,
1333
1535
  domain: [low, high === low ? low + 1 : high]
1334
1536
  }),
1335
- /* @__PURE__ */ jsx7(ReferenceLine, {
1537
+ /* @__PURE__ */ jsx8(ReferenceLine, {
1336
1538
  y: 0,
1337
1539
  stroke: "var(--atbl-border)"
1338
1540
  }),
1339
- area && /* @__PURE__ */ jsx7(ChartArea, {
1541
+ area && /* @__PURE__ */ jsx8(ChartArea, {
1340
1542
  dataKey: "plotValue",
1341
1543
  stroke: "none",
1342
1544
  fill: "var(--atbl-accent)",
1343
1545
  dot: false,
1344
1546
  activeDot: false
1345
1547
  }),
1346
- /* @__PURE__ */ jsx7(ChartLine, {
1548
+ /* @__PURE__ */ jsx8(ChartLine, {
1347
1549
  dataKey: "plotValue",
1348
- dot: ({ cx, cy, index }) => /* @__PURE__ */ jsx7(ChartDot, {
1550
+ dot: ({ cx, cy, index }) => /* @__PURE__ */ jsx8(ChartDot, {
1349
1551
  cx,
1350
1552
  cy,
1351
1553
  active: index === activeIndex
@@ -1355,17 +1557,17 @@ function TrendChart({
1355
1557
  ]
1356
1558
  })
1357
1559
  }),
1358
- /* @__PURE__ */ jsx7("div", {
1560
+ /* @__PURE__ */ jsx8("div", {
1359
1561
  className: "altertable-trend-points",
1360
- children: items.map((item, index) => /* @__PURE__ */ jsx7(Tooltip2, {
1562
+ children: items.map((item, index) => /* @__PURE__ */ jsx8(Tooltip2, {
1361
1563
  variant: "chart",
1362
1564
  onOpenChange: (open) => setActiveIndex((current) => open ? index : current === index ? undefined : current),
1363
- content: /* @__PURE__ */ jsxs4(Fragment, {
1565
+ content: /* @__PURE__ */ jsxs6(Fragment2, {
1364
1566
  children: [
1365
- /* @__PURE__ */ jsx7("strong", {
1567
+ /* @__PURE__ */ jsx8("strong", {
1366
1568
  children: item.label
1367
1569
  }),
1368
- /* @__PURE__ */ jsxs4("span", {
1570
+ /* @__PURE__ */ jsxs6("span", {
1369
1571
  children: [
1370
1572
  formatValue(item.value),
1371
1573
  " ",
@@ -1375,7 +1577,7 @@ function TrendChart({
1375
1577
  ]
1376
1578
  }),
1377
1579
  className: "altertable-chart-column",
1378
- children: /* @__PURE__ */ jsxs4("button", {
1580
+ children: /* @__PURE__ */ jsxs6("button", {
1379
1581
  "data-atbl-focus": "ring",
1380
1582
  "data-atbl-control": "default",
1381
1583
  type: "button",
@@ -1383,10 +1585,10 @@ function TrendChart({
1383
1585
  className: "altertable-trend-point",
1384
1586
  "aria-label": `${item.label}: ${formatValue(item.value)} ${unit}`,
1385
1587
  children: [
1386
- /* @__PURE__ */ jsx7("span", {
1588
+ /* @__PURE__ */ jsx8("span", {
1387
1589
  className: "altertable-trend-hit"
1388
1590
  }),
1389
- /* @__PURE__ */ jsx7("span", {
1591
+ /* @__PURE__ */ jsx8("span", {
1390
1592
  className: "altertable-trend-label",
1391
1593
  children: item.label
1392
1594
  })
@@ -1403,176 +1605,23 @@ function TrendChart({
1403
1605
  }
1404
1606
 
1405
1607
  // src/react/ui/LineChart.tsx
1406
- import { jsx as jsx8 } from "react/jsx-runtime";
1608
+ import { jsx as jsx9 } from "react/jsx-runtime";
1407
1609
  function LineChart(props) {
1408
- return /* @__PURE__ */ jsx8(TrendChart, {
1610
+ return /* @__PURE__ */ jsx9(TrendChart, {
1409
1611
  ...props
1410
1612
  });
1411
1613
  }
1412
1614
  // src/react/ui/AreaChart.tsx
1413
- import { jsx as jsx9 } from "react/jsx-runtime";
1615
+ import { jsx as jsx10 } from "react/jsx-runtime";
1414
1616
  function AreaChart(props) {
1415
- return /* @__PURE__ */ jsx9(TrendChart, {
1617
+ return /* @__PURE__ */ jsx10(TrendChart, {
1416
1618
  ...props,
1417
1619
  area: true
1418
1620
  });
1419
1621
  }
1420
- // src/react/ui/ChartLegend.tsx
1421
- import {
1422
- Children,
1423
- useId,
1424
- useState as useState2
1425
- } from "react";
1426
- import { jsx as jsx10, jsxs as jsxs5, Fragment as Fragment2 } from "react/jsx-runtime";
1427
- function ChartLegendRoot({
1428
- children,
1429
- layout = "horizontal",
1430
- align = "start",
1431
- maxVisibleItems = 6,
1432
- className,
1433
- "aria-label": ariaLabel = "Chart legend",
1434
- ...props
1435
- }) {
1436
- const [expanded, setExpanded] = useState2(false);
1437
- const listId = useId();
1438
- const items = Children.toArray(children);
1439
- const limit = Number.isFinite(maxVisibleItems) ? Math.max(1, Math.floor(maxVisibleItems)) : 6;
1440
- const visibleCount = items.length > limit ? limit - 1 : limit;
1441
- const hiddenCount = Math.max(0, items.length - visibleCount);
1442
- return /* @__PURE__ */ jsx10("div", {
1443
- className: "altertable-chart-legend-container",
1444
- children: /* @__PURE__ */ jsxs5("ul", {
1445
- id: listId,
1446
- ...props,
1447
- "aria-label": ariaLabel,
1448
- "data-layout": layout,
1449
- "data-align": align,
1450
- className: classNames("altertable-chart-legend", className),
1451
- children: [
1452
- expanded ? items : items.slice(0, visibleCount),
1453
- hiddenCount > 0 && /* @__PURE__ */ jsx10("li", {
1454
- className: "altertable-chart-legend-overflow",
1455
- children: /* @__PURE__ */ jsx10("button", {
1456
- type: "button",
1457
- "data-atbl-control": "action",
1458
- "data-atbl-focus": "ring",
1459
- className: "altertable-chart-legend-action",
1460
- "aria-expanded": expanded,
1461
- "aria-controls": props.id ?? listId,
1462
- onClick: () => setExpanded((value) => !value),
1463
- children: expanded ? "Show fewer" : `+${hiddenCount} more`
1464
- })
1465
- })
1466
- ]
1467
- })
1468
- });
1469
- }
1470
- function ChartLegendItem({
1471
- inactive,
1472
- className,
1473
- ...props
1474
- }) {
1475
- return /* @__PURE__ */ jsx10("li", {
1476
- ...props,
1477
- "data-inactive": inactive || undefined,
1478
- className: classNames("altertable-chart-legend-item", className)
1479
- });
1480
- }
1481
- function ChartLegendMarker({
1482
- kind,
1483
- color = kind === "bar" ? chartBarFill : "var(--atbl-accent)",
1484
- className,
1485
- style,
1486
- ...props
1487
- }) {
1488
- return /* @__PURE__ */ jsxs5("svg", {
1489
- ...props,
1490
- "aria-hidden": "true",
1491
- focusable: "false",
1492
- viewBox: "0 0 20 12",
1493
- "data-kind": kind,
1494
- className: classNames("altertable-chart-legend-marker", className),
1495
- style: { color, ...style },
1496
- children: [
1497
- kind === "bar" && /* @__PURE__ */ jsx10("path", {
1498
- d: "M6 11V3Q6 1 8 1H12Q14 1 14 3V11Z",
1499
- fill: "currentColor"
1500
- }),
1501
- kind === "slice" && /* @__PURE__ */ jsx10("rect", {
1502
- x: "5",
1503
- y: "1",
1504
- width: "10",
1505
- height: "10",
1506
- rx: "2",
1507
- fill: "currentColor"
1508
- }),
1509
- kind === "point" && /* @__PURE__ */ jsx10("circle", {
1510
- cx: "10",
1511
- cy: "6",
1512
- r: "3",
1513
- fill: "currentColor"
1514
- }),
1515
- kind === "line" && /* @__PURE__ */ jsxs5(Fragment2, {
1516
- children: [
1517
- /* @__PURE__ */ jsx10("path", {
1518
- d: "M1 6H19",
1519
- fill: "none",
1520
- stroke: "currentColor",
1521
- strokeWidth: "2",
1522
- strokeLinecap: "round"
1523
- }),
1524
- /* @__PURE__ */ jsx10("circle", {
1525
- cx: "10",
1526
- cy: "6",
1527
- r: "2.5",
1528
- fill: "var(--atbl-surface)",
1529
- stroke: "currentColor",
1530
- strokeWidth: "2"
1531
- })
1532
- ]
1533
- }),
1534
- kind === "area" && /* @__PURE__ */ jsxs5(Fragment2, {
1535
- children: [
1536
- /* @__PURE__ */ jsx10("path", {
1537
- d: "M1 10L7 5L13 7L19 2V11H1Z",
1538
- fill: "currentColor",
1539
- fillOpacity: "0.14"
1540
- }),
1541
- /* @__PURE__ */ jsx10("path", {
1542
- d: "M1 10L7 5L13 7L19 2",
1543
- fill: "none",
1544
- stroke: "currentColor",
1545
- strokeWidth: "2",
1546
- strokeLinejoin: "round",
1547
- strokeLinecap: "round"
1548
- })
1549
- ]
1550
- })
1551
- ]
1552
- });
1553
- }
1554
- function ChartLegendLabel({
1555
- className,
1556
- children,
1557
- title,
1558
- ...props
1559
- }) {
1560
- return /* @__PURE__ */ jsx10("span", {
1561
- ...props,
1562
- title: title ?? (typeof children === "string" ? children : undefined),
1563
- className: classNames("altertable-chart-legend-label", className),
1564
- children
1565
- });
1566
- }
1567
- var ChartLegend = Object.assign(ChartLegendRoot, {
1568
- Item: ChartLegendItem,
1569
- Marker: ChartLegendMarker,
1570
- Label: ChartLegendLabel
1571
- });
1572
-
1573
1622
  // src/react/ui/PieChart.tsx
1574
1623
  import { useState as useState3 } from "react";
1575
- import { jsx as jsx11, jsxs as jsxs6, Fragment as Fragment3 } from "react/jsx-runtime";
1624
+ import { jsx as jsx11, jsxs as jsxs7, Fragment as Fragment3 } from "react/jsx-runtime";
1576
1625
  function PieChart({
1577
1626
  items,
1578
1627
  unit,
@@ -1610,12 +1659,12 @@ function PieChart({
1610
1659
  }).format(share);
1611
1660
  }
1612
1661
  function detail(item) {
1613
- return /* @__PURE__ */ jsxs6(Fragment3, {
1662
+ return /* @__PURE__ */ jsxs7(Fragment3, {
1614
1663
  children: [
1615
1664
  /* @__PURE__ */ jsx11("strong", {
1616
1665
  children: item.label
1617
1666
  }),
1618
- /* @__PURE__ */ jsxs6("span", {
1667
+ /* @__PURE__ */ jsxs7("span", {
1619
1668
  children: [
1620
1669
  formatValue(item.value),
1621
1670
  " ",
@@ -1628,16 +1677,16 @@ function PieChart({
1628
1677
  });
1629
1678
  }
1630
1679
  const preview = slices.find((item) => item.id === previewId);
1631
- return /* @__PURE__ */ jsxs6("section", {
1680
+ return /* @__PURE__ */ jsxs7("section", {
1632
1681
  className: "altertable-pie-chart",
1633
1682
  "aria-label": ariaLabel,
1634
1683
  children: [
1635
1684
  items.length === 0 && /* @__PURE__ */ jsx11("span", {
1636
1685
  children: "No data"
1637
1686
  }),
1638
- items.length > 0 && /* @__PURE__ */ jsxs6(Fragment3, {
1687
+ items.length > 0 && /* @__PURE__ */ jsxs7(Fragment3, {
1639
1688
  children: [
1640
- /* @__PURE__ */ jsxs6("div", {
1689
+ /* @__PURE__ */ jsxs7("div", {
1641
1690
  className: "altertable-pie-plot",
1642
1691
  children: [
1643
1692
  /* @__PURE__ */ jsx11(Tooltip2, {
@@ -1690,7 +1739,7 @@ function PieChart({
1690
1739
  variant: "chart",
1691
1740
  content: detail(item),
1692
1741
  onOpenChange: (open) => setLegendId((current) => open ? item.id : current === item.id ? null : current),
1693
- children: /* @__PURE__ */ jsxs6("button", {
1742
+ children: /* @__PURE__ */ jsxs7("button", {
1694
1743
  "data-atbl-focus": "ring",
1695
1744
  "data-atbl-control": "default",
1696
1745
  type: "button",
@@ -1698,7 +1747,7 @@ function PieChart({
1698
1747
  "aria-label": `${item.label}: ${formatValue(item.value)} ${unit}, ${percent(item.share)}`,
1699
1748
  children: [
1700
1749
  /* @__PURE__ */ jsx11(ChartLegend.Marker, {
1701
- kind: "slice",
1750
+ kind: "square",
1702
1751
  color: item.color
1703
1752
  }),
1704
1753
  /* @__PURE__ */ jsx11(ChartLegend.Label, {
@@ -1722,7 +1771,7 @@ function PieChart({
1722
1771
  });
1723
1772
  }
1724
1773
  // src/react/ui/ScatterChart.tsx
1725
- import { jsx as jsx12, jsxs as jsxs7, Fragment as Fragment4 } from "react/jsx-runtime";
1774
+ import { jsx as jsx12, jsxs as jsxs8, Fragment as Fragment4 } from "react/jsx-runtime";
1726
1775
  function formatNumber(value) {
1727
1776
  return new Intl.NumberFormat(undefined, { maximumFractionDigits: 1 }).format(value);
1728
1777
  }
@@ -1751,16 +1800,16 @@ function ScatterChart({
1751
1800
  validateChartItems("scatter", items);
1752
1801
  const xScale = scale(items.map((item) => item.x));
1753
1802
  const yScale = scale(items.map((item) => item.y));
1754
- return /* @__PURE__ */ jsxs7("section", {
1803
+ return /* @__PURE__ */ jsxs8("section", {
1755
1804
  className: "altertable-scatter-chart",
1756
1805
  "aria-label": ariaLabel,
1757
1806
  children: [
1758
1807
  items.length === 0 && /* @__PURE__ */ jsx12("span", {
1759
1808
  children: "No data"
1760
1809
  }),
1761
- items.length > 0 && /* @__PURE__ */ jsxs7(Fragment4, {
1810
+ items.length > 0 && /* @__PURE__ */ jsxs8(Fragment4, {
1762
1811
  children: [
1763
- /* @__PURE__ */ jsxs7("p", {
1812
+ /* @__PURE__ */ jsxs8("p", {
1764
1813
  className: "altertable-scatter-axis-title",
1765
1814
  children: [
1766
1815
  yLabel,
@@ -1769,7 +1818,7 @@ function ScatterChart({
1769
1818
  }),
1770
1819
  /* @__PURE__ */ jsx12("div", {
1771
1820
  className: "altertable-scatter-axes",
1772
- children: /* @__PURE__ */ jsxs7("div", {
1821
+ children: /* @__PURE__ */ jsxs8("div", {
1773
1822
  className: "altertable-scatter-plot",
1774
1823
  children: [
1775
1824
  yScale.ticks.map((value, index) => /* @__PURE__ */ jsx12("div", {
@@ -1791,12 +1840,12 @@ function ScatterChart({
1791
1840
  }, index)),
1792
1841
  items.map((item) => /* @__PURE__ */ jsx12(Tooltip2, {
1793
1842
  variant: "chart",
1794
- content: /* @__PURE__ */ jsxs7(Fragment4, {
1843
+ content: /* @__PURE__ */ jsxs8(Fragment4, {
1795
1844
  children: [
1796
1845
  /* @__PURE__ */ jsx12("strong", {
1797
1846
  children: item.label
1798
1847
  }),
1799
- /* @__PURE__ */ jsxs7("span", {
1848
+ /* @__PURE__ */ jsxs8("span", {
1800
1849
  children: [
1801
1850
  xLabel,
1802
1851
  ": ",
@@ -1805,7 +1854,7 @@ function ScatterChart({
1805
1854
  xUnit
1806
1855
  ]
1807
1856
  }),
1808
- /* @__PURE__ */ jsxs7("span", {
1857
+ /* @__PURE__ */ jsxs8("span", {
1809
1858
  children: [
1810
1859
  yLabel,
1811
1860
  ": ",
@@ -1834,7 +1883,7 @@ function ScatterChart({
1834
1883
  ]
1835
1884
  })
1836
1885
  }),
1837
- /* @__PURE__ */ jsxs7("p", {
1886
+ /* @__PURE__ */ jsxs8("p", {
1838
1887
  className: "altertable-scatter-axis-title altertable-scatter-x-title",
1839
1888
  children: [
1840
1889
  xLabel,
@@ -1849,7 +1898,7 @@ function ScatterChart({
1849
1898
  // src/react/ui/BarChart.tsx
1850
1899
  import { useLayoutEffect as useLayoutEffect2, useRef as useRef2, useState as useState4 } from "react";
1851
1900
  import { BarChart as RechartsBarChart, Cell } from "recharts";
1852
- import { jsx as jsx13, jsxs as jsxs8, Fragment as Fragment5 } from "react/jsx-runtime";
1901
+ import { jsx as jsx13, jsxs as jsxs9, Fragment as Fragment5 } from "react/jsx-runtime";
1853
1902
  function BarChart({
1854
1903
  items,
1855
1904
  unit,
@@ -1865,7 +1914,7 @@ function BarChart({
1865
1914
  element.scrollLeft = element.scrollWidth - element.clientWidth;
1866
1915
  }, [items]);
1867
1916
  const maximum = items.reduce((max, item) => Math.max(max, item.value), 1);
1868
- return /* @__PURE__ */ jsxs8("section", {
1917
+ return /* @__PURE__ */ jsxs9("section", {
1869
1918
  className: "altertable-selectable-bars",
1870
1919
  "aria-label": ariaLabel,
1871
1920
  children: [
@@ -1875,14 +1924,14 @@ function BarChart({
1875
1924
  /* @__PURE__ */ jsx13("div", {
1876
1925
  className: "altertable-selectable-bars-scroll",
1877
1926
  ref: scrollport,
1878
- children: /* @__PURE__ */ jsxs8("div", {
1927
+ children: /* @__PURE__ */ jsxs9("div", {
1879
1928
  className: "altertable-selectable-bars-plot",
1880
1929
  style: { minWidth: items.length * 45 },
1881
1930
  children: [
1882
1931
  /* @__PURE__ */ jsx13("div", {
1883
1932
  className: "altertable-chart-canvas",
1884
1933
  "aria-hidden": "true",
1885
- children: /* @__PURE__ */ jsxs8(RechartsBarChart, {
1934
+ children: /* @__PURE__ */ jsxs9(RechartsBarChart, {
1886
1935
  data: items.map((item) => ({
1887
1936
  ...item,
1888
1937
  plotValue: item.value / maximum
@@ -1921,12 +1970,12 @@ function BarChart({
1921
1970
  children: items.map((item) => /* @__PURE__ */ jsx13(Tooltip2, {
1922
1971
  variant: "chart",
1923
1972
  onOpenChange: (open) => setActiveId((current) => open ? item.id : current === item.id ? undefined : current),
1924
- content: /* @__PURE__ */ jsxs8(Fragment5, {
1973
+ content: /* @__PURE__ */ jsxs9(Fragment5, {
1925
1974
  children: [
1926
1975
  /* @__PURE__ */ jsx13("strong", {
1927
1976
  children: item.label
1928
1977
  }),
1929
- /* @__PURE__ */ jsxs8("span", {
1978
+ /* @__PURE__ */ jsxs9("span", {
1930
1979
  children: [
1931
1980
  formatValue(item.value),
1932
1981
  " ",
@@ -1936,7 +1985,7 @@ function BarChart({
1936
1985
  ]
1937
1986
  }),
1938
1987
  className: "altertable-chart-column",
1939
- children: /* @__PURE__ */ jsxs8("button", {
1988
+ children: /* @__PURE__ */ jsxs9("button", {
1940
1989
  "data-atbl-focus": "ring",
1941
1990
  "data-atbl-control": "default",
1942
1991
  type: "button",
@@ -1967,14 +2016,14 @@ import {
1967
2016
  Radio as AriaRadio,
1968
2017
  Label
1969
2018
  } from "react-aria-components";
1970
- import { jsx as jsx14, jsxs as jsxs9, Fragment as Fragment6 } from "react/jsx-runtime";
2019
+ import { jsx as jsx14, jsxs as jsxs10, Fragment as Fragment6 } from "react/jsx-runtime";
1971
2020
  function RadioGroup({
1972
2021
  label,
1973
2022
  className,
1974
2023
  children,
1975
2024
  ...props
1976
2025
  }) {
1977
- return /* @__PURE__ */ jsxs9(AriaRadioGroup, {
2026
+ return /* @__PURE__ */ jsxs10(AriaRadioGroup, {
1978
2027
  ...props,
1979
2028
  className: classNames("altertable-radio-group", className),
1980
2029
  children: [
@@ -1992,7 +2041,7 @@ function Radio({ className, children, ...props }) {
1992
2041
  "data-atbl-control": "action",
1993
2042
  "data-atbl-focus": "ring",
1994
2043
  className: classNames("altertable-radio", className),
1995
- children: (state) => /* @__PURE__ */ jsxs9(Fragment6, {
2044
+ children: (state) => /* @__PURE__ */ jsxs10(Fragment6, {
1996
2045
  children: [
1997
2046
  /* @__PURE__ */ jsx14("span", {
1998
2047
  className: "altertable-radio-mark",
@@ -2024,7 +2073,7 @@ function SegmentedControl({
2024
2073
  });
2025
2074
  }
2026
2075
  // src/react/ui/CheckboxGroup.tsx
2027
- import { jsx as jsx15, jsxs as jsxs10 } from "react/jsx-runtime";
2076
+ import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
2028
2077
  function CheckboxGroup({
2029
2078
  label,
2030
2079
  values,
@@ -2035,7 +2084,7 @@ function CheckboxGroup({
2035
2084
  ...props
2036
2085
  }) {
2037
2086
  invariant(new Set(values).size === values.length, "Checkbox group values must be unique.");
2038
- return /* @__PURE__ */ jsxs10("fieldset", {
2087
+ return /* @__PURE__ */ jsxs11("fieldset", {
2039
2088
  ...props,
2040
2089
  disabled,
2041
2090
  className: classNames("altertable-checkbox-group", className),
@@ -2056,9 +2105,12 @@ function CheckboxGroup({
2056
2105
  ]
2057
2106
  });
2058
2107
  }
2108
+ // src/react/ui/ComposedChart.tsx
2109
+ import { useState as useState5 } from "react";
2110
+
2059
2111
  // src/react/ui/ComposedChartLegend.tsx
2060
2112
  import { Legend } from "recharts";
2061
- import { jsx as jsx16, jsxs as jsxs11, Fragment as Fragment7 } from "react/jsx-runtime";
2113
+ import { jsx as jsx16, jsxs as jsxs12, Fragment as Fragment7 } from "react/jsx-runtime";
2062
2114
  var legendMarkerKinds = {
2063
2115
  square: "area",
2064
2116
  rect: "bar",
@@ -2073,7 +2125,7 @@ function LegendContent({
2073
2125
  align,
2074
2126
  labelStyle,
2075
2127
  iconType,
2076
- iconSize = 20,
2128
+ iconSize = 8,
2077
2129
  inactiveColor = "var(--atbl-muted)",
2078
2130
  position,
2079
2131
  verticalAlign,
@@ -2095,15 +2147,18 @@ function LegendContent({
2095
2147
  if (entry.type === "none")
2096
2148
  return null;
2097
2149
  const type = iconType ?? entry.type;
2098
- const kind = legendMarkerKinds[type ?? "none"] ?? "point";
2150
+ const kind = iconType ? legendMarkerKinds[type ?? "none"] ?? "point" : "square";
2099
2151
  const formatLabel = entry.formatter ?? formatter;
2100
2152
  const label = formatLabel ? formatLabel(entry.value, entry, index) : entry.value;
2101
- const content = /* @__PURE__ */ jsxs11(Fragment7, {
2153
+ const content = /* @__PURE__ */ jsxs12(Fragment7, {
2102
2154
  children: [
2103
2155
  type !== "none" && (entry.legendIcon ?? /* @__PURE__ */ jsx16(ChartLegend.Marker, {
2104
2156
  kind,
2105
2157
  color: entry.inactive ? inactiveColor : entry.color,
2106
- style: { width: iconSize, height: iconSize * 0.6 }
2158
+ style: {
2159
+ width: iconSize,
2160
+ height: kind === "square" ? iconSize : iconSize * 0.6
2161
+ }
2107
2162
  })),
2108
2163
  /* @__PURE__ */ jsx16(ChartLegend.Label, {
2109
2164
  style: labelStyle,
@@ -2132,7 +2187,7 @@ function ComposedChartLegend({
2132
2187
  ...props
2133
2188
  }) {
2134
2189
  return /* @__PURE__ */ jsx16(Legend, {
2135
- iconSize: 20,
2190
+ iconSize: 8,
2136
2191
  inactiveColor: "var(--atbl-muted)",
2137
2192
  content: /* @__PURE__ */ jsx16(LegendContent, {
2138
2193
  maxVisibleItems
@@ -2164,9 +2219,17 @@ function ComposedChartRoot({
2164
2219
  className,
2165
2220
  ...props
2166
2221
  }) {
2222
+ const [pointerFocus, setPointerFocus] = useState5(false);
2167
2223
  return /* @__PURE__ */ jsx17("section", {
2168
2224
  className: ["altertable-composed-chart", className].filter(Boolean).join(" "),
2169
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
+ },
2170
2233
  children: /* @__PURE__ */ jsx17(RechartsComposedChart, {
2171
2234
  "data-atbl-focus": "inset",
2172
2235
  ...props,
@@ -2199,6 +2262,806 @@ var ComposedChart = Object.assign(ComposedChartRoot, {
2199
2262
  LabelList,
2200
2263
  ErrorBar
2201
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", {
2360
+ children: [
2361
+ /* @__PURE__ */ jsxs13("span", {
2362
+ children: [
2363
+ /* @__PURE__ */ jsx18(ChartLegend.Marker, {
2364
+ kind: "square",
2365
+ color: `var(--atbl-chart-${index % 8 + 1})`
2366
+ }),
2367
+ item.label
2368
+ ]
2369
+ }),
2370
+ /* @__PURE__ */ jsx18("strong", {
2371
+ children: formatPercent2(reading(item, steps.length - 1).conversion)
2372
+ })
2373
+ ]
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
+ });
2530
+ }
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})`
2699
+ }),
2700
+ /* @__PURE__ */ jsx19(ChartLegend.Label, {
2701
+ children: item.label
2702
+ })
2703
+ ]
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
+ })
3063
+ });
3064
+ }
2202
3065
  export {
2203
3066
  AboutData2 as AboutData,
2204
3067
  AppIcon2 as AppIcon,
@@ -2222,11 +3085,13 @@ export {
2222
3085
  DimensionPicker2 as DimensionPicker,
2223
3086
  FilterActions2 as FilterActions,
2224
3087
  FilterBar2 as FilterBar,
3088
+ FunnelChart,
2225
3089
  GettingStarted,
2226
3090
  GlossaryDefinition,
2227
3091
  GlossaryExplanation,
2228
3092
  GradientScroll2 as GradientScroll,
2229
3093
  HelpPopover2 as HelpPopover,
3094
+ JourneyChart,
2230
3095
  LineChart,
2231
3096
  LiveControl2 as LiveControl,
2232
3097
  Menu2 as Menu,
@@ -2244,6 +3109,7 @@ export {
2244
3109
  PresentStory2 as PresentStory,
2245
3110
  Radio,
2246
3111
  RadioGroup,
3112
+ RetentionChart,
2247
3113
  ScatterChart,
2248
3114
  SearchField2 as SearchField,
2249
3115
  SearchMatch2 as SearchMatch,
@@ -2266,7 +3132,8 @@ export {
2266
3132
  isPlainKeyEvent2 as isPlainKeyEvent,
2267
3133
  searchItems2 as searchItems,
2268
3134
  useAppVariables2 as useAppVariables,
3135
+ useSvgId,
2269
3136
  useViewTab2 as useViewTab
2270
3137
  };
2271
3138
 
2272
- //# debugId=FCD41F839B3A5B8964756E2164756E21
3139
+ //# debugId=6CFD84691873BB8C64756E2164756E21