@k2b/ui 0.49.0 → 0.50.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 (265) hide show
  1. package/dist/base.css +40 -2
  2. package/dist/browser/actions/ContextMenu.js +3 -0
  3. package/dist/browser/actions/ContextMenu.js.map +1 -1
  4. package/dist/browser/charts/core/area.js +190 -0
  5. package/dist/browser/charts/core/area.js.map +1 -0
  6. package/dist/browser/charts/core/calendar.js +186 -0
  7. package/dist/browser/charts/core/calendar.js.map +1 -0
  8. package/dist/browser/charts/core/chart-map-data.js +9 -0
  9. package/dist/browser/charts/core/chart-map-data.js.map +1 -0
  10. package/dist/browser/charts/core/index.js +12 -0
  11. package/dist/browser/charts/core/index.js.map +1 -0
  12. package/dist/browser/charts/core/inspection.js +9 -0
  13. package/dist/browser/charts/core/inspection.js.map +1 -0
  14. package/dist/browser/charts/core/registry.js +28 -0
  15. package/dist/browser/charts/core/registry.js.map +1 -0
  16. package/dist/browser/charts/core/render.js +103 -0
  17. package/dist/browser/charts/core/render.js.map +1 -0
  18. package/dist/browser/charts/core/snapshot.js +65 -0
  19. package/dist/browser/charts/core/snapshot.js.map +1 -0
  20. package/dist/browser/charts/core/svg.js +2542 -0
  21. package/dist/browser/charts/core/svg.js.map +1 -0
  22. package/dist/browser/charts/core/termination.test-fixture.js +162 -0
  23. package/dist/browser/charts/core/termination.test-fixture.js.map +1 -0
  24. package/dist/browser/charts/core/text.js +13 -0
  25. package/dist/browser/charts/core/text.js.map +1 -0
  26. package/dist/browser/charts/core/types.js +8 -0
  27. package/dist/browser/charts/core/types.js.map +1 -0
  28. package/dist/browser/charts.js +4 -2
  29. package/dist/browser/charts.js.map +1 -1
  30. package/dist/browser/content/BalanceSummary.js +106 -0
  31. package/dist/browser/content/BalanceSummary.js.map +1 -0
  32. package/dist/browser/content/Chart.js +134 -115
  33. package/dist/browser/content/Chart.js.map +1 -1
  34. package/dist/browser/content/ChartExplorer.js +366 -122
  35. package/dist/browser/content/ChartExplorer.js.map +1 -1
  36. package/dist/browser/content/ChartSnapshotView.js +16 -9
  37. package/dist/browser/content/ChartSnapshotView.js.map +1 -1
  38. package/dist/browser/content/DataTable.js +676 -293
  39. package/dist/browser/content/DataTable.js.map +1 -1
  40. package/dist/browser/content/DataTableTools.js +119 -0
  41. package/dist/browser/content/DataTableTools.js.map +1 -0
  42. package/dist/browser/content/FlowAmount.js +82 -0
  43. package/dist/browser/content/FlowAmount.js.map +1 -0
  44. package/dist/browser/content/balance-summary-messages.js +26 -0
  45. package/dist/browser/content/balance-summary-messages.js.map +1 -0
  46. package/dist/browser/content/chart-bar.js +6 -6
  47. package/dist/browser/content/chart-bar.js.map +1 -1
  48. package/dist/browser/content/chart-calendar.js +43 -0
  49. package/dist/browser/content/chart-calendar.js.map +1 -0
  50. package/dist/browser/content/chart-inspection-core.js +164 -37
  51. package/dist/browser/content/chart-inspection-core.js.map +1 -1
  52. package/dist/browser/content/chart-inspection.js.map +1 -1
  53. package/dist/browser/content/chart-line-gaps.js +2 -2
  54. package/dist/browser/content/chart-line-gaps.js.map +1 -1
  55. package/dist/browser/content/chart-map-viewport.js.map +1 -1
  56. package/dist/browser/content/chart-message-catalog.js +164 -0
  57. package/dist/browser/content/chart-message-catalog.js.map +1 -0
  58. package/dist/browser/content/chart-messages.js +2 -138
  59. package/dist/browser/content/chart-messages.js.map +1 -1
  60. package/dist/browser/content/chart-snapshot.js +1 -62
  61. package/dist/browser/content/chart-snapshot.js.map +1 -1
  62. package/dist/browser/content/chart-state-timeline.js +1 -1
  63. package/dist/browser/content/chart-state-timeline.js.map +1 -1
  64. package/dist/browser/content/chart-svg.js +2 -1
  65. package/dist/browser/content/chart-svg.js.map +1 -1
  66. package/dist/browser/content/chart-time-axis.js +139 -0
  67. package/dist/browser/content/chart-time-axis.js.map +1 -0
  68. package/dist/browser/content/data-table-columns.js +160 -0
  69. package/dist/browser/content/data-table-columns.js.map +1 -0
  70. package/dist/browser/content/data-table-messages.js +38 -2
  71. package/dist/browser/content/data-table-messages.js.map +1 -1
  72. package/dist/browser/content/flow-amount-messages.js +26 -0
  73. package/dist/browser/content/flow-amount-messages.js.map +1 -0
  74. package/dist/browser/content/index.js +4 -0
  75. package/dist/browser/content/index.js.map +1 -1
  76. package/dist/browser/inputs/DatePicker.js +5 -4
  77. package/dist/browser/inputs/DatePicker.js.map +1 -1
  78. package/dist/browser/inputs/SelectChip.js +50 -17
  79. package/dist/browser/inputs/SelectChip.js.map +1 -1
  80. package/dist/browser/inputs/date-picker.js +13 -0
  81. package/dist/browser/inputs/date-picker.js.map +1 -1
  82. package/dist/browser/intl/Format.js +31 -8
  83. package/dist/browser/intl/Format.js.map +1 -1
  84. package/dist/browser/layout/AppWorkspace.js +7 -1
  85. package/dist/browser/layout/AppWorkspace.js.map +1 -1
  86. package/dist/browser/surfaces/BudgetList.js +281 -0
  87. package/dist/browser/surfaces/BudgetList.js.map +1 -0
  88. package/dist/browser/surfaces/ProgressBar.js +134 -21
  89. package/dist/browser/surfaces/ProgressBar.js.map +1 -1
  90. package/dist/browser/surfaces/StatCell.js +320 -144
  91. package/dist/browser/surfaces/StatCell.js.map +1 -1
  92. package/dist/browser/surfaces/StatGrid.js +75 -57
  93. package/dist/browser/surfaces/StatGrid.js.map +1 -1
  94. package/dist/browser/surfaces/budget-messages.js +64 -0
  95. package/dist/browser/surfaces/budget-messages.js.map +1 -0
  96. package/dist/browser/surfaces/delta-badge.js +65 -0
  97. package/dist/browser/surfaces/delta-badge.js.map +1 -0
  98. package/dist/browser/surfaces/index.js +2 -1
  99. package/dist/browser/surfaces/index.js.map +1 -1
  100. package/dist/browser/surfaces/person-color.js +2 -2
  101. package/dist/browser/surfaces/person-color.js.map +1 -1
  102. package/dist/browser/surfaces/stat-cell-messages.js +41 -0
  103. package/dist/browser/surfaces/stat-cell-messages.js.map +1 -0
  104. package/dist/browser/widgets/Dashboard.js +137 -0
  105. package/dist/browser/widgets/Dashboard.js.map +1 -0
  106. package/dist/browser/widgets/index.js +1 -0
  107. package/dist/browser/widgets/index.js.map +1 -1
  108. package/dist/ssr/actions/ContextMenu.js +3 -0
  109. package/dist/ssr/actions/ContextMenu.js.map +1 -1
  110. package/dist/ssr/charts/core/area.js +190 -0
  111. package/dist/ssr/charts/core/area.js.map +1 -0
  112. package/dist/ssr/charts/core/calendar.js +186 -0
  113. package/dist/ssr/charts/core/calendar.js.map +1 -0
  114. package/dist/ssr/charts/core/chart-map-data.js +9 -0
  115. package/dist/ssr/charts/core/chart-map-data.js.map +1 -0
  116. package/dist/ssr/charts/core/index.js +12 -0
  117. package/dist/ssr/charts/core/index.js.map +1 -0
  118. package/dist/ssr/charts/core/inspection.js +9 -0
  119. package/dist/ssr/charts/core/inspection.js.map +1 -0
  120. package/dist/ssr/charts/core/registry.js +28 -0
  121. package/dist/ssr/charts/core/registry.js.map +1 -0
  122. package/dist/ssr/charts/core/render.js +103 -0
  123. package/dist/ssr/charts/core/render.js.map +1 -0
  124. package/dist/ssr/charts/core/snapshot.js +65 -0
  125. package/dist/ssr/charts/core/snapshot.js.map +1 -0
  126. package/dist/ssr/charts/core/svg.js +2542 -0
  127. package/dist/ssr/charts/core/svg.js.map +1 -0
  128. package/dist/ssr/charts/core/termination.test-fixture.js +162 -0
  129. package/dist/ssr/charts/core/termination.test-fixture.js.map +1 -0
  130. package/dist/ssr/charts/core/text.js +13 -0
  131. package/dist/ssr/charts/core/text.js.map +1 -0
  132. package/dist/ssr/charts/core/types.js +8 -0
  133. package/dist/ssr/charts/core/types.js.map +1 -0
  134. package/dist/ssr/charts.js +4 -2
  135. package/dist/ssr/charts.js.map +1 -1
  136. package/dist/ssr/content/BalanceSummary.js +75 -0
  137. package/dist/ssr/content/BalanceSummary.js.map +1 -0
  138. package/dist/ssr/content/Chart.js +90 -95
  139. package/dist/ssr/content/Chart.js.map +1 -1
  140. package/dist/ssr/content/ChartExplorer.js +190 -46
  141. package/dist/ssr/content/ChartExplorer.js.map +1 -1
  142. package/dist/ssr/content/ChartSnapshotView.js +5 -1
  143. package/dist/ssr/content/ChartSnapshotView.js.map +1 -1
  144. package/dist/ssr/content/DataTable.js +287 -78
  145. package/dist/ssr/content/DataTable.js.map +1 -1
  146. package/dist/ssr/content/DataTableTools.js +106 -0
  147. package/dist/ssr/content/DataTableTools.js.map +1 -0
  148. package/dist/ssr/content/FlowAmount.js +58 -0
  149. package/dist/ssr/content/FlowAmount.js.map +1 -0
  150. package/dist/ssr/content/balance-summary-messages.js +26 -0
  151. package/dist/ssr/content/balance-summary-messages.js.map +1 -0
  152. package/dist/ssr/content/chart-bar.js +6 -6
  153. package/dist/ssr/content/chart-bar.js.map +1 -1
  154. package/dist/ssr/content/chart-calendar.js +43 -0
  155. package/dist/ssr/content/chart-calendar.js.map +1 -0
  156. package/dist/ssr/content/chart-inspection-core.js +164 -37
  157. package/dist/ssr/content/chart-inspection-core.js.map +1 -1
  158. package/dist/ssr/content/chart-inspection.js.map +1 -1
  159. package/dist/ssr/content/chart-line-gaps.js +2 -2
  160. package/dist/ssr/content/chart-line-gaps.js.map +1 -1
  161. package/dist/ssr/content/chart-map-viewport.js.map +1 -1
  162. package/dist/ssr/content/chart-message-catalog.js +164 -0
  163. package/dist/ssr/content/chart-message-catalog.js.map +1 -0
  164. package/dist/ssr/content/chart-messages.js +2 -138
  165. package/dist/ssr/content/chart-messages.js.map +1 -1
  166. package/dist/ssr/content/chart-snapshot.js +1 -62
  167. package/dist/ssr/content/chart-snapshot.js.map +1 -1
  168. package/dist/ssr/content/chart-state-timeline.js +1 -1
  169. package/dist/ssr/content/chart-state-timeline.js.map +1 -1
  170. package/dist/ssr/content/chart-svg.js +2 -1
  171. package/dist/ssr/content/chart-svg.js.map +1 -1
  172. package/dist/ssr/content/chart-time-axis.js +139 -0
  173. package/dist/ssr/content/chart-time-axis.js.map +1 -0
  174. package/dist/ssr/content/data-table-columns.js +160 -0
  175. package/dist/ssr/content/data-table-columns.js.map +1 -0
  176. package/dist/ssr/content/data-table-messages.js +38 -2
  177. package/dist/ssr/content/data-table-messages.js.map +1 -1
  178. package/dist/ssr/content/flow-amount-messages.js +26 -0
  179. package/dist/ssr/content/flow-amount-messages.js.map +1 -0
  180. package/dist/ssr/content/index.js +4 -0
  181. package/dist/ssr/content/index.js.map +1 -1
  182. package/dist/ssr/inputs/DatePicker.js +5 -4
  183. package/dist/ssr/inputs/DatePicker.js.map +1 -1
  184. package/dist/ssr/inputs/SelectChip.js +26 -9
  185. package/dist/ssr/inputs/SelectChip.js.map +1 -1
  186. package/dist/ssr/inputs/date-picker.js +13 -0
  187. package/dist/ssr/inputs/date-picker.js.map +1 -1
  188. package/dist/ssr/intl/Format.js +22 -2
  189. package/dist/ssr/intl/Format.js.map +1 -1
  190. package/dist/ssr/layout/AppWorkspace.js +7 -1
  191. package/dist/ssr/layout/AppWorkspace.js.map +1 -1
  192. package/dist/ssr/surfaces/BudgetList.js +170 -0
  193. package/dist/ssr/surfaces/BudgetList.js.map +1 -0
  194. package/dist/ssr/surfaces/ProgressBar.js +96 -5
  195. package/dist/ssr/surfaces/ProgressBar.js.map +1 -1
  196. package/dist/ssr/surfaces/StatCell.js +157 -51
  197. package/dist/ssr/surfaces/StatCell.js.map +1 -1
  198. package/dist/ssr/surfaces/StatGrid.js +24 -35
  199. package/dist/ssr/surfaces/StatGrid.js.map +1 -1
  200. package/dist/ssr/surfaces/budget-messages.js +64 -0
  201. package/dist/ssr/surfaces/budget-messages.js.map +1 -0
  202. package/dist/ssr/surfaces/delta-badge.js +41 -0
  203. package/dist/ssr/surfaces/delta-badge.js.map +1 -0
  204. package/dist/ssr/surfaces/index.js +2 -1
  205. package/dist/ssr/surfaces/index.js.map +1 -1
  206. package/dist/ssr/surfaces/person-color.js +2 -2
  207. package/dist/ssr/surfaces/person-color.js.map +1 -1
  208. package/dist/ssr/surfaces/stat-cell-messages.js +41 -0
  209. package/dist/ssr/surfaces/stat-cell-messages.js.map +1 -0
  210. package/dist/ssr/widgets/Dashboard.js +84 -0
  211. package/dist/ssr/widgets/Dashboard.js.map +1 -0
  212. package/dist/ssr/widgets/index.js +1 -0
  213. package/dist/ssr/widgets/index.js.map +1 -1
  214. package/dist/styles.css +1 -1
  215. package/dist/types/charts/core/area.d.ts +62 -0
  216. package/dist/types/charts/core/calendar.d.ts +57 -0
  217. package/dist/types/charts/core/chart-map-data.d.ts +3 -0
  218. package/dist/types/charts/core/index.d.ts +11 -0
  219. package/dist/types/charts/core/inspection.d.ts +16 -0
  220. package/dist/types/charts/core/registry.d.ts +27 -0
  221. package/dist/types/charts/core/render.d.ts +40 -0
  222. package/dist/types/charts/core/snapshot.d.ts +27 -0
  223. package/dist/types/charts/core/svg.d.ts +714 -0
  224. package/dist/types/charts/core/termination.test-fixture.d.ts +1 -0
  225. package/dist/types/charts/core/text.d.ts +5 -0
  226. package/dist/types/charts/core/types.d.ts +31 -0
  227. package/dist/types/charts.d.ts +4 -2
  228. package/dist/types/content/BalanceSummary.d.ts +29 -0
  229. package/dist/types/content/Chart.d.ts +26 -36
  230. package/dist/types/content/ChartExplorer.d.ts +43 -2
  231. package/dist/types/content/ChartSnapshotView.d.ts +1 -0
  232. package/dist/types/content/DataTable.d.ts +28 -0
  233. package/dist/types/content/DataTableTools.d.ts +44 -0
  234. package/dist/types/content/FlowAmount.d.ts +28 -0
  235. package/dist/types/content/balance-summary-messages.d.ts +11 -0
  236. package/dist/types/content/chart-bar.d.ts +1 -1
  237. package/dist/types/content/chart-calendar.d.ts +4 -0
  238. package/dist/types/content/chart-inspection-core.d.ts +21 -16
  239. package/dist/types/content/chart-inspection.d.ts +5 -1
  240. package/dist/types/content/chart-map-viewport.d.ts +1 -1
  241. package/dist/types/content/chart-message-catalog.d.ts +84 -0
  242. package/dist/types/content/chart-messages.d.ts +2 -33
  243. package/dist/types/content/chart-snapshot.d.ts +1 -26
  244. package/dist/types/content/chart-svg.d.ts +1 -1
  245. package/dist/types/content/chart-time-axis.d.ts +26 -0
  246. package/dist/types/content/data-table-columns.d.ts +72 -0
  247. package/dist/types/content/data-table-messages.d.ts +18 -0
  248. package/dist/types/content/flow-amount-messages.d.ts +11 -0
  249. package/dist/types/content/index.d.ts +10 -2
  250. package/dist/types/inputs/SelectChip.d.ts +2 -0
  251. package/dist/types/inputs/date-picker.d.ts +6 -0
  252. package/dist/types/intl/Format.d.ts +5 -0
  253. package/dist/types/layout/AppWorkspace.d.ts +5 -0
  254. package/dist/types/surfaces/BudgetList.d.ts +42 -0
  255. package/dist/types/surfaces/ProgressBar.d.ts +38 -2
  256. package/dist/types/surfaces/StatCell.d.ts +35 -2
  257. package/dist/types/surfaces/StatGrid.d.ts +3 -1
  258. package/dist/types/surfaces/budget-messages.d.ts +30 -0
  259. package/dist/types/surfaces/delta-badge.d.ts +10 -0
  260. package/dist/types/surfaces/index.d.ts +4 -3
  261. package/dist/types/surfaces/person-color.d.ts +2 -2
  262. package/dist/types/surfaces/stat-cell-messages.d.ts +19 -0
  263. package/dist/types/widgets/Dashboard.d.ts +42 -0
  264. package/dist/types/widgets/index.d.ts +1 -0
  265. package/package.json +7 -1
@@ -1,11 +1,8 @@
1
1
  import { positionTooltipSurface } from "../feedback/tooltip-position";
2
- export const sameChartDatum = (a, b) => a.role === b.role && a.index === b.index && a.seriesIndex === b.seriesIndex;
3
- const object = value => typeof value === "object" && value !== null;
4
- const pair = value => Array.isArray(value) && value.length === 2 && value.every(n => typeof n === "number" && Number.isFinite(n));
5
- const roles = new Set(["point", "item", "bin", "box", "outlier", "value", "cell", "interval"]);
6
- export function isChartDatum(value) {
7
- return object(value) && Number.isInteger(value.index) && typeof value.index === "number" && value.index >= 0 && (value.seriesIndex === undefined || typeof value.seriesIndex === "number" && Number.isInteger(value.seriesIndex) && value.seriesIndex >= 0) && typeof value.role === "string" && roles.has(value.role) && pair(value.anchor) && (value.grid === undefined || pair(value.grid)) && (value.label === undefined || typeof value.label === "string") && Array.isArray(value.values) && value.values.every(field => object(field) && typeof field.key === "string" && (typeof field.value === "string" || typeof field.value === "number" && Number.isFinite(field.value)) && (field.formatted === undefined || typeof field.formatted === "string"));
8
- }
2
+ export { isChartDatum, sameChartDatum } from "../charts/core/inspection";
3
+ import { isChartDatum, sameChartDatum } from "../charts/core/inspection";
4
+
5
+ /** Host policy for inspecting an overflowing drawing. */
9
6
 
10
7
  /** Adds or removes only the tooltip id, keeping descriptions the host put on the container. */
11
8
  const describe = (container, id, on) => {
@@ -14,6 +11,13 @@ const describe = (container, id, on) => {
14
11
  if (ids.length) container.setAttribute("aria-describedby", ids.join(" "));else container.removeAttribute("aria-describedby");
15
12
  };
16
13
 
14
+ /** Mark roles that stand for a whole category: hovering anywhere in its column (or row) inspects it. */
15
+ const rowLayout = container => Boolean(container?.querySelector('svg[data-bar-layout="rows"]'));
16
+ const categoryRoles = {
17
+ bar: "item",
18
+ histogram: "bin",
19
+ boxplot: "box"
20
+ };
17
21
  /** Enhances existing SVG nodes. Pointer/focus changes never rebuild the chart. */
18
22
  export function createChartInspector(options) {
19
23
  const source = Symbol("chart cursor");
@@ -58,7 +62,7 @@ export function createChartInspector(options) {
58
62
  // Read the actual paint before applying inspection styles, including
59
63
  // custom series and threshold colors. Invisible points use their line.
60
64
  const point = item.element.querySelector(".stdlib-chart-inspection-point");
61
- const source = point ? options.container()?.querySelector(options.kind() === "line" ? `.stdlib-chart-line.stdlib-chart-series-${(item.datum.seriesIndex ?? 0) % 8}` : ".stdlib-chart-sparkline, .stdlib-chart-stat-sparkline") : item.element.querySelector(".stdlib-chart-bar-gauge-fill, .stdlib-chart-gauge-fill, .stdlib-chart-stat-value") ?? item.element.querySelector("rect, path, circle");
65
+ const source = point ? options.container()?.querySelector(options.kind() === "line" ? `.stdlib-chart-line.stdlib-chart-series-${(item.datum.seriesIndex ?? 0) % 8}` : ".stdlib-chart-sparkline, .stdlib-chart-stat-sparkline") : item.element.querySelector(".stdlib-chart-bar-gauge-fill, .stdlib-chart-gauge-fill, .stdlib-chart-stat-value") ?? item.element.querySelector(":is(rect, path, circle):not(.k2b-chart-category-band)");
62
66
  const paint = source ? source.ownerDocument.defaultView?.getComputedStyle(source) : undefined;
63
67
  const color = (point || item.element.hasAttribute("data-chart-reference") ? paint?.stroke : paint?.fill) || `var(--stdlib-chart-c${(item.datum.seriesIndex ?? 0) % 8 + 1})`;
64
68
  return `color-mix(in srgb, ${color} 72%, var(--k2b-chart-highlight-mix))`;
@@ -107,7 +111,22 @@ export function createChartInspector(options) {
107
111
  });
108
112
  };
109
113
  const rawX = entry => entry.datum.values.find(field => field.key === "x")?.value;
110
- const seriesChart = () => options.kind() === "line" || options.kind() === "sparkline";
114
+ const category = entry => options.kind() === "area" ? entry.datum.index : rawX(entry);
115
+ const seriesChart = () => options.kind() === "line" || options.kind() === "area" || options.kind() === "sparkline";
116
+ const updateLegend = items => {
117
+ const root = options.legend?.() ?? options.container();
118
+ for (const node of root?.querySelectorAll("[data-chart-legend-value]") ?? []) {
119
+ if (!items) {
120
+ node.textContent = node.getAttribute("data-default") ?? "";
121
+ continue;
122
+ }
123
+ const series = Number(node.getAttribute("data-series-index"));
124
+ const field = items.find(item => item.datum.seriesIndex === series)?.datum.values.find(field => field.key === node.getAttribute("data-field"));
125
+ node.textContent = field ? field.formatted ?? (typeof field.value === "number" ? new Intl.NumberFormat(options.locale?.() ?? "en", {
126
+ maximumFractionDigits: 6
127
+ }).format(field.value) : field.value) : "—";
128
+ }
129
+ };
111
130
  const clearHighlight = () => {
112
131
  for (const entry of highlighted) {
113
132
  entry.element.removeAttribute("data-inspected");
@@ -123,11 +142,17 @@ export function createChartInspector(options) {
123
142
  options.container()?.removeAttribute("data-cursor-source");
124
143
  pinned = false;
125
144
  active = undefined;
145
+ updateLegend(undefined);
126
146
  clearHighlight();
127
147
  const container = options.container(),
128
148
  id = options.tooltip()?.id;
129
149
  if (container && id) describe(container, id, false);
130
150
  options.container()?.removeAttribute("data-inspecting");
151
+ const inspected = options.container();
152
+ if (inspected) {
153
+ for (const name of ["left", "top", "width", "height"]) inspected.style.removeProperty(`--k2b-chart-band-${name}`);
154
+ if (!inspected.getAttribute("style")) inspected.removeAttribute("style");
155
+ }
131
156
  const tooltip = options.tooltip();
132
157
  if (tooltip) {
133
158
  try {
@@ -157,14 +182,15 @@ export function createChartInspector(options) {
157
182
  anchor = options.anchor(),
158
183
  container = options.container();
159
184
  if (!tooltip || !anchor || !container || !options.enabled()) return;
160
- if (active === entry && tooltip.matches(":popover-open")) return;
185
+ if ((active === entry || seriesChart() && active && category(active) === category(entry)) && tooltip.matches(":popover-open")) return;
161
186
  clearHighlight();
162
187
  active = entry;
163
- highlighted = options.kind() === "line" ? records().filter(candidate => rawX(candidate) === rawX(entry)) : [entry];
188
+ highlighted = options.kind() === "line" || options.kind() === "area" ? records().filter(candidate => category(candidate) === category(entry)) : [entry];
164
189
  for (const item of highlighted) {
165
190
  item.element.style.setProperty("--k2b-chart-inspection-color", highlightColor(item));
166
191
  item.element.setAttribute("data-inspected", "true");
167
192
  }
193
+ updateLegend(highlighted);
168
194
  const document = tooltip.ownerDocument;
169
195
  const contents = highlighted.map(item => ({
170
196
  item,
@@ -190,6 +216,7 @@ export function createChartInspector(options) {
190
216
  }
191
217
  contents.sort((a, b) => (a.item.datum.seriesIndex ?? 0) - (b.item.datum.seriesIndex ?? 0));
192
218
  }
219
+ if (options.kind() === "area") contents.reverse();
193
220
  tooltip.replaceChildren();
194
221
  const sharedTitle = typeof x === "number" ? options.cursor?.()?.formatX?.(x) : undefined;
195
222
  const commonTitle = sharedTitle ?? (contents.every(({
@@ -215,10 +242,10 @@ export function createChartInspector(options) {
215
242
  for (const row of content.rows) {
216
243
  const line = document.createElement("span");
217
244
  line.className = "k2b-chart__tooltip-row";
218
- if (options.kind() === "line") {
245
+ if (options.kind() === "line" || options.kind() === "area") {
219
246
  const swatch = document.createElement("i");
220
247
  swatch.setAttribute("aria-hidden", "true");
221
- const series = container.querySelector(`.stdlib-chart-line.stdlib-chart-series-${(item.datum.seriesIndex ?? 0) % 8}`);
248
+ const series = container.querySelector(`.stdlib-chart-line.stdlib-chart-series-${(item.datum.seriesIndex ?? 0) % 8}, .k2b-area-edge-${(item.datum.seriesIndex ?? 0) % 8}`);
222
249
  swatch.style.background = series ? getComputedStyle(series).stroke : `var(--stdlib-chart-c${(item.datum.seriesIndex ?? 0) % 8 + 1})`;
223
250
  line.append(swatch);
224
251
  }
@@ -230,13 +257,38 @@ export function createChartInspector(options) {
230
257
  tooltip.append(line);
231
258
  }
232
259
  }
260
+ if (options.kind() === "area") {
261
+ const total = entry.datum.values.find(field => field.key === "total");
262
+ const totalLabel = container.querySelector("svg[data-chart-total-label]")?.getAttribute("data-chart-total-label");
263
+ if (total && totalLabel) {
264
+ const line = document.createElement("span");
265
+ line.className = "k2b-chart__tooltip-row";
266
+ const label = document.createElement("span");
267
+ label.textContent = `${totalLabel}: `;
268
+ const value = document.createElement("b");
269
+ value.textContent = total.formatted ?? String(total.value);
270
+ line.append(label, value);
271
+ tooltip.append(line);
272
+ }
273
+ }
233
274
  const rect = container.getBoundingClientRect(),
234
275
  point = screenPoint(entry);
235
- anchor.style.left = `${point.x - rect.left}px`;
236
- anchor.style.top = `${point.y - rect.top}px`;
276
+ const slot = categoryBands().find(candidate => candidate.entry === entry);
277
+ // A renderer that draws its own band inside the category's mark keeps it; the overlay only fills the others.
278
+ const band = slot && !entry.element.querySelector(".k2b-chart-category-band") ? slot : undefined;
279
+ const rows = rowLayout(container);
280
+ // A column's tooltip waits at the top of the plot, so it stays put while the pointer moves within the column.
281
+ anchor.style.left = `${(slot && !rows ? (slot.left + slot.right) / 2 : point.x) - rect.left + container.scrollLeft}px`;
282
+ anchor.style.top = `${(slot && !rows ? slot.top : point.y) - rect.top + container.scrollTop}px`;
237
283
  updatePlotBounds();
238
- container.style.setProperty("--k2b-chart-inspection-x", `${point.x - rect.left}px`);
239
- container.setAttribute("data-inspecting", seriesChart() ? "series" : "datum");
284
+ container.style.setProperty("--k2b-chart-inspection-x", `${point.x - rect.left + container.scrollLeft}px`);
285
+ if (band) {
286
+ container.style.setProperty("--k2b-chart-band-left", `${band.left - rect.left + container.scrollLeft}px`);
287
+ container.style.setProperty("--k2b-chart-band-top", `${band.top - rect.top + container.scrollTop}px`);
288
+ container.style.setProperty("--k2b-chart-band-width", `${band.right - band.left}px`);
289
+ container.style.setProperty("--k2b-chart-band-height", `${band.bottom - band.top}px`);
290
+ }
291
+ container.setAttribute("data-inspecting", seriesChart() ? "series" : band ? "category" : "datum");
240
292
  describe(container, tooltip.id, true);
241
293
  try {
242
294
  if (!tooltip.matches(":popover-open")) tooltip.showPopover();
@@ -245,6 +297,50 @@ export function createChartInspector(options) {
245
297
  /* SVG titles remain available without the Popover API. */
246
298
  }
247
299
  };
300
+ /**
301
+ * The screen slot of every category, halfway to its neighbours. A light band fills the inspected slot instead of
302
+ * dimming other marks or outlining a bar, and the pointer anywhere in a slot keeps the same category.
303
+ */
304
+ const categoryBands = () => {
305
+ const role = categoryRoles[options.kind()];
306
+ const container = options.container();
307
+ if (!role || !container) return [];
308
+ const rows = rowLayout(container);
309
+ const slots = records().filter(entry => entry.datum.role === role).map(entry => ({
310
+ entry,
311
+ rect: entry.element.getBoundingClientRect()
312
+ }));
313
+ if (!slots.length) return [];
314
+ const guides = Array.from(container.querySelectorAll(".stdlib-chart-grid, .stdlib-chart-axis")).map(node => node.getBoundingClientRect());
315
+ // The band spans the plot across the categories: grid lines and every mark, so rows without value ticks, whose
316
+ // only guide is one vertical baseline, still get a band from the baseline to the longest bar.
317
+ const spans = [...guides, ...slots.map(slot => slot.rect)];
318
+ const start = Math.min(...spans.map(rect => rows ? rect.left : rect.top));
319
+ const end = Math.max(...spans.map(rect => rows ? rect.right : rect.bottom));
320
+ const center = rect => rows ? rect.top + rect.height / 2 : rect.left + rect.width / 2;
321
+ slots.sort((a, b) => center(a.rect) - center(b.rect));
322
+ return slots.map((slot, index) => {
323
+ const middle = center(slot.rect);
324
+ const previous = slots[index - 1],
325
+ next = slots[index + 1];
326
+ const half = previous || next ? Math.abs(center((next ?? previous).rect) - middle) / 2 : (rows ? slot.rect.height : slot.rect.width) * 0.75;
327
+ const low = previous ? (center(previous.rect) + middle) / 2 : middle - half;
328
+ const high = next ? (center(next.rect) + middle) / 2 : middle + half;
329
+ return rows ? {
330
+ entry: slot.entry,
331
+ left: start,
332
+ right: end,
333
+ top: low,
334
+ bottom: high
335
+ } : {
336
+ entry: slot.entry,
337
+ left: low,
338
+ right: high,
339
+ top: start,
340
+ bottom: end
341
+ };
342
+ });
343
+ };
248
344
  const updatePlotBounds = () => {
249
345
  const container = options.container();
250
346
  if (!container) return;
@@ -293,6 +389,27 @@ export function createChartInspector(options) {
293
389
  const direct = target?.closest("[data-chart-datum]");
294
390
  const hit = direct ? byElement.get(direct) : undefined;
295
391
  if (hit && nodes.includes(hit)) return hit;
392
+ const bands = categoryBands();
393
+ if (bands.length) {
394
+ // Marks of their own inside a category, such as box-plot outliers, keep their forgiving hit radius.
395
+ const role = categoryRoles[options.kind()];
396
+ let loose,
397
+ gap = 24;
398
+ for (const entry of nodes) {
399
+ if (entry.datum.role === role) continue;
400
+ const point = screenPoint(entry);
401
+ const distance = Math.hypot(point.x - x, point.y - y);
402
+ if (distance <= gap) {
403
+ gap = distance;
404
+ loose = entry;
405
+ }
406
+ }
407
+ if (loose) return loose;
408
+ const rows = rowLayout(options.container());
409
+ const frame = options.container()?.getBoundingClientRect();
410
+ const inside = frame && x >= frame.left && x <= frame.right && y >= frame.top && y <= frame.bottom;
411
+ return inside ? bands.find(band => rows ? y >= band.top && y <= band.bottom : x >= band.left && x <= band.right)?.entry : undefined;
412
+ }
296
413
  if (seriesChart()) {
297
414
  const plot = updatePlotBounds();
298
415
  // SVG transforms and DOM rectangles can round the same painted edge
@@ -367,14 +484,20 @@ export function createChartInspector(options) {
367
484
  } else close();
368
485
  });
369
486
  };
487
+ let pointerFocusing = false;
370
488
  const down = event => {
489
+ pointerFocusing = true;
371
490
  pointerStart = {
372
491
  x: event.clientX,
373
492
  y: event.clientY
374
493
  };
375
494
  moved = false;
376
495
  };
496
+ const up = () => {
497
+ pointerFocusing = false;
498
+ };
377
499
  const click = event => {
500
+ pointerFocusing = false;
378
501
  if (!options.enabled() || moved || event.target instanceof Element && event.target.closest("button,a")) return;
379
502
  const entry = nearest(event.clientX, event.clientY, event.target instanceof Element ? event.target : null);
380
503
  if (!entry) {
@@ -390,8 +513,12 @@ export function createChartInspector(options) {
390
513
  };
391
514
  const focus = event => {
392
515
  if (event.target !== options.container() || !options.enabled()) return;
516
+ if (pointerFocusing && options.nav?.()?.pointer === false) {
517
+ pointerFocusing = false;
518
+ return;
519
+ }
393
520
  const nodes = visibleRecords();
394
- const first = options.kind() === "line" ? sortedPoints.at(-1) : nodes[0];
521
+ const first = options.nav?.()?.last ? nodes.at(-1) : options.kind() === "line" ? sortedPoints.at(-1) : nodes[0];
395
522
  if (first) show(first);
396
523
  };
397
524
  const key = event => {
@@ -422,10 +549,10 @@ export function createChartInspector(options) {
422
549
  if (!["Home", "End", "ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"].includes(event.key)) return false;
423
550
  let nodes = visibleRecords();
424
551
  if (seriesChart()) nodes = [...nodes].sort((a, b) => a.datum.anchor[0] - b.datum.anchor[0]);
425
- if (options.kind() === "line") {
552
+ if (options.kind() === "line" || options.kind() === "area") {
426
553
  const seen = new Set();
427
554
  nodes = nodes.filter(entry => {
428
- const x = rawX(entry);
555
+ const x = category(entry);
429
556
  if (seen.has(x)) return false;
430
557
  seen.add(x);
431
558
  return true;
@@ -434,7 +561,7 @@ export function createChartInspector(options) {
434
561
  if (!nodes.length) return false;
435
562
  event.preventDefault();
436
563
  pinned = false;
437
- let index = active ? options.kind() === "line" ? nodes.findIndex(entry => rawX(entry) === rawX(active)) : nodes.indexOf(active) : -1;
564
+ let index = active ? options.kind() === "line" || options.kind() === "area" ? nodes.findIndex(entry => category(entry) === category(active)) : nodes.indexOf(active) : -1;
438
565
  if (event.key === "Home") index = 0;else if (event.key === "End") index = nodes.length - 1;else if (active?.datum.grid && ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(event.key)) {
439
566
  const [x, y] = active.datum.grid;
440
567
  const dx = event.key === "ArrowLeft" ? -1 : event.key === "ArrowRight" ? 1 : 0;
@@ -442,7 +569,9 @@ export function createChartInspector(options) {
442
569
  const next = nodes.filter(entry => entry.datum.grid && (dx ? entry.datum.grid[1] === y && (entry.datum.grid[0] - x) * dx > 0 : entry.datum.grid[0] === x && (entry.datum.grid[1] - y) * dy > 0)).sort((a, b) => Math.abs(a.datum.grid[0] - x) + Math.abs(a.datum.grid[1] - y) - Math.abs(b.datum.grid[0] - x) - Math.abs(b.datum.grid[1] - y))[0];
443
570
  if (next) index = nodes.indexOf(next);
444
571
  } else index += event.key === "ArrowLeft" || event.key === "ArrowUp" ? -1 : 1;
445
- show(nodes[Math.max(0, Math.min(nodes.length - 1, index))]);
572
+ const next = nodes[Math.max(0, Math.min(nodes.length - 1, index))];
573
+ options.nav?.()?.reveal?.(next.element);
574
+ show(next);
446
575
  return true;
447
576
  };
448
577
  return {
@@ -452,6 +581,7 @@ export function createChartInspector(options) {
452
581
  destroy: () => close(false),
453
582
  move,
454
583
  down,
584
+ up,
455
585
  click,
456
586
  focus,
457
587
  key,
@@ -469,6 +599,7 @@ export function closeChartInspectionOnOutside(options) {
469
599
  if (event.target instanceof Node && !options.container()?.contains(event.target)) options.close();
470
600
  };
471
601
  const scroll = event => {
602
+ if (event.target === options.container() && options.keepScroll?.()) return;
472
603
  if (event.target instanceof Node && options.tooltip()?.contains(event.target)) return;
473
604
  options.close();
474
605
  };
@@ -502,25 +633,27 @@ export function inspectChart(container, options) {
502
633
  if (addedTabindex) container.setAttribute("tabindex", "0");
503
634
  const inspector = createChartInspector({
504
635
  container: () => container,
636
+ locale: () => options.locale ?? "en",
637
+ legend: () => options.legend,
505
638
  cursor: () => options.cursor,
506
639
  tooltip: () => tooltip,
507
640
  anchor: () => anchor,
508
641
  kind: () => options.kind,
642
+ nav: () => options.nav,
509
643
  enabled: () => true,
510
644
  format: options.format,
511
645
  select: selection => options.select?.(selection)
512
646
  });
513
- container.addEventListener("pointerdown", inspector.down);
514
- container.addEventListener("pointermove", inspector.move);
515
- container.addEventListener("click", inspector.click);
516
- container.addEventListener("pointerleave", inspector.leave);
517
- container.addEventListener("focusin", inspector.focus);
518
- container.addEventListener("focusout", inspector.close);
519
- container.addEventListener("keydown", inspector.key);
647
+ const listen = (name, listener) => {
648
+ container.addEventListener(name, listener);
649
+ return () => container.removeEventListener(name, listener);
650
+ };
651
+ const removeListeners = [listen("pointerdown", inspector.down), listen("pointerup", inspector.up), listen("pointercancel", inspector.up), listen("pointermove", inspector.move), listen("click", inspector.click), listen("pointerleave", inspector.leave), listen("focusin", inspector.focus), listen("focusout", inspector.close), listen("keydown", inspector.key)];
520
652
  const removeOutside = closeChartInspectionOnOutside({
521
653
  container: () => container,
522
654
  tooltip: () => tooltip,
523
- close: inspector.close
655
+ close: inspector.close,
656
+ keepScroll: () => options.nav?.keepScroll
524
657
  });
525
658
  const unlinkCursor = inspector.linkCursor(options.cursor);
526
659
  let destroyed = false;
@@ -534,13 +667,7 @@ export function inspectChart(container, options) {
534
667
  destroy() {
535
668
  if (destroyed) return;
536
669
  destroyed = true;
537
- container.removeEventListener("pointerdown", inspector.down);
538
- container.removeEventListener("pointermove", inspector.move);
539
- container.removeEventListener("click", inspector.click);
540
- container.removeEventListener("pointerleave", inspector.leave);
541
- container.removeEventListener("focusin", inspector.focus);
542
- container.removeEventListener("focusout", inspector.close);
543
- container.removeEventListener("keydown", inspector.key);
670
+ for (const remove of removeListeners) remove();
544
671
  removeOutside();
545
672
  unlinkCursor();
546
673
  inspector.destroy();