@konce-pt/react 0.15.1 → 0.16.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.
@@ -10,12 +10,20 @@ export interface KptChartTooltipState {
10
10
  y: number;
11
11
  label: string;
12
12
  rows: KptChartTooltipRow[];
13
+ /** Kierunek ruchu do tego punktu: 1 w prawo, -1 w lewo, 0 bez kierunku - stąd wjeżdża etykieta. */
14
+ dir?: number;
15
+ /** Dymek już był na ekranie i jedzie dalej - etykieta animuje zmianę (pierwsze pokazanie stoi). */
16
+ live?: boolean;
13
17
  }
14
18
  export interface KptChartTooltipProps {
15
19
  state: KptChartTooltipState | null;
16
20
  }
17
21
  /**
18
- * Dymek wykresu - pozycjonowany procentowo względem kontenera, niezależnie od skali SVG, więc
19
- * nie trzeba przeliczać jednostek viewBoxa na piksele. Element wewnętrzny biblioteki.
22
+ * Dymek wykresu - pozycjonowany względem obszaru wykresu, niezależnie od skali SVG. Element
23
+ * wewnętrzny biblioteki, bliźniak `kpt-chart-tooltip` z portu Angulara (te same klasy i SCSS).
24
+ *
25
+ * Nie wychodzi poza obszar wykresu: przycięcie liczy się z faktycznej szerokości dymka. Przy
26
+ * chowaniu zostaje z ostatnią treścią i gaśnie (`--hidden`); cyfry wartości stoją na bębnach
27
+ * (`reelCells` z `@konce-pt/chart`), które przy kolejnym punkcie kręcą się do nowej liczby.
20
28
  */
21
29
  export declare function KptChartTooltip({ state }: Readonly<KptChartTooltipProps>): import("react").JSX.Element | null;
@@ -1,13 +1,49 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useLayoutEffect, useRef, useState } from 'react';
3
+ import { reelCells } from '@konce-pt/chart';
2
4
  import { cn } from "../internal/cn.js";
5
+ const DIGITS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
3
6
  /**
4
- * Dymek wykresu - pozycjonowany procentowo względem kontenera, niezależnie od skali SVG, więc
5
- * nie trzeba przeliczać jednostek viewBoxa na piksele. Element wewnętrzny biblioteki.
7
+ * Dymek wykresu - pozycjonowany względem obszaru wykresu, niezależnie od skali SVG. Element
8
+ * wewnętrzny biblioteki, bliźniak `kpt-chart-tooltip` z portu Angulara (te same klasy i SCSS).
9
+ *
10
+ * Nie wychodzi poza obszar wykresu: przycięcie liczy się z faktycznej szerokości dymka. Przy
11
+ * chowaniu zostaje z ostatnią treścią i gaśnie (`--hidden`); cyfry wartości stoją na bębnach
12
+ * (`reelCells` z `@konce-pt/chart`), które przy kolejnym punkcie kręcą się do nowej liczby.
6
13
  */
7
14
  export function KptChartTooltip({ state }) {
8
- if (!state)
15
+ // Ostatnia treść zostaje na czas wygaszania (wzorzec "poprzednia wartość w stanie", bez efektu).
16
+ const [shown, setShown] = useState(state);
17
+ if (state && state !== shown)
18
+ setShown(state);
19
+ const bubbleRef = useRef(null);
20
+ const labelRef = useRef(null);
21
+ const lastLabel = useRef('');
22
+ useLayoutEffect(() => {
23
+ const el = bubbleRef.current;
24
+ if (!shown || !el)
25
+ return;
26
+ // obszar wykresu (`.kpt-chart__canvas`, position: relative) - host dymka nie ma wymiarów
27
+ const box = el.offsetParent;
28
+ if (box) {
29
+ const width = box.clientWidth;
30
+ const half = el.offsetWidth / 2;
31
+ const x = (shown.x / 100) * width;
32
+ el.style.left = `${half * 2 >= width ? width / 2 : Math.min(width - half, Math.max(half, x))}px`;
33
+ }
34
+ // Nowa kategoria: etykieta wjeżdża z kierunku ruchu. Pierwsze pokazanie stoi. Nagłówek nigdy nie
35
+ // znika: start od 40% widoczności, a przy szybkim przejeździe trwający wjazd nie startuje od nowa
36
+ // (wtedy tylko podmienia się tekst) - inaczej przy każdej kategorii etykieta gasła.
37
+ const label = labelRef.current;
38
+ if (label && shown.label !== lastLabel.current) {
39
+ if (shown.live && lastLabel.current && typeof label.animate === 'function' && !label.getAnimations().length) {
40
+ const dir = shown.dir ?? 0;
41
+ label.animate({ opacity: [0.4, 1], filter: ['blur(1.5px)', 'blur(0px)'], translate: [`${dir * 6}px ${dir ? 0 : 4}px`, '0 0'] }, { duration: 220, easing: 'cubic-bezier(0.2, 0, 0, 1)' });
42
+ }
43
+ lastLabel.current = shown.label;
44
+ }
45
+ });
46
+ if (!shown)
9
47
  return null;
10
- // Margines 6% trzyma dymek w granicach kontenera przy skrajnych punktach.
11
- const left = Math.min(94, Math.max(6, state.x));
12
- return (_jsx("div", { className: "kpt-chart-tooltip-host", children: _jsxs("div", { className: cn('kpt-chart__tooltip', state.y < 22 && 'kpt-chart__tooltip--flip'), style: { left: `${left}%`, top: `${state.y}%` }, role: "tooltip", children: [state.label ? _jsx("div", { className: "kpt-chart__tooltip-title", children: state.label }) : null, state.rows.map((row, index) => (_jsxs("div", { className: "kpt-chart__tooltip-row", children: [_jsx("span", { className: "kpt-chart__tooltip-dot", style: { backgroundColor: row.color } }), _jsx("span", { className: "kpt-chart__tooltip-name", children: row.name }), _jsx("span", { className: "kpt-chart__tooltip-val", children: row.value })] }, index)))] }) }));
48
+ return (_jsx("div", { className: "kpt-chart-tooltip-host", children: _jsxs("div", { ref: bubbleRef, className: cn('kpt-chart__tooltip', shown.y < 22 && 'kpt-chart__tooltip--flip', !state && 'kpt-chart__tooltip--hidden'), style: { top: `${shown.y}%` }, role: "tooltip", "aria-hidden": state ? undefined : true, children: [shown.label ? (_jsx("div", { className: "kpt-chart__tooltip-title", children: _jsx("span", { ref: labelRef, className: "kpt-chart__tooltip-label", children: shown.label }) })) : null, shown.rows.map((row, index) => (_jsxs("div", { className: "kpt-chart__tooltip-row", children: [_jsx("span", { className: "kpt-chart__tooltip-dot", style: { backgroundColor: row.color } }), _jsx("span", { className: "kpt-chart__tooltip-name", children: row.name }), _jsxs("span", { className: "kpt-chart__tooltip-val", children: [_jsx("span", { className: "kpt-chart__sr", children: row.value }), _jsx("span", { className: "kpt-chart__reels", "aria-hidden": "true", children: reelCells(row.value).map((cell) => cell.kind === 'digit' ? (_jsx("span", { className: "kpt-chart__reel", children: _jsx("span", { className: "kpt-chart__reel-strip", style: { ['--kpt-chart-digit']: cell.digit }, children: DIGITS.map((d) => (_jsx("span", { children: d }, d))) }) }, cell.key)) : (_jsx("span", { className: "kpt-chart__reel-text", children: cell.text }, cell.key))) })] })] }, index)))] }) }));
13
49
  }
@@ -1,13 +1,39 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- import { useId, useState } from 'react';
4
- import { areaPath, bandScale, barLayout, chartAccessibleName, chartDataTable, extent, formatNumber, linePath, linearScale, niceScale, pieSlices, plotArea, polarSlices, polarToCartesian, polygonPath, radarGrid, radarPoints, radialArea, seriesColor, } from '@konce-pt/chart';
3
+ import { useEffect, useId, useRef, useState } from 'react';
4
+ import { KptChartMotion, areaPath, bandScale, barLayout, chartAccessibleName, chartDataTable, dataScale, deltaKind, domainScale, extent, formatNumber, holdDomain, linePath, linearScale, niceScale, pieSlices, plotArea, polarSlices, polarToCartesian, polygonPath, radarGrid, radarPoints, radialArea, sameChartShape, seriesColor, significantDelta, } from '@konce-pt/chart';
5
5
  import { cn } from "../internal/cn.js";
6
+ import { useKptMotion } from "../motion/motion-context.js";
6
7
  import { useKptMessages } from "../i18n/i18n-context.js";
7
8
  import { KptChartLegend } from "./chart-legend.js";
8
9
  import { KptChartTooltip } from "./chart-tooltip.js";
9
10
  const VERTICAL_INSETS = { top: 12, right: 16, bottom: 28, left: 46 };
10
11
  const HORIZONTAL_INSETS = { top: 12, right: 16, bottom: 28, left: 76 };
12
+ /** Szerokość pasma smugi światła (jednostki viewBoxa) - połowa po każdej stronie czoła fali. */
13
+ const SHEEN_HALF = 90;
14
+ /** Zjechanie z wykresu: dymek czeka chwilę, zanim zgaśnie (powrót kursora go zatrzymuje). */
15
+ const TIP_HIDE_DELAY = 60;
16
+ const EMPTY_FRAME = { values: null, domain: null, ghost: null, sheen: null, tip: null };
17
+ /** Indeks najbliższej pozycji (środka kategorii) do współrzędnej kursora. */
18
+ function nearestIndex(positions, at) {
19
+ let best = 0;
20
+ positions.forEach((p, i) => {
21
+ if (Math.abs(p - at) < Math.abs((positions[best] ?? p) - at))
22
+ best = i;
23
+ });
24
+ return best;
25
+ }
26
+ /** Różnica ze znakiem (plus albo minus z myślnikiem ASCII). */
27
+ function signed(delta) {
28
+ const text = formatNumber(Math.abs(Math.round(delta)));
29
+ return delta > 0 ? `+${text}` : `-${text}`;
30
+ }
31
+ /** Koniec słupka po stronie wartości (dla ujemnych - po drugiej stronie osi). */
32
+ function barEnd(bar, vertical) {
33
+ if (vertical)
34
+ return bar.value >= 0 ? bar.rect.y : bar.rect.y + bar.rect.height;
35
+ return bar.value >= 0 ? bar.rect.x + bar.rect.width : bar.rect.x;
36
+ }
11
37
  function styleFor(align) {
12
38
  if (align === 'center')
13
39
  return 'center';
@@ -32,7 +58,6 @@ export function KptChart({ config, width = 600, height = 360, locale, dictionary
32
58
  const dataTable = chartDataTable(config, activeLocale);
33
59
  const [hidden, setHidden] = useState(new Set());
34
60
  const [highlighted, setHighlighted] = useState(null);
35
- const [tooltip, setTooltip] = useState(null);
36
61
  /** Indeks kategorii pod kursorem - prowadnica crosshair w wykresie liniowym. */
37
62
  const [activeIndex, setActiveIndex] = useState(null);
38
63
  const anim = {
@@ -41,6 +66,70 @@ export function KptChart({ config, width = 600, height = 360, locale, dictionary
41
66
  delay: config.animation?.delay ?? 0,
42
67
  stagger: config.animation?.stagger ?? 60,
43
68
  };
69
+ // -- Ruch sterowany z kodu (pętla klatek w `@konce-pt/chart`, wspólna z portem Angulara) --
70
+ const motionConfig = useKptMotion();
71
+ const [frame, setFrame] = useState(EMPTY_FRAME);
72
+ const [motion] = useState(() => new KptChartMotion((f) => setFrame(f)));
73
+ const [tipContent, setTipContent] = useState(null);
74
+ const [ripple, setRipple] = useState(null);
75
+ const prevConfig = useRef(null);
76
+ const lastTipIndex = useRef(null);
77
+ const hideTimer = useRef(undefined);
78
+ const rippleId = useRef(0);
79
+ /** Ruch wyłączają: config, globalny `KptMotionProvider enabled={false}` i systemowe "ogranicz ruch". */
80
+ const motionAllowed = () => {
81
+ if (!anim.enabled || motionConfig?.enabled === false)
82
+ return false;
83
+ return typeof matchMedia === 'undefined' || !matchMedia('(prefers-reduced-motion: reduce)').matches;
84
+ };
85
+ const tipMotion = () => motionAllowed() && config.tooltip?.motion !== false;
86
+ useEffect(() => () => motion.stop(), [motion]);
87
+ useEffect(() => () => clearTimeout(hideTimer.current), []);
88
+ // Nowe wartości w tym samym wykresie -> przejście; inny wykres -> zwykłe wejście z CSS.
89
+ useEffect(() => {
90
+ const prev = prevConfig.current;
91
+ prevConfig.current = config;
92
+ if (!prev || !sameChartShape(prev, config)) {
93
+ motion.reset();
94
+ return;
95
+ }
96
+ if (prev.series.every((s, i) => s.data.every((v, k) => v === config.series[i]?.data[k])))
97
+ return;
98
+ const allowed = (config.animation?.enabled ?? true) &&
99
+ motionConfig?.enabled !== false &&
100
+ (typeof matchMedia === 'undefined' || !matchMedia('(prefers-reduced-motion: reduce)').matches);
101
+ if (!allowed || config.animation?.update === false) {
102
+ motion.cancelData();
103
+ return;
104
+ }
105
+ const from = motion.current(prev.series.map((s) => s.data));
106
+ const to = config.series.map((s) => [...s.data]);
107
+ const withAxis = config.type === 'line' || config.type === 'bar';
108
+ const visible = (rows) => rows.filter((_, i) => !hidden.has(`ser:${config.series[i]?.name ?? ''}`));
109
+ const scales = withAxis
110
+ ? { start: dataScale(visible(from)), hold: dataScale([holdDomain(visible(from), visible(to))]), end: dataScale(visible(to)) }
111
+ : null;
112
+ motion.transition(from, to, { delta: withAxis && config.animation?.delta !== false, scales });
113
+ }, [config, hidden, motion, motionConfig]);
114
+ /** Config z wartościami z bieżącej klatki ruchu - z niego liczy się geometria. */
115
+ const view = frame.values
116
+ ? { ...config, series: config.series.map((s, i) => ({ ...s, data: frame.values?.[i] ?? s.data })) }
117
+ : config;
118
+ const tooltip = tipContent && frame.tip ? { x: (frame.tip.x / width) * 100, y: (frame.tip.y / height) * 100, ...tipContent } : null;
119
+ const ghostState = frame.ghost;
120
+ const sheenId = `${descId}-sheen`;
121
+ const horizontal = config.type === 'bar' && config.orientation === 'horizontal';
122
+ const sheen = (() => {
123
+ if (!frame.sheen)
124
+ return null;
125
+ const plot = plotArea(width, height, horizontal ? HORIZONTAL_INSETS : VERTICAL_INSETS);
126
+ const start = horizontal ? plot.y : plot.x;
127
+ const length = horizontal ? plot.height : plot.width;
128
+ const mid = start - SHEEN_HALF + (length + 2 * SHEEN_HALF) * frame.sheen.progress;
129
+ return horizontal
130
+ ? { x1: 0, x2: 0, y1: mid - SHEEN_HALF, y2: mid + SHEEN_HALF, opacity: frame.sheen.opacity }
131
+ : { x1: mid - SHEEN_HALF, x2: mid + SHEEN_HALF, y1: 0, y2: 0, opacity: frame.sheen.opacity };
132
+ })();
44
133
  const legendOpts = {
45
134
  display: config.legend?.display ?? true,
46
135
  position: config.legend?.position ?? 'bottom',
@@ -63,14 +152,45 @@ export function KptChart({ config, width = 600, height = 360, locale, dictionary
63
152
  hidden: hidden.has(`ser:${s.name}`),
64
153
  }));
65
154
  const dim = (key) => highlighted !== null && highlighted !== key;
66
- const showTip = (tipX, tipY, name, label, value, color) => {
155
+ const row = (name, label, value, color) => {
156
+ const format = config.tooltip?.format;
157
+ return { name, value: format ? format({ series: name, label, value }) : formatNumber(value), color };
158
+ };
159
+ const openTip = (x, y, label, rows, index) => {
67
160
  if (config.tooltip?.enabled === false)
68
161
  return;
69
- const format = config.tooltip?.format;
70
- const text = format ? format({ series: name, label, value }) : formatNumber(value);
71
- setTooltip({ x: (tipX / width) * 100, y: (tipY / height) * 100, label, rows: [{ name, value: text, color }] });
162
+ clearTimeout(hideTimer.current);
163
+ const first = tipContent === null;
164
+ const animate = tipMotion();
165
+ const dir = first || index === null || lastTipIndex.current === null ? 0 : Math.sign(index - lastTipIndex.current);
166
+ lastTipIndex.current = index;
167
+ setTipContent({ label, rows, dir, live: !first && animate });
168
+ motion.moveTip(x, y, first || !animate);
169
+ };
170
+ /** Wycinki i radar: dymek jednego elementu pod kursorem. */
171
+ const showTip = (tipX, tipY, name, label, value, color) => openTip(tipX, tipY, label, [row(name, label, value, color)], null);
172
+ const hideTip = () => {
173
+ clearTimeout(hideTimer.current);
174
+ hideTimer.current = setTimeout(() => {
175
+ setTipContent(null);
176
+ setActiveIndex(null);
177
+ setRipple(null);
178
+ lastTipIndex.current = null;
179
+ motion.hideTip();
180
+ }, TIP_HIDE_DELAY);
181
+ };
182
+ /** Ten sam punkt pod kursorem - dymek zostaje (i nie zgaśnie, jeśli właśnie miał). */
183
+ const keepTip = (idx) => {
184
+ if (tipContent === null || lastTipIndex.current !== idx)
185
+ return false;
186
+ clearTimeout(hideTimer.current);
187
+ return true;
188
+ };
189
+ /** Kursor w viewBoxie wykresu (SVG skaluje się do kontenera). */
190
+ const svgPoint = (event) => {
191
+ const matrix = event.currentTarget.ownerSVGElement?.getScreenCTM();
192
+ return matrix ? new DOMPoint(event.clientX, event.clientY).matrixTransform(matrix.inverse()) : null;
72
193
  };
73
- const hideTip = () => setTooltip(null);
74
194
  const toggle = (key) => {
75
195
  const next = new Set(hidden);
76
196
  if (next.has(key))
@@ -81,19 +201,21 @@ export function KptChart({ config, width = 600, height = 360, locale, dictionary
81
201
  };
82
202
  // -- Wykres liniowy --
83
203
  const lineVm = (() => {
84
- if (config.type !== 'line')
204
+ if (view.type !== 'line')
85
205
  return null;
86
- const active = config.series.map((s, i) => ({ s, i })).filter(({ s }) => !hidden.has(`ser:${s.name}`));
206
+ const active = view.series.map((s, i) => ({ s, i })).filter(({ s }) => !hidden.has(`ser:${s.name}`));
87
207
  const values = active.flatMap(({ s }) => s.data);
88
208
  const [mn, mx] = extent(values.length ? values : [0]);
89
- const scale = niceScale(Math.min(0, mn), Math.max(0, mx), 5);
209
+ const scale = frame.domain
210
+ ? domainScale(frame.domain.target, frame.domain.min, frame.domain.max)
211
+ : niceScale(Math.min(0, mn), Math.max(0, mx), 5);
90
212
  const plot = plotArea(width, height, VERTICAL_INSETS);
91
213
  const yScale = linearScale(scale.min, scale.max, plot.y + plot.height, plot.y);
92
- const count = config.labels.length;
214
+ const count = view.labels.length;
93
215
  const xAt = (i) => count > 1 ? plot.x + (i / (count - 1)) * plot.width : plot.x + plot.width / 2;
94
216
  const baseline = yScale(0);
95
- const interpolation = config.interpolation ?? 'linear';
96
- const showPoints = config.points ?? true;
217
+ const interpolation = view.interpolation ?? 'linear';
218
+ const showPoints = view.points ?? true;
97
219
  const grid = scale.ticks.map((t) => ({
98
220
  x1: plot.x,
99
221
  y1: yScale(t),
@@ -106,49 +228,89 @@ export function KptChart({ config, width = 600, height = 360, locale, dictionary
106
228
  text: formatNumber(t),
107
229
  anchor: 'end',
108
230
  }));
109
- const catLabels = config.labels.map((label, i) => ({
231
+ const catLabels = view.labels.map((label, i) => ({
110
232
  x: xAt(i),
111
233
  y: plot.y + plot.height + 18,
112
234
  text: label,
113
235
  anchor: 'middle',
114
236
  }));
237
+ const series = active.map(({ s, i }) => ({
238
+ key: `ser:${s.name}`,
239
+ name: s.name,
240
+ color: seriesColor(s.color, i),
241
+ i,
242
+ points: s.data.map((v, idx) => ({ x: xAt(idx), y: yScale(v), value: v, label: view.labels[idx] ?? '' })),
243
+ }));
115
244
  const crosshairCat = config.crosshair && activeIndex !== null ? catLabels[activeIndex] : undefined;
245
+ // Prowadnica i pigułka jadą razem z dymkiem.
246
+ const crossX = crosshairCat ? (frame.tip?.x ?? crosshairCat.x) : 0;
116
247
  const pillWidth = crosshairCat ? Math.max(28, crosshairCat.text.length * 7 + 16) : 0;
117
- return (_jsxs(_Fragment, { children: [_jsx("g", { className: "kpt-chart__grid", children: grid.map((g, i) => (_jsx("line", { className: "kpt-chart__gridline", x1: g.x1, y1: g.y1, x2: g.x2, y2: g.y2 }, i))) }), crosshairCat ? (_jsx("line", { className: "kpt-chart__crosshair", x1: crosshairCat.x, y1: plot.y, x2: crosshairCat.x, y2: plot.y + plot.height })) : null, _jsx("g", { className: "kpt-chart__axis", children: [...valueLabels, ...catLabels].map((l, i) => (_jsx("text", { x: l.x, y: l.y, textAnchor: l.anchor, children: l.text }, i))) }), crosshairCat ? (_jsxs("g", { className: "kpt-chart__cat-active", children: [_jsx("rect", { x: crosshairCat.x - pillWidth / 2, y: crosshairCat.y - 13, width: pillWidth, height: 18, rx: 9 }), _jsx("text", { x: crosshairCat.x, y: crosshairCat.y, textAnchor: "middle", children: crosshairCat.text })] })) : null, active.map(({ s, i }) => {
118
- const key = `ser:${s.name}`;
119
- const color = seriesColor(s.color, i);
120
- const points = s.data.map((v, idx) => ({
121
- x: xAt(idx),
122
- y: yScale(v),
123
- value: v,
124
- label: config.labels[idx] ?? '',
125
- }));
126
- return (_jsxs("g", { className: cn('kpt-chart__series', dim(key) && 'kpt-chart__series--dim'), children: [config.area ? (_jsx("path", { className: "kpt-chart__area", d: areaPath(points, baseline, interpolation), style: { fill: color } })) : null, _jsx("path", { className: "kpt-chart__line", d: linePath(points, interpolation), style: { stroke: color }, pathLength: 1 }), points.map((p, idx) => (_jsxs("g", { children: [showPoints ? (_jsx("circle", { className: cn('kpt-chart__point', activeIndex === idx && 'kpt-chart__point--active'), cx: p.x, cy: p.y, r: 4, style: { fill: color, ['--kpt-chart-i']: idx } })) : null, _jsx("circle", { className: "kpt-chart__hit", cx: p.x, cy: p.y, r: 12, onPointerEnter: () => {
127
- setActiveIndex(idx);
128
- showTip(p.x, p.y, s.name, p.label, p.value, color);
129
- }, onPointerLeave: () => {
130
- setActiveIndex(null);
131
- hideTip();
132
- } })] }, idx)))] }, key));
133
- })] }));
248
+ /** Najbliższa kategoria w całym obszarze wykresu: wiersze wszystkich serii, dymek nad najwyższym punktem. */
249
+ const onMove = (event) => {
250
+ const p = svgPoint(event);
251
+ if (!p || !series.length)
252
+ return;
253
+ const idx = nearestIndex(catLabels.map((l) => l.x), p.x);
254
+ if (keepTip(idx))
255
+ return;
256
+ const hits = series.map((s) => ({ s, pt: s.points[idx] })).filter((e) => !!e.pt);
257
+ const top = hits.reduce((a, b) => (b.pt.y < a.pt.y ? b : a), hits[0]);
258
+ if (!top)
259
+ return;
260
+ const label = config.labels[idx] ?? '';
261
+ const moved = tipContent !== null && lastTipIndex.current !== null;
262
+ setActiveIndex(idx);
263
+ openTip(top.pt.x, top.pt.y, label, hits.map((e) => row(e.s.name, label, e.pt.value, e.s.color)), idx);
264
+ if (moved && tipMotion())
265
+ setRipple({ id: ++rippleId.current, x: top.pt.x, y: top.pt.y, color: top.s.color });
266
+ };
267
+ return (_jsxs(_Fragment, { children: [_jsx("g", { className: "kpt-chart__grid", children: grid.map((g, i) => (_jsx("line", { className: "kpt-chart__gridline", x1: g.x1, y1: g.y1, x2: g.x2, y2: g.y2 }, i))) }), crosshairCat ? (_jsx("line", { className: "kpt-chart__crosshair", x1: crossX, y1: plot.y, x2: crossX, y2: plot.y + plot.height })) : null, _jsx("g", { className: "kpt-chart__axis", children: [...valueLabels, ...catLabels].map((l, i) => (_jsx("text", { x: l.x, y: l.y, textAnchor: l.anchor, children: l.text }, i))) }), crosshairCat ? (_jsxs("g", { className: "kpt-chart__cat-active", children: [_jsx("rect", { x: crossX - pillWidth / 2, y: crosshairCat.y - 13, width: pillWidth, height: 18, rx: 9 }), _jsx("text", { x: crossX, y: crosshairCat.y, textAnchor: "middle", children: crosshairCat.text })] })) : null, ghostState
268
+ ? series.map((s) => {
269
+ const prev = ghostState.prev[s.i] ?? [];
270
+ const next = ghostState.next[s.i] ?? [];
271
+ const gpts = prev.map((v, idx) => ({ x: xAt(idx), y: yScale(v) }));
272
+ return (_jsxs("g", { className: "kpt-chart__ghost", "aria-hidden": "true", children: [_jsx("path", { className: "kpt-chart__ghost-line", d: linePath(gpts, interpolation) }), gpts.map((p, idx) => (_jsx("line", { className: "kpt-chart__delta", "data-kind": deltaKind(prev[idx] ?? 0, next[idx] ?? 0), x1: p.x, x2: p.x, y1: p.y, y2: s.points[idx]?.y ?? p.y }, idx)))] }, `${ghostState.id}:${s.key}`));
273
+ })
274
+ : null, series.map((s) => (_jsxs("g", { className: cn('kpt-chart__series', dim(s.key) && 'kpt-chart__series--dim'), children: [view.area ? (_jsx("path", { className: "kpt-chart__area", d: areaPath(s.points, baseline, interpolation), style: { fill: s.color } })) : null, _jsx("path", { className: "kpt-chart__line", d: linePath(s.points, interpolation), style: { stroke: s.color }, pathLength: 1 }), showPoints
275
+ ? s.points.map((p, idx) => (_jsx("circle", { className: cn('kpt-chart__point', activeIndex === idx && 'kpt-chart__point--active'), cx: p.x, cy: p.y, r: 4, style: { fill: s.color, ['--kpt-chart-i']: idx } }, idx)))
276
+ : null] }, s.key))), sheen
277
+ ? series.map((s) => (_jsx("path", { className: "kpt-chart__sheen-line", d: linePath(s.points, interpolation), stroke: `url(#${sheenId})`, style: { opacity: sheen.opacity }, "aria-hidden": "true" }, s.key)))
278
+ : null, ghostState
279
+ ? series.flatMap((s) => {
280
+ const prev = ghostState.prev[s.i] ?? [];
281
+ const next = ghostState.next[s.i] ?? [];
282
+ return next.flatMap((v, idx) => {
283
+ const before = prev[idx] ?? 0;
284
+ if (!significantDelta(before, v))
285
+ return [];
286
+ const kind = deltaKind(before, v);
287
+ const y = s.points[idx]?.y ?? 0;
288
+ return [
289
+ _jsx("text", { className: "kpt-chart__chip", "data-kind": kind, x: xAt(idx), y: kind === 'up' ? y - 12 : y + 20, textAnchor: "middle", style: { ['--kpt-chart-i']: idx }, "aria-hidden": "true", children: signed(v - before) }, `${ghostState.id}:${s.key}:${idx}`),
290
+ ];
291
+ });
292
+ })
293
+ : null, ripple ? (_jsx("circle", { className: "kpt-chart__ripple", cx: ripple.x, cy: ripple.y, r: 5, style: { stroke: ripple.color }, "aria-hidden": "true" }, ripple.id)) : null, _jsx("rect", { className: "kpt-chart__hover", x: plot.x - 12, y: plot.y - 12, width: plot.width + 24, height: plot.height + 24, onPointerMove: onMove, onPointerLeave: hideTip })] }));
134
294
  })();
135
295
  // -- Wykres słupkowy --
136
296
  const barVm = (() => {
137
- if (config.type !== 'bar')
297
+ if (view.type !== 'bar')
138
298
  return null;
139
- const orientation = config.orientation ?? 'vertical';
140
- const active = config.series.map((s, i) => ({ s, i })).filter(({ s }) => !hidden.has(`ser:${s.name}`));
141
- const plot = plotArea(width, height, orientation === 'vertical' ? VERTICAL_INSETS : HORIZONTAL_INSETS);
142
- const layout = barLayout(active.map(({ s }) => s.data), config.labels.length, plot, orientation, config.borderRadius ?? 4);
299
+ const orientation = view.orientation ?? 'vertical';
300
+ const vertical = orientation === 'vertical';
301
+ const active = view.series.map((s, i) => ({ s, i })).filter(({ s }) => !hidden.has(`ser:${s.name}`));
302
+ const plot = plotArea(width, height, vertical ? VERTICAL_INSETS : HORIZONTAL_INSETS);
303
+ const radius = view.borderRadius ?? 4;
304
+ const layout = barLayout(active.map(({ s }) => s.data), view.labels.length, plot, orientation, radius, frame.domain ? domainScale(frame.domain.target, frame.domain.min, frame.domain.max) : undefined);
143
305
  const colors = active.map(({ s, i }) => seriesColor(s.color, i));
144
306
  const names = active.map(({ s }) => s.name);
145
- const cat = orientation === 'vertical'
146
- ? bandScale(config.labels.length, plot.x, plot.x + plot.width, 0.2)
147
- : bandScale(config.labels.length, plot.y, plot.y + plot.height, 0.2);
148
- const grid = orientation === 'vertical'
307
+ const cat = vertical
308
+ ? bandScale(view.labels.length, plot.x, plot.x + plot.width, 0.2)
309
+ : bandScale(view.labels.length, plot.y, plot.y + plot.height, 0.2);
310
+ const grid = vertical
149
311
  ? layout.ticks.map((t) => ({ x1: plot.x, y1: t.pos, x2: plot.x + plot.width, y2: t.pos }))
150
312
  : layout.ticks.map((t) => ({ x1: t.pos, y1: plot.y, x2: t.pos, y2: plot.y + plot.height }));
151
- const valueLabels = orientation === 'vertical'
313
+ const valueLabels = vertical
152
314
  ? layout.ticks.map((t) => ({ x: plot.x - 8, y: t.pos + 3, text: formatNumber(t.value), anchor: 'end' }))
153
315
  : layout.ticks.map((t) => ({
154
316
  x: t.pos,
@@ -156,22 +318,66 @@ export function KptChart({ config, width = 600, height = 360, locale, dictionary
156
318
  text: formatNumber(t.value),
157
319
  anchor: 'middle',
158
320
  }));
159
- const catLabels = orientation === 'vertical'
160
- ? config.labels.map((l, i) => ({ x: cat.center(i), y: plot.y + plot.height + 18, text: l, anchor: 'middle' }))
161
- : config.labels.map((l, i) => ({ x: plot.x - 8, y: cat.center(i) + 3, text: l, anchor: 'end' }));
162
- return (_jsxs(_Fragment, { children: [_jsx("g", { className: "kpt-chart__grid", children: grid.map((g, i) => (_jsx("line", { className: "kpt-chart__gridline", x1: g.x1, y1: g.y1, x2: g.x2, y2: g.y2 }, i))) }), _jsx("g", { className: "kpt-chart__axis", children: [...valueLabels, ...catLabels].map((l, i) => (_jsx("text", { x: l.x, y: l.y, textAnchor: l.anchor, children: l.text }, i))) }), _jsx("g", { className: "kpt-chart__bars", "data-orientation": orientation, children: layout.bars.map((b, order) => {
163
- const key = `ser:${names[b.seriesIndex]}`;
164
- const color = colors[b.seriesIndex];
165
- const tipX = orientation === 'vertical' ? b.rect.x + b.rect.width / 2 : b.rect.x + b.rect.width;
166
- const tipY = orientation === 'vertical' ? b.rect.y : b.rect.y + b.rect.height / 2;
167
- return (_jsx("path", { className: cn('kpt-chart__bar', dim(key) && 'kpt-chart__series--dim'), d: b.path, style: { fill: color, ['--kpt-chart-i']: order }, onPointerEnter: () => showTip(tipX, tipY, names[b.seriesIndex], config.labels[b.categoryIndex] ?? '', b.value, color), onPointerLeave: hideTip }, `${key}#${order}`));
168
- }) })] }));
321
+ const catLabels = vertical
322
+ ? view.labels.map((l, i) => ({ x: cat.center(i), y: plot.y + plot.height + 18, text: l, anchor: 'middle' }))
323
+ : view.labels.map((l, i) => ({ x: plot.x - 8, y: cat.center(i) + 3, text: l, anchor: 'end' }));
324
+ const bars = layout.bars.map((b, order) => ({
325
+ b,
326
+ order,
327
+ key: `ser:${names[b.seriesIndex]}`,
328
+ color: colors[b.seriesIndex] ?? '',
329
+ name: names[b.seriesIndex] ?? '',
330
+ tipX: vertical ? b.rect.x + b.rect.width / 2 : b.rect.x + b.rect.width,
331
+ tipY: vertical ? b.rect.y : b.rect.y + b.rect.height / 2,
332
+ }));
333
+ // Słupki-duchy w tej samej skali i odcinki zmiany od końca ducha do końca bieżącego słupka.
334
+ const ghostBars = ghostState
335
+ ? (() => {
336
+ const prevRows = active.map(({ i }) => ghostState.prev[i] ?? []);
337
+ const nextRows = active.map(({ i }) => ghostState.next[i] ?? []);
338
+ return barLayout(prevRows, view.labels.length, plot, orientation, radius, layout.scale).bars.map((g, k) => {
339
+ const cur = layout.bars[k] ?? g;
340
+ const a = barEnd(g, vertical);
341
+ const z = barEnd(cur, vertical);
342
+ const lo = Math.min(a, z);
343
+ const hi = Math.max(a, z);
344
+ return {
345
+ key: `${ghostState.id}:${k}`,
346
+ path: g.path,
347
+ kind: deltaKind(prevRows[g.seriesIndex]?.[g.categoryIndex] ?? 0, nextRows[g.seriesIndex]?.[g.categoryIndex] ?? 0),
348
+ delta: vertical
349
+ ? { x: cur.rect.x, y: lo, width: cur.rect.width, height: hi - lo }
350
+ : { x: lo, y: cur.rect.y, width: hi - lo, height: cur.rect.height },
351
+ };
352
+ });
353
+ })()
354
+ : [];
355
+ /** Najbliższa kategoria w całym obszarze wykresu: wiersze wszystkich serii tej kategorii. */
356
+ const onMove = (event) => {
357
+ const p = svgPoint(event);
358
+ if (!p)
359
+ return;
360
+ const idx = nearestIndex(catLabels.map((l) => (vertical ? l.x : l.y - 3)), vertical ? p.x : p.y);
361
+ if (keepTip(idx))
362
+ return;
363
+ const group = bars.filter((e) => e.b.categoryIndex === idx);
364
+ const center = catLabels[idx];
365
+ if (!group.length || !center)
366
+ return;
367
+ const label = config.labels[idx] ?? '';
368
+ const x = vertical ? center.x : Math.max(...group.map((e) => e.tipX));
369
+ const y = vertical ? Math.min(...group.map((e) => e.tipY)) : center.y - 3;
370
+ openTip(x, y, label, group.map((e) => row(e.name, label, e.b.value, e.color)), idx);
371
+ };
372
+ return (_jsxs(_Fragment, { children: [_jsx("g", { className: "kpt-chart__grid", children: grid.map((g, i) => (_jsx("line", { className: "kpt-chart__gridline", x1: g.x1, y1: g.y1, x2: g.x2, y2: g.y2 }, i))) }), _jsx("g", { className: "kpt-chart__axis", children: [...valueLabels, ...catLabels].map((l, i) => (_jsx("text", { x: l.x, y: l.y, textAnchor: l.anchor, children: l.text }, i))) }), ghostBars.length ? (_jsx("g", { className: "kpt-chart__ghost", "aria-hidden": "true", children: ghostBars.map((g) => (_jsx("path", { className: "kpt-chart__ghost-bar", d: g.path }, g.key))) })) : null, _jsx("g", { className: "kpt-chart__bars", "data-orientation": orientation, children: bars.map((e) => (_jsx("path", { className: cn('kpt-chart__bar', dim(e.key) && 'kpt-chart__series--dim'), d: e.b.path, style: { fill: e.color, ['--kpt-chart-i']: e.order } }, `${e.key}#${e.order}`))) }), ghostBars.length ? (_jsx("g", { className: "kpt-chart__ghost", "aria-hidden": "true", children: ghostBars.map((g) => (_jsx("rect", { className: "kpt-chart__delta-bar", "data-kind": g.kind, rx: 2, ...g.delta }, g.key))) })) : null, sheen
373
+ ? bars.map((e) => (_jsx("path", { className: "kpt-chart__sheen-bar", d: e.b.path, fill: `url(#${sheenId})`, style: { opacity: sheen.opacity * 0.7 }, "aria-hidden": "true" }, `${e.key}#${e.order}`)))
374
+ : null, _jsx("rect", { className: "kpt-chart__hover", x: plot.x, y: plot.y, width: plot.width, height: plot.height, onPointerMove: onMove, onPointerLeave: hideTip })] }));
169
375
  })();
170
376
  // -- Pie / doughnut / polar area --
171
377
  const pieVm = (() => {
172
378
  if (!isPieLike)
173
379
  return null;
174
- const first = config.series[0];
380
+ const first = view.series[0];
175
381
  if (!first)
176
382
  return null;
177
383
  const { cx, cy, radius } = radialArea(width, height, 8);
@@ -198,7 +404,7 @@ export function KptChart({ config, width = 600, height = 360, locale, dictionary
198
404
  return null;
199
405
  const { cx, cy, radius } = radialArea(width, height, 30);
200
406
  const grid = radarGrid(config.labels.length, cx, cy, radius, 4);
201
- const active = config.series.map((s, i) => ({ s, i })).filter(({ s }) => !hidden.has(`ser:${s.name}`));
407
+ const active = view.series.map((s, i) => ({ s, i })).filter(({ s }) => !hidden.has(`ser:${s.name}`));
202
408
  let max = 1;
203
409
  for (const { s } of active)
204
410
  for (const v of s.data)
@@ -235,5 +441,5 @@ export function KptChart({ config, width = 600, height = 360, locale, dictionary
235
441
  fontWeight: config.subtitle.font?.weight,
236
442
  paddingTop: config.subtitle.padding?.top,
237
443
  paddingBottom: config.subtitle.padding?.bottom,
238
- }, children: config.subtitle.text })) : null, _jsxs("div", { className: "kpt-chart__body", children: [_jsxs("div", { className: "kpt-chart__canvas", children: [_jsxs("svg", { className: "kpt-chart__svg", viewBox: `0 0 ${width} ${height}`, role: "img", "aria-label": accessibleName, "aria-describedby": config.description ? descId : undefined, preserveAspectRatio: "xMidYMid meet", children: [lineVm, barVm, radarVm, pieVm] }), config.description ? (_jsx("p", { className: "kpt-chart__sr", id: descId, children: config.description })) : null, _jsxs("table", { className: "kpt-chart__sr", children: [_jsxs("caption", { children: [t('chart.dataTable'), ": ", accessibleName] }), _jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { scope: "col", children: t('chart.category') }), dataTable.columns.map((col, i) => (_jsx("th", { scope: "col", children: col }, i)))] }) }), _jsx("tbody", { children: dataTable.rows.map((row, i) => (_jsxs("tr", { children: [_jsx("th", { scope: "row", children: row.label }), row.values.map((value, j) => (_jsx("td", { children: value }, j)))] }, i))) })] }), _jsx(KptChartTooltip, { state: tooltip })] }), legendOpts.display ? (_jsx(KptChartLegend, { items: legendItems, position: legendOpts.position, align: legendOpts.align, pointStyle: legendOpts.pointStyle, title: legendOpts.title, onToggle: toggle, onHighlight: setHighlighted })) : null] })] }) }));
444
+ }, children: config.subtitle.text })) : null, _jsxs("div", { className: "kpt-chart__body", children: [_jsxs("div", { className: "kpt-chart__canvas", children: [_jsxs("svg", { className: "kpt-chart__svg", viewBox: `0 0 ${width} ${height}`, role: "img", "aria-label": accessibleName, "aria-describedby": config.description ? descId : undefined, preserveAspectRatio: "xMidYMid meet", children: [sheen ? (_jsx("defs", { children: _jsxs("linearGradient", { id: sheenId, gradientUnits: "userSpaceOnUse", x1: sheen.x1, x2: sheen.x2, y1: sheen.y1, y2: sheen.y2, children: [_jsx("stop", { offset: "0", className: "kpt-chart__sheen-stop", stopOpacity: 0 }), _jsx("stop", { offset: "0.5", className: "kpt-chart__sheen-stop", stopOpacity: 0.95 }), _jsx("stop", { offset: "1", className: "kpt-chart__sheen-stop", stopOpacity: 0 })] }) })) : null, lineVm, barVm, radarVm, pieVm] }), config.description ? (_jsx("p", { className: "kpt-chart__sr", id: descId, children: config.description })) : null, _jsxs("table", { className: "kpt-chart__sr", children: [_jsxs("caption", { children: [t('chart.dataTable'), ": ", accessibleName] }), _jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { scope: "col", children: t('chart.category') }), dataTable.columns.map((col, i) => (_jsx("th", { scope: "col", children: col }, i)))] }) }), _jsx("tbody", { children: dataTable.rows.map((row, i) => (_jsxs("tr", { children: [_jsx("th", { scope: "row", children: row.label }), row.values.map((value, j) => (_jsx("td", { children: value }, j)))] }, i))) })] }), _jsx(KptChartTooltip, { state: tooltip })] }), legendOpts.display ? (_jsx(KptChartLegend, { items: legendItems, position: legendOpts.position, align: legendOpts.align, pointStyle: legendOpts.pointStyle, title: legendOpts.title, onToggle: toggle, onHighlight: setHighlighted })) : null] })] }) }));
239
445
  }
@@ -27,13 +27,27 @@ Scales, paths, bar layout and arcs come from `@konce-pt/chart` - pure functions
27
27
  coverage, shared with the Angular port. The component only turns them into SVG elements; there is
28
28
  no DOM manipulation and no canvas.
29
29
 
30
- ## Points have a bigger hit target than they look
31
- A 4px point is not something a pointer can reliably hit, so each one is paired with an invisible
32
- 12px circle that carries the events. The visible point only reacts to the state.
30
+ ## Hover covers the whole plot
31
+ Line and bar charts listen on an invisible surface over the plot: the pointer anywhere picks the nearest
32
+ category, the tooltip lists every visible series there and travels from category to category without
33
+ fading out in between. Slices and radar points react to the element itself. The bubble never leaves the
34
+ chart - it is clamped by its real width.
35
+
36
+ ## Motion: data updates and the tooltip
37
+ A new `config` with the same type, categories and series names is a data update: the values travel as a
38
+ wave from left to right on springs, with a light sheen at the wave front. Line and bar charts keep the
39
+ previous state for 1.7 s - a dashed ghost, up/down segments (`--kpt-chart-delta-up` / `--kpt-chart-delta-down`)
40
+ and the difference above points that changed by 2% or more - and the value axis waits for the ghost to
41
+ fade before zooming to the new data. The tooltip, the crosshair and the category pill travel between
42
+ categories, the digits roll on reels and the category name slides in from the direction of travel.
43
+ `animation.update: false`, `animation.delta: false` and `tooltip.motion: false` switch the parts off;
44
+ `animation.enabled: false`, `<KptMotionProvider enabled={false}>` and the system "reduce motion" setting
45
+ switch off all code-driven motion. The frame loop and its numbers live in `@konce-pt/chart`
46
+ (`KptChartMotion`, `springStep`, `reelCells`), shared with the Angular port.
33
47
 
34
48
  ## Interaction is local state
35
- Hidden series, the highlighted one, the tooltip and the active category are `useState` - the config
36
- stays a plain object the caller owns, and nothing is written back into it.
49
+ Hidden series, the highlighted one, the tooltip, the active category and the motion frame are
50
+ `useState` - the config stays a plain object the caller owns, and nothing is written back into it.
37
51
 
38
52
  ## Examples
39
53
  <KptChart config={{ type: 'line', labels: ['Jan', 'Feb'], series: [{ name: 'Sales', data: [12, 19] }] }} />
@@ -49,7 +63,8 @@ stays a plain object the caller owns, and nothing is written back into it.
49
63
  />
50
64
 
51
65
  ## Tokens
52
- `--kpt-chart-{duration,stagger,delay}` for the animation; series colours come from
66
+ `--kpt-chart-{duration,stagger,delay}` for the animation; `--kpt-chart-sheen`,
67
+ `--kpt-chart-delta-up` and `--kpt-chart-delta-down` for the data update; series colours come from
53
68
  `KPT_CHART_PALETTE` or `series.color`.
54
69
 
55
70
  ## Accessibility
@@ -16,6 +16,25 @@ import { useKptOverlayContainer } from "./overlay-container.js";
16
16
  const overlayStack = [];
17
17
  /** Ta sama warstwa co `--kpt-z-overlay` w tokenach - panel ma być nad treścią aplikacji. */
18
18
  const Z_INDEX = 'var(--kpt-z-overlay, 1100)';
19
+ /** Odstęp panelu od krawędzi okna (px) - jak `OVERLAY_VIEWPORT_MARGIN` w porcie Angulara. */
20
+ const VIEWPORT_MARGIN = 8;
21
+ /**
22
+ * Lewa krawędź panelu: wyrównanie z `align`, a gdy panel wyszedłby poza okno - druga krawędź
23
+ * wyzwalacza (dzwonek w prawym rogu paska otwiera się w lewo). Gdy nie mieści się żadna,
24
+ * panel jest dosuwany do okna z marginesem - jak `cdkConnectedOverlayPush` w porcie Angulara.
25
+ */
26
+ function panelLeft(rect, panelWidth, align) {
27
+ const viewport = document.documentElement.clientWidth;
28
+ const fits = (left) => left >= VIEWPORT_MARGIN && left + panelWidth <= viewport - VIEWPORT_MARGIN;
29
+ const start = rect.left;
30
+ const end = rect.right - panelWidth;
31
+ const [preferred, other] = align === 'end' ? [end, start] : [start, end];
32
+ if (panelWidth === 0 || fits(preferred))
33
+ return preferred;
34
+ if (fits(other))
35
+ return other;
36
+ return Math.max(VIEWPORT_MARGIN, Math.min(preferred, viewport - panelWidth - VIEWPORT_MARGIN));
37
+ }
19
38
  export function KptOverlay({ open, anchor, onClose, matchAnchorWidth = false, minAnchorWidth = false, offset = 4, align = 'start', withBackdrop = true, preferAbove = false, children, }) {
20
39
  const container = useKptOverlayContainer();
21
40
  const panelRef = useRef(null);
@@ -34,7 +53,7 @@ export function KptOverlay({ open, anchor, onClose, matchAnchorWidth = false, mi
34
53
  const flip = preferAbove ? fitsAbove : panelHeight > 0 && below < panelHeight + offset && rect.top > below;
35
54
  setPosition({
36
55
  top: flip ? rect.top - panelHeight - offset : rect.bottom + offset,
37
- left: align === 'end' ? rect.right - panelWidth : rect.left,
56
+ left: panelLeft(rect, matchAnchorWidth ? rect.width : panelWidth, align),
38
57
  ...(matchAnchorWidth ? { width: rect.width } : {}),
39
58
  ...(minAnchorWidth ? { minWidth: rect.width } : {}),
40
59
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@konce-pt/react",
3
- "version": "0.15.1",
3
+ "version": "0.16.0",
4
4
  "description": "Open-source React 19 component library on the Koncept UI design system - the same kpt-* CSS layer and --kpt-* tokens as the Angular port. Zero runtime dependencies.",
5
5
  "license": "MIT",
6
6
  "author": "konce.pt",
@@ -65,19 +65,19 @@
65
65
  "leaflet": "^1.9.0"
66
66
  },
67
67
  "dependencies": {
68
- "@konce-pt/backdrop": "0.15.1",
69
- "@konce-pt/chart": "0.15.1",
70
- "@konce-pt/icons": "0.15.1",
71
- "@konce-pt/mention": "0.15.1",
72
- "@konce-pt/i18n": "0.15.1",
73
- "@konce-pt/roadmap": "0.15.1",
74
- "@konce-pt/datetime": "0.15.1",
75
- "@konce-pt/speech": "0.15.1",
76
- "@konce-pt/motion": "0.15.1",
77
- "@konce-pt/table": "0.15.1",
78
- "@konce-pt/tokens": "0.15.1",
79
- "@konce-pt/chat": "0.15.1",
80
- "@konce-pt/map": "0.15.1"
68
+ "@konce-pt/chart": "0.16.0",
69
+ "@konce-pt/icons": "0.16.0",
70
+ "@konce-pt/chat": "0.16.0",
71
+ "@konce-pt/datetime": "0.16.0",
72
+ "@konce-pt/roadmap": "0.16.0",
73
+ "@konce-pt/speech": "0.16.0",
74
+ "@konce-pt/table": "0.16.0",
75
+ "@konce-pt/backdrop": "0.16.0",
76
+ "@konce-pt/map": "0.16.0",
77
+ "@konce-pt/motion": "0.16.0",
78
+ "@konce-pt/tokens": "0.16.0",
79
+ "@konce-pt/i18n": "0.16.0",
80
+ "@konce-pt/mention": "0.16.0"
81
81
  },
82
82
  "devDependencies": {
83
83
  "@types/react": "^19.2.0",