@automattic/charts 1.10.2 → 1.11.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 (30) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/TOKENS.md +72 -0
  3. package/dist/index.cjs +149 -44
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.css +60 -47
  6. package/dist/index.d.cts +17 -7
  7. package/dist/index.d.ts +17 -7
  8. package/dist/index.js +150 -45
  9. package/dist/index.js.map +1 -1
  10. package/package.json +1 -1
  11. package/src/charts/conversion-funnel-chart/conversion-funnel-chart.module.scss +7 -7
  12. package/src/charts/heatmap-chart/heatmap-chart.module.scss +18 -13
  13. package/src/charts/heatmap-chart/heatmap-chart.tsx +11 -7
  14. package/src/charts/heatmap-chart/private/heatmap-legend.tsx +3 -3
  15. package/src/charts/heatmap-chart/test/heatmap-chart.test.tsx +13 -7
  16. package/src/charts/leaderboard-chart/hooks/index.ts +1 -0
  17. package/src/charts/leaderboard-chart/hooks/use-fitted-row-count.ts +115 -0
  18. package/src/charts/leaderboard-chart/leaderboard-chart.module.scss +47 -20
  19. package/src/charts/leaderboard-chart/leaderboard-chart.tsx +72 -16
  20. package/src/charts/leaderboard-chart/test/leaderboard-chart-fit-rows.test.tsx +365 -0
  21. package/src/charts/leaderboard-chart/test/leaderboard-chart.test.tsx +73 -2
  22. package/src/charts/leaderboard-chart/types.ts +12 -0
  23. package/src/charts/line-chart/line-chart.module.scss +2 -2
  24. package/src/charts/private/grid-control/grid-control.module.scss +1 -1
  25. package/src/charts/private/svg-empty-state/svg-empty-state.module.scss +1 -1
  26. package/src/charts/private/x-zoom.module.scss +12 -10
  27. package/src/components/tooltip/base-tooltip.module.scss +3 -2
  28. package/src/components/trend-indicator/trend-indicator.module.scss +6 -5
  29. package/src/providers/chart-context/themes.ts +33 -19
  30. package/src/types.ts +2 -2
package/CHANGELOG.md CHANGED
@@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.11.0] - 2026-07-27
9
+ ### Added
10
+ - Charts: add a `fitRows` prop to LeaderboardChart so a chart in a fixed-height container shows only the rows that fit instead of scrolling. [#50688]
11
+
12
+ ### Changed
13
+ - Charts: standardise package CSS custom properties on the --a8c-charts-{category}-{name} naming convention and document the token catalog; existing public override variables keep working via deprecated aliases. [#50656]
14
+
15
+ ### Fixed
16
+ - Leaderboard: Distinguish unavailable percentage changes from missing comparison data. [#50690]
17
+
18
+ ## [1.10.3] - 2026-07-22
19
+ ### Fixed
20
+ - LeaderboardChart: Keep row spacing and column alignment identical whether or not a row is interactive. [#50657]
21
+
8
22
  ## [1.10.2] - 2026-07-20
9
23
  ### Added
10
24
  - Add HeatmapChart min/max cell size bounds and floor an all-zero heatmap at the scale bottom instead of full intensity. [#50517]
@@ -931,6 +945,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
931
945
  - Fixed lints following ESLint rule changes for TS [#40584]
932
946
  - Fixing a bug in Chart storybook data. [#40640]
933
947
 
948
+ [1.11.0]: https://github.com/Automattic/charts/compare/v1.10.3...v1.11.0
949
+ [1.10.3]: https://github.com/Automattic/charts/compare/v1.10.2...v1.10.3
934
950
  [1.10.2]: https://github.com/Automattic/charts/compare/v1.10.1...v1.10.2
935
951
  [1.10.1]: https://github.com/Automattic/charts/compare/v1.10.0...v1.10.1
936
952
  [1.10.0]: https://github.com/Automattic/charts/compare/v1.9.0...v1.10.0
package/TOKENS.md ADDED
@@ -0,0 +1,72 @@
1
+ # Charts CSS custom-property catalog
2
+
3
+ `@automattic/charts` exposes its themeable values as CSS custom properties under a
4
+ single convention: `--a8c-charts-{category}-{name}`. `{category}` mirrors the WPDS
5
+ token type (`color`, `dimension`, `border-radius`).
6
+
7
+ ## How it resolves today
8
+
9
+ Nothing emits these variables at runtime yet (that is CHARTS-203). Each reference in
10
+ the library carries its default inline as a fallback:
11
+
12
+ ```scss
13
+ stroke: var(--a8c-charts-color-grid, var(--wpds-color-stroke-surface-neutral, #dbdbdb));
14
+ ```
15
+
16
+ So an `--a8c-charts-*` variable you set on a chart container wins; otherwise the
17
+ mapped `--wpds-*` token wins; otherwise the spec-value hex wins. Setting any
18
+ `--a8c-charts-*` variable on a chart ancestor overrides that role.
19
+
20
+ ## Tier 1 — semantic catalog (shared, themeable roles)
21
+
22
+ | Role | Maps to `--wpds-*` | Fallback |
23
+ |---|---|---|
24
+ | `--a8c-charts-color-grid` | `--wpds-color-stroke-surface-neutral` | `#dbdbdb` |
25
+ | `--a8c-charts-color-axis` | `--wpds-color-stroke-surface-neutral` | `#dbdbdb` |
26
+ | `--a8c-charts-color-tick` | `--wpds-color-stroke-surface-neutral` | `#dbdbdb` |
27
+ | `--a8c-charts-color-label` | `--wpds-color-foreground-content-neutral` | `#1e1e1e` |
28
+ | `--a8c-charts-color-label-secondary` | `--wpds-color-foreground-content-neutral-weak` | `#707070` |
29
+ | `--a8c-charts-color-label-inverse` | `--wpds-color-foreground-interactive-neutral-strong` | `#f0f0f0` |
30
+ | `--a8c-charts-color-label-on-fill` | _(none — white-on-series-fill, no WPDS fit)_ | `#FFFFFF` |
31
+ | `--a8c-charts-color-annotation` | `--wpds-color-foreground-content-neutral` | `#1e1e1e` |
32
+ | `--a8c-charts-color-trend-up` | `--wpds-color-foreground-content-success-weak` | `#008030` |
33
+ | `--a8c-charts-color-trend-down` | `--wpds-color-foreground-content-error-weak` | `#cc1818` |
34
+ | `--a8c-charts-color-trend-neutral` | `--wpds-color-foreground-content-neutral-weak` | `#707070` |
35
+ | `--a8c-charts-color-background` | `--wpds-color-background-surface-neutral-strong` | `#fff` |
36
+ | `--a8c-charts-color-surface-secondary` | `--wpds-color-background-surface-neutral-weak` | `#f4f4f4` |
37
+ | `--a8c-charts-color-track` | `--wpds-color-background-track-neutral-weak` | `#f0f0f0` |
38
+ | `--a8c-charts-color-tooltip-surface` | _(none — translucent dark surface, no WPDS fit)_ | `rgba(0,0,0,0.85)` |
39
+ | `--a8c-charts-color-focus` | `--wpds-color-stroke-focus` | `#3858e9` |
40
+
41
+ `--a8c-charts-color-axis` (axis line) and `--a8c-charts-color-tick` (tick marks)
42
+ resolve to the same value as `--a8c-charts-color-grid` by default. They are kept as
43
+ distinct roles so axis, tick marks, and gridlines can be themed independently; each
44
+ maps directly to its own `--wpds-*` token rather than chaining through `grid`.
45
+
46
+ ## Tier 2 — component-specific variables
47
+
48
+ Instance styling knobs, on the same convention but not shared semantic roles:
49
+ `--a8c-charts-border-radius-leaderboard-bar`,
50
+ `--a8c-charts-dimension-leaderboard-bar-hover-inset`,
51
+ `--a8c-charts-color-heatmap-*`, `--a8c-charts-dimension-heatmap-*`,
52
+ `--a8c-charts-heatmap-cell-intensity`.
53
+
54
+ `--a8c-charts-heatmap-cell-intensity` is the one variable without a `{category}`
55
+ segment: it holds a unitless 0–1 scalar consumed inside `color-mix()`, not a colour.
56
+
57
+ The heatmap Tier-2 variables (`--a8c-charts-color-heatmap-*`,
58
+ `--a8c-charts-dimension-heatmap-*`, `--a8c-charts-heatmap-cell-intensity`) are
59
+ **component-emitted** — the chart sets them from JS per render — not consumer
60
+ override points.
61
+
62
+ ## Public override variables
63
+
64
+ These are documented, supported override points. Deprecated names still work — they
65
+ are read as an inner fallback behind the new name:
66
+
67
+ | New | Deprecated alias |
68
+ |---|---|
69
+ | `--a8c-charts-color-trend-up` | `--charts-trend-up-color` |
70
+ | `--a8c-charts-color-trend-down` | `--charts-trend-down-color` |
71
+ | `--a8c-charts-color-trend-neutral` | `--charts-trend-neutral-color` |
72
+ | `--a8c-charts-border-radius-leaderboard-bar` | `--a8c--charts--leaderboard--bar--border-radius` |
package/dist/index.cjs CHANGED
@@ -725,9 +725,9 @@ const getChartColor = (index, colorCache) => {
725
725
  * Default theme configuration
726
726
  */
727
727
  const defaultTheme = {
728
- backgroundColor: "var(--wpds-color-background-surface-neutral-strong, #fff)",
728
+ backgroundColor: "var(--a8c-charts-color-background, var(--wpds-color-background-surface-neutral-strong, #fff))",
729
729
  labelBackgroundColor: "transparent",
730
- labelTextColor: "#FFFFFF",
730
+ labelTextColor: "var(--a8c-charts-color-label-on-fill, #FFFFFF)",
731
731
  colors: [
732
732
  "#98C8DF",
733
733
  "#006DAB",
@@ -736,59 +736,59 @@ const defaultTheme = {
736
736
  "#FF8C8F"
737
737
  ],
738
738
  gridStyles: {
739
- stroke: "var(--wpds-color-stroke-surface-neutral, #dbdbdb)",
739
+ stroke: "var(--a8c-charts-color-grid, var(--wpds-color-stroke-surface-neutral, #dbdbdb))",
740
740
  strokeWidth: 1
741
741
  },
742
742
  tickLength: 4,
743
743
  gridColor: "",
744
744
  gridColorDark: "",
745
745
  xTickLineStyles: {
746
- stroke: "var(--wpds-color-stroke-surface-neutral, #dbdbdb)",
746
+ stroke: "var(--a8c-charts-color-tick, var(--wpds-color-stroke-surface-neutral, #dbdbdb))",
747
747
  strokeWidth: 1
748
748
  },
749
749
  xAxisLineStyles: {
750
- stroke: "var(--wpds-color-stroke-surface-neutral, #dbdbdb)",
750
+ stroke: "var(--a8c-charts-color-axis, var(--wpds-color-stroke-surface-neutral, #dbdbdb))",
751
751
  strokeWidth: 1
752
752
  },
753
753
  legend: {
754
- labelStyles: { color: "var(--wpds-color-foreground-content-neutral, #1e1e1e)" },
754
+ labelStyles: { color: "var(--a8c-charts-color-label, var(--wpds-color-foreground-content-neutral, #1e1e1e))" },
755
755
  containerStyles: {},
756
756
  shapeStyles: []
757
757
  },
758
758
  seriesLineStyles: [],
759
759
  glyphs: [],
760
760
  svgLabelSmall: {
761
- fill: "var(--wpds-color-foreground-content-neutral, #1e1e1e)",
761
+ fill: "var(--a8c-charts-color-label, var(--wpds-color-foreground-content-neutral, #1e1e1e))",
762
762
  fontFamily: "inherit"
763
763
  },
764
764
  svgLabelBig: { fontFamily: "inherit" },
765
765
  annotationStyles: {
766
766
  label: {
767
- anchorLineStroke: "var(--wpds-color-foreground-content-neutral, #1e1e1e)",
768
- backgroundFill: "var(--wpds-color-background-surface-neutral-strong, #fff)"
767
+ anchorLineStroke: "var(--a8c-charts-color-annotation, var(--wpds-color-foreground-content-neutral, #1e1e1e))",
768
+ backgroundFill: "var(--a8c-charts-color-background, var(--wpds-color-background-surface-neutral-strong, #fff))"
769
769
  },
770
- connector: { stroke: "var(--wpds-color-foreground-content-neutral, #1e1e1e)" },
770
+ connector: { stroke: "var(--a8c-charts-color-annotation, var(--wpds-color-foreground-content-neutral, #1e1e1e))" },
771
771
  circleSubject: {
772
772
  stroke: "transparent",
773
- fill: "var(--wpds-color-foreground-content-neutral, #1e1e1e)",
773
+ fill: "var(--a8c-charts-color-annotation, var(--wpds-color-foreground-content-neutral, #1e1e1e))",
774
774
  radius: 5
775
775
  }
776
776
  },
777
- geoChart: { featureFillColor: "var(--wpds-color-background-surface-neutral-weak, #f4f4f4)" },
777
+ geoChart: { featureFillColor: "var(--a8c-charts-color-surface-secondary, var(--wpds-color-background-surface-neutral-weak, #f4f4f4))" },
778
778
  leaderboardChart: {
779
779
  rowGap: 12,
780
780
  columnGap: 4,
781
781
  labelSpacing: "xs",
782
782
  deltaColors: [
783
- "var(--wpds-color-foreground-content-error-weak, #cc1818)",
784
- "var(--wpds-color-foreground-content-neutral-weak, #707070)",
785
- "var(--wpds-color-foreground-content-success-weak, #008030)"
783
+ "var(--a8c-charts-color-trend-down, var(--wpds-color-foreground-content-error-weak, #cc1818))",
784
+ "var(--a8c-charts-color-trend-neutral, var(--wpds-color-foreground-content-neutral-weak, #707070))",
785
+ "var(--a8c-charts-color-trend-up, var(--wpds-color-foreground-content-success-weak, #008030))"
786
786
  ]
787
787
  },
788
788
  conversionFunnelChart: {
789
- backgroundColor: "var(--wpds-color-background-surface-neutral-weak, #f4f4f4)",
790
- positiveChangeColor: "var(--wpds-color-foreground-content-success-weak, #008030)",
791
- negativeChangeColor: "var(--wpds-color-foreground-content-error-weak, #cc1818)"
789
+ backgroundColor: "var(--a8c-charts-color-surface-secondary, var(--wpds-color-background-surface-neutral-weak, #f4f4f4))",
790
+ positiveChangeColor: "var(--a8c-charts-color-trend-up, var(--wpds-color-foreground-content-success-weak, #008030))",
791
+ negativeChangeColor: "var(--a8c-charts-color-trend-down, var(--wpds-color-foreground-content-error-weak, #cc1818))"
792
792
  },
793
793
  lineChart: { lineStyles: { comparison: {
794
794
  strokeDasharray: "4 4",
@@ -6427,9 +6427,9 @@ const HeatmapLegend = ({ steps = 5, lessLabel, moreLabel }) => {
6427
6427
  "aria-hidden": "true",
6428
6428
  className: heatmap_chart_module_default["heatmap-chart__legend-swatch"],
6429
6429
  style: {
6430
- "--heatmap-primary": primaryColorHex,
6431
- "--heatmap-bg": backgroundColor,
6432
- "--intensity": intensity
6430
+ "--a8c-charts-color-heatmap-primary": primaryColorHex,
6431
+ "--a8c-charts-color-heatmap-background": backgroundColor,
6432
+ "--a8c-charts-heatmap-cell-intensity": intensity
6433
6433
  }
6434
6434
  }, index);
6435
6435
  })
@@ -6587,17 +6587,17 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
6587
6587
  children: (0, _wordpress_i18n.__)("No data available", "jetpack-charts")
6588
6588
  })
6589
6589
  });
6590
- const columnTrack = compact ? "var(--heatmap-cell-size)" : `minmax(${minCellWidth ?? 0}px, ${maxCellWidth ? `${maxCellWidth}px` : "1fr"})`;
6591
- const rowTrack = compact ? "var(--heatmap-cell-size)" : `minmax(${minCellHeight ?? 0}px, ${maxCellHeight ? `${maxCellHeight}px` : "1fr"})`;
6590
+ const columnTrack = compact ? "var(--a8c-charts-dimension-heatmap-cell-size)" : `minmax(${minCellWidth ?? 0}px, ${maxCellWidth ? `${maxCellWidth}px` : "1fr"})`;
6591
+ const rowTrack = compact ? "var(--a8c-charts-dimension-heatmap-cell-size)" : `minmax(${minCellHeight ?? 0}px, ${maxCellHeight ? `${maxCellHeight}px` : "1fr"})`;
6592
6592
  const gridStyle = {
6593
- "--heatmap-primary": primaryColorHex,
6594
- "--heatmap-bg": theme.backgroundColor,
6593
+ "--a8c-charts-color-heatmap-primary": primaryColorHex,
6594
+ "--a8c-charts-color-heatmap-background": theme.backgroundColor,
6595
6595
  gridTemplateColumns: `auto repeat(${columns}, ${columnTrack})`,
6596
6596
  gridTemplateRows: `auto repeat(${rows}, ${rowTrack})`
6597
6597
  };
6598
6598
  if (compact) {
6599
- gridStyle["--heatmap-cell-gap"] = `${compactCellGap}px`;
6600
- gridStyle["--heatmap-cell-size"] = `${compactCellSize}px`;
6599
+ gridStyle["--a8c-charts-dimension-heatmap-cell-gap"] = `${compactCellGap}px`;
6600
+ gridStyle["--a8c-charts-dimension-heatmap-cell-size"] = `${compactCellSize}px`;
6601
6601
  }
6602
6602
  const activeDescendant = selectedIndex !== void 0 ? `${chartId}-cell-${Math.floor(selectedIndex / rows)}-${selectedIndex % rows}` : void 0;
6603
6603
  const heightCapped = !compact && Boolean(maxCellHeight);
@@ -6674,7 +6674,7 @@ const HeatmapChartInternal = ({ data, chartId: providedChartId, width = 0, heigh
6674
6674
  [heatmap_chart_module_default["heatmap-chart__cell--strong"]]: present && cellHasLightText(normalized),
6675
6675
  [heatmap_chart_module_default["heatmap-chart__cell--selected"]]: selectedIndex === flatIndex
6676
6676
  }),
6677
- style: present ? { "--intensity": normalized } : void 0,
6677
+ style: present ? { "--a8c-charts-heatmap-cell-intensity": normalized } : void 0,
6678
6678
  onMouseMove: handleCellMouseMove,
6679
6679
  onMouseLeave: handleCellMouseLeave,
6680
6680
  children: drawValues && present && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -9007,6 +9007,90 @@ function UnconnectedGrid(props, forwardedRef) {
9007
9007
  }
9008
9008
  var component_default = contextConnect(UnconnectedGrid, "Grid");
9009
9009
  //#endregion
9010
+ //#region src/charts/leaderboard-chart/hooks/use-fitted-row-count.ts
9011
+ /**
9012
+ * Fractional layout rounding can put a row's bottom a hair past the container's
9013
+ * without any visible clipping. Allow that much slack before hiding a row.
9014
+ *
9015
+ * Exported so tests and stories assert against the real tolerance rather than
9016
+ * restating it.
9017
+ */
9018
+ const SUBPIXEL_TOLERANCE = .5;
9019
+ /**
9020
+ * Counts how many leading rows fit inside the content container.
9021
+ *
9022
+ * Rows are read from the DOM rather than derived from constants: row height
9023
+ * depends on the theme's row gap, label wrapping, and whatever a caller renders
9024
+ * as a label, so any restated number would drift.
9025
+ *
9026
+ * Every row is a direct grid child marked with `data-row-index`; interactive rows
9027
+ * use a button wrapper and non-interactive rows use a div wrapper.
9028
+ *
9029
+ * @param enabled - Whether to measure at all. When false every row fits.
9030
+ * @param rowCount - Total number of rows rendered.
9031
+ * @param data - Rendered row data, whose geometry may change without changing the count.
9032
+ * @return Ref for the content container and the number of leading rows that fit.
9033
+ */
9034
+ function useFittedRowCount(enabled, rowCount, data) {
9035
+ const contentRef = (0, react$1.useRef)(null);
9036
+ const [fittedCount, setFittedCount] = (0, react$1.useState)(rowCount);
9037
+ const [isMeasurable, setIsMeasurable] = (0, react$1.useState)(true);
9038
+ const measure = (0, react$1.useCallback)(() => {
9039
+ const content = contentRef.current;
9040
+ if (!content) return;
9041
+ content.scrollTop = 0;
9042
+ const rows = content.querySelectorAll(":scope > [data-leaderboard-grid] > [data-row-index]");
9043
+ const rowBottoms = [];
9044
+ rows.forEach((row) => {
9045
+ const index = Number(row.getAttribute("data-row-index"));
9046
+ if (!Number.isInteger(index) || index < 0 || index >= rowCount) return;
9047
+ const { bottom } = row.getBoundingClientRect();
9048
+ rowBottoms[index] = Math.max(rowBottoms[index] ?? -Infinity, bottom);
9049
+ });
9050
+ if (rowBottoms.length === 0) {
9051
+ setIsMeasurable(false);
9052
+ setFittedCount(rowCount);
9053
+ return;
9054
+ }
9055
+ setIsMeasurable(true);
9056
+ const contentBottom = content.getBoundingClientRect().bottom + SUBPIXEL_TOLERANCE;
9057
+ let fits = 0;
9058
+ while (fits < rowBottoms.length && rowBottoms[fits] <= contentBottom) fits++;
9059
+ setFittedCount((current) => current === fits ? current : fits);
9060
+ }, [rowCount]);
9061
+ (0, react$1.useLayoutEffect)(() => {
9062
+ if (!enabled) {
9063
+ setFittedCount(rowCount);
9064
+ return;
9065
+ }
9066
+ measure();
9067
+ }, [
9068
+ enabled,
9069
+ rowCount,
9070
+ data,
9071
+ measure
9072
+ ]);
9073
+ (0, react$1.useLayoutEffect)(() => {
9074
+ if (!enabled) return;
9075
+ const content = contentRef.current;
9076
+ if (!content) return;
9077
+ const observer = new ResizeObserver(measure);
9078
+ observer.observe(content);
9079
+ const grid = content.querySelector(":scope > [data-leaderboard-grid]");
9080
+ if (grid) observer.observe(grid);
9081
+ return () => observer.disconnect();
9082
+ }, [
9083
+ enabled,
9084
+ rowCount,
9085
+ measure
9086
+ ]);
9087
+ return {
9088
+ contentRef,
9089
+ fittedCount: enabled ? fittedCount : rowCount,
9090
+ isMeasurable
9091
+ };
9092
+ }
9093
+ //#endregion
9010
9094
  //#region src/charts/leaderboard-chart/hooks/use-leaderboard-legend-items.ts
9011
9095
  /**
9012
9096
  * Hook to create legend items from leaderboard data
@@ -9067,15 +9151,18 @@ var leaderboard_chart_module_default = {
9067
9151
  "deltaPlaceholder": "a8ccharts-GovfoW-deltaPlaceholder",
9068
9152
  "deltaValue": "a8ccharts-GovfoW-deltaValue",
9069
9153
  "emptyState": "a8ccharts-GovfoW-emptyState",
9154
+ "fitEmptyState": "a8ccharts-GovfoW-fitEmptyState",
9070
9155
  "interactiveRow": "a8ccharts-GovfoW-interactiveRow",
9071
9156
  "is-overlay": "a8ccharts-GovfoW-is-overlay",
9072
9157
  "label": "a8ccharts-GovfoW-label",
9073
9158
  "leaderboardChart": "a8ccharts-GovfoW-leaderboardChart",
9074
9159
  "leaderboardChart__content": "a8ccharts-GovfoW-leaderboardChart__content",
9160
+ "leaderboardChart__content--fit": "a8ccharts-GovfoW-leaderboardChart__content--fit",
9075
9161
  "leaderboardChart--loading": "a8ccharts-GovfoW-leaderboardChart--loading",
9076
9162
  "leaderboardChart--responsive": "a8ccharts-GovfoW-leaderboardChart--responsive",
9077
9163
  "overlap": "a8ccharts-GovfoW-overlap",
9078
9164
  "overlayLabel": "a8ccharts-GovfoW-overlayLabel",
9165
+ "row": "a8ccharts-GovfoW-row",
9079
9166
  "stretch": "a8ccharts-GovfoW-stretch",
9080
9167
  "valueContainer": "a8ccharts-GovfoW-valueContainer"
9081
9168
  };
@@ -9115,8 +9202,9 @@ const defaultDeltaFormatter = (value) => {
9115
9202
  * @param share - The bar's share of the row width, as a percentage.
9116
9203
  * @return A CSS width value.
9117
9204
  */
9118
- const getBarWidth = (share) => `calc(${share}% - var(--a8c--charts--leaderboard--bar--hover-inset, 0px) * ${share} / 100)`;
9119
- const hasComparisonValue = (entry) => entry.previousValue != null && entry.previousShare != null && entry.delta != null;
9205
+ const getBarWidth = (share) => `calc(${share}% - var(--a8c-charts-dimension-leaderboard-bar-hover-inset, 0px) * ${share} / 100)`;
9206
+ const hasPreviousValue = (entry) => entry.previousValue != null && entry.previousShare != null;
9207
+ const hasDeltaValue = (entry) => hasPreviousValue(entry) && entry.delta != null;
9120
9208
  const BarLabel = ({ label }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: typeof label === "string" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Text$2, {
9121
9209
  className: leaderboard_chart_module_default.label,
9122
9210
  children: label
@@ -9134,7 +9222,7 @@ const BarWithLabel = ({ entry, withComparison, withOverlayLabel, primaryColor, s
9134
9222
  backgroundColor: primaryColor
9135
9223
  }
9136
9224
  }),
9137
- showComparisonBar && hasComparisonValue(entry) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9225
+ showComparisonBar && hasPreviousValue(entry) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9138
9226
  className: (0, clsx.default)(leaderboard_chart_module_default.bar, { [leaderboard_chart_module_default["bar--animated"]]: animation }),
9139
9227
  style: {
9140
9228
  width: getBarWidth(entry.previousShare),
@@ -9160,6 +9248,7 @@ const BarWithLabel = ({ entry, withComparison, withOverlayLabel, primaryColor, s
9160
9248
  * @param props.valueFormatter - Custom formatter for values
9161
9249
  * @param props.deltaFormatter - Custom formatter for delta values
9162
9250
  * @param props.loading - Whether the chart is in loading state
9251
+ * @param props.fitRows - Whether to show only the rows that fit the chart's height
9163
9252
  * @param props.animation - Whether the chart should animate on load
9164
9253
  * @param props.showLegend - Whether to show legend
9165
9254
  * @param props.legend - Legend configuration (orientation, position, alignment, shape, shapeStyles, interactive)
@@ -9170,7 +9259,7 @@ const BarWithLabel = ({ entry, withComparison, withOverlayLabel, primaryColor, s
9170
9259
  * @param props.style - Custom styling for the chart container
9171
9260
  * @return JSX element representing the leaderboard chart
9172
9261
  */
9173
- const LeaderboardChartInternal = ({ data, chartId: providedChartId, width: propWidth, height: propHeight, withComparison = false, withOverlayLabel = false, primaryColor, secondaryColor, valueFormatter = defaultValueFormatter, deltaFormatter = defaultDeltaFormatter, animation, loading = false, showLegend = false, legend = {}, legendLabels, gap = "md", className, style, children }) => {
9262
+ const LeaderboardChartInternal = ({ data, chartId: providedChartId, width: propWidth, height: propHeight, withComparison = false, withOverlayLabel = false, primaryColor, secondaryColor, valueFormatter = defaultValueFormatter, deltaFormatter = defaultDeltaFormatter, animation, loading = false, fitRows = false, showLegend = false, legend = {}, legendLabels, gap = "md", className, style, children }) => {
9174
9263
  const legendInteractive = legend.interactive ?? false;
9175
9264
  const legendPosition = legend.position ?? "bottom";
9176
9265
  const chartId = useChartId(providedChartId);
@@ -9239,6 +9328,8 @@ const LeaderboardChartInternal = ({ data, chartId: providedChartId, width: propW
9239
9328
  }), [withComparison, withOverlayLabel])
9240
9329
  });
9241
9330
  const prefersReducedMotion = usePrefersReducedMotion();
9331
+ const { contentRef, fittedCount, isMeasurable } = useFittedRowCount(fitRows && !allSeriesHidden, data?.length ?? 0, data);
9332
+ const shouldFitRows = fitRows && isMeasurable;
9242
9333
  if (!data || data.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SingleChartContext.Provider, {
9243
9334
  value: { chartId },
9244
9335
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChartLayout, {
@@ -9291,20 +9382,27 @@ const LeaderboardChartInternal = ({ data, chartId: providedChartId, width: propW
9291
9382
  height: propHeight || void 0
9292
9383
  },
9293
9384
  trailingContent: nonLegendChildren,
9294
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9295
- className: leaderboard_chart_module_default.leaderboardChart__content,
9296
- children: allSeriesHidden ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9385
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9386
+ ref: contentRef,
9387
+ className: (0, clsx.default)(leaderboard_chart_module_default.leaderboardChart__content, { [leaderboard_chart_module_default["leaderboardChart__content--fit"]]: shouldFitRows }),
9388
+ children: [shouldFitRows && fittedCount === 0 && !allSeriesHidden && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9389
+ className: (0, clsx.default)(leaderboard_chart_module_default.emptyState, leaderboard_chart_module_default.fitEmptyState),
9390
+ children: (0, _wordpress_i18n.__)("Not enough space to display data", "jetpack-charts")
9391
+ }), allSeriesHidden ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9297
9392
  className: leaderboard_chart_module_default.emptyState,
9298
9393
  children: (0, _wordpress_i18n.__)("All series are hidden. Click legend items to show data.", "jetpack-charts")
9299
9394
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(component_default, {
9300
9395
  templateColumns: "minmax(0, 1fr) auto",
9301
9396
  rowGap,
9302
9397
  columnGap,
9303
- children: data.map((entry) => {
9398
+ "data-leaderboard-grid": true,
9399
+ children: data.map((entry, rowIndex) => {
9400
+ const rowStyle = shouldFitRows && rowIndex >= fittedCount ? { visibility: "hidden" } : void 0;
9304
9401
  const showComparisonColumn = withComparison && isComparisonVisible;
9305
- const hasDeltaValue = hasComparisonValue(entry);
9306
- const showComparisonValue = showComparisonColumn && hasDeltaValue;
9307
- const showComparisonPlaceholder = showComparisonColumn && !hasDeltaValue;
9402
+ const hasPreviousPeriodValue = hasPreviousValue(entry);
9403
+ const hasDelta = hasDeltaValue(entry);
9404
+ const showComparisonValue = showComparisonColumn && hasDelta;
9405
+ const showComparisonPlaceholder = showComparisonColumn && !hasDelta;
9308
9406
  const colorIndex = showComparisonValue ? Math.sign(entry.delta) + 1 : 1;
9309
9407
  const deltaColor = deltaColors[colorIndex];
9310
9408
  const rowCells = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Stack, {
@@ -9336,17 +9434,19 @@ const LeaderboardChartInternal = ({ data, chartId: providedChartId, width: propW
9336
9434
  style: { color: deltaColor },
9337
9435
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9338
9436
  "aria-hidden": "true",
9339
- children: "-"
9437
+ children: "—"
9340
9438
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(component_default$1, {
9341
9439
  as: "span",
9342
- children: (0, _wordpress_i18n.__)("No comparison data", "jetpack-charts")
9440
+ children: hasPreviousPeriodValue ? (0, _wordpress_i18n.__)("Percentage change unavailable", "jetpack-charts") : (0, _wordpress_i18n.__)("No comparison data", "jetpack-charts")
9343
9441
  })]
9344
9442
  })
9345
9443
  ]
9346
9444
  })] });
9347
9445
  if (entry.onClick) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
9348
9446
  type: "button",
9349
- className: leaderboard_chart_module_default.interactiveRow,
9447
+ "data-row-index": rowIndex,
9448
+ style: rowStyle,
9449
+ className: (0, clsx.default)(leaderboard_chart_module_default.row, leaderboard_chart_module_default.interactiveRow),
9350
9450
  onClick: entry.onClick,
9351
9451
  "aria-label": entry.ariaLabel,
9352
9452
  children: [rowCells, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_wordpress_icons.Icon, {
@@ -9355,9 +9455,14 @@ const LeaderboardChartInternal = ({ data, chartId: providedChartId, width: propW
9355
9455
  size: 24
9356
9456
  })]
9357
9457
  }, entry.id);
9358
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react$1.Fragment, { children: rowCells }, entry.id);
9458
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9459
+ "data-row-index": rowIndex,
9460
+ style: rowStyle,
9461
+ className: leaderboard_chart_module_default.row,
9462
+ children: rowCells
9463
+ }, entry.id);
9359
9464
  })
9360
- })
9465
+ })]
9361
9466
  })
9362
9467
  })
9363
9468
  });