@godxjp/ui 32.14.0 → 33.0.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 (39) hide show
  1. package/README.md +4 -4
  2. package/agent/START-HERE.md +7 -7
  3. package/agent/components/HeatmapChart.json +97 -0
  4. package/agent/components/Tabs.json +1 -1
  5. package/agent/components/Toolbar.json +2 -9
  6. package/agent/components-index.json +5 -0
  7. package/agent/components.json +100 -10
  8. package/agent/index.json +7 -7
  9. package/agent/llms.txt +6 -6
  10. package/agent/tokens.json +114 -6
  11. package/dist/components/charts/chart-summary.d.ts +32 -1
  12. package/dist/components/charts/chart-summary.js +49 -0
  13. package/dist/components/charts/heatmap-chart.d.ts +19 -0
  14. package/dist/components/charts/heatmap-chart.js +293 -0
  15. package/dist/components/charts/index.d.ts +3 -0
  16. package/dist/components/charts/index.js +2 -0
  17. package/dist/components/feedback/tooltip.d.ts +9 -0
  18. package/dist/components/feedback/tooltip.js +4 -1
  19. package/dist/components/navigation/index.d.ts +1 -1
  20. package/dist/components/navigation/toolbar.d.ts +2 -2
  21. package/dist/components/navigation/toolbar.js +0 -3
  22. package/dist/contracts/measurement.json +1 -1
  23. package/dist/i18n/messages/en.json +8 -1
  24. package/dist/i18n/messages/ja.json +8 -1
  25. package/dist/i18n/messages/vi.json +8 -1
  26. package/dist/props/components/charts.prop.d.ts +58 -0
  27. package/dist/props/components/navigation.prop.d.ts +10 -12
  28. package/dist/props/registry.d.ts +37 -5
  29. package/dist/props/registry.js +36 -5
  30. package/dist/styles/chart-layout.css +161 -0
  31. package/dist/styles/layers.json +8 -1
  32. package/dist/styles/layout.css +0 -33
  33. package/dist/styles.css +1 -1
  34. package/dist/tokens/components/chart.css +27 -0
  35. package/dist/tokens/components/navigation.css +0 -2
  36. package/dist/tokens/foundation.css +12 -0
  37. package/docs/migrations/v33.md +60 -0
  38. package/package.json +6 -2
  39. package/scripts/ui-audit.mjs +13 -0
@@ -3,7 +3,7 @@
3
3
  * alternative (WCAG 1.1.1). No JSX — filename pascal is intentionally NOT
4
4
  * exported, so this is not a catalog primitive.
5
5
  */
6
- import type { ChartDatum, ChartSeriesProp } from "../../props/components/charts.prop.js";
6
+ import type { ChartDatum, ChartSeriesProp, HeatmapCategory, HeatmapDatum } from "../../props/components/charts.prop.js";
7
7
  type Formatter = Intl.NumberFormat;
8
8
  export type ChartSummary = {
9
9
  rows: string[];
@@ -39,4 +39,35 @@ export declare function trendRatios(data: ChartDatum[], valueKey: string): numbe
39
39
  * datum).
40
40
  */
41
41
  export declare function resolveEmphasizedIndex(index: number | undefined, length: number): number;
42
+ /** Number of steps in the `--chart-heat-1..N` ramp. */
43
+ export declare const HEAT_STEPS = 5;
44
+ /** The lookup key of one heatmap cell. */
45
+ export declare function heatmapCellKey(row: string | number, column: string | number): string;
46
+ export type HeatmapModel = {
47
+ /** Finite values by cell key. A key that is absent is an EMPTY cell, never a zero. */
48
+ values: Map<string, number>;
49
+ /** Ramp step 1..HEAT_STEPS by cell key. */
50
+ steps: Map<string, number>;
51
+ /** 1-based rank of the top-N cells by cell key. */
52
+ ranks: Map<string, number>;
53
+ /** Scale domain: `min(0, smallest)` .. largest. */
54
+ min: number;
55
+ max: number;
56
+ /** Count of cells with a value inside the rows × columns matrix. */
57
+ present: number;
58
+ /** Count of matrix cells with no value. */
59
+ empty: number;
60
+ /** The highest cell, for the one-line summary. */
61
+ highest: {
62
+ row: HeatmapCategory;
63
+ column: HeatmapCategory;
64
+ value: number;
65
+ } | null;
66
+ };
67
+ /**
68
+ * Bin a heatmap's cells onto the sequential ramp. The domain starts at zero (or the smallest value,
69
+ * when that is negative) so the fill encodes MAGNITUDE — a heatmap of 900..1000 whose 900 painted
70
+ * as "empty-light" would read as a quiet hour. Cells outside `rows` × `columns` are ignored.
71
+ */
72
+ export declare function buildHeatmapModel(rows: HeatmapCategory[], columns: HeatmapCategory[], data: HeatmapDatum[], topN?: number): HeatmapModel;
42
73
  export {};
@@ -40,11 +40,60 @@ function resolveEmphasizedIndex(index, length) {
40
40
  const resolved = index < 0 ? length + index : index;
41
41
  return resolved >= 0 && resolved < length ? resolved : -1;
42
42
  }
43
+ const HEAT_STEPS = 5;
44
+ function heatmapCellKey(row, column) {
45
+ return `${String(row)}\0${String(column)}`;
46
+ }
47
+ function buildHeatmapModel(rows, columns, data, topN) {
48
+ const rowByKey = new Map(rows.map((row) => [String(row.key), row]));
49
+ const columnByKey = new Map(columns.map((column) => [String(column.key), column]));
50
+ const values = /* @__PURE__ */ new Map();
51
+ const order = [];
52
+ for (const datum of data) {
53
+ const row = rowByKey.get(String(datum.row));
54
+ const column = columnByKey.get(String(datum.column));
55
+ if (!row || !column) continue;
56
+ if (typeof datum.value !== "number" || !Number.isFinite(datum.value)) continue;
57
+ const key = heatmapCellKey(row.key, column.key);
58
+ if (values.has(key)) continue;
59
+ values.set(key, datum.value);
60
+ order.push({ key, value: datum.value, row, column });
61
+ }
62
+ let min = 0;
63
+ let max = 0;
64
+ for (const { value } of order) {
65
+ if (value < min) min = value;
66
+ if (value > max) max = value;
67
+ }
68
+ const steps = /* @__PURE__ */ new Map();
69
+ for (const { key, value } of order) {
70
+ const ratio = max > min ? (value - min) / (max - min) : max > 0 ? 1 : 0;
71
+ steps.set(key, Math.min(HEAT_STEPS, Math.floor(ratio * HEAT_STEPS) + 1));
72
+ }
73
+ const ranked = [...order].sort((a, b) => b.value - a.value);
74
+ const ranks = /* @__PURE__ */ new Map();
75
+ const n = topN !== void 0 && Number.isInteger(topN) && topN > 0 ? topN : 0;
76
+ ranked.slice(0, n).forEach((cell, index) => ranks.set(cell.key, index + 1));
77
+ const top = ranked[0];
78
+ return {
79
+ values,
80
+ steps,
81
+ ranks,
82
+ min,
83
+ max,
84
+ present: order.length,
85
+ empty: rows.length * columns.length - order.length,
86
+ highest: top ? { row: top.row, column: top.column, value: top.value } : null
87
+ };
88
+ }
43
89
  export {
90
+ HEAT_STEPS,
44
91
  buildCartesianSummary,
92
+ buildHeatmapModel,
45
93
  buildPieSummary,
46
94
  buildSeriesRows,
47
95
  buildTrendSummary,
96
+ heatmapCellKey,
48
97
  resolveEmphasizedIndex,
49
98
  trendRatios
50
99
  };
@@ -0,0 +1,19 @@
1
+ import * as React from "react";
2
+ import type { HeatmapChartProp } from "../../props/components/charts.prop.js";
3
+ export type { HeatmapChartProp, HeatmapChartProp as HeatmapChartProps, HeatmapCategory, HeatmapDatum, } from "../../props/components/charts.prop.js";
4
+ /**
5
+ * HeatmapChart — a **dependency-free** rows × columns matrix (weekday × hour revenue, order counts
6
+ * per slot). Cell fill steps through the sequential `--chart-heat-1..5` ramp.
7
+ *
8
+ * - **One structure for sight and for AT.** The matrix IS a native data table (`<th scope>` headers,
9
+ * one cell per slot) with `role="grid"`, so the screen-reader text alternative is the same element
10
+ * the eye reads — no second, drifting copy. Each cell carries its formatted value as text.
11
+ * - **Keyboard** — WAI-ARIA APG data grid: one tab stop (roving tabindex), arrows move between
12
+ * cells (mirrored under RTL), Home/End go to the row's ends, Ctrl+Home/End to the first/last
13
+ * cell. A focused or hovered cell shows ONE shared tooltip; Escape dismisses it.
14
+ * - **Never colour alone** — an empty cell is outlined (dashed) and reads "no data"; a zero is a
15
+ * filled step-1 cell that reads "0"; a top-N cell wears a ring and reads "top N".
16
+ * - **Narrow screens** — the matrix never shrinks a cell below its `size` tier; it scrolls inside
17
+ * its own box with the row header pinned, so the page never scrolls sideways.
18
+ */
19
+ export declare function HeatmapChart({ rows, columns, data, label, showCaption, description, size, numberFormat, topN, showLegend, emptyMessage, className, id, ref, }: HeatmapChartProp): React.JSX.Element;
@@ -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
@@ -2,7 +2,7 @@ export { Tabs, TabsContent, TabsList, TabsTrigger } from "./tabs.js";
2
2
  export { Pagination, PaginationContent, PaginationItem, PaginationLink, PaginationEllipsis, PaginationPrevious, PaginationNext, } from "./pagination.js";
3
3
  export { Steps } from "./steps.js";
4
4
  export { FilterBar, FilterBarGroup, Toolbar, ToolbarGroup } from "./toolbar.js";
5
- export type { FilterBarChipProp, FilterBarChipProps, FilterBarFilterProp, FilterBarFilterProps, FilterBarGroupProp, FilterBarGroupProps, FilterBarOverflowProp, FilterBarProp, FilterBarProps, FilterBarSearchProp, FilterBarSearchProps, ToolbarGroupProps, ToolbarProps, } from "./toolbar.js";
5
+ export type { FilterBarChipProp, FilterBarChipProps, FilterBarFilterProp, FilterBarFilterProps, FilterBarGroupProp, FilterBarGroupProps, FilterBarProp, FilterBarProps, FilterBarSearchProp, FilterBarSearchProps, ToolbarGroupProps, ToolbarProps, } from "./toolbar.js";
6
6
  export { AppSettingPicker } from "./app-setting-picker.js";
7
7
  export type { AppSettingPickerProp, AppSettingPickerProps, AppSettingPickerMenuProp, AppSettingPickerCycleProp, AppSettingKind, AppSettingCycleKind, } from "./app-setting-picker.js";
8
8
  export type { BreadcrumbItemProp as BreadcrumbItem } from "../../props/vocabulary/navigation.prop.js";
@@ -1,6 +1,6 @@
1
1
  import type { ToolbarGroupProp, ToolbarProp } from "../../props/components/navigation.prop.js";
2
- export type { FilterBarChipProp, FilterBarChipProp as FilterBarChipProps, FilterBarFilterProp, FilterBarFilterProp as FilterBarFilterProps, FilterBarOverflowProp, FilterBarSearchProp, FilterBarSearchProp as FilterBarSearchProps, ToolbarGroupProp, ToolbarGroupProp as ToolbarGroupProps, ToolbarGroupProp as FilterBarGroupProp, ToolbarGroupProp as FilterBarGroupProps, ToolbarProp, ToolbarProp as ToolbarProps, ToolbarProp as FilterBarProp, ToolbarProp as FilterBarProps, } from "../../props/components/navigation.prop.js";
3
- export declare function Toolbar({ onClear, hasActiveFilters, sticky, overflow, search, filters, chips, onChipRemove, actions, resultCount, loading, disabled, error, clearDisplay, views, viewsPlacement, className, children, }: ToolbarProp): import("react").JSX.Element;
2
+ export type { FilterBarChipProp, FilterBarChipProp as FilterBarChipProps, FilterBarFilterProp, FilterBarFilterProp as FilterBarFilterProps, FilterBarSearchProp, FilterBarSearchProp as FilterBarSearchProps, ToolbarGroupProp, ToolbarGroupProp as ToolbarGroupProps, ToolbarGroupProp as FilterBarGroupProp, ToolbarGroupProp as FilterBarGroupProps, ToolbarProp, ToolbarProp as ToolbarProps, ToolbarProp as FilterBarProp, ToolbarProp as FilterBarProps, } from "../../props/components/navigation.prop.js";
3
+ export declare function Toolbar({ onClear, hasActiveFilters, sticky, search, filters, chips, onChipRemove, actions, resultCount, loading, disabled, error, clearDisplay, views, viewsPlacement, className, children, }: ToolbarProp): import("react").JSX.Element;
4
4
  export declare function ToolbarGroup({ label, controlId, className, children }: ToolbarGroupProp): import("react").JSX.Element;
5
5
  /** Canonical list-page filter strip. `Toolbar` remains available for backwards compatibility. */
6
6
  export declare const FilterBar: typeof Toolbar;
@@ -13,7 +13,6 @@ function Toolbar({
13
13
  onClear,
14
14
  hasActiveFilters = true,
15
15
  sticky = false,
16
- overflow = "wrap",
17
16
  search,
18
17
  filters,
19
18
  chips,
@@ -73,7 +72,6 @@ function Toolbar({
73
72
  {
74
73
  role: "toolbar",
75
74
  "aria-label": t("navigation.toolbar.ariaLabel"),
76
- "data-overflow": overflow,
77
75
  className: cn("ui-toolbar", sticky && "ui-toolbar-sticky", className),
78
76
  children: [
79
77
  children,
@@ -98,7 +96,6 @@ function Toolbar({
98
96
  role: "toolbar",
99
97
  "aria-label": t("navigation.toolbar.ariaLabel"),
100
98
  "aria-busy": loading || void 0,
101
- "data-overflow": overflow,
102
99
  className: "ui-toolbar",
103
100
  children: [
104
101
  views != null && viewsPlacement === "inline" && /* @__PURE__ */ jsx("div", { className: "ui-filter-bar-views", "data-placement": "inline", children: views }),
@@ -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": "33.0.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. */