@krizaka/charts 0.3.0 → 0.4.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 (172) hide show
  1. package/README.md +1 -1
  2. package/dist/actions-5VG352VZ.js +53 -0
  3. package/dist/actions-5VG352VZ.js.map +1 -0
  4. package/dist/bar.d.ts +3 -3
  5. package/dist/bar.js +7 -6
  6. package/dist/candlestick.d.ts +2 -2
  7. package/dist/candlestick.js +6 -5
  8. package/dist/card.d.ts +52 -0
  9. package/dist/card.js +19 -0
  10. package/dist/card.js.map +1 -0
  11. package/dist/{cartesian-BRhNQ3ib.d.ts → cartesian-CEY_wwe_.d.ts} +12 -2
  12. package/dist/chart.d.ts +2 -2
  13. package/dist/chart.js +5 -4
  14. package/dist/{chunk-BV4DBGN7.js → chunk-7W4T255Q.js} +4 -4
  15. package/dist/{chunk-W5ILLSPK.js → chunk-AGSSNMK5.js} +5 -5
  16. package/dist/{chunk-VKZZY2QD.js → chunk-FE4LUGAE.js} +5 -5
  17. package/dist/{chunk-ARARFMNE.js → chunk-FIQHMJZK.js} +5 -5
  18. package/dist/chunk-FIQHMJZK.js.map +1 -0
  19. package/dist/{chunk-ANUCSRUN.js → chunk-GD7TWEN3.js} +3 -3
  20. package/dist/chunk-GRRFPI27.js +89 -0
  21. package/dist/chunk-GRRFPI27.js.map +1 -0
  22. package/dist/{chunk-SLEJDQ3A.js → chunk-HH65IEUV.js} +3 -3
  23. package/dist/{chunk-OIRHLJBC.js → chunk-IJTI6VJD.js} +3 -3
  24. package/dist/{chunk-WVTIKOII.js → chunk-IURCLBUQ.js} +2 -2
  25. package/dist/{chunk-CVWSMUUB.js → chunk-JST4HJC3.js} +33 -15
  26. package/dist/chunk-JST4HJC3.js.map +1 -0
  27. package/dist/{chunk-IKQV3NSL.js → chunk-KERGFVSN.js} +3 -3
  28. package/dist/{chunk-XDDALY4C.js → chunk-KMQ7RANJ.js} +5 -5
  29. package/dist/{chunk-NCIQMRV3.js → chunk-LLUQEK5J.js} +5 -5
  30. package/dist/{chunk-AA5EWWYV.js → chunk-LZPZWBBY.js} +2 -2
  31. package/dist/{chunk-PBQV3B6O.js → chunk-MK2ZFUWM.js} +31 -16
  32. package/dist/chunk-MK2ZFUWM.js.map +1 -0
  33. package/dist/{chunk-CSSZLFLO.js → chunk-MRGMLDNS.js} +5 -5
  34. package/dist/{chunk-I3D7KUGN.js → chunk-NLTZWJLV.js} +3 -3
  35. package/dist/{chunk-PWMMXRMC.js → chunk-NV52AKHG.js} +3 -3
  36. package/dist/{chunk-MXLITIGU.js → chunk-NWJ5VDN7.js} +5 -5
  37. package/dist/{chunk-3CJHBJXA.js → chunk-OVASCEGF.js} +12 -73
  38. package/dist/chunk-OVASCEGF.js.map +1 -0
  39. package/dist/{chunk-EQLC2QVN.js → chunk-OZOAKP5F.js} +4 -4
  40. package/dist/chunk-OZOAKP5F.js.map +1 -0
  41. package/dist/{chunk-KNDHO67X.js → chunk-P2NK5L3B.js} +3 -3
  42. package/dist/{chunk-JTKR7ZWP.js → chunk-P6RS2L23.js} +140 -36
  43. package/dist/chunk-P6RS2L23.js.map +1 -0
  44. package/dist/{chunk-W5FX4TU3.js → chunk-PGD6SH5I.js} +30 -2
  45. package/dist/chunk-PGD6SH5I.js.map +1 -0
  46. package/dist/{chunk-4LBXEAGC.js → chunk-PWZHIGPO.js} +5 -5
  47. package/dist/{chunk-L2EKYWVI.js → chunk-VEOJNMPF.js} +3 -3
  48. package/dist/chunk-YHQFW2PR.js +71 -0
  49. package/dist/chunk-YHQFW2PR.js.map +1 -0
  50. package/dist/{chunk-C4MFZLJM.js → chunk-Z4T3JFNU.js} +2 -2
  51. package/dist/{chunk-EEP7PS2L.js → chunk-ZF7LVQTR.js} +3 -3
  52. package/dist/{context-BgYUymEO.d.ts → context-DNW7bJYw.d.ts} +19 -0
  53. package/dist/credits.d.ts +1 -1
  54. package/dist/credits.js +7 -6
  55. package/dist/earnings.d.ts +3 -3
  56. package/dist/earnings.js +8 -7
  57. package/dist/{frame-I7_xYGy5.d.ts → frame-B1HDNm7c.d.ts} +9 -5
  58. package/dist/funnel.d.ts +2 -2
  59. package/dist/funnel.js +6 -5
  60. package/dist/gauge.d.ts +2 -2
  61. package/dist/gauge.js +4 -3
  62. package/dist/geo.d.ts +2 -2
  63. package/dist/geo.js +5 -4
  64. package/dist/globe.d.ts +2 -2
  65. package/dist/globe.js +4 -3
  66. package/dist/goal.d.ts +2 -2
  67. package/dist/goal.js +5 -4
  68. package/dist/heatmap.d.ts +2 -2
  69. package/dist/heatmap.js +6 -5
  70. package/dist/index.d.ts +5 -3
  71. package/dist/index.js +45 -37
  72. package/dist/latency.d.ts +3 -3
  73. package/dist/latency.js +8 -7
  74. package/dist/line.d.ts +3 -3
  75. package/dist/line.js +7 -6
  76. package/dist/native.d.ts +19 -0
  77. package/dist/native.js +13 -1
  78. package/dist/native.js.map +1 -1
  79. package/dist/pie.d.ts +2 -2
  80. package/dist/pie.js +6 -5
  81. package/dist/provider.d.ts +2 -2
  82. package/dist/provider.js +4 -2
  83. package/dist/radar.d.ts +2 -2
  84. package/dist/radar.js +6 -5
  85. package/dist/sankey.d.ts +2 -2
  86. package/dist/sankey.js +6 -5
  87. package/dist/scatter.d.ts +2 -2
  88. package/dist/scatter.js +6 -5
  89. package/dist/sparkline.d.ts +2 -2
  90. package/dist/sparkline.js +6 -5
  91. package/dist/table-sheet-CQBIAV5N.js +36 -0
  92. package/dist/table-sheet-CQBIAV5N.js.map +1 -0
  93. package/dist/throughput.d.ts +3 -3
  94. package/dist/throughput.js +8 -7
  95. package/dist/ticker.d.ts +3 -3
  96. package/dist/ticker.js +8 -7
  97. package/dist/treemap.d.ts +2 -2
  98. package/dist/treemap.js +7 -6
  99. package/package.json +295 -53
  100. package/registry/area-chart.json +82 -12
  101. package/registry/bar-chart.json +82 -12
  102. package/registry/calendar-heatmap.json +46 -18
  103. package/registry/candlestick.json +46 -18
  104. package/registry/chart-card.json +235 -0
  105. package/registry/chart.json +40 -12
  106. package/registry/charts-provider.json +2 -2
  107. package/registry/earnings-chart.json +82 -12
  108. package/registry/examples/chart-card/before-after.d.ts +4 -0
  109. package/registry/examples/chart-card/before-after.js +22 -0
  110. package/registry/examples/chart-card/before-after.tsx +22 -0
  111. package/registry/examples/chart-card/jobs-activity.d.ts +4 -0
  112. package/registry/examples/chart-card/jobs-activity.js +20 -0
  113. package/registry/examples/chart-card/jobs-activity.tsx +20 -0
  114. package/registry/examples/chart-card/sparse-and-empty.d.ts +4 -0
  115. package/registry/examples/chart-card/sparse-and-empty.js +16 -0
  116. package/registry/examples/chart-card/sparse-and-empty.tsx +18 -0
  117. package/registry/examples/chart-card/usage-donut.d.ts +4 -0
  118. package/registry/examples/chart-card/usage-donut.js +18 -0
  119. package/registry/examples/chart-card/usage-donut.tsx +18 -0
  120. package/registry/examples/charts-provider/french-orazaka.js +1 -1
  121. package/registry/examples/charts-provider/french-orazaka.tsx +1 -1
  122. package/registry/examples/kpi-stat/dashboard-row.d.ts +4 -0
  123. package/registry/examples/kpi-stat/dashboard-row.js +19 -0
  124. package/registry/examples/kpi-stat/dashboard-row.tsx +17 -0
  125. package/registry/examples/kpi-stat/single.d.ts +4 -0
  126. package/registry/examples/kpi-stat/single.js +11 -0
  127. package/registry/examples/kpi-stat/single.tsx +6 -0
  128. package/registry/funnel.json +46 -18
  129. package/registry/gauge.json +42 -14
  130. package/registry/geo-map.json +46 -18
  131. package/registry/globe.json +40 -12
  132. package/registry/goal-progress.json +36 -8
  133. package/registry/heatmap.json +46 -18
  134. package/registry/index.json +49 -1
  135. package/registry/job-throughput.json +82 -12
  136. package/registry/kpi-stat.json +296 -0
  137. package/registry/latency-chart.json +82 -12
  138. package/registry/line-chart.json +82 -12
  139. package/registry/pie-chart.json +92 -36
  140. package/registry/price-ticker.json +82 -12
  141. package/registry/radar-chart.json +46 -18
  142. package/registry/sankey.json +46 -18
  143. package/registry/scatter-chart.json +46 -18
  144. package/registry/sparkline.json +38 -10
  145. package/registry/treemap.json +40 -12
  146. package/dist/chunk-3CJHBJXA.js.map +0 -1
  147. package/dist/chunk-ARARFMNE.js.map +0 -1
  148. package/dist/chunk-CVWSMUUB.js.map +0 -1
  149. package/dist/chunk-EQLC2QVN.js.map +0 -1
  150. package/dist/chunk-JTKR7ZWP.js.map +0 -1
  151. package/dist/chunk-PBQV3B6O.js.map +0 -1
  152. package/dist/chunk-W5FX4TU3.js.map +0 -1
  153. /package/dist/{chunk-BV4DBGN7.js.map → chunk-7W4T255Q.js.map} +0 -0
  154. /package/dist/{chunk-W5ILLSPK.js.map → chunk-AGSSNMK5.js.map} +0 -0
  155. /package/dist/{chunk-VKZZY2QD.js.map → chunk-FE4LUGAE.js.map} +0 -0
  156. /package/dist/{chunk-ANUCSRUN.js.map → chunk-GD7TWEN3.js.map} +0 -0
  157. /package/dist/{chunk-SLEJDQ3A.js.map → chunk-HH65IEUV.js.map} +0 -0
  158. /package/dist/{chunk-OIRHLJBC.js.map → chunk-IJTI6VJD.js.map} +0 -0
  159. /package/dist/{chunk-WVTIKOII.js.map → chunk-IURCLBUQ.js.map} +0 -0
  160. /package/dist/{chunk-IKQV3NSL.js.map → chunk-KERGFVSN.js.map} +0 -0
  161. /package/dist/{chunk-XDDALY4C.js.map → chunk-KMQ7RANJ.js.map} +0 -0
  162. /package/dist/{chunk-NCIQMRV3.js.map → chunk-LLUQEK5J.js.map} +0 -0
  163. /package/dist/{chunk-AA5EWWYV.js.map → chunk-LZPZWBBY.js.map} +0 -0
  164. /package/dist/{chunk-CSSZLFLO.js.map → chunk-MRGMLDNS.js.map} +0 -0
  165. /package/dist/{chunk-I3D7KUGN.js.map → chunk-NLTZWJLV.js.map} +0 -0
  166. /package/dist/{chunk-PWMMXRMC.js.map → chunk-NV52AKHG.js.map} +0 -0
  167. /package/dist/{chunk-MXLITIGU.js.map → chunk-NWJ5VDN7.js.map} +0 -0
  168. /package/dist/{chunk-KNDHO67X.js.map → chunk-P2NK5L3B.js.map} +0 -0
  169. /package/dist/{chunk-4LBXEAGC.js.map → chunk-PWZHIGPO.js.map} +0 -0
  170. /package/dist/{chunk-L2EKYWVI.js.map → chunk-VEOJNMPF.js.map} +0 -0
  171. /package/dist/{chunk-C4MFZLJM.js.map → chunk-Z4T3JFNU.js.map} +0 -0
  172. /package/dist/{chunk-EEP7PS2L.js.map → chunk-ZF7LVQTR.js.map} +0 -0
@@ -1,72 +1,7 @@
1
1
  "use client";
2
-
3
- // src/core/labels.ts
4
- var DEFAULT_LABELS = {
5
- showData: "Show data",
6
- hideData: "Hide data",
7
- tableCaption: "{title} \u2014 data",
8
- exportPng: "PNG",
9
- exportSvg: "SVG",
10
- exportCsv: "CSV",
11
- loading: "Loading chart\u2026",
12
- empty: "No data for this period yet.",
13
- error: "The chart could not be loaded.",
14
- keyboardHint: "Use the arrow keys to move between points, Home and End to jump to the ends.",
15
- roleDescription: "chart",
16
- summaryIntro: "{kind}, {count}.",
17
- summarySeries: "{label}: from {first} to {last}",
18
- summaryRange: "lowest {min} ({minAt}), highest {max} ({maxAt})",
19
- summaryTrendUp: "up {change}",
20
- summaryTrendDown: "down {change}",
21
- summaryTrendFlat: "stable",
22
- summaryTotal: "total {total}",
23
- summaryThreshold: "{label} at {value}",
24
- summaryShare: "{label} {value} ({share})",
25
- summaryCorrelation: "correlation {r}",
26
- summaryConversion: "{from} \u2192 {to} {rate}",
27
- summaryOverall: "overall {rate}",
28
- summaryPeak: "peak {value} ({at})",
29
- summaryActive: "{count} with a value",
30
- summaryOhlc: "opened at {open}, closed at {close}, low {low}, high {high}",
31
- kinds: {
32
- line: "Line chart",
33
- area: "Area chart",
34
- bar: "Bar chart",
35
- pie: "Pie chart",
36
- donut: "Donut chart",
37
- gauge: "Gauge",
38
- sparkline: "Sparkline",
39
- radar: "Radar chart",
40
- heatmap: "Heatmap",
41
- calendar: "Calendar heatmap",
42
- sankey: "Flow diagram",
43
- funnel: "Funnel",
44
- candlestick: "Candlestick chart",
45
- treemap: "Treemap",
46
- scatter: "Scatter chart",
47
- map: "Map",
48
- globe: "Globe",
49
- chart: "Chart"
50
- },
51
- category: "Category",
52
- value: "Value",
53
- total: "Total",
54
- noValue: "\u2014"
55
- };
56
- function mergeLabels(...layers) {
57
- let out = DEFAULT_LABELS;
58
- for (const layer of layers) {
59
- if (!layer) continue;
60
- out = { ...out, ...layer, kinds: { ...out.kinds, ...layer.kinds } };
61
- }
62
- return out;
63
- }
64
- function capitalize(text) {
65
- return text.charAt(0).toLocaleUpperCase() + text.slice(1);
66
- }
67
- function fill(template, values) {
68
- return template.replace(/\{(\w+)\}/g, (match, name) => name in values ? String(values[name]) : match);
69
- }
2
+ import {
3
+ mergeLabels
4
+ } from "./chunk-GRRFPI27.js";
70
5
 
71
6
  // src/web/context.tsx
72
7
  import { createContext, useContext, useMemo } from "react";
@@ -88,6 +23,11 @@ function useChartSettings(own) {
88
23
  [own.locale, own.brand, own.labels, shared]
89
24
  );
90
25
  }
26
+ var CardContext = createContext(null);
27
+ var ChartCardProvider = CardContext.Provider;
28
+ function useChartCard() {
29
+ return useContext(CardContext);
30
+ }
91
31
 
92
32
  // src/core/palette.ts
93
33
  var HUES = {
@@ -102,7 +42,7 @@ var HUES = {
102
42
  };
103
43
  var ORDERS = {
104
44
  krizaka: ["blue", "green", "magenta", "yellow", "aqua", "orange", "violet", "red"],
105
- orazaka: ["orange", "aqua", "blue", "yellow", "magenta", "green", "violet", "red"],
45
+ orazaka: ["orange", "blue", "aqua", "yellow", "magenta", "green", "violet", "red"],
106
46
  orochia: ["violet", "magenta", "yellow", "aqua", "orange", "blue", "green", "red"]
107
47
  };
108
48
  function palette(brand, mode) {
@@ -171,11 +111,10 @@ function contrast(a, b) {
171
111
  }
172
112
 
173
113
  export {
174
- DEFAULT_LABELS,
175
- capitalize,
176
- fill,
177
114
  ChartsProvider,
178
115
  useChartSettings,
116
+ ChartCardProvider,
117
+ useChartCard,
179
118
  HUES,
180
119
  ORDERS,
181
120
  palette,
@@ -183,4 +122,4 @@ export {
183
122
  sequential,
184
123
  contrast
185
124
  };
186
- //# sourceMappingURL=chunk-3CJHBJXA.js.map
125
+ //# sourceMappingURL=chunk-OVASCEGF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/web/context.tsx","../src/core/palette.ts"],"sourcesContent":["\"use client\";\n// One place for what every chart of an app shares: the locale its figures are written in, the brand its palette\n// leads with, and the words it says (translated once, from the app's catalogue). Each chart may still override them.\nimport { createContext, type ReactNode, useContext, useMemo } from \"react\";\n\nimport { type ChartLabels, type ChartLabelsInput, mergeLabels } from \"../core/labels\";\nimport type { BrandId, Locale } from \"../core/types\";\n\nexport interface ChartsSettings {\n /** The locale of every figure and date (`\"fr-FR\"`). Default `\"en-US\"`. */\n locale?: Locale;\n /**\n * The brand whose palette the charts use. Without it, a chart follows a `.brand-<id>` ancestor, else Krizaka. Set it\n * when the app loads a brand stylesheet on `:root` (`@krizaka/tokens/brands/orazaka.css`).\n */\n brand?: BrandId;\n /** The words, translated: the buttons, the states, the written summary. */\n labels?: ChartLabelsInput;\n}\n\ninterface ResolvedSettings {\n readonly locale: Locale;\n readonly brand?: BrandId;\n readonly labels: ChartLabels;\n readonly raw?: ChartLabelsInput;\n}\n\nconst DEFAULTS: ResolvedSettings = { locale: \"en-US\", labels: mergeLabels() };\nconst Context = createContext<ResolvedSettings>(DEFAULTS);\n\nexport interface ChartsProviderProps extends ChartsSettings {\n children: ReactNode;\n}\n\n/** Sets the locale, the brand and the words of every chart below it. */\nexport function ChartsProvider({ locale, brand, labels, children }: ChartsProviderProps) {\n const parent = useContext(Context);\n const value = useMemo<ResolvedSettings>(() => {\n const raw: ChartLabelsInput | undefined = labels ? { ...parent.raw, ...labels, kinds: { ...parent.raw?.kinds, ...labels.kinds } } : parent.raw;\n return { locale: locale ?? parent.locale, brand: brand ?? parent.brand, labels: mergeLabels(raw), raw };\n }, [locale, brand, labels, parent]);\n return <Context.Provider value={value}>{children}</Context.Provider>;\n}\n\n/** The settings of a chart: its own props over the provider's over the defaults. */\nexport function useChartSettings(own: ChartsSettings): { locale: Locale; brand?: BrandId; labels: ChartLabels } {\n const shared = useContext(Context);\n return useMemo(\n () => ({ locale: own.locale ?? shared.locale, brand: own.brand ?? shared.brand, labels: own.labels ? mergeLabels(shared.raw, own.labels) : shared.labels }),\n [own.locale, own.brand, own.labels, shared],\n );\n}\n\n/**\n * What a `ChartCard` hands the chart inside it: the heading (title, insight, description) the card shows once, and the\n * extra controls (a period switch) that sit beside the chart's actions menu. A chart outside a card has none.\n */\nexport interface ChartCardContextValue {\n readonly title?: string;\n readonly insight?: ReactNode;\n readonly description?: ReactNode;\n readonly extra?: ReactNode;\n}\n\nconst CardContext = createContext<ChartCardContextValue | null>(null);\n\nexport const ChartCardProvider = CardContext.Provider;\n\n/** The card the chart is drawn in, or null. */\nexport function useChartCard(): ChartCardContextValue | null {\n return useContext(CardContext);\n}\n","// The data palettes. One categorical set of eight hues, stepped for each theme (the dark steps are chosen for the\n// dark surface, not flipped), ordered per brand so that the first series wears the brand's own hue — Krizaka blue,\n// Orazaka orange, Orochia violet then magenta, its gradient. Every order was chosen among the ones that pass the\n// colour-vision checks (scripts/palette.test: lightness band, chroma floor, adjacent ΔE under protan/deutan ≥ 8 —\n// Orochia light 7.2, inside the 6–8 band that requires the secondary encoding every chart carries: legend, direct\n// labels, data table —, normal-vision ΔE ≥ 15, ≥ 3:1 on the surface, the first three slots distinct all-pairs).\n// A 9th series never gets a generated hue: it folds into “Other” or a second chart.\nimport type { BrandId } from \"./types\";\n\nexport type Mode = \"dark\" | \"light\";\n\n/** The eight hues, light and dark steps. */\nexport const HUES = {\n blue: { light: \"#2a78d6\", dark: \"#3987e5\" },\n orange: { light: \"#eb6834\", dark: \"#d95926\" },\n aqua: { light: \"#1baf7a\", dark: \"#199e70\" },\n yellow: { light: \"#eda100\", dark: \"#c98500\" },\n magenta: { light: \"#e87ba4\", dark: \"#d55181\" },\n green: { light: \"#008300\", dark: \"#008300\" },\n violet: { light: \"#6250d6\", dark: \"#9085e9\" },\n red: { light: \"#e34948\", dark: \"#e66767\" },\n} as const;\n\nexport type Hue = keyof typeof HUES;\n\n/** The order of the hues for each brand: slot 1 is the brand. */\nexport const ORDERS: Record<BrandId, readonly Hue[]> = {\n krizaka: [\"blue\", \"green\", \"magenta\", \"yellow\", \"aqua\", \"orange\", \"violet\", \"red\"],\n orazaka: [\"orange\", \"blue\", \"aqua\", \"yellow\", \"magenta\", \"green\", \"violet\", \"red\"],\n orochia: [\"violet\", \"magenta\", \"yellow\", \"aqua\", \"orange\", \"blue\", \"green\", \"red\"],\n};\n\n/** The categorical palette of a brand in a theme: eight hex colours, slot 1 first. */\nexport function palette(brand: BrandId, mode: Mode): string[] {\n return ORDERS[brand].map((hue) => HUES[hue][mode]);\n}\n\n/** The diverging poles (below / above the midpoint): blue ↔ red, a neutral grey between them. */\nexport const DIVERGING = {\n light: { low: HUES.blue.light, high: HUES.red.light },\n dark: { low: HUES.blue.dark, high: HUES.red.dark },\n} as const;\n\n// --- Colour arithmetic (OKLab), for the sequential ramps the canvas engine needs as plain colours. ---------------\n\ntype Rgb = [number, number, number];\n\n/** `#rrggbb`, `#rgb`, `rgb(r, g, b)` / `rgba(…)` or `rgb(r g b / a)` → [r, g, b] in 0–255; null otherwise. */\nexport function parseColor(color: string): Rgb | null {\n const hex = color.trim().match(/^#([\\da-f]{3}|[\\da-f]{6})$/i);\n if (hex) {\n const h = hex[1]!.length === 3 ? [...hex[1]!].map((c) => c + c).join(\"\") : hex[1]!;\n return [0, 2, 4].map((i) => Number.parseInt(h.slice(i, i + 2), 16)) as Rgb;\n }\n const rgb = color.trim().match(/^rgba?\\(\\s*([\\d.]+)[\\s,]+([\\d.]+)[\\s,]+([\\d.]+)/i);\n if (rgb) return [Number(rgb[1]), Number(rgb[2]), Number(rgb[3])];\n return null;\n}\n\nconst toLinear = (c: number) => {\n const v = c / 255;\n return v <= 0.04045 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;\n};\nconst fromLinear = (v: number) => {\n const c = v <= 0.0031308 ? v * 12.92 : 1.055 * v ** (1 / 2.4) - 0.055;\n return Math.round(Math.min(1, Math.max(0, c)) * 255);\n};\n\nfunction toOklab([r8, g8, b8]: Rgb): Rgb {\n const [r, g, b] = [toLinear(r8), toLinear(g8), toLinear(b8)];\n const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);\n const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);\n const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);\n return [0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s, 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s, 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s];\n}\n\nfunction fromOklab([L, a, b]: Rgb): Rgb {\n const l = (L + 0.3963377774 * a + 0.2158037573 * b) ** 3;\n const m = (L - 0.1055613458 * a - 0.0638541728 * b) ** 3;\n const s = (L - 0.0894841775 * a - 1.291485548 * b) ** 3;\n return [\n fromLinear(4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s),\n fromLinear(-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s),\n fromLinear(-0.0041960863 * l - 0.7034186147 * m + 1.707614031 * s),\n ];\n}\n\nconst hex = ([r, g, b]: Rgb) => `#${[r, g, b].map((c) => c.toString(16).padStart(2, \"0\")).join(\"\")}`;\n\n/** `a` → `b` at `t` (0–1), interpolated in OKLab (even steps to the eye). Unparseable colours return `b`. */\nexport function mix(a: string, b: string, t: number): string {\n const ca = parseColor(a);\n const cb = parseColor(b);\n if (!ca || !cb) return b;\n const la = toOklab(ca);\n const lb = toOklab(cb);\n return hex(fromOklab([0, 1, 2].map((i) => la[i]! + (lb[i]! - la[i]!) * t) as Rgb));\n}\n\n/**\n * A sequential ramp of `steps` colours, one hue from near the surface (`0`, “almost nothing”) to the full hue: the\n * magnitude of a heatmap, a calendar or a choropleth. The first step is still distinct from the surface.\n */\nexport function sequential(hue: string, surface: string, steps = 5): string[] {\n return Array.from({ length: steps }, (_, i) => mix(surface, hue, 0.22 + (0.78 * i) / Math.max(1, steps - 1)));\n}\n\n/** WCAG contrast ratio between two colours (for the tests and the labels drawn inside a mark). */\nexport function contrast(a: string, b: string): number {\n const lum = (c: string) => {\n const rgb = parseColor(c);\n if (!rgb) return 0;\n const [r, g, bl] = rgb.map(toLinear) as Rgb;\n return 0.2126 * r + 0.7152 * g + 0.0722 * bl;\n };\n const [hi, lo] = [lum(a), lum(b)].sort((x, y) => y - x) as [number, number];\n return (hi + 0.05) / (lo + 0.05);\n}\n"],"mappings":"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","names":["hex"]}
@@ -10,10 +10,10 @@ import {
10
10
  ChartFrame,
11
11
  colorOf,
12
12
  useSettings
13
- } from "./chunk-JTKR7ZWP.js";
13
+ } from "./chunk-P6RS2L23.js";
14
14
  import {
15
15
  fill
16
- } from "./chunk-3CJHBJXA.js";
16
+ } from "./chunk-GRRFPI27.js";
17
17
 
18
18
  // src/gauge/gauge.tsx
19
19
  import { useMemo } from "react";
@@ -33,7 +33,7 @@ function Gauge(props) {
33
33
  `${labels.kinds.gauge}: ${text} (${min === 0 ? "" : `${fmt(min)} \u2013 `}${fmt(max)})${zone?.label ? `, ${zone.label}` : ""}.`,
34
34
  target ? `${fill(labels.summaryThreshold, { label: target.label, value: fmt(target.value) })}.` : ""
35
35
  ].filter(Boolean).join(" ");
36
- const table = { columns: [labels.category, labels.value], rows: [[props.title, text], ["min", fmt(min)], ["max", fmt(max)], ...target ? [[target.label, fmt(target.value)]] : []] };
36
+ const table = { columns: [labels.category, labels.value], rows: [[props.title ?? "", text], ["min", fmt(min)], ["max", fmt(max)], ...target ? [[target.label, fmt(target.value)]] : []] };
37
37
  const at = (v) => START + (Math.min(max, Math.max(min, v)) - min) / (max - min || 1) * (END - START);
38
38
  return /* @__PURE__ */ jsx(ChartFrame, { kind: "gauge", frame: { ...props, dataTable: false }, defaultHeight: 180, table, summary, isEmpty: false, navigable: false, children: (width, height) => {
39
39
  const radius = Math.max(20, Math.min(width / 2 - 12, (height - 30) / 1.58));
@@ -70,4 +70,4 @@ function Gauge(props) {
70
70
  export {
71
71
  Gauge
72
72
  };
73
- //# sourceMappingURL=chunk-EQLC2QVN.js.map
73
+ //# sourceMappingURL=chunk-OZOAKP5F.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/gauge/gauge.tsx"],"sourcesContent":["\"use client\";\n// One value against its range: a 240° arc, the value written in the middle, an optional target tick and coloured\n// zones (a quota that turns to warning, then danger). Plain SVG; exposed as a `meter` (value, range and its words).\nimport { useMemo } from \"react\";\n\nimport { valueFormatter } from \"../core/format\";\nimport { fill } from \"../core/labels\";\nimport { polar, strokeArcPath } from \"../core/shape\";\nimport type { Threshold, Tone, ValueFormat } from \"../core/types\";\nimport { ChartFrame, type ChartFrameProps, useSettings } from \"../web/frame\";\nimport { colorOf } from \"../web/parts\";\n\nexport interface GaugeZone {\n /** The zone runs from the previous one's end (or `min`) to this value. */\n readonly to: number;\n readonly tone: Tone;\n /** Its name, in the summary and the table (“Over quota”). */\n readonly label?: string;\n}\n\nexport interface GaugeProps extends Omit<ChartFrameProps, \"dataTable\" | \"exportable\"> {\n /** The value. */\n/** The value. */\n value: number;\n /** The start of the range. Default 0. */\n/** The start of the range. Default 0. */\n min?: number;\n /** The end of the range. Default 100. */\n/** The end of the range. Default 100. */\n max?: number;\n /** How the value is written. */\n/** How the value and the range are written. */\n format?: ValueFormat;\n /** The words under the value (“of 50 000 credits”). */\n caption?: string;\n /** A tick on the arc: the objective. */\n target?: Threshold;\n /** Coloured zones along the arc: the value takes the tone of the zone it is in. */\n zones?: readonly GaugeZone[];\n /** The tone of the value without zones. Default `\"accent\"`. */\n/** The tone of the value without zones. Default `\"accent\"`. */\n tone?: Tone;\n}\n\nconst START = (-120 * Math.PI) / 180;\nconst END = (120 * Math.PI) / 180;\n\n/** One figure against its range — credits used of a plan, a challenge's progress, a load. */\nexport function Gauge(props: GaugeProps) {\n const { value, min = 0, max = 100, format, caption, target, zones = [], tone = \"accent\" } = props;\n const { locale, labels } = useSettings(props);\n const fmt = useMemo(() => valueFormatter(format, locale, labels.noValue), [format, locale, labels.noValue]);\n const clamped = Math.min(max, Math.max(min, value));\n const ratio = max > min ? (clamped - min) / (max - min) : 0;\n const zone = zones.find((z) => clamped <= z.to) ?? zones[zones.length - 1];\n const color = colorOf(zone?.tone ?? tone, 1);\n const text = fmt(value);\n const summary = [\n `${labels.kinds.gauge}: ${text} (${min === 0 ? \"\" : `${fmt(min)} – `}${fmt(max)})${zone?.label ? `, ${zone.label}` : \"\"}.`,\n target ? `${fill(labels.summaryThreshold, { label: target.label, value: fmt(target.value) })}.` : \"\",\n ]\n .filter(Boolean)\n .join(\" \");\n const table = { columns: [labels.category, labels.value], rows: [[props.title ?? \"\", text], [\"min\", fmt(min)], [\"max\", fmt(max)], ...(target ? [[target.label, fmt(target.value)]] : [])] };\n const at = (v: number) => START + ((Math.min(max, Math.max(min, v)) - min) / (max - min || 1)) * (END - START);\n\n return (\n <ChartFrame kind=\"gauge\" frame={{ ...props, dataTable: false }} defaultHeight={180} table={table} summary={summary} isEmpty={false} navigable={false}>\n {(width, height) => {\n // The arc spans 240°: its lowest points sit at half the radius below the centre, the range labels under them.\n const radius = Math.max(20, Math.min(width / 2 - 12, (height - 30) / 1.58));\n const cx = width / 2;\n const cy = 8 + radius;\n const stroke = Math.max(8, radius * 0.16);\n let from = min;\n return (\n <div role=\"meter\" aria-label={props.title} aria-valuemin={min} aria-valuemax={max} aria-valuenow={clamped} aria-valuetext={caption ? `${text} ${caption}` : text} style={{ height: \"100%\" }}>\n <svg className=\"kzc-svg\" width={width} height={height} viewBox={`0 0 ${width} ${height}`} aria-hidden>\n <path d={strokeArcPath(cx, cy, radius, START, END)} style={{ fill: \"none\", stroke: \"var(--kz-surface-3)\", strokeWidth: stroke, strokeLinecap: \"round\" }} />\n {zones.map((z, i) => {\n const d = strokeArcPath(cx, cy, radius + stroke / 2 + 4, at(from), at(z.to));\n from = z.to;\n return <path key={i} d={d} data-tone={z.tone} style={{ fill: \"none\", stroke: \"var(--kzc-tone)\", strokeWidth: 3, opacity: 0.7 }} />;\n })}\n {ratio > 0 && (\n <path d={strokeArcPath(cx, cy, radius, START, at(clamped))} pathLength={1} className=\"kzc-line kzc-enter-line\" style={{ stroke: color, strokeWidth: stroke }} />\n )}\n {target && (\n <g data-tone={target.tone ?? \"neutral\"}>\n <line\n x1={polar(cx, cy, radius - stroke, at(target.value))[0]}\n y1={polar(cx, cy, radius - stroke, at(target.value))[1]}\n x2={polar(cx, cy, radius + stroke, at(target.value))[0]}\n y2={polar(cx, cy, radius + stroke, at(target.value))[1]}\n style={{ stroke: \"var(--kzc-text)\", strokeWidth: 2, strokeLinecap: \"round\" }}\n />\n </g>\n )}\n <text x={cx} y={cy} textAnchor=\"middle\" dominantBaseline=\"central\" className=\"kzc-strong\" style={{ fontSize: Math.max(12, Math.min(32, radius / 2.6, (radius * 1.45) / (text.length * 0.58))) }}>\n {text}\n </text>\n {caption && (\n <text x={cx} y={cy + Math.max(16, radius / 3.2)} textAnchor=\"middle\" dominantBaseline=\"central\">\n {caption}\n </text>\n )}\n <text x={polar(cx, cy, radius, START)[0]} y={polar(cx, cy, radius, START)[1] + stroke + 6} textAnchor=\"middle\">\n {fmt(min)}\n </text>\n <text x={polar(cx, cy, radius, END)[0]} y={polar(cx, cy, radius, END)[1] + stroke + 6} textAnchor=\"middle\">\n {fmt(max)}\n </text>\n </svg>\n </div>\n );\n }}\n </ChartFrame>\n );\n}\n"],"mappings":"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","names":[]}
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import {
3
3
  LineChart
4
- } from "./chunk-C4MFZLJM.js";
4
+ } from "./chunk-Z4T3JFNU.js";
5
5
  import {
6
6
  Kpi,
7
7
  toneOf
@@ -17,7 +17,7 @@ import {
17
17
  } from "./chunk-XLLYNNC6.js";
18
18
  import {
19
19
  useSettings
20
- } from "./chunk-JTKR7ZWP.js";
20
+ } from "./chunk-P6RS2L23.js";
21
21
 
22
22
  // src/needs/ticker.ts
23
23
  function tickerModel(bids, opening, openedAt) {
@@ -75,4 +75,4 @@ function PriceTicker({ bids, opening, openedAt, currency, reserve, timeFormat =
75
75
  export {
76
76
  PriceTicker
77
77
  };
78
- //# sourceMappingURL=chunk-KNDHO67X.js.map
78
+ //# sourceMappingURL=chunk-P2NK5L3B.js.map
@@ -2,9 +2,12 @@
2
2
  import {
3
3
  HUES,
4
4
  ORDERS,
5
- fill,
5
+ useChartCard,
6
6
  useChartSettings
7
- } from "./chunk-3CJHBJXA.js";
7
+ } from "./chunk-OVASCEGF.js";
8
+ import {
9
+ fill
10
+ } from "./chunk-GRRFPI27.js";
8
11
 
9
12
  // src/web/hooks.ts
10
13
  import { useEffect, useState, useSyncExternalStore } from "react";
@@ -89,14 +92,16 @@ function useMounted() {
89
92
  }
90
93
 
91
94
  // src/web/parts.tsx
92
- import { jsx, jsxs } from "react/jsx-runtime";
95
+ import { useCallback, useState as useState2 } from "react";
96
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
93
97
  var cx = (...names) => names.filter(Boolean).join(" ");
94
98
  var TONES = {
95
99
  neutral: "var(--kz-text-secondary)",
96
100
  accent: "var(--kz-accent)",
97
101
  success: "var(--kz-success)",
98
102
  warning: "var(--kz-warning)",
99
- danger: "var(--kz-danger)",
103
+ // A failure is stated, not shouted: the danger hue eased toward the muted text so a block of it stays calm.
104
+ danger: "color-mix(in oklab, var(--kz-danger) 78%, var(--kz-text-muted))",
100
105
  info: "var(--kz-info)"
101
106
  };
102
107
  function colorOf(color, slot) {
@@ -105,19 +110,54 @@ function colorOf(color, slot) {
105
110
  if (color in TONES) return TONES[color];
106
111
  return color;
107
112
  }
108
- function Legend({ items }) {
109
- if (items.length < 2) return null;
110
- return /* @__PURE__ */ jsx("ul", { className: "kzc-legend", "aria-hidden": true, children: items.map((item) => /* @__PURE__ */ jsxs("li", { children: [
111
- /* @__PURE__ */ jsx(
113
+ function Legend({ items, off, onToggle, toggleName, always }) {
114
+ if (items.length < 2 && !always) return null;
115
+ const live = items.filter((item) => !off?.has(item.label)).length;
116
+ return /* @__PURE__ */ jsx("ul", { className: "kzc-legend", "aria-hidden": onToggle ? void 0 : true, children: items.map((item) => {
117
+ const isOff = off?.has(item.label) ?? false;
118
+ const key = /* @__PURE__ */ jsx(
112
119
  "span",
113
120
  {
114
121
  className: "kzc-key",
115
122
  "data-shape": item.shape,
116
123
  style: { background: item.dashed ? `repeating-linear-gradient(90deg, ${item.color} 0 4px, transparent 4px 7px)` : item.color }
117
124
  }
118
- ),
119
- item.label
120
- ] }, item.label)) });
125
+ );
126
+ const text = /* @__PURE__ */ jsxs(Fragment, { children: [
127
+ item.label,
128
+ item.detail && /* @__PURE__ */ jsx("span", { className: "kzc-legend-detail", children: item.detail })
129
+ ] });
130
+ return /* @__PURE__ */ jsx("li", { children: onToggle ? /* @__PURE__ */ jsxs(
131
+ "button",
132
+ {
133
+ type: "button",
134
+ className: "kzc-chip",
135
+ "aria-pressed": !isOff,
136
+ title: toggleName?.(item.label),
137
+ disabled: !isOff && live <= 1,
138
+ onClick: () => onToggle(item.label),
139
+ children: [
140
+ key,
141
+ text
142
+ ]
143
+ }
144
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
145
+ key,
146
+ text
147
+ ] }) }, item.label);
148
+ }) });
149
+ }
150
+ function useToggles() {
151
+ const [off, setOff] = useState2(() => /* @__PURE__ */ new Set());
152
+ const toggle = useCallback((label) => {
153
+ setOff((current) => {
154
+ const next = new Set(current);
155
+ if (next.has(label)) next.delete(label);
156
+ else next.add(label);
157
+ return next;
158
+ });
159
+ }, []);
160
+ return { off, toggle };
121
161
  }
122
162
  function Tooltip({ x, y, width, title, rows, note }) {
123
163
  const flip = x > width / 2;
@@ -152,11 +192,19 @@ var tone = (name, value) => `.kzc [data-tone=${name}]{--kzc-tone:${value}}`;
152
192
  var CSS = `${PALETTE}
153
193
  .kzc{--kzc-text:var(--kz-text-primary);--kzc-text-2:var(--kz-text-secondary);--kzc-text-3:var(--kz-text-muted);--kzc-grid:var(--kz-border-subtle);--kzc-axis:var(--kz-border-default);--kzc-surface:var(--kz-surface-1);--kzc-tone:var(--kz-text-secondary);margin:0;min-width:0;display:flex;flex-direction:column;gap:8px;color:var(--kzc-text);font-family:var(--kz-font-sans,system-ui,sans-serif);font-variant-numeric:tabular-nums}
154
194
  ${tone("neutral", "var(--kz-text-secondary)")}${tone("accent", "var(--kz-accent)")}${tone("success", "var(--kz-success)")}${tone("warning", "var(--kz-warning)")}${tone("danger", "var(--kz-danger)")}${tone("info", "var(--kz-info)")}
155
- .kzc-head{display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:space-between;gap:4px 12px}
156
- .kzc-heading{display:flex;flex-direction:column;gap:2px;min-width:0}
195
+ .kzc-head{display:flex;align-items:flex-start;justify-content:space-between;gap:4px 12px}
196
+ .kzc-heading{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1 1 auto}
157
197
  .kzc-title{margin:0;font-family:var(--kz-font-display,inherit);font-size:14px;font-weight:600;line-height:20px;color:var(--kzc-text)}
198
+ .kzc-heading[data-insight] .kzc-title{font-family:var(--kz-font-sans,inherit);font-size:12px;line-height:16px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--kzc-text-2)}
199
+ .kzc-insight{margin:2px 0 0;font-family:var(--kz-font-display,inherit);font-size:20px;line-height:26px;font-weight:700;letter-spacing:-.01em;color:var(--kzc-text);font-variant-numeric:tabular-nums}
200
+ .kzc-note{margin:0;font-size:12px;line-height:18px;color:var(--kzc-text-2)}
158
201
  .kzc-desc{margin:0;font-size:13px;line-height:18px;color:var(--kzc-text-2)}
159
- .kzc-tools{display:flex;flex-wrap:wrap;gap:4px}
202
+ .kzc-tools{display:flex;flex-wrap:wrap;flex:none;align-items:center;justify-content:flex-end;gap:4px;max-width:100%}
203
+ .kzc-more{appearance:none;display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;margin:-4px -4px 0 0;padding:0;border:0;border-radius:var(--kz-radius-md,8px);background:transparent;color:var(--kzc-text-2);cursor:pointer;transition:background-color .15s,color .15s}
204
+ .kzc-more:hover:not(:disabled){background:var(--kz-surface-2);color:var(--kzc-text)}
205
+ .kzc-more:disabled{cursor:default;opacity:.7}
206
+ .kzc-more:focus-visible{outline:2px solid var(--kz-ring);outline-offset:2px}
207
+ .kzc-more svg{width:18px;height:18px}
160
208
  .kzc-btn{appearance:none;display:inline-flex;align-items:center;gap:4px;height:28px;padding:0 10px;border:1px solid var(--kz-border-default);border-radius:var(--kz-radius-md,8px);background:transparent;color:var(--kzc-text-2);font:inherit;font-size:12px;font-weight:500;cursor:pointer;transition:background-color .15s,color .15s}
161
209
  .kzc-btn:hover{background:var(--kz-surface-2);color:var(--kzc-text)}
162
210
  .kzc-btn:focus-visible,.kzc-plot:focus-visible{outline:2px solid var(--kz-ring);outline-offset:2px}
@@ -177,9 +225,16 @@ ${tone("neutral", "var(--kz-text-secondary)")}${tone("accent", "var(--kz-accent)
177
225
  .kzc[data-active] .kzc-mark[data-dim]{opacity:.35}
178
226
  .kzc-legend{display:flex;flex-wrap:wrap;gap:4px 14px;margin:0;padding:0;list-style:none;font-size:12px;color:var(--kzc-text-2)}
179
227
  .kzc-legend li{display:inline-flex;align-items:center;gap:6px}
228
+ .kzc-legend-detail{margin-left:2px;color:var(--kzc-text-2)}
229
+ .kzc-chip{appearance:none;display:inline-flex;align-items:center;gap:6px;height:26px;padding:0 10px;border:1px solid var(--kz-border-default);border-radius:999px;background:transparent;color:var(--kzc-text);font:inherit;font-size:12px;cursor:pointer;transition:background-color .15s,color .15s,border-color .15s,opacity .15s}
230
+ .kzc-chip:hover:not(:disabled){background:var(--kz-surface-2)}
231
+ .kzc-chip[aria-pressed=false]{color:var(--kzc-text-2);border-style:dashed}
232
+ .kzc-chip[aria-pressed=false] .kzc-key{opacity:.35}
233
+ .kzc-chip:disabled{cursor:default}
234
+ .kzc-chip:focus-visible{outline:2px solid var(--kz-ring);outline-offset:2px}
180
235
  .kzc-key{width:10px;height:10px;border-radius:3px;flex:none}
181
236
  .kzc-key[data-shape=line]{height:3px;border-radius:2px;width:14px}
182
- .kzc-tip{position:absolute;z-index:2;pointer-events:none;min-width:140px;max-width:260px;padding:8px 10px;border:1px solid var(--kz-border-default);border-radius:var(--kz-radius-lg,10px);background:var(--kz-surface-2);color:var(--kzc-text);box-shadow:var(--kz-shadow-lg);font-size:12px;line-height:18px}
237
+ .kzc-tip{animation:none;position:absolute;z-index:2;pointer-events:none;min-width:140px;max-width:260px;padding:8px 10px;border:1px solid var(--kz-border-default);border-radius:var(--kz-radius-lg,10px);background:var(--kz-surface-2);color:var(--kzc-text);box-shadow:var(--kz-shadow-lg);font-size:12px;line-height:18px}
183
238
  .kzc-tip-title{margin:0 0 4px;font-weight:600;color:var(--kzc-text)}
184
239
  .kzc-tip-row{display:flex;align-items:center;gap:8px;margin:0}
185
240
  .kzc-tip-row span:nth-child(2){flex:1;color:var(--kzc-text-2)}
@@ -193,7 +248,8 @@ ${tone("neutral", "var(--kz-text-secondary)")}${tone("accent", "var(--kz-accent)
193
248
  .kzc-table th,.kzc-table td{padding:6px 10px;border-top:1px solid var(--kz-border-subtle);text-align:right;white-space:nowrap}
194
249
  .kzc-table th[scope=row],.kzc-table thead th:first-child{text-align:left;font-weight:500;color:var(--kzc-text)}
195
250
  .kzc-table thead th{position:sticky;top:0;background:var(--kz-surface-1);color:var(--kzc-text-2);font-weight:500}
196
- .kzc-state{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;padding:16px;text-align:center;font-size:13px;color:var(--kzc-text-2);border:1px dashed var(--kz-border-default);border-radius:var(--kz-radius-lg,10px)}
251
+ .kzc-state{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;padding:16px;text-align:center;font-size:13px;line-height:18px;color:var(--kzc-text-2);background:color-mix(in oklab,var(--kz-surface-2) 55%,transparent);border-radius:var(--kz-radius-lg,10px)}
252
+ .kzc-state svg{width:28px;height:28px;color:var(--kzc-text-3)}
197
253
  .kzc-state[data-state=error]{color:var(--kz-danger-text,var(--kzc-text))}
198
254
  .kzc-skeleton{position:absolute;inset:0;display:flex;align-items:flex-end;gap:6%;padding:12px 4% 24px;border-radius:var(--kz-radius-lg,10px);overflow:hidden}
199
255
  .kzc-skeleton i{flex:1;border-radius:4px 4px 0 0;background:var(--kz-surface-2)}
@@ -205,6 +261,11 @@ ${tone("neutral", "var(--kz-text-secondary)")}${tone("accent", "var(--kz-accent)
205
261
  .kzc-kpi-label{font-size:13px;color:var(--kzc-text-2)}
206
262
  .kzc-kpi-delta{display:inline-flex;align-items:center;gap:4px;font-size:13px;font-weight:600;color:var(--kzc-text)}
207
263
  .kzc-kpi-delta svg{width:12px;height:12px;flex:none;color:var(--kzc-tone)}
264
+ .kzc-kpistat{min-width:0}
265
+ .kzc-kpistat[data-good=down] [data-trend=up] svg{color:var(--kz-danger)}
266
+ .kzc-kpistat[data-good=down] [data-trend=down] svg{color:var(--kz-success)}
267
+ .kzc-card{min-width:0}
268
+ .kzc-card-foot{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:4px 12px;margin:12px 0 0;padding-top:10px;border-top:1px solid var(--kz-border-subtle);font-size:12px;line-height:18px;color:var(--kzc-text-3)}
208
269
  .kzc-meter{position:relative;height:10px;border-radius:999px;background:var(--kz-surface-3);overflow:hidden;--kzc-tone:var(--kz-accent)}
209
270
  .kzc-meter>i{position:absolute;inset:0 auto 0 0;border-radius:inherit;background:var(--kzc-tone);transition:width .6s var(--kz-ease,ease-out)}
210
271
  .kzc-meter[data-tone=warning]{--kzc-tone:var(--kz-warning)}.kzc-meter[data-tone=danger]{--kzc-tone:var(--kz-danger)}
@@ -214,9 +275,9 @@ ${tone("neutral", "var(--kz-text-secondary)")}${tone("accent", "var(--kz-accent)
214
275
  .kzc-skeleton i{animation:kzc-pulse 1.6s ease-in-out infinite}
215
276
  .kzc[data-inview] .kzc-enter-line{stroke-dasharray:1;stroke-dashoffset:1;animation:kzc-draw .9s var(--kz-ease,ease-out) forwards}
216
277
  .kzc[data-inview] .kzc-enter-fade{opacity:0;animation:kzc-fade .6s .3s var(--kz-ease,ease-out) forwards}
217
- .kzc[data-inview] .kzc-enter-grow{transform:scaleY(0);transform-box:fill-box;transform-origin:bottom;animation:kzc-grow .6s var(--kz-ease,ease-out) forwards}
278
+ .kzc[data-inview] .kzc-enter-grow{transform:scaleY(0);transform-box:fill-box;transform-origin:bottom;animation:kzc-grow .6s calc(min(var(--kzc-i,0),24)*22ms) var(--kz-ease,ease-out) forwards}
218
279
  .kzc[data-inview] .kzc-enter-grow[data-negative]{transform-origin:top}
219
- .kzc[data-inview] .kzc-enter-growx{transform:scaleX(0);transform-box:fill-box;transform-origin:left;animation:kzc-growx .6s var(--kz-ease,ease-out) forwards}
280
+ .kzc[data-inview] .kzc-enter-growx{transform:scaleX(0);transform-box:fill-box;transform-origin:left;animation:kzc-growx .6s calc(min(var(--kzc-i,0),24)*22ms) var(--kz-ease,ease-out) forwards}
220
281
  .kzc[data-inview] .kzc-enter-growx[data-negative]{transform-origin:right}
221
282
  .kzc[data-inview] .kzc-enter-pop{opacity:0;transform:scale(.92);transform-box:view-box;transform-origin:center;animation:kzc-pop .6s var(--kz-ease,ease-out) forwards}
222
283
  .kzc:not([data-inview]) .kzc-enter-line,.kzc:not([data-inview]) .kzc-enter-fade,.kzc:not([data-inview]) .kzc-enter-grow,.kzc:not([data-inview]) .kzc-enter-growx,.kzc:not([data-inview]) .kzc-enter-pop{visibility:hidden}
@@ -237,7 +298,7 @@ function ChartStyles() {
237
298
  }
238
299
 
239
300
  // src/web/frame.tsx
240
- import { useCallback, useId, useMemo, useRef, useState as useState2 } from "react";
301
+ import { lazy, Suspense, useCallback as useCallback2, useId, useMemo, useRef, useState as useState3 } from "react";
241
302
 
242
303
  // src/web/export.ts
243
304
  function toCsv(rows) {
@@ -297,6 +358,7 @@ function svgToPng(markup, width, height, background, scale = 2) {
297
358
 
298
359
  // src/web/frame.tsx
299
360
  import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
361
+ var ChartActions = lazy(() => import("./actions-5VG352VZ.js"));
300
362
  function useSettings(frame) {
301
363
  return useChartSettings(frame);
302
364
  }
@@ -309,6 +371,7 @@ function ChartFrame({
309
371
  isEmpty,
310
372
  children,
311
373
  legend,
374
+ note,
312
375
  tooltip,
313
376
  announcement,
314
377
  handlers,
@@ -320,8 +383,12 @@ function ChartFrame({
320
383
  compact,
321
384
  pending
322
385
  }) {
323
- const { title, description, hideTitle, height = defaultHeight, loading, error, empty, summaryText, dataTable = true, exportable, live, id, className, style } = frame;
386
+ const card = useChartCard();
387
+ const { hideTitle, height = defaultHeight, loading, error, empty, summaryText, dataTable = true, exportable, actionsMenu = true, live, id, className, style } = frame;
324
388
  const { brand, labels } = useChartSettings(frame);
389
+ const title = frame.title ?? card?.title ?? labels.kinds[kind];
390
+ const insight = frame.insight ?? card?.insight;
391
+ const description = frame.description ?? card?.description;
325
392
  const uid = useId();
326
393
  const ids = { title: `${uid}-t`, summary: `${uid}-s`, hint: `${uid}-h`, table: `${uid}-d` };
327
394
  const ownRef = useRef(null);
@@ -331,10 +398,15 @@ function ChartFrame({
331
398
  const measured = useWidth(ref);
332
399
  const width = mounted ? measured : 0;
333
400
  const inView = useInView(figureRef, { once: true });
334
- const [showTable, setShowTable] = useState2(false);
335
- const formats = useMemo(() => exportable === true ? ["png", "svg", "csv"] : exportable || [], [exportable]);
401
+ const [showTable, setShowTable] = useState3(false);
402
+ const [copied, setCopied] = useState3(false);
403
+ const formats = useMemo(() => {
404
+ const all = actionsMenu ? ["csv", "png", "svg"] : ["png", "svg", "csv"];
405
+ if (exportable === void 0) return actionsMenu ? all : [];
406
+ return exportable === true ? all : exportable || [];
407
+ }, [exportable, actionsMenu]);
336
408
  const state = error ? "error" : loading ? "loading" : isEmpty ? "empty" : "ready";
337
- const doExport = useCallback(
409
+ const doExport = useCallback2(
338
410
  async (format) => {
339
411
  if (format === "csv") {
340
412
  download(fileName(title, "csv"), new Blob([`\uFEFF${toCsv([table.columns, ...table.rows])}`], { type: "text/csv;charset=utf-8" }));
@@ -358,6 +430,16 @@ function ChartFrame({
358
430
  },
359
431
  [exportImage, ref, table, title]
360
432
  );
433
+ const doCopy = useCallback2(async () => {
434
+ const text = [table.columns, ...table.rows].map((row) => row.join(" ")).join("\n");
435
+ try {
436
+ await navigator.clipboard.writeText(text);
437
+ setCopied(true);
438
+ setTimeout(() => setCopied(false), 2500);
439
+ } catch {
440
+ setCopied(false);
441
+ }
442
+ }, [table]);
361
443
  const drawWidth = width || ssrWidth || 0;
362
444
  let plot;
363
445
  if (state === "loading") {
@@ -368,7 +450,10 @@ function ChartFrame({
368
450
  } else if (state === "error") {
369
451
  plot = /* @__PURE__ */ jsx3("div", { className: "kzc-state", "data-state": "error", role: "alert", children: error === true ? labels.error : error });
370
452
  } else if (state === "empty") {
371
- plot = empty ?? /* @__PURE__ */ jsx3("div", { className: "kzc-state", "data-state": "empty", role: "status", children: labels.empty });
453
+ plot = empty ?? /* @__PURE__ */ jsxs2("div", { className: "kzc-state", "data-state": "empty", role: "status", children: [
454
+ /* @__PURE__ */ jsx3("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx3("path", { d: "M4 20V10M10 20V4M16 20v-7M22 20H2" }) }),
455
+ labels.empty
456
+ ] });
372
457
  } else if (drawWidth > 0) {
373
458
  plot = children(drawWidth, height);
374
459
  } else {
@@ -393,16 +478,26 @@ function ChartFrame({
393
478
  "aria-labelledby": ids.title,
394
479
  children: [
395
480
  /* @__PURE__ */ jsx3(ChartStyles, {}),
396
- /* @__PURE__ */ jsxs2("div", { className: !hideTitle || description || !compact && (dataTable || formats.length > 0) ? "kzc-head" : "kzc-sr", children: [
397
- /* @__PURE__ */ jsxs2("div", { className: "kzc-heading", children: [
398
- /* @__PURE__ */ jsx3("p", { id: ids.title, className: hideTitle ? "kzc-sr" : "kzc-title", children: title }),
399
- description && /* @__PURE__ */ jsx3("p", { className: "kzc-desc", children: description })
400
- ] }),
401
- (dataTable || formats.length > 0) && !compact && state !== "loading" && state !== "error" && /* @__PURE__ */ jsxs2("div", { className: "kzc-tools", children: [
402
- dataTable && !isEmpty && /* @__PURE__ */ jsx3("button", { type: "button", className: "kzc-btn", "aria-expanded": showTable, "aria-controls": ids.table, onClick: () => setShowTable((v) => !v), children: showTable ? labels.hideData : labels.showData }),
403
- !isEmpty && formats.map((format) => /* @__PURE__ */ jsx3("button", { type: "button", className: "kzc-btn", onClick: () => void doExport(format), "aria-label": `${title} \u2014 ${format.toUpperCase()}`, children: format === "png" ? labels.exportPng : format === "svg" ? labels.exportSvg : labels.exportCsv }, format))
404
- ] })
405
- ] }),
481
+ (() => {
482
+ const tools = !compact && state !== "loading" && state !== "error";
483
+ const menu = actionsMenu && tools && !isEmpty && (dataTable || formats.length > 0);
484
+ const legacy = !actionsMenu && tools && (dataTable || formats.length > 0);
485
+ const visible = !hideTitle || description || insight || menu || legacy || card?.extra;
486
+ return /* @__PURE__ */ jsxs2("div", { className: visible ? "kzc-head" : "kzc-sr", children: [
487
+ /* @__PURE__ */ jsxs2("div", { className: "kzc-heading", "data-insight": insight ? "" : void 0, children: [
488
+ /* @__PURE__ */ jsx3("p", { id: ids.title, className: hideTitle ? "kzc-sr" : "kzc-title", children: title }),
489
+ insight && /* @__PURE__ */ jsx3("p", { className: "kzc-insight", children: insight }),
490
+ description && /* @__PURE__ */ jsx3("p", { className: "kzc-desc", children: description })
491
+ ] }),
492
+ (menu || legacy || card?.extra) && /* @__PURE__ */ jsxs2("div", { className: "kzc-tools", children: [
493
+ card?.extra,
494
+ menu && mounted && /* @__PURE__ */ jsx3(Suspense, { fallback: /* @__PURE__ */ jsx3(MoreButton, { label: labels.actions }), children: /* @__PURE__ */ jsx3(ChartActions, { title, table, labels, formats, viewTable: dataTable, onExport: (format) => void doExport(format), onCopy: () => void doCopy() }) }),
495
+ menu && !mounted && /* @__PURE__ */ jsx3(MoreButton, { label: labels.actions }),
496
+ legacy && !isEmpty && dataTable && /* @__PURE__ */ jsx3("button", { type: "button", className: "kzc-btn", "aria-expanded": showTable, "aria-controls": ids.table, onClick: () => setShowTable((v) => !v), children: showTable ? labels.hideData : labels.showData }),
497
+ legacy && !isEmpty && formats.map((format) => /* @__PURE__ */ jsx3("button", { type: "button", className: "kzc-btn", onClick: () => void doExport(format), "aria-label": `${title} \u2014 ${format.toUpperCase()}`, children: format === "png" ? labels.exportPng : format === "svg" ? labels.exportSvg : labels.exportCsv }, format))
498
+ ] })
499
+ ] });
500
+ })(),
406
501
  legend,
407
502
  /* @__PURE__ */ jsxs2(
408
503
  "div",
@@ -424,9 +519,10 @@ function ChartFrame({
424
519
  ]
425
520
  }
426
521
  ),
522
+ ready && note && /* @__PURE__ */ jsx3("p", { className: "kzc-note", children: note }),
427
523
  /* @__PURE__ */ jsx3("p", { id: ids.summary, className: frame.summary === "visible" ? "kzc-summary" : "kzc-sr", children: summaryText ?? summary }),
428
524
  ready && navigable && /* @__PURE__ */ jsx3("span", { id: ids.hint, className: "kzc-sr", children: labels.keyboardHint }),
429
- /* @__PURE__ */ jsx3("div", { className: "kzc-live", "aria-live": "polite", "aria-atomic": "true", children: live ? "" : announcement }),
525
+ /* @__PURE__ */ jsx3("div", { className: "kzc-live", "aria-live": "polite", "aria-atomic": "true", children: copied ? labels.copied : live ? "" : announcement }),
430
526
  !isEmpty && /* @__PURE__ */ jsx3("div", { id: ids.table, className: showTable ? "kzc-table-wrap" : "kzc-sr", children: /* @__PURE__ */ jsxs2("table", { className: "kzc-table", children: [
431
527
  /* @__PURE__ */ jsx3("caption", { children: fill(labels.tableCaption, { title }) }),
432
528
  /* @__PURE__ */ jsx3("thead", { children: /* @__PURE__ */ jsx3("tr", { children: table.columns.map((column, i) => /* @__PURE__ */ jsx3("th", { scope: "col", children: column }, i)) }) }),
@@ -438,6 +534,13 @@ function ChartFrame({
438
534
  }
439
535
  );
440
536
  }
537
+ function MoreButton({ label }) {
538
+ return /* @__PURE__ */ jsx3("button", { type: "button", className: "kzc-more", "aria-label": label, disabled: true, children: /* @__PURE__ */ jsxs2("svg", { viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": true, width: "16", height: "16", children: [
539
+ /* @__PURE__ */ jsx3("circle", { cx: "5", cy: "12", r: "1.8" }),
540
+ /* @__PURE__ */ jsx3("circle", { cx: "12", cy: "12", r: "1.8" }),
541
+ /* @__PURE__ */ jsx3("circle", { cx: "19", cy: "12", r: "1.8" })
542
+ ] }) });
543
+ }
441
544
  function resolveColor(color, scope) {
442
545
  if (typeof document === "undefined") return color;
443
546
  const probe = document.createElement("span");
@@ -457,6 +560,7 @@ export {
457
560
  cx,
458
561
  colorOf,
459
562
  Legend,
563
+ useToggles,
460
564
  Tooltip,
461
565
  stepColor,
462
566
  RampLegend,
@@ -465,4 +569,4 @@ export {
465
569
  ChartFrame,
466
570
  resolveColor
467
571
  };
468
- //# sourceMappingURL=chunk-JTKR7ZWP.js.map
572
+ //# sourceMappingURL=chunk-P6RS2L23.js.map