@godxjp/ui 32.14.0 → 32.15.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.
@@ -0,0 +1,293 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { cn } from "../../lib/utils.js";
5
+ import { EmptyState } from "../data-display/empty-state.js";
6
+ import { Tooltip, TooltipAnchorContext, TooltipContent } from "../feedback/tooltip.js";
7
+ import { useTranslation } from "../../i18n/use-translation.js";
8
+ import { useChartNumberFormat } from "./chart-frame.js";
9
+ import { HEAT_STEPS, buildHeatmapModel, heatmapCellKey } from "./chart-summary.js";
10
+ const OPEN_DELAY = 200;
11
+ const CLOSE_GRACE = 100;
12
+ const RAMP = Array.from({ length: HEAT_STEPS }, (_, index) => index + 1);
13
+ function HeatmapChart({
14
+ rows,
15
+ columns,
16
+ data,
17
+ label,
18
+ showCaption = true,
19
+ description,
20
+ size = "md",
21
+ numberFormat,
22
+ topN,
23
+ showLegend = true,
24
+ emptyMessage,
25
+ className,
26
+ id,
27
+ ref
28
+ }) {
29
+ const { t } = useTranslation();
30
+ const fmt = useChartNumberFormat(numberFormat);
31
+ const countFmt = useChartNumberFormat();
32
+ const model = React.useMemo(
33
+ () => buildHeatmapModel(rows, columns, data, topN),
34
+ [rows, columns, data, topN]
35
+ );
36
+ const hasData = rows.length > 0 && columns.length > 0 && model.present > 0;
37
+ const reactId = React.useId();
38
+ const baseId = id ?? reactId;
39
+ const titleId = `${baseId}-title`;
40
+ const descId = `${baseId}-desc`;
41
+ const [active, setActive] = React.useState([0, 0]);
42
+ const activeRow = Math.min(active[0], Math.max(0, rows.length - 1));
43
+ const activeColumn = Math.min(active[1], Math.max(0, columns.length - 1));
44
+ const scrollRef = React.useRef(null);
45
+ const cellRefs = React.useRef(/* @__PURE__ */ new Map());
46
+ const anchorRef = React.useRef(null);
47
+ const timer = React.useRef(void 0);
48
+ const [tip, setTip] = React.useState(null);
49
+ React.useEffect(() => () => clearTimeout(timer.current), []);
50
+ const showTip = (r, c, node, delay) => {
51
+ clearTimeout(timer.current);
52
+ timer.current = setTimeout(() => {
53
+ anchorRef.current = node;
54
+ setTip([r, c]);
55
+ }, delay);
56
+ };
57
+ const hideTip = () => {
58
+ clearTimeout(timer.current);
59
+ timer.current = setTimeout(() => setTip(null), CLOSE_GRACE);
60
+ };
61
+ React.useEffect(() => {
62
+ if (!tip) return void 0;
63
+ const onKeyDown = (event) => {
64
+ if (event.key === "Escape") {
65
+ clearTimeout(timer.current);
66
+ setTip(null);
67
+ }
68
+ };
69
+ document.addEventListener("keydown", onKeyDown);
70
+ return () => document.removeEventListener("keydown", onKeyDown);
71
+ }, [tip]);
72
+ const reveal = (cell) => {
73
+ const scroller = scrollRef.current;
74
+ const header = cell.parentElement?.querySelector("th");
75
+ if (!scroller || !header) return;
76
+ const cellBox = cell.getBoundingClientRect();
77
+ const headerBox = header.getBoundingClientRect();
78
+ if (getComputedStyle(scroller).direction === "rtl") {
79
+ if (cellBox.right > headerBox.left) scroller.scrollLeft += cellBox.right - headerBox.left;
80
+ } else if (cellBox.left < headerBox.right) {
81
+ scroller.scrollLeft -= headerBox.right - cellBox.left;
82
+ }
83
+ };
84
+ const moveTo = (r, c) => {
85
+ const row = Math.max(0, Math.min(rows.length - 1, r));
86
+ const column = Math.max(0, Math.min(columns.length - 1, c));
87
+ setActive([row, column]);
88
+ const cell = cellRefs.current.get(`${row}:${column}`);
89
+ if (cell) {
90
+ cell.focus();
91
+ reveal(cell);
92
+ }
93
+ };
94
+ const onGridKeyDown = (event) => {
95
+ const rtl = getComputedStyle(event.currentTarget).direction === "rtl";
96
+ const lastRow = rows.length - 1;
97
+ const lastColumn = columns.length - 1;
98
+ const ctrl = event.ctrlKey || event.metaKey;
99
+ switch (event.key) {
100
+ case "ArrowRight":
101
+ moveTo(activeRow, activeColumn + (rtl ? -1 : 1));
102
+ break;
103
+ case "ArrowLeft":
104
+ moveTo(activeRow, activeColumn + (rtl ? 1 : -1));
105
+ break;
106
+ case "ArrowDown":
107
+ moveTo(activeRow + 1, activeColumn);
108
+ break;
109
+ case "ArrowUp":
110
+ moveTo(activeRow - 1, activeColumn);
111
+ break;
112
+ case "Home":
113
+ moveTo(ctrl ? 0 : activeRow, 0);
114
+ break;
115
+ case "End":
116
+ moveTo(ctrl ? lastRow : activeRow, lastColumn);
117
+ break;
118
+ case "Escape":
119
+ clearTimeout(timer.current);
120
+ setTip(null);
121
+ return;
122
+ default:
123
+ return;
124
+ }
125
+ event.preventDefault();
126
+ };
127
+ const cellText = (r, c) => {
128
+ const key = heatmapCellKey(rows[r].key, columns[c].key);
129
+ const value = model.values.get(key);
130
+ const rank = model.ranks.get(key);
131
+ return {
132
+ value: value === void 0 ? t("chart.heatmapNoData") : fmt.format(value),
133
+ top: rank === void 0 ? void 0 : t("chart.heatmapTop", { rank: countFmt.format(rank) })
134
+ };
135
+ };
136
+ const summary = model.highest ? `${t("chart.heatmapSummary", {
137
+ rows: countFmt.format(rows.length),
138
+ columns: countFmt.format(columns.length)
139
+ })} ${t("chart.heatmapHighest", {
140
+ value: fmt.format(model.highest.value),
141
+ row: model.highest.row.label,
142
+ column: model.highest.column.label
143
+ })}` : "";
144
+ const tipText = tip && tip[0] < rows.length && tip[1] < columns.length ? cellText(tip[0], tip[1]) : null;
145
+ const span = model.max - model.min;
146
+ const bound = (step) => model.min + span * step / HEAT_STEPS;
147
+ return /* @__PURE__ */ jsxs(
148
+ "figure",
149
+ {
150
+ ref,
151
+ id,
152
+ "data-slot": "chart",
153
+ "data-size": size,
154
+ className: cn("ui-chart ui-chart-heatmap", className),
155
+ "aria-labelledby": titleId,
156
+ "aria-describedby": descId,
157
+ children: [
158
+ /* @__PURE__ */ jsx("figcaption", { id: titleId, className: showCaption ? "ui-chart-title" : "sr-only", children: label }),
159
+ hasData ? /* @__PURE__ */ jsxs(Fragment, { children: [
160
+ /* @__PURE__ */ jsx("div", { ref: scrollRef, className: "ui-chart-heatmap-scroll", children: /* @__PURE__ */ jsxs(
161
+ "table",
162
+ {
163
+ role: "grid",
164
+ "aria-labelledby": titleId,
165
+ className: "ui-chart-heatmap-grid",
166
+ onKeyDown: onGridKeyDown,
167
+ children: [
168
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
169
+ /* @__PURE__ */ jsx("td", { className: "ui-chart-heatmap-corner" }),
170
+ columns.map((column) => /* @__PURE__ */ jsx(
171
+ "th",
172
+ {
173
+ scope: "col",
174
+ className: "ui-chart-heatmap-column-header",
175
+ children: column.label
176
+ },
177
+ String(column.key)
178
+ ))
179
+ ] }) }),
180
+ /* @__PURE__ */ jsx("tbody", { children: rows.map((row, r) => /* @__PURE__ */ jsxs("tr", { children: [
181
+ /* @__PURE__ */ jsx("th", { scope: "row", className: "ui-chart-heatmap-row-header", children: row.label }),
182
+ columns.map((column, c) => {
183
+ const key = heatmapCellKey(row.key, column.key);
184
+ const empty = !model.values.has(key);
185
+ const text = cellText(r, c);
186
+ return /* @__PURE__ */ jsx(
187
+ "td",
188
+ {
189
+ role: "gridcell",
190
+ ref: (node) => {
191
+ if (node) cellRefs.current.set(`${r}:${c}`, node);
192
+ else cellRefs.current.delete(`${r}:${c}`);
193
+ },
194
+ tabIndex: r === activeRow && c === activeColumn ? 0 : -1,
195
+ className: "ui-chart-heatmap-cell ui-focus-ring",
196
+ "data-step": empty ? void 0 : model.steps.get(key),
197
+ "data-empty": empty ? "" : void 0,
198
+ "data-emphasized": model.ranks.has(key) ? "" : void 0,
199
+ onFocus: (event) => {
200
+ setActive([r, c]);
201
+ showTip(r, c, event.currentTarget, 0);
202
+ },
203
+ onBlur: hideTip,
204
+ onPointerEnter: (event) => showTip(r, c, event.currentTarget, OPEN_DELAY),
205
+ onPointerLeave: hideTip,
206
+ children: /* @__PURE__ */ jsx("span", { className: "sr-only", children: text.top ? `${text.value}, ${text.top}` : text.value })
207
+ },
208
+ String(column.key)
209
+ );
210
+ })
211
+ ] }, String(row.key))) })
212
+ ]
213
+ }
214
+ ) }),
215
+ showLegend ? /* @__PURE__ */ jsxs("div", { className: "ui-chart-heatmap-legend", children: [
216
+ /* @__PURE__ */ jsxs("div", { className: "ui-chart-heatmap-legend-scale", children: [
217
+ /* @__PURE__ */ jsx("span", { className: "ui-chart-heatmap-legend-label", "aria-hidden": "true", children: fmt.format(model.min) }),
218
+ /* @__PURE__ */ jsx(
219
+ "ul",
220
+ {
221
+ role: "list",
222
+ className: "ui-chart-heatmap-legend-ramp",
223
+ "aria-label": t("chart.heatmapLegend"),
224
+ children: RAMP.map((step) => /* @__PURE__ */ jsx("li", { className: "ui-chart-heatmap-legend-swatch", "data-step": step, children: /* @__PURE__ */ jsx("span", { className: "sr-only", children: fmt.formatRange(bound(step - 1), bound(step)) }) }, step))
225
+ }
226
+ ),
227
+ /* @__PURE__ */ jsx("span", { className: "ui-chart-heatmap-legend-label", "aria-hidden": "true", children: fmt.format(model.max) })
228
+ ] }),
229
+ model.empty > 0 ? /* @__PURE__ */ jsxs("span", { className: "ui-chart-heatmap-legend-item", children: [
230
+ /* @__PURE__ */ jsx(
231
+ "span",
232
+ {
233
+ className: "ui-chart-heatmap-legend-swatch",
234
+ "data-empty": "",
235
+ "aria-hidden": "true"
236
+ }
237
+ ),
238
+ t("chart.heatmapNoData")
239
+ ] }) : null,
240
+ model.ranks.size > 0 ? /* @__PURE__ */ jsxs("span", { className: "ui-chart-heatmap-legend-item", children: [
241
+ /* @__PURE__ */ jsx(
242
+ "span",
243
+ {
244
+ className: "ui-chart-heatmap-legend-swatch",
245
+ "data-step": HEAT_STEPS,
246
+ "data-emphasized": "",
247
+ "aria-hidden": "true"
248
+ }
249
+ ),
250
+ t("chart.heatmapLegendTop", { count: countFmt.format(model.ranks.size) })
251
+ ] }) : null
252
+ ] }) : null
253
+ ] }) : /* @__PURE__ */ jsx(EmptyState, { title: emptyMessage ?? t("chart.empty") }),
254
+ /* @__PURE__ */ jsxs("p", { id: descId, className: "sr-only", children: [
255
+ description ? /* @__PURE__ */ jsxs(Fragment, { children: [
256
+ description,
257
+ " "
258
+ ] }) : null,
259
+ hasData ? summary : null
260
+ ] }),
261
+ /* @__PURE__ */ jsx(
262
+ Tooltip,
263
+ {
264
+ open: tip !== null,
265
+ onOpenChange: (next) => {
266
+ if (!next) setTip(null);
267
+ },
268
+ children: /* @__PURE__ */ jsx(TooltipAnchorContext.Provider, { value: anchorRef, children: tip && tipText ? /* @__PURE__ */ jsxs(
269
+ TooltipContent,
270
+ {
271
+ className: "ui-chart-heatmap-tooltip",
272
+ onPointerEnter: () => clearTimeout(timer.current),
273
+ onPointerLeave: hideTip,
274
+ children: [
275
+ /* @__PURE__ */ jsx("span", { className: "ui-chart-heatmap-tooltip-title", children: t("chart.heatmapCell", {
276
+ row: rows[tip[0]]?.label ?? "",
277
+ column: columns[tip[1]]?.label ?? "",
278
+ value: tipText.value
279
+ }) }),
280
+ tipText.top ? /* @__PURE__ */ jsx("span", { className: "ui-chart-heatmap-tooltip-note", children: tipText.top }) : null
281
+ ]
282
+ },
283
+ `${tip[0]}:${tip[1]}`
284
+ ) : null })
285
+ }
286
+ )
287
+ ]
288
+ }
289
+ );
290
+ }
291
+ export {
292
+ HeatmapChart
293
+ };
@@ -9,6 +9,9 @@ export type { BarChartProp, BarChartProps } from "./bar-chart.js";
9
9
  */
10
10
  export { CompactBarTrend } from "./compact-bar-trend.js";
11
11
  export type { CompactBarTrendProp, CompactBarTrendProps } from "./compact-bar-trend.js";
12
+ /** Dependency-free rows × columns heatmap — isolated entry `@godxjp/ui/charts/heatmap-chart`. */
13
+ export { HeatmapChart } from "./heatmap-chart.js";
14
+ export type { HeatmapChartProp, HeatmapChartProps, HeatmapCategory, HeatmapDatum, } from "./heatmap-chart.js";
12
15
  export { AreaChart } from "./area-chart.js";
13
16
  export type { AreaChartProp, AreaChartProps } from "./area-chart.js";
14
17
  export { PieChart } from "./pie-chart.js";
@@ -1,6 +1,7 @@
1
1
  import { LineChart } from "./line-chart.js";
2
2
  import { BarChart } from "./bar-chart.js";
3
3
  import { CompactBarTrend } from "./compact-bar-trend.js";
4
+ import { HeatmapChart } from "./heatmap-chart.js";
4
5
  import { AreaChart } from "./area-chart.js";
5
6
  import { PieChart } from "./pie-chart.js";
6
7
  import { CHART_COLORS } from "./chart-frame.js";
@@ -9,6 +10,7 @@ export {
9
10
  BarChart,
10
11
  CHART_COLORS,
11
12
  CompactBarTrend,
13
+ HeatmapChart,
12
14
  LineChart,
13
15
  PieChart
14
16
  };
@@ -1,6 +1,15 @@
1
1
  import * as React from "react";
2
2
  type Side = "top" | "right" | "bottom" | "left";
3
3
  type Align = "start" | "center" | "end";
4
+ /**
5
+ * Anchors `TooltipContent` to an element that is NOT a `TooltipTrigger` (gh#1045). `TooltipTrigger`
6
+ * wraps its child in RAC's `Focusable`, which stamps `tabIndex=0` on it — right for a button, wrong for a truncated `Text` inside a `treeitem`, where it
7
+ * would add a second tab stop. The ellipsis tooltip drives `open` itself and hands its own element
8
+ * here as the positioning target (RAC `Tooltip`'s `triggerRef`). HeatmapChart (gh#1330) does the
9
+ * same with ONE tooltip for its whole grid, anchored to the active cell. Module-internal: not in
10
+ * the feedback index.
11
+ */
12
+ export declare const TooltipAnchorContext: React.Context<React.RefObject<Element | null> | null>;
4
13
  interface TooltipProviderProps {
5
14
  /** Delay mở dùng chung cho mọi `Tooltip` bên trong, tính bằng ms. */
6
15
  delayDuration?: number;
@@ -12,7 +12,9 @@ import {
12
12
  import { cn } from "../../lib/utils.js";
13
13
  import { Slot } from "../../lib/slot.js";
14
14
  const DEFAULT_DELAY_DURATION = 200;
15
- const TooltipAnchorContext = React.createContext(null);
15
+ const TooltipAnchorContext = React.createContext(
16
+ null
17
+ );
16
18
  function toPlacement(side, align) {
17
19
  if (align === "center") return side;
18
20
  if (side === "top" || side === "bottom") return `${side} ${align}`;
@@ -223,6 +225,7 @@ function EllipsisTooltip({
223
225
  export {
224
226
  EllipsisTooltip,
225
227
  Tooltip,
228
+ TooltipAnchorContext,
226
229
  TooltipContent,
227
230
  TooltipProvider,
228
231
  TooltipTrigger
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$comment": "AUTO-GENERATED by scripts/gen-measurement-contract.mjs — do not edit. Read this instead of guessing: docs/MEASUREMENT-CONTRACT.md.",
3
- "version": "32.14.0",
3
+ "version": "32.15.0",
4
4
  "targetSize": {
5
5
  "standard": "WCAG 2.2 SC 2.5.8 Target Size (Minimum), level AA — 24×24 CSS px",
6
6
  "min": 24,
@@ -787,7 +787,14 @@
787
787
  "summaryCartesian": "Chart with {series} series across {points} data points",
788
788
  "summaryPie": "Pie chart with {slices} slices",
789
789
  "summaryTrend": "Bar trend with {points} data points",
790
- "trendCurrent": "current"
790
+ "trendCurrent": "current",
791
+ "heatmapSummary": "Heatmap, {rows} × {columns}",
792
+ "heatmapHighest": "Highest: {value} at {row}, {column}",
793
+ "heatmapNoData": "No data",
794
+ "heatmapTop": "Top {rank}",
795
+ "heatmapLegend": "Color scale",
796
+ "heatmapLegendTop": "Top {count}",
797
+ "heatmapCell": "{row}, {column}: {value}"
791
798
  },
792
799
  "rangeTimeline": {
793
800
  "childRows": "Child rows",
@@ -761,7 +761,14 @@
761
761
  "summaryCartesian": "{series}系列・{points}データ点のグラフ",
762
762
  "summaryPie": "{slices}区分の円グラフ",
763
763
  "summaryTrend": "{points}データ点の推移棒グラフ",
764
- "trendCurrent": "現在"
764
+ "trendCurrent": "現在",
765
+ "heatmapSummary": "ヒートマップ({rows}行×{columns}列)",
766
+ "heatmapHighest": "最大 {value}({row}・{column})",
767
+ "heatmapNoData": "データなし",
768
+ "heatmapTop": "上位{rank}位",
769
+ "heatmapLegend": "色の濃さの凡例",
770
+ "heatmapLegendTop": "上位{count}件",
771
+ "heatmapCell": "{row}・{column}:{value}"
765
772
  },
766
773
  "rangeTimeline": {
767
774
  "childRows": "子の行",
@@ -762,7 +762,14 @@
762
762
  "summaryCartesian": "Biểu đồ gồm {series} chuỗi trên {points} điểm dữ liệu",
763
763
  "summaryPie": "Biểu đồ tròn gồm {slices} phần",
764
764
  "summaryTrend": "Biểu đồ cột xu hướng gồm {points} điểm dữ liệu",
765
- "trendCurrent": "hiện tại"
765
+ "trendCurrent": "hiện tại",
766
+ "heatmapSummary": "Bản đồ nhiệt {rows} × {columns}",
767
+ "heatmapHighest": "Cao nhất: {value} tại {row}, {column}",
768
+ "heatmapNoData": "Không có dữ liệu",
769
+ "heatmapTop": "Hạng {rank}",
770
+ "heatmapLegend": "Thang màu",
771
+ "heatmapLegendTop": "{count} ô cao nhất",
772
+ "heatmapCell": "{row}, {column}: {value}"
766
773
  },
767
774
  "rangeTimeline": {
768
775
  "childRows": "Hàng con",
@@ -138,6 +138,64 @@ export type CompactBarTrendProp = {
138
138
  id?: IdProp;
139
139
  ref?: React.Ref<HTMLElement>;
140
140
  };
141
+ /** One category on a HeatmapChart axis — a row (weekday) or a column (hour). */
142
+ export type HeatmapCategory = {
143
+ /** Stable key the `data` cells refer to (`row` / `column`). */
144
+ key: string | number;
145
+ /** Already-translated display label — the axis tick and the table header. */
146
+ label: string;
147
+ };
148
+ /** One HeatmapChart cell. A cell absent from `data`, or with a non-finite `value`, is EMPTY. */
149
+ export type HeatmapDatum = {
150
+ /** `key` of the row this cell belongs to. */
151
+ row: string | number;
152
+ /** `key` of the column this cell belongs to. */
153
+ column: string | number;
154
+ /** The measured value. `null`/`undefined`/`NaN` mean "no data" — not zero. */
155
+ value: number | null | undefined;
156
+ };
157
+ /**
158
+ * @see HeatmapChart
159
+ *
160
+ * Dependency-free (no `recharts`) rows × columns matrix — a weekday × hour heatmap. Cell intensity
161
+ * steps through the `--chart-heat-1..5` ramp; an empty cell is outlined, a zero cell is filled.
162
+ */
163
+ export type HeatmapChartProp = {
164
+ /** Row categories, top to bottom (the y axis — weekdays). */
165
+ rows: HeatmapCategory[];
166
+ /** Column categories, start to end (the x axis — hours). */
167
+ columns: HeatmapCategory[];
168
+ /** Cell values, keyed by `row` + `column`. Order does not matter; a missing cell is empty. */
169
+ data: HeatmapDatum[];
170
+ /** Accessible name + visible caption for the chart. REQUIRED (the grid needs a name). */
171
+ label: LabelProp;
172
+ /**
173
+ * Paint `label` as a visible caption above the plot. `false` keeps it in the DOM as `sr-only`,
174
+ * so the figure and its grid keep their accessible name.
175
+ */
176
+ showCaption?: boolean;
177
+ /** Extra context appended to the screen-reader description. */
178
+ description?: DescriptionProp;
179
+ /**
180
+ * CELL size tier — `xs|sm|md|lg` (`--chart-heatmap-cell-size-*`). Not a canvas height: the matrix
181
+ * is as tall as its rows, and when the columns do not fit it scrolls inside its own box.
182
+ */
183
+ size?: SizeProp;
184
+ /** `Intl.NumberFormat` options for cell values, the tooltip and the legend (locale is automatic). */
185
+ numberFormat?: Intl.NumberFormatOptions;
186
+ /**
187
+ * Emphasize the N highest cells with an outline ring and a "top N" note in the tooltip and the
188
+ * text alternative — never colour alone. Ties keep `data` order. Omitted or 0 = no emphasis.
189
+ */
190
+ topN?: number;
191
+ /** Show the colour-scale legend under the matrix. */
192
+ showLegend?: boolean;
193
+ /** Message shown when there are no rows, no columns or no cells. Defaults to a localized "no data". */
194
+ emptyMessage?: EmptyMessageProp;
195
+ className?: ClassNameProp;
196
+ id?: IdProp;
197
+ ref?: React.Ref<HTMLElement>;
198
+ };
141
199
  /** @see AreaChart */
142
200
  export type AreaChartProp = ChartCartesianBase & {
143
201
  /** Stack series areas instead of overlaying them. */
@@ -2331,6 +2331,43 @@ export declare const COMPONENT_PROP_REGISTRY: {
2331
2331
  readonly reason: "Forwarded ref to the <figure> element.";
2332
2332
  }];
2333
2333
  };
2334
+ readonly HeatmapChartProp: {
2335
+ readonly group: "data-display";
2336
+ readonly file: "components/charts.prop.ts";
2337
+ readonly vocabulary: readonly ["LabelProp", "DescriptionProp", "SizeProp", "EmptyMessageProp", "ClassNameProp", "IdProp", {
2338
+ readonly field: "rows";
2339
+ readonly local: true;
2340
+ readonly reason: "Row categories (y axis) — key + translated label.";
2341
+ }, {
2342
+ readonly field: "columns";
2343
+ readonly local: true;
2344
+ readonly reason: "Column categories (x axis) — key + translated label.";
2345
+ }, {
2346
+ readonly field: "data";
2347
+ readonly local: true;
2348
+ readonly reason: "Cell values keyed by row + column.";
2349
+ }, {
2350
+ readonly field: "showCaption";
2351
+ readonly local: true;
2352
+ readonly reason: "Paint the caption; false keeps it sr-only so the accessible name survives.";
2353
+ }, {
2354
+ readonly field: "numberFormat";
2355
+ readonly local: true;
2356
+ readonly reason: "Intl.NumberFormat options for cell values, tooltip and legend.";
2357
+ }, {
2358
+ readonly field: "topN";
2359
+ readonly local: true;
2360
+ readonly reason: "Emphasize the N highest cells (ring + text note, never colour alone).";
2361
+ }, {
2362
+ readonly field: "showLegend";
2363
+ readonly local: true;
2364
+ readonly reason: "Chart-specific legend toggle.";
2365
+ }, {
2366
+ readonly field: "ref";
2367
+ readonly local: true;
2368
+ readonly reason: "Forwarded ref to the <figure> element.";
2369
+ }];
2370
+ };
2334
2371
  readonly AreaChartProp: {
2335
2372
  readonly group: "data-display";
2336
2373
  readonly file: "components/charts.prop.ts";
@@ -2762,6 +2762,42 @@ const COMPONENT_PROP_REGISTRY = {
2762
2762
  { field: "ref", local: true, reason: "Forwarded ref to the <figure> element." }
2763
2763
  ]
2764
2764
  },
2765
+ HeatmapChartProp: {
2766
+ group: "data-display",
2767
+ file: "components/charts.prop.ts",
2768
+ vocabulary: [
2769
+ "LabelProp",
2770
+ "DescriptionProp",
2771
+ "SizeProp",
2772
+ "EmptyMessageProp",
2773
+ "ClassNameProp",
2774
+ "IdProp",
2775
+ { field: "rows", local: true, reason: "Row categories (y axis) \u2014 key + translated label." },
2776
+ {
2777
+ field: "columns",
2778
+ local: true,
2779
+ reason: "Column categories (x axis) \u2014 key + translated label."
2780
+ },
2781
+ { field: "data", local: true, reason: "Cell values keyed by row + column." },
2782
+ {
2783
+ field: "showCaption",
2784
+ local: true,
2785
+ reason: "Paint the caption; false keeps it sr-only so the accessible name survives."
2786
+ },
2787
+ {
2788
+ field: "numberFormat",
2789
+ local: true,
2790
+ reason: "Intl.NumberFormat options for cell values, tooltip and legend."
2791
+ },
2792
+ {
2793
+ field: "topN",
2794
+ local: true,
2795
+ reason: "Emphasize the N highest cells (ring + text note, never colour alone)."
2796
+ },
2797
+ { field: "showLegend", local: true, reason: "Chart-specific legend toggle." },
2798
+ { field: "ref", local: true, reason: "Forwarded ref to the <figure> element." }
2799
+ ]
2800
+ },
2765
2801
  AreaChartProp: {
2766
2802
  group: "data-display",
2767
2803
  file: "components/charts.prop.ts",