@konce-pt/react 0.17.0 → 0.18.1
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.
- package/dist/charts/chart.js +63 -27
- package/dist/charts/llms.txt +21 -1
- package/dist/charts-entry/public-api.d.ts +1 -1
- package/dist/data-table/data-table.d.ts +8 -1
- package/dist/data-table/data-table.js +30 -11
- package/dist/data-table/llms.txt +6 -1
- package/dist/dialog/dialog.js +7 -0
- package/dist/input/input.d.ts +6 -1
- package/dist/input/input.js +2 -2
- package/dist/input/llms.txt +4 -0
- package/dist/popover/llms.txt +3 -0
- package/dist/public-api.d.ts +2 -2
- package/dist/select/llms.txt +5 -1
- package/dist/select/select.d.ts +24 -2
- package/dist/select/select.js +0 -19
- package/package.json +14 -14
package/dist/charts/chart.js
CHANGED
|
@@ -1,12 +1,35 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useId, useRef, useState } from 'react';
|
|
4
|
-
import { KptChartMotion, areaPath, bandScale, barLayout, chartAccessibleName, chartDataTable, dataScale, deltaKind, domainScale,
|
|
4
|
+
import { KptChartMotion, areaPath, axisBase, axisLabelIndices, bandPath, bandScale, barLayout, chartScale, chartAccessibleName, chartDataTable, clampLabel, dataScale, deltaKind, domainScale, estimateLabelWidth, formatNumber, linePath, linearScale, pieSlices, plotArea, polarSlices, polarToCartesian, polygonPath, radarGrid, radarPoints, radialArea, sameChartShape, scaleRows, seriesColor, significantDelta, stackSeries, } from '@konce-pt/chart';
|
|
5
5
|
import { cn } from "../internal/cn.js";
|
|
6
6
|
import { useKptMotion } from "../motion/motion-context.js";
|
|
7
7
|
import { useKptMessages } from "../i18n/i18n-context.js";
|
|
8
8
|
import { KptChartLegend } from "./chart-legend.js";
|
|
9
9
|
import { KptChartTooltip } from "./chart-tooltip.js";
|
|
10
|
+
/** Wysokość linii etykiety kategorii na osi pionowej (słupki poziome) - jednostki viewBoxa. */
|
|
11
|
+
const CATEGORY_LINE = 12;
|
|
12
|
+
/**
|
|
13
|
+
* Etykiety osi poziomej: wszystkie, jeśli się mieszczą, inaczej równy podział 9/5/3/2
|
|
14
|
+
* (`axisLabelIndices`). Pełna nazwa każdej kategorii zostaje w pigułce prowadnicy i w dymku.
|
|
15
|
+
*/
|
|
16
|
+
function bottomLabels(labels, centers, y, width) {
|
|
17
|
+
const sizes = labels.map((l) => estimateLabelWidth(l));
|
|
18
|
+
const shown = new Set(axisLabelIndices(centers, sizes));
|
|
19
|
+
return labels.map((text, i) => ({
|
|
20
|
+
at: centers[i] ?? 0,
|
|
21
|
+
x: clampLabel(centers[i] ?? 0, sizes[i] ?? 0, 0, width),
|
|
22
|
+
y,
|
|
23
|
+
text,
|
|
24
|
+
anchor: 'middle',
|
|
25
|
+
visible: shown.has(i),
|
|
26
|
+
}));
|
|
27
|
+
}
|
|
28
|
+
/** Etykiety osi pionowej (słupki poziome): ten sam podział, rozmiarem jest wysokość linii. */
|
|
29
|
+
function sideLabels(labels, centers, x) {
|
|
30
|
+
const shown = new Set(axisLabelIndices(centers, centers.map(() => CATEGORY_LINE), 2));
|
|
31
|
+
return labels.map((text, i) => ({ at: centers[i] ?? 0, x, y: (centers[i] ?? 0) + 3, text, anchor: 'end', visible: shown.has(i) }));
|
|
32
|
+
}
|
|
10
33
|
const VERTICAL_INSETS = { top: 12, right: 16, bottom: 28, left: 46 };
|
|
11
34
|
const HORIZONTAL_INSETS = { top: 12, right: 16, bottom: 28, left: 76 };
|
|
12
35
|
/** Szerokość pasma smugi światła (jednostki viewBoxa) - połowa po każdej stronie czoła fali. */
|
|
@@ -106,8 +129,11 @@ export function KptChart({ config, width = 600, height = 360, locale, dictionary
|
|
|
106
129
|
const to = config.series.map((s) => [...s.data]);
|
|
107
130
|
const withAxis = config.type === 'line' || config.type === 'bar';
|
|
108
131
|
const visible = (rows) => rows.filter((_, i) => !hidden.has(`ser:${config.series[i]?.name ?? ''}`));
|
|
132
|
+
const axis = { valueAxis: config.valueAxis, stacked: config.stacked };
|
|
133
|
+
// Skala trzymana na czas ducha obejmuje oba stany - z wierszy już złożonych w stos.
|
|
134
|
+
const held = [...scaleRows(visible(from), config.stacked), ...scaleRows(visible(to), config.stacked)];
|
|
109
135
|
const scales = withAxis
|
|
110
|
-
? { start: dataScale(visible(from)), hold:
|
|
136
|
+
? { start: dataScale(visible(from), axis), hold: chartScale(held, { valueAxis: config.valueAxis }), end: dataScale(visible(to), axis) }
|
|
111
137
|
: null;
|
|
112
138
|
motion.transition(from, to, { delta: withAxis && config.animation?.delta !== false, scales });
|
|
113
139
|
}, [config, hidden, motion, motionConfig]);
|
|
@@ -204,16 +230,19 @@ export function KptChart({ config, width = 600, height = 360, locale, dictionary
|
|
|
204
230
|
if (view.type !== 'line')
|
|
205
231
|
return null;
|
|
206
232
|
const active = view.series.map((s, i) => ({ s, i })).filter(({ s }) => !hidden.has(`ser:${s.name}`));
|
|
207
|
-
const
|
|
208
|
-
const [mn, mx] = extent(values.length ? values : [0]);
|
|
233
|
+
const rows = active.map(({ s }) => s.data);
|
|
209
234
|
const scale = frame.domain
|
|
210
235
|
? domainScale(frame.domain.target, frame.domain.min, frame.domain.max)
|
|
211
|
-
:
|
|
236
|
+
: chartScale(rows, { valueAxis: view.valueAxis, stacked: view.stacked });
|
|
237
|
+
// Stos: punkty serii na narastającej sumie, pasmo od linii serii pod nią.
|
|
238
|
+
const stack = view.stacked ? stackSeries(rows) : null;
|
|
212
239
|
const plot = plotArea(width, height, VERTICAL_INSETS);
|
|
213
240
|
const yScale = linearScale(scale.min, scale.max, plot.y + plot.height, plot.y);
|
|
214
241
|
const count = view.labels.length;
|
|
215
242
|
const xAt = (i) => count > 1 ? plot.x + (i / (count - 1)) * plot.width : plot.x + plot.width / 2;
|
|
216
|
-
|
|
243
|
+
// Punkt nie wychodzi poza oś - przypięte `valueAxis.min`/`max` mogą uciąć wartość.
|
|
244
|
+
const yAt = (v) => yScale(Math.min(scale.max, Math.max(scale.min, v)));
|
|
245
|
+
const baseline = yScale(axisBase(scale));
|
|
217
246
|
const interpolation = view.interpolation ?? 'linear';
|
|
218
247
|
const showPoints = view.points ?? true;
|
|
219
248
|
const grid = scale.ticks.map((t) => ({
|
|
@@ -228,29 +257,35 @@ export function KptChart({ config, width = 600, height = 360, locale, dictionary
|
|
|
228
257
|
text: formatNumber(t),
|
|
229
258
|
anchor: 'end',
|
|
230
259
|
}));
|
|
231
|
-
const catLabels = view.labels.map((
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
text: label,
|
|
235
|
-
anchor: 'middle',
|
|
236
|
-
}));
|
|
237
|
-
const series = active.map(({ s, i }) => ({
|
|
260
|
+
const catLabels = bottomLabels(view.labels, view.labels.map((_, i) => xAt(i)), plot.y + plot.height + 18, width);
|
|
261
|
+
const catTicks = view.labels.map((_, i) => ({ x: xAt(i), y: plot.y + plot.height }));
|
|
262
|
+
const series = active.map(({ s, i }, k) => ({
|
|
238
263
|
key: `ser:${s.name}`,
|
|
239
264
|
name: s.name,
|
|
240
265
|
color: seriesColor(s.color, i),
|
|
241
266
|
i,
|
|
242
|
-
points: s.data.map((v, idx) => ({ x: xAt(idx), y:
|
|
267
|
+
points: s.data.map((v, idx) => ({ x: xAt(idx), y: yAt(stack?.top[k]?.[idx] ?? v), value: v, label: view.labels[idx] ?? '' })),
|
|
268
|
+
floor: stack ? s.data.map((_, idx) => ({ x: xAt(idx), y: yAt(stack.base[k]?.[idx] ?? 0) })) : null,
|
|
243
269
|
}));
|
|
270
|
+
// Duch w stosie stoi na sumach poprzedniego stanu, tak jak bieżące punkty.
|
|
271
|
+
const ghostRows = ghostState
|
|
272
|
+
? (() => {
|
|
273
|
+
const prev = active.map(({ i }) => ghostState.prev[i] ?? []);
|
|
274
|
+
return view.stacked ? stackSeries(prev).top : prev;
|
|
275
|
+
})()
|
|
276
|
+
: [];
|
|
244
277
|
const crosshairCat = config.crosshair && activeIndex !== null ? catLabels[activeIndex] : undefined;
|
|
245
278
|
// Prowadnica i pigułka jadą razem z dymkiem.
|
|
246
|
-
const crossX = crosshairCat ? (frame.tip?.x ?? crosshairCat.
|
|
279
|
+
const crossX = crosshairCat ? (frame.tip?.x ?? crosshairCat.at) : 0;
|
|
247
280
|
const pillWidth = crosshairCat ? Math.max(28, crosshairCat.text.length * 7 + 16) : 0;
|
|
281
|
+
// Pigułka przy skrajnej kategorii przesuwa się do środka zamiast wyjść poza viewBox.
|
|
282
|
+
const pillX = crosshairCat ? clampLabel(crossX, pillWidth, 0, width) : 0;
|
|
248
283
|
/** Najbliższa kategoria w całym obszarze wykresu: wiersze wszystkich serii, dymek nad najwyższym punktem. */
|
|
249
284
|
const onMove = (event) => {
|
|
250
285
|
const p = svgPoint(event);
|
|
251
286
|
if (!p || !series.length)
|
|
252
287
|
return;
|
|
253
|
-
const idx = nearestIndex(catLabels.map((l) => l.
|
|
288
|
+
const idx = nearestIndex(catLabels.map((l) => l.at), p.x);
|
|
254
289
|
if (keepTip(idx))
|
|
255
290
|
return;
|
|
256
291
|
const hits = series.map((s) => ({ s, pt: s.points[idx] })).filter((e) => !!e.pt);
|
|
@@ -264,14 +299,14 @@ export function KptChart({ config, width = 600, height = 360, locale, dictionary
|
|
|
264
299
|
if (moved && tipMotion())
|
|
265
300
|
setRipple({ id: ++rippleId.current, x: top.pt.x, y: top.pt.y, color: top.s.color });
|
|
266
301
|
};
|
|
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:
|
|
302
|
+
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.filter((l) => l.visible)].map((l, i) => (_jsx("text", { x: l.x, y: l.y, textAnchor: l.anchor, children: l.text }, i))) }), _jsx("g", { className: "kpt-chart__cat-ticks", "aria-hidden": "true", children: catTicks.map((tk, i) => (_jsx("circle", { className: cn('kpt-chart__cat-tick', activeIndex === i && 'kpt-chart__cat-tick--active'), r: 2, cx: tk.x, cy: tk.y }, i))) }), crosshairCat ? (_jsxs("g", { className: "kpt-chart__cat-active", children: [_jsx("rect", { x: pillX - pillWidth / 2, y: crosshairCat.y - 13, width: pillWidth, height: 18, rx: 9 }), _jsx("text", { x: pillX, y: crosshairCat.y, textAnchor: "middle", children: crosshairCat.text })] })) : null, ghostState
|
|
268
303
|
? series.map((s) => {
|
|
269
304
|
const prev = ghostState.prev[s.i] ?? [];
|
|
270
305
|
const next = ghostState.next[s.i] ?? [];
|
|
271
|
-
const gpts = prev.map((v, idx) => ({ x: xAt(idx), y:
|
|
306
|
+
const gpts = (ghostRows[series.indexOf(s)] ?? prev).map((v, idx) => ({ x: xAt(idx), y: yAt(v) }));
|
|
272
307
|
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
308
|
})
|
|
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
|
|
309
|
+
: 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: s.floor ? bandPath(s.points, s.floor, interpolation) : 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
310
|
? 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
311
|
: null] }, s.key))), sheen
|
|
277
312
|
? 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)))
|
|
@@ -301,7 +336,7 @@ export function KptChart({ config, width = 600, height = 360, locale, dictionary
|
|
|
301
336
|
const active = view.series.map((s, i) => ({ s, i })).filter(({ s }) => !hidden.has(`ser:${s.name}`));
|
|
302
337
|
const plot = plotArea(width, height, vertical ? VERTICAL_INSETS : HORIZONTAL_INSETS);
|
|
303
338
|
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);
|
|
339
|
+
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, { stacked: view.stacked, valueAxis: view.valueAxis });
|
|
305
340
|
const colors = active.map(({ s, i }) => seriesColor(s.color, i));
|
|
306
341
|
const names = active.map(({ s }) => s.name);
|
|
307
342
|
const cat = vertical
|
|
@@ -318,9 +353,10 @@ export function KptChart({ config, width = 600, height = 360, locale, dictionary
|
|
|
318
353
|
text: formatNumber(t.value),
|
|
319
354
|
anchor: 'middle',
|
|
320
355
|
}));
|
|
356
|
+
const centers = view.labels.map((_, i) => cat.center(i));
|
|
321
357
|
const catLabels = vertical
|
|
322
|
-
? view.labels
|
|
323
|
-
: view.labels
|
|
358
|
+
? bottomLabels(view.labels, centers, plot.y + plot.height + 18, width)
|
|
359
|
+
: sideLabels(view.labels, centers, plot.x - 8);
|
|
324
360
|
const bars = layout.bars.map((b, order) => ({
|
|
325
361
|
b,
|
|
326
362
|
order,
|
|
@@ -335,7 +371,7 @@ export function KptChart({ config, width = 600, height = 360, locale, dictionary
|
|
|
335
371
|
? (() => {
|
|
336
372
|
const prevRows = active.map(({ i }) => ghostState.prev[i] ?? []);
|
|
337
373
|
const nextRows = active.map(({ i }) => ghostState.next[i] ?? []);
|
|
338
|
-
return barLayout(prevRows, view.labels.length, plot, orientation, radius, layout.scale).bars.map((g, k) => {
|
|
374
|
+
return barLayout(prevRows, view.labels.length, plot, orientation, radius, layout.scale, { stacked: view.stacked }).bars.map((g, k) => {
|
|
339
375
|
const cur = layout.bars[k] ?? g;
|
|
340
376
|
const a = barEnd(g, vertical);
|
|
341
377
|
const z = barEnd(cur, vertical);
|
|
@@ -357,7 +393,7 @@ export function KptChart({ config, width = 600, height = 360, locale, dictionary
|
|
|
357
393
|
const p = svgPoint(event);
|
|
358
394
|
if (!p)
|
|
359
395
|
return;
|
|
360
|
-
const idx = nearestIndex(catLabels.map((l) =>
|
|
396
|
+
const idx = nearestIndex(catLabels.map((l) => l.at), vertical ? p.x : p.y);
|
|
361
397
|
if (keepTip(idx))
|
|
362
398
|
return;
|
|
363
399
|
const group = bars.filter((e) => e.b.categoryIndex === idx);
|
|
@@ -365,11 +401,11 @@ export function KptChart({ config, width = 600, height = 360, locale, dictionary
|
|
|
365
401
|
if (!group.length || !center)
|
|
366
402
|
return;
|
|
367
403
|
const label = config.labels[idx] ?? '';
|
|
368
|
-
const x = vertical ? center.
|
|
369
|
-
const y = vertical ? Math.min(...group.map((e) => e.tipY)) : center.
|
|
404
|
+
const x = vertical ? center.at : Math.max(...group.map((e) => e.tipX));
|
|
405
|
+
const y = vertical ? Math.min(...group.map((e) => e.tipY)) : center.at;
|
|
370
406
|
openTip(x, y, label, group.map((e) => row(e.name, label, e.b.value, e.color)), idx);
|
|
371
407
|
};
|
|
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
|
|
408
|
+
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.filter((l) => l.visible)].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
409
|
? 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
410
|
: null, _jsx("rect", { className: "kpt-chart__hover", x: plot.x, y: plot.y, width: plot.width, height: plot.height, onPointerMove: onMove, onPointerLeave: hideTip })] }));
|
|
375
411
|
})();
|
package/dist/charts/llms.txt
CHANGED
|
@@ -19,9 +19,29 @@ proportionally (`width={300} height={180}`) instead of letting a 600-unit chart
|
|
|
19
19
|
|
|
20
20
|
## The config is the API
|
|
21
21
|
`{ type, labels, series, points?, area?, interpolation?, orientation?, borderRadius?, innerRadius?,
|
|
22
|
-
crosshair?, tooltip?, legend?, title?, subtitle?, animation?, ariaLabel?, description? }` - the same shape the Angular port
|
|
22
|
+
valueAxis?, stacked?, crosshair?, tooltip?, legend?, title?, subtitle?, animation?, ariaLabel?, description? }` - the same shape the Angular port
|
|
23
23
|
takes, because both read it from `@konce-pt/chart`.
|
|
24
24
|
|
|
25
|
+
## Value axis and stacking (line, bar)
|
|
26
|
+
- `valueAxis?: { zero?, min?, max? }` - the value axis starts at zero by default, so a bar and the area under a line
|
|
27
|
+
show the size directly. `zero: false` fits the axis to the data (a weight of 79-90 kg is no longer a flat line under
|
|
28
|
+
the ceiling); `min` / `max` pin an end of the axis exactly and win over `zero` - a value beyond a pinned end is
|
|
29
|
+
cut at the edge. Bars and areas grow from zero, or from the end of the axis nearer to it
|
|
30
|
+
- `stacked?: boolean` - stacked series: bars on top of each other in one band per category (positive values up,
|
|
31
|
+
negative down; only the outer segment is rounded), lines as running totals with a band under each one (with
|
|
32
|
+
`area`). The axis covers the totals; the tooltip shows the values of the series, not the totals
|
|
33
|
+
- The rules live in `@konce-pt/chart`: `axisScale`, `chartScale`, `stackSeries`, `bandPath` (tested)
|
|
34
|
+
|
|
35
|
+
## Category axis (line, bar)
|
|
36
|
+
- When the category labels do not fit, the axis shows an even subset instead of overlapping text: all of
|
|
37
|
+
them if they fit, otherwise the densest split that fits - 9 labels (eighths of the axis), 5 (first, 1/4,
|
|
38
|
+
middle, 3/4, last), 3, and finally the two ends only. Horizontal bars apply the same rule to the rows
|
|
39
|
+
- The choice is made in viewBox units, so it follows `width`, not the on-screen size; the outer labels and the
|
|
40
|
+
crosshair pill are shifted inward so they never leave the chart
|
|
41
|
+
- A line chart marks every category with a small dot on the axis; the active one grows on hover. The full
|
|
42
|
+
name of any category stays in the crosshair pill and the tooltip
|
|
43
|
+
- The rules live in `@konce-pt/chart`: `axisLabelIndices`, `estimateLabelWidth`, `clampLabel` (tested)
|
|
44
|
+
|
|
25
45
|
## The geometry lives in a package
|
|
26
46
|
Scales, paths, bar layout and arcs come from `@konce-pt/chart` - pure functions with `node --test`
|
|
27
47
|
coverage, shared with the Angular port. The component only turns them into SVG elements; there is
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { KptChart } from '../charts/chart.tsx';
|
|
2
2
|
export type { KptChartProps } from '../charts/chart.tsx';
|
|
3
|
-
export type { KptChartConfig, KptChartSeries, KptChartType, KptLineInterpolation, KptBarOrientation, KptLegendOptions, KptLegendPosition, KptLegendTitle, KptAlign, KptPointStyle, KptTitleOptions, KptTooltipOptions, KptTooltipContext, KptAnimationOptions, KptFont, KptPadding, } from '@konce-pt/chart';
|
|
3
|
+
export type { KptChartConfig, KptChartSeries, KptChartType, KptLineInterpolation, KptBarOrientation, KptLegendOptions, KptLegendPosition, KptLegendTitle, KptAlign, KptPointStyle, KptTitleOptions, KptTooltipOptions, KptTooltipContext, KptAnimationOptions, KptValueAxisOptions, KptFont, KptPadding, } from '@konce-pt/chart';
|
|
4
4
|
export { KPT_CHART_PALETTE, seriesColor, linePath, areaPath, pieSlices, polarSlices, barLayout, radarGrid, radarPoints, niceScale, } from '@konce-pt/chart';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ComponentPropsWithRef, type ReactNode } from 'react';
|
|
2
|
+
import { type KptColumnGroup } from '@konce-pt/table';
|
|
2
3
|
import { type KptI18nOverrides } from '../i18n/i18n-context.tsx';
|
|
3
4
|
import { type KptSpeechColor } from '../speech/speech-button.tsx';
|
|
4
5
|
import type { KptCellEditEvent, KptCellEditRenderers, KptCellRenderers, KptColumn, KptTableLazyState } from './data-table-types.ts';
|
|
@@ -45,6 +46,12 @@ export interface KptDataTableProps<T> extends Omit<ComponentPropsWithRef<'div'>,
|
|
|
45
46
|
/** Szerokość kolumny bez liczalnego `width` w trybie `virtualScroll` (`px`/`rem`/`em`). */
|
|
46
47
|
columnWidth?: string;
|
|
47
48
|
rowHeight?: number;
|
|
49
|
+
/**
|
|
50
|
+
* Nagłówki grupowane: wiersz nad etykietami, w którym sąsiednie kolumny grupy dzielą komórkę
|
|
51
|
+
* (`[{ header: 'Makroskładniki', fields: ['carbs', 'protein', 'fat'] }]`). Liczony z kolumn
|
|
52
|
+
* widocznych, w bieżącej kolejności - ukryta albo przestawiona kolumna wypada z grupy sama.
|
|
53
|
+
*/
|
|
54
|
+
columnGroups?: readonly KptColumnGroup[];
|
|
48
55
|
reorderable?: boolean;
|
|
49
56
|
/**
|
|
50
57
|
* Przykleja kolumny systemowe (rozwijanie, zaznaczanie) do lewej krawędzi także bez kolumn `frozen`.
|
|
@@ -83,4 +90,4 @@ export interface KptDataTableProps<T> extends Omit<ComponentPropsWithRef<'div'>,
|
|
|
83
90
|
/** Treść po lewej stronie paginatora (odpowiednik slotu `[kptPaginatorActions]`). */
|
|
84
91
|
paginatorActions?: ReactNode;
|
|
85
92
|
}
|
|
86
|
-
export declare function KptDataTable<T = Record<string, unknown>>({ columns, data, filterable, filterSpeech, filterSpeechLang, filterSpeechColor, filterRowPosition, pageSize, pageSizeOptions, selectable, loading, ariaLabel, caption, clickableRows, emptyMessage, rowKey, scrollHeight, virtualScroll, columnWidth, rowHeight, reorderable, frozenSystemColumns, exportable, exportFilename, lazy, totalRecords, expandable, multiExpand, summaryLabel, summaryValues, selection, defaultSelection, onSelectionChange, expandedRows, defaultExpandedRows, onExpandedRowsChange, hiddenFields, defaultHiddenFields, onHiddenFieldsChange, onRowClick, onLazyLoad, onCellEdit, cells, cellEditors, renderRowDetail, paginatorActions, locale, dictionary, className, style, ...rest }: Readonly<KptDataTableProps<T>>): import("react").JSX.Element;
|
|
93
|
+
export declare function KptDataTable<T = Record<string, unknown>>({ columns, data, filterable, filterSpeech, filterSpeechLang, filterSpeechColor, filterRowPosition, pageSize, pageSizeOptions, selectable, loading, ariaLabel, caption, clickableRows, emptyMessage, rowKey, scrollHeight, virtualScroll, columnWidth, rowHeight, reorderable, columnGroups, frozenSystemColumns, exportable, exportFilename, lazy, totalRecords, expandable, multiExpand, summaryLabel, summaryValues, selection, defaultSelection, onSelectionChange, expandedRows, defaultExpandedRows, onExpandedRowsChange, hiddenFields, defaultHiddenFields, onHiddenFieldsChange, onRowClick, onLazyLoad, onCellEdit, cells, cellEditors, renderRowDetail, paginatorActions, locale, dictionary, className, style, ...rest }: Readonly<KptDataTableProps<T>>): import("react").JSX.Element;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useEffectEvent, useId, useLayoutEffect, useMemo, useRef, useState, } from 'react';
|
|
4
|
-
import { aggregate, columnOffsets, createGlobalFilter, cssLengthToPx, isEmptyFilterValue, matchesColumnFilter, sortRows, toText, virtualColumnRange, virtualRange, } from '@konce-pt/table';
|
|
4
|
+
import { aggregate, columnOffsets, createGlobalFilter, cssLengthToPx, groupHeaderCells, isEmptyFilterValue, matchesColumnFilter, sortRows, toText, virtualColumnRange, virtualRange, } from '@konce-pt/table';
|
|
5
5
|
import { cn } from "../internal/cn.js";
|
|
6
6
|
import { useControllableState } from "../internal/use-controllable-state.js";
|
|
7
7
|
import { useKptMessages } from "../i18n/i18n-context.js";
|
|
@@ -82,7 +82,7 @@ const EMPTY_FIELDS = [];
|
|
|
82
82
|
function getValue(row, field) {
|
|
83
83
|
return row[field];
|
|
84
84
|
}
|
|
85
|
-
export function KptDataTable({ columns, data = EMPTY_ROWS, filterable = false, filterSpeech = false, filterSpeechLang, filterSpeechColor, filterRowPosition = 'bottom', pageSize = 0, pageSizeOptions = [5, 10, 20, 50], selectable = 'none', loading = false, ariaLabel = null, caption = null, clickableRows, emptyMessage, rowKey = null, scrollHeight = null, virtualScroll = false, columnWidth = '10rem', rowHeight = 41, reorderable = false, frozenSystemColumns = false, exportable = false, exportFilename = 'export.csv', lazy = false, totalRecords = null, expandable = false, multiExpand = true, summaryLabel, summaryValues = null, selection, defaultSelection = EMPTY_ROWS, onSelectionChange, expandedRows, defaultExpandedRows = EMPTY_ROWS, onExpandedRowsChange, hiddenFields, defaultHiddenFields = EMPTY_FIELDS, onHiddenFieldsChange, onRowClick, onLazyLoad, onCellEdit, cells, cellEditors, renderRowDetail, paginatorActions, locale, dictionary, className, style, ...rest }) {
|
|
85
|
+
export function KptDataTable({ columns, data = EMPTY_ROWS, filterable = false, filterSpeech = false, filterSpeechLang, filterSpeechColor, filterRowPosition = 'bottom', pageSize = 0, pageSizeOptions = [5, 10, 20, 50], selectable = 'none', loading = false, ariaLabel = null, caption = null, clickableRows, emptyMessage, rowKey = null, scrollHeight = null, virtualScroll = false, columnWidth = '10rem', rowHeight = 41, reorderable = false, columnGroups, frozenSystemColumns = false, exportable = false, exportFilename = 'export.csv', lazy = false, totalRecords = null, expandable = false, multiExpand = true, summaryLabel, summaryValues = null, selection, defaultSelection = EMPTY_ROWS, onSelectionChange, expandedRows, defaultExpandedRows = EMPTY_ROWS, onExpandedRowsChange, hiddenFields, defaultHiddenFields = EMPTY_FIELDS, onHiddenFieldsChange, onRowClick, onLazyLoad, onCellEdit, cells, cellEditors, renderRowDetail, paginatorActions, locale, dictionary, className, style, ...rest }) {
|
|
86
86
|
const { t, locale: activeLocale } = useKptMessages({ locale, dictionary });
|
|
87
87
|
const uid = useId();
|
|
88
88
|
const rowsClickable = clickableRows ?? onRowClick !== undefined;
|
|
@@ -149,6 +149,8 @@ export function KptDataTable({ columns, data = EMPTY_ROWS, filterable = false, f
|
|
|
149
149
|
const [viewport, setViewport] = useState({ height: 400, width: 1200 });
|
|
150
150
|
const scrollFrame = useRef(0);
|
|
151
151
|
const [theadOffset, setTheadOffset] = useState(0);
|
|
152
|
+
/** Offset sticky trzeciego wiersza nagłówka (grupy + etykiety + filtry). */
|
|
153
|
+
const [theadOffset2, setTheadOffset2] = useState(0);
|
|
152
154
|
const scrollRef = useRef(null);
|
|
153
155
|
const theadRef = useRef(null);
|
|
154
156
|
const editorHostRef = useRef(null);
|
|
@@ -234,14 +236,17 @@ export function KptDataTable({ columns, data = EMPTY_ROWS, filterable = false, f
|
|
|
234
236
|
return;
|
|
235
237
|
requestLazyLoad({ pageIndex, pageSize: pageSizeState, sortField, sortDir, filter: filterText, columnFilters });
|
|
236
238
|
}, [lazy, pageIndex, pageSizeState, sortField, sortDir, filterText, columnFilters]);
|
|
237
|
-
//
|
|
239
|
+
// Offsety sticky drugiego i trzeciego wiersza nagłówka = wysokość jednego i dwóch pierwszych.
|
|
238
240
|
useLayoutEffect(() => {
|
|
239
|
-
const
|
|
241
|
+
const rows = theadRef.current?.rows;
|
|
240
242
|
// Dokładna wysokość, nie `offsetHeight`: ten zaokrągla (46,5 -> 47) i przy skali ekranu innej
|
|
241
243
|
// niż 100% między wierszami nagłówka zostawała szpara, przez którą prześwitywały wiersze.
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
244
|
+
const h1 = rows?.[0]?.getBoundingClientRect().height ?? 0;
|
|
245
|
+
const h2 = rows?.[1]?.getBoundingClientRect().height ?? 0;
|
|
246
|
+
if (rows?.[0])
|
|
247
|
+
setTheadOffset(h1);
|
|
248
|
+
setTheadOffset2(h1 + h2);
|
|
249
|
+
}, [filterRowPosition, hasColumnFilters, visibleColumns, columnGroups]);
|
|
245
250
|
// Rozmiar viewportu dla wirtualizacji - także po zmianie rozmiaru kontenera bez przewinięcia.
|
|
246
251
|
useLayoutEffect(() => {
|
|
247
252
|
const el = scrollRef.current;
|
|
@@ -400,8 +405,22 @@ export function KptDataTable({ columns, data = EMPTY_ROWS, filterable = false, f
|
|
|
400
405
|
const tableWidth = virtualScroll
|
|
401
406
|
? scrollable.pinnedWidth + scrollable.offsets[scrollable.offsets.length - 1]
|
|
402
407
|
: undefined;
|
|
403
|
-
/**
|
|
404
|
-
|
|
408
|
+
/**
|
|
409
|
+
* Wiersz grup nad etykietami: komórki nad pozycjami `slots`. Komórka przykleja się do lewej, gdy
|
|
410
|
+
* wszystkie jej kolumny są zamrożone - inaczej przewija się z nimi. Pusta lista = bez wiersza.
|
|
411
|
+
*/
|
|
412
|
+
const groupCells = columnGroups?.length
|
|
413
|
+
? groupHeaderCells(slots.map((s) => s.meta?.col.field ?? null), columnGroups).map((cell) => {
|
|
414
|
+
const metas = slots.slice(cell.start, cell.start + cell.span).map((s) => s.meta);
|
|
415
|
+
const spacer = metas.some((m) => m === null);
|
|
416
|
+
const frozen = !spacer && metas.every((m) => m?.frozen);
|
|
417
|
+
return { ...cell, key: slots[cell.start]?.key ?? String(cell.start), spacer, frozen, left: frozen ? (metas[0]?.left ?? undefined) : undefined };
|
|
418
|
+
})
|
|
419
|
+
: [];
|
|
420
|
+
/** Wiersz grup jest pierwszym wierszem nagłówka - przesuwa `aria-rowindex` pozostałych. */
|
|
421
|
+
const groupRows = groupCells.length ? 1 : 0;
|
|
422
|
+
/** Liczba wierszy nagłówka (grupy, etykiety, ewentualnie filtry) - przesunięcie `aria-rowindex`. */
|
|
423
|
+
const headerRowCount = groupRows + (hasColumnFilters ? 2 : 1);
|
|
405
424
|
/** `aria-colindex` komórki kolumny (od 1, z kolumnami systemowymi) - tylko w trybie wirtualnym. */
|
|
406
425
|
const colIndex = (meta) => virtualScroll ? leadingCells + meta.index + 1 : undefined;
|
|
407
426
|
// -- Przestawianie kolumn --
|
|
@@ -671,7 +690,7 @@ export function KptDataTable({ columns, data = EMPTY_ROWS, filterable = false, f
|
|
|
671
690
|
return null;
|
|
672
691
|
}
|
|
673
692
|
};
|
|
674
|
-
const filterRowIndex = filterRowPosition === 'top' ? 1 : 2;
|
|
693
|
+
const filterRowIndex = groupRows + (filterRowPosition === 'top' ? 1 : 2);
|
|
675
694
|
const filterRow = (_jsxs("tr", { className: "kpt-data-table__filter-row", "aria-rowindex": virtualScroll ? filterRowIndex : undefined, children: [hasRowDetail ? (_jsx("th", { className: cn('kpt-data-table__expander', hasFrozen && 'kpt-data-table__cell--frozen'), style: { left: expanderStickyLeft } })) : null, selectable !== 'none' ? (_jsx("th", { className: cn('kpt-data-table__checkbox', hasFrozen && 'kpt-data-table__cell--frozen'), style: { left: checkboxStickyLeft } })) : null, slots.map((slot) => slot.meta ? (_jsx("th", { style: { width: virtualScroll ? undefined : slot.meta.col.width, left: slot.meta.left }, "data-align": slot.meta.align, "aria-colindex": colIndex(slot.meta), className: cn(slot.meta.frozen && 'kpt-data-table__cell--frozen'), children: filterCell(slot.meta.col) }, slot.key)) : (hspacer(slot)))] }));
|
|
676
695
|
const editor = (row, col) => {
|
|
677
696
|
const custom = cellEditors?.[col.field];
|
|
@@ -743,7 +762,7 @@ export function KptDataTable({ columns, data = EMPTY_ROWS, filterable = false, f
|
|
|
743
762
|
startEdit(row, col, event);
|
|
744
763
|
}, children: editing ? (_jsx("div", { ref: editorHostRef, className: "kpt-data-table__editor", onClick: (event) => event.stopPropagation(), onBlur: onEditorFocusOut, children: editor(row, col) })) : cells?.[col.field] ? (cells[col.field](row, getValue(row, col.field))) : getValue(row, col.field) }, slot.key));
|
|
745
764
|
})] }), hasRowDetail && isExpanded(row) && !virtualScroll ? (_jsx("tr", { className: "kpt-data-table__detail-row", children: _jsx("td", { className: "kpt-data-table__detail", colSpan: colspan, children: renderRowDetail?.(row) }) })) : null] }, rowKey ? String(getValue(row, rowKey)) : index));
|
|
746
|
-
return (_jsxs("div", { className: cn('kpt-data-table', className), style: { ['--kpt-thead-offset']: `${theadOffset}px`, ...style }, ...rest, children: [filterable || exportable ? (_jsxs("div", { className: "kpt-data-table__toolbar", children: [filterable ? (_jsxs("div", { className: "kpt-data-table__search", "data-speech": filterSpeechState, children: [_jsx("input", { ref: filterRef, className: "kpt-data-table__filter", type: "search", placeholder: t('dataTable.search'), "aria-label": t('dataTable.search'), value: filterInput, onChange: (event) => onFilterInput(event.target.value) }), filterSpeech ? (_jsx(KptSpeechButton, { listeningColor: filterSpeechColor, speech: filterMic, size: "sm", unsupported: "hide", hostProps: { className: 'kpt-data-table__speech' } })) : null] })) : null, exportable ? (_jsx(KptButton, { variant: "outline", size: "sm", onClick: () => exportTableCsv(visibleColumns, (lazy ? data : sorted), exportFilename), children: t('dataTable.exportCsv') })) : null] })) : null, _jsx("div", { ref: scrollRef, className: "kpt-data-table__scroll", style: { maxHeight: scrollHeight ?? undefined }, onScroll: onScroll, children: _jsxs("table", { className: cn('kpt-data-table__table', hasFrozen && 'kpt-data-table__table--frozen', virtualScroll && 'kpt-data-table__table--virtual'), style: { width: tableWidth }, "aria-label": ariaLabel ?? undefined, "aria-busy": loading || undefined, "aria-rowcount": virtualScroll ? headerRowCount + sorted.length + (hasSummary ? 1 : 0) : undefined, "aria-colcount": virtualScroll ? leadingCells + visibleColumns.length : undefined, children: [caption ? _jsx("caption", { className: "kpt-data-table__caption", children: _jsx("span", { className: "kpt-data-table__caption-text", children: caption }) }) : null, virtualScroll ? (_jsxs("colgroup", { children: [hasRowDetail ? _jsx("col", { className: "kpt-data-table__col-expander" }) : null, selectable !== 'none' ? _jsx("col", { className: "kpt-data-table__col-checkbox" }) : null, slots.map((slot) => (_jsx("col", { style: { width: slot.width } }, slot.key)))] })) : null, _jsxs("thead", { ref: theadRef, children: [hasColumnFilters && filterRowPosition === 'top' ? filterRow : null, _jsxs("tr", { "aria-rowindex": virtualScroll ? (hasColumnFilters && filterRowPosition === 'top' ? 2 : 1) : undefined, children: [hasRowDetail ? (_jsx("th", { className: cn('kpt-data-table__expander', hasFrozen && 'kpt-data-table__cell--frozen'), style: { left: expanderStickyLeft } })) : null, selectable === 'multiple' ? (_jsx("th", { className: cn('kpt-data-table__checkbox', hasFrozen && 'kpt-data-table__cell--frozen'), style: { left: checkboxStickyLeft }, children: _jsx("input", { type: "checkbox", checked: pageAllSelected, ref: (el) => {
|
|
765
|
+
return (_jsxs("div", { className: cn('kpt-data-table', className), style: { ['--kpt-thead-offset']: `${theadOffset}px`, ['--kpt-thead-offset-2']: `${theadOffset2}px`, ...style }, ...rest, children: [filterable || exportable ? (_jsxs("div", { className: "kpt-data-table__toolbar", children: [filterable ? (_jsxs("div", { className: "kpt-data-table__search", "data-speech": filterSpeechState, children: [_jsx("input", { ref: filterRef, className: "kpt-data-table__filter", type: "search", placeholder: t('dataTable.search'), "aria-label": t('dataTable.search'), value: filterInput, onChange: (event) => onFilterInput(event.target.value) }), filterSpeech ? (_jsx(KptSpeechButton, { listeningColor: filterSpeechColor, speech: filterMic, size: "sm", unsupported: "hide", hostProps: { className: 'kpt-data-table__speech' } })) : null] })) : null, exportable ? (_jsx(KptButton, { variant: "outline", size: "sm", onClick: () => exportTableCsv(visibleColumns, (lazy ? data : sorted), exportFilename), children: t('dataTable.exportCsv') })) : null] })) : null, _jsx("div", { ref: scrollRef, className: "kpt-data-table__scroll", style: { maxHeight: scrollHeight ?? undefined }, onScroll: onScroll, children: _jsxs("table", { className: cn('kpt-data-table__table', hasFrozen && 'kpt-data-table__table--frozen', virtualScroll && 'kpt-data-table__table--virtual'), style: { width: tableWidth }, "aria-label": ariaLabel ?? undefined, "aria-busy": loading || undefined, "aria-rowcount": virtualScroll ? headerRowCount + sorted.length + (hasSummary ? 1 : 0) : undefined, "aria-colcount": virtualScroll ? leadingCells + visibleColumns.length : undefined, children: [caption ? _jsx("caption", { className: "kpt-data-table__caption", children: _jsx("span", { className: "kpt-data-table__caption-text", children: caption }) }) : null, virtualScroll ? (_jsxs("colgroup", { children: [hasRowDetail ? _jsx("col", { className: "kpt-data-table__col-expander" }) : null, selectable !== 'none' ? _jsx("col", { className: "kpt-data-table__col-checkbox" }) : null, slots.map((slot) => (_jsx("col", { style: { width: slot.width } }, slot.key)))] })) : null, _jsxs("thead", { ref: theadRef, children: [groupCells.length ? (_jsxs("tr", { className: "kpt-data-table__group-row", "aria-rowindex": virtualScroll ? 1 : undefined, children: [hasRowDetail ? (_jsx("th", { className: cn('kpt-data-table__expander', hasFrozen && 'kpt-data-table__cell--frozen'), style: { left: expanderStickyLeft } })) : null, selectable !== 'none' ? (_jsx("th", { className: cn('kpt-data-table__checkbox', hasFrozen && 'kpt-data-table__cell--frozen'), style: { left: checkboxStickyLeft } })) : null, groupCells.map((cell) => cell.spacer ? (_jsx("td", { className: "kpt-data-table__hspacer", colSpan: cell.span, "aria-hidden": "true" }, cell.key)) : (_jsx("th", { className: cn('kpt-data-table__group', !cell.header && 'kpt-data-table__group--empty', cell.frozen && 'kpt-data-table__cell--frozen'), style: { left: cell.left }, colSpan: cell.span > 1 ? cell.span : undefined, scope: cell.header ? 'colgroup' : undefined, children: cell.header }, cell.key)))] })) : null, hasColumnFilters && filterRowPosition === 'top' ? filterRow : null, _jsxs("tr", { "aria-rowindex": virtualScroll ? groupRows + (hasColumnFilters && filterRowPosition === 'top' ? 2 : 1) : undefined, children: [hasRowDetail ? (_jsx("th", { className: cn('kpt-data-table__expander', hasFrozen && 'kpt-data-table__cell--frozen'), style: { left: expanderStickyLeft } })) : null, selectable === 'multiple' ? (_jsx("th", { className: cn('kpt-data-table__checkbox', hasFrozen && 'kpt-data-table__cell--frozen'), style: { left: checkboxStickyLeft }, children: _jsx("input", { type: "checkbox", checked: pageAllSelected, ref: (el) => {
|
|
747
766
|
if (el)
|
|
748
767
|
el.indeterminate = pageSomeSelected;
|
|
749
768
|
}, "aria-label": t('dataTable.selectAll'), onChange: toggleAllOnPage }) })) : selectable === 'single' ? (_jsx("th", { className: cn('kpt-data-table__checkbox', hasFrozen && 'kpt-data-table__cell--frozen'), style: { left: checkboxStickyLeft } })) : null, slots.map((slot) => {
|
package/dist/data-table/llms.txt
CHANGED
|
@@ -18,6 +18,11 @@ rendered header.
|
|
|
18
18
|
|
|
19
19
|
## Props
|
|
20
20
|
- `columns` (required), `data`
|
|
21
|
+
- `columnGroups`: KptColumnGroup[] - grouped headers: a row above the column labels where adjacent columns of a
|
|
22
|
+
group share one cell (`[{ header: 'Macros', fields: ['carbs', 'protein', 'fat'] }]`, `scope="colgroup"`). It is
|
|
23
|
+
computed from the visible columns in their current order: a hidden column drops out, a group split by reordering
|
|
24
|
+
gets two cells with the same text, a column without a group gets an empty cell. A cell sticks to the left only
|
|
25
|
+
when all its columns are `frozen`. With column filters the header has three rows and all of them stay sticky
|
|
21
26
|
- `filterable`, `filterRowPosition` (`'bottom'` default), `pageSize` (0 = no pagination),
|
|
22
27
|
`pageSizeOptions`, `selectable` (`'none' | 'single' | 'multiple'`), `loading`, `emptyMessage`,
|
|
23
28
|
`rowKey`, `scrollHeight`, `virtualScroll` (rows **and** columns, see below), `columnWidth`
|
|
@@ -113,7 +118,7 @@ The toolbar, the states and the filter labels come from the `dataTable` namespac
|
|
|
113
118
|
the expanded row `--kpt-table-detail-bg`, the summary row `--kpt-table-summary-bg`.
|
|
114
119
|
|
|
115
120
|
## Not ported yet
|
|
116
|
-
|
|
121
|
+
A ready column-toggle UI is not part of either
|
|
117
122
|
port - `hiddenFields` is the hook to build one on.
|
|
118
123
|
|
|
119
124
|
## Filter by voice
|
package/dist/dialog/dialog.js
CHANGED
|
@@ -50,9 +50,16 @@ export function KptDialog({ open, onOpenChange, header = '', size = 'md', dismis
|
|
|
50
50
|
* `close` przychodzi z każdej drogi. Przy `dismissible=false` zamknąć wolno tylko z kodu:
|
|
51
51
|
* Chromium z CloseWatcherem pozwala anulować `cancel` jedynie po aktywacji użytkownika, więc drugi
|
|
52
52
|
* Escape z rzędu zamyka okno mimo `preventDefault` - wtedy otwieramy je z powrotem.
|
|
53
|
+
*
|
|
54
|
+
* Zdarzenie `close` jest asynchroniczne, więc może dojść, gdy okno jest już znowu otwarte: pod
|
|
55
|
+
* `StrictMode` efekt montuje się dwa razy (`showModal` -> sprzątanie `close` -> `showModal`)
|
|
56
|
+
* i dialog zamontowany od razu z `open` dostawał `onOpenChange(false)` od własnego sprzątania.
|
|
57
|
+
* Otwarty element znaczy, że to zamknięcie jest nieaktualne.
|
|
53
58
|
*/
|
|
54
59
|
const onClose = () => {
|
|
55
60
|
const el = dialogRef.current;
|
|
61
|
+
if (el?.open)
|
|
62
|
+
return;
|
|
56
63
|
if (!dismissible && open && el && !el.open) {
|
|
57
64
|
el.showModal();
|
|
58
65
|
return;
|
package/dist/input/input.d.ts
CHANGED
|
@@ -42,6 +42,11 @@ export interface KptInputProps extends Omit<ComponentPropsWithRef<'input'>, 'typ
|
|
|
42
42
|
* gdzie zastępuje systemowe "x" (ukryte zawsze, także przy `clearable={false}`).
|
|
43
43
|
*/
|
|
44
44
|
clearable?: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Ikona z rejestru na początku pola (`search`, `mail`...). Dekoracyjna - nazwę pola daje etykieta
|
|
47
|
+
* albo `aria-label`, więc ikona nie trafia do drzewa dostępności.
|
|
48
|
+
*/
|
|
49
|
+
icon?: string;
|
|
45
50
|
}
|
|
46
51
|
/**
|
|
47
52
|
* Pole tekstowe Koncept UI. Renderuje natywny `<input>` w hoście `.kpt-input-host`.
|
|
@@ -57,4 +62,4 @@ export interface KptInputProps extends Omit<ComponentPropsWithRef<'input'>, 'typ
|
|
|
57
62
|
* <KptInput name="email" type="email" defaultValue="" />
|
|
58
63
|
* <KptInput value={email} onValueChange={setEmail} invalid={!ok} touched={blurred} />
|
|
59
64
|
*/
|
|
60
|
-
export declare function KptInput({ type, required, invalid, touched, onValueChange, onTouch, hostProps, className, id, onChange, onBlur, ref, speech, speechLang, speechColor, speechMode, clearable, onKeyDown, locale, dictionary, 'aria-invalid': ariaInvalid, 'aria-describedby': ariaDescribedBy, ...rest }: Readonly<KptInputProps>): import("react").JSX.Element;
|
|
65
|
+
export declare function KptInput({ type, required, invalid, touched, onValueChange, onTouch, hostProps, className, id, onChange, onBlur, ref, speech, speechLang, speechColor, speechMode, clearable, icon, onKeyDown, locale, dictionary, 'aria-invalid': ariaInvalid, 'aria-describedby': ariaDescribedBy, ...rest }: Readonly<KptInputProps>): import("react").JSX.Element;
|
package/dist/input/input.js
CHANGED
|
@@ -30,7 +30,7 @@ function setFieldValue(field, value) {
|
|
|
30
30
|
* <KptInput name="email" type="email" defaultValue="" />
|
|
31
31
|
* <KptInput value={email} onValueChange={setEmail} invalid={!ok} touched={blurred} />
|
|
32
32
|
*/
|
|
33
|
-
export function KptInput({ type = 'text', required = false, invalid = false, touched, onValueChange, onTouch, hostProps, className, id, onChange, onBlur, ref, speech = false, speechLang, speechColor, speechMode = 'insert', clearable, onKeyDown, locale, dictionary, 'aria-invalid': ariaInvalid, 'aria-describedby': ariaDescribedBy, ...rest }) {
|
|
33
|
+
export function KptInput({ type = 'text', required = false, invalid = false, touched, onValueChange, onTouch, hostProps, className, id, onChange, onBlur, ref, speech = false, speechLang, speechColor, speechMode = 'insert', clearable, icon, onKeyDown, locale, dictionary, 'aria-invalid': ariaInvalid, 'aria-describedby': ariaDescribedBy, ...rest }) {
|
|
34
34
|
const { className: hostClassName, ...hostRest } = hostProps ?? {};
|
|
35
35
|
const field = useKptFieldA11y(id, 'kpt-input');
|
|
36
36
|
const innerRef = useRef(null);
|
|
@@ -92,5 +92,5 @@ export function KptInput({ type = 'text', required = false, invalid = false, tou
|
|
|
92
92
|
onBlur?.(event);
|
|
93
93
|
onTouch?.();
|
|
94
94
|
}, [onBlur, onTouch]);
|
|
95
|
-
return (_jsxs("span", { className: cn('kpt-input-host', hostClassName), "data-speech": speechState, "data-clear": showClear ? '' : undefined, ...hostRest, children: [_jsx("input", { className: cn('kpt-input__field', className), id: field.id, "aria-describedby": joinIds(ariaDescribedBy, field.describedBy), type: type, "aria-required": required || undefined, "aria-invalid": ariaInvalid ?? (showInvalid || undefined), onChange: handleChange, onBlur: handleBlur, onKeyDown: handleKeyDown, ref: composeRefs(innerRef, ref), ...rest }), showClear ? (_jsx("button", { type: "button", className: "kpt-input__clear", tabIndex: -1, "aria-label": t('input.clear'), onMouseDown: (event) => event.preventDefault(), onClick: clear, children: _jsx(KptIcon, { name: "x" }) })) : null, speech ? (_jsx(KptSpeechButton, { listeningColor: speechColor, speech: mic, size: "sm", unsupported: "hide", disabled: !!rest.disabled || !!rest.readOnly, hostProps: { className: 'kpt-input__speech' } })) : null] }));
|
|
95
|
+
return (_jsxs("span", { className: cn('kpt-input-host', hostClassName), "data-speech": speechState, "data-clear": showClear ? '' : undefined, "data-icon": icon ? '' : undefined, ...hostRest, children: [icon ? _jsx(KptIcon, { className: "kpt-input__icon", name: icon }) : null, _jsx("input", { className: cn('kpt-input__field', className), id: field.id, "aria-describedby": joinIds(ariaDescribedBy, field.describedBy), type: type, "aria-required": required || undefined, "aria-invalid": ariaInvalid ?? (showInvalid || undefined), onChange: handleChange, onBlur: handleBlur, onKeyDown: handleKeyDown, ref: composeRefs(innerRef, ref), ...rest }), showClear ? (_jsx("button", { type: "button", className: "kpt-input__clear", tabIndex: -1, "aria-label": t('input.clear'), onMouseDown: (event) => event.preventDefault(), onClick: clear, children: _jsx(KptIcon, { name: "x" }) })) : null, speech ? (_jsx(KptSpeechButton, { listeningColor: speechColor, speech: mic, size: "sm", unsupported: "hide", disabled: !!rest.disabled || !!rest.readOnly, hostProps: { className: 'kpt-input__speech' } })) : null] }));
|
|
96
96
|
}
|
package/dist/input/llms.txt
CHANGED
|
@@ -16,6 +16,9 @@ Import: `import { KptInput } from '@konce-pt/react';`
|
|
|
16
16
|
- `required`: boolean - see the note below
|
|
17
17
|
- `invalid`: boolean, `touched`: boolean - the error state
|
|
18
18
|
- `clearable`: boolean (default: `true` only for `type="search"`) - a clear button, see "Clear button"
|
|
19
|
+
- `icon`: string - a registry icon at the start of the field (`search`, `mail`...). Decorative: the field's
|
|
20
|
+
name comes from the label or the aria label, so the icon stays out of the accessibility tree; a click on it
|
|
21
|
+
lands in the field
|
|
19
22
|
- `hostProps` - props for the `.kpt-input-host` wrapper
|
|
20
23
|
- `className`, `ref`, `name`, `placeholder`, `autoComplete` and every other `<input>` prop go to
|
|
21
24
|
the inner `<input>`
|
|
@@ -66,6 +69,7 @@ dictionary. The host gets `data-clear` while the button is visible.
|
|
|
66
69
|
|
|
67
70
|
<KptInput type="search" value={query} onValueChange={setQuery} />
|
|
68
71
|
<KptInput clearable value={coupon} onValueChange={setCoupon} />
|
|
72
|
+
<KptInput type="search" icon="search" value={query} onValueChange={setQuery} />
|
|
69
73
|
|
|
70
74
|
## Tokens
|
|
71
75
|
`--kpt-form-field-{bg,text,placeholder,border,border-hover,border-focus,border-error,radius}`.
|
package/dist/popover/llms.txt
CHANGED
|
@@ -17,6 +17,9 @@ Import: `import { KptPopover } from '@konce-pt/react';`
|
|
|
17
17
|
- `placement`: 'bottom' (default) | 'top' - the preferred side; it flips when there is no room
|
|
18
18
|
- `maxWidth`: string - the panel's maximum width, any CSS length (`'24rem'`; default `20rem`). The panel is
|
|
19
19
|
never wider than the window minus the 8 px overlay margin
|
|
20
|
+
- `ariaLabel`: string - the accessible name of the panel (`role="dialog"`); give one whenever the panel has
|
|
21
|
+
no visible heading. It names the panel, not the host - `aria-label` in the rest props would land on the
|
|
22
|
+
host `<span>`
|
|
20
23
|
- `className`, `ref` and every other `<span>` prop go to the host
|
|
21
24
|
|
|
22
25
|
## The panel is a portal
|
package/dist/public-api.d.ts
CHANGED
|
@@ -74,7 +74,7 @@ export { KptDataTable } from './data-table/data-table.tsx';
|
|
|
74
74
|
export type { KptDataTableProps } from './data-table/data-table.tsx';
|
|
75
75
|
export { exportTableCsv } from './data-table/export-csv.ts';
|
|
76
76
|
export type { KptCellEditContext, KptCellEditEvent, KptCellEditRenderers, KptCellRenderers, KptColumn, KptColumnEdit, KptColumnEditType, KptSortDir, KptTableLazyState, } from './data-table/data-table-types.ts';
|
|
77
|
-
export type { KptColumnFilter, KptColumnFilterType, KptColumnSummary, KptSummaryFn } from '@konce-pt/table';
|
|
77
|
+
export type { KptColumnFilter, KptColumnFilterType, KptColumnGroup, KptColumnSummary, KptSummaryFn } from '@konce-pt/table';
|
|
78
78
|
export { KptDataView } from './data-view/data-view.tsx';
|
|
79
79
|
export type { KptDataViewLayout, KptDataViewProps } from './data-view/data-view.tsx';
|
|
80
80
|
export { KptTree } from './tree/tree.tsx';
|
|
@@ -123,7 +123,7 @@ export type { KptOverlayProps } from './internal/overlay.tsx';
|
|
|
123
123
|
export { KptButtonGroup } from './button-group/button-group.tsx';
|
|
124
124
|
export type { KptButtonGroupProps } from './button-group/button-group.tsx';
|
|
125
125
|
export { KptSelect } from './select/select.tsx';
|
|
126
|
-
export type { KptSelectProps } from './select/select.tsx';
|
|
126
|
+
export type { KptSelectMultipleProps, KptSelectProps, KptSelectSingleProps } from './select/select.tsx';
|
|
127
127
|
export type { KptAlertVariant, KptNavigateEvent, KptOption } from './shared/types.ts';
|
|
128
128
|
export { KptFormField } from './form-field/form-field.tsx';
|
|
129
129
|
export type { KptFormFieldLabelType, KptFormFieldProps } from './form-field/form-field.tsx';
|
package/dist/select/llms.txt
CHANGED
|
@@ -15,7 +15,11 @@ A select on its own panel. Variants: single, with a search box (`filter`) and mu
|
|
|
15
15
|
|
|
16
16
|
## Props
|
|
17
17
|
- `options`: `KptOption<T>[]` - `{ label, value, disabled? }`
|
|
18
|
-
- `value` / `defaultValue` / `onValueChange` - a single `T`, or `T[]` in `multiple` mode
|
|
18
|
+
- `value` / `defaultValue` / `onValueChange` - a single `T`, or `T[]` in `multiple` mode. The handler type
|
|
19
|
+
follows `multiple` (overloads): without it `(value: T) => void` - a single select cannot clear its
|
|
20
|
+
choice, so it never reports an array or `null`; with `multiple` `(value: T[]) => void`. A flag computed at
|
|
21
|
+
run time (`multiple={flag}`) keeps the wide `T | T[] | null`. Types: `KptSelectSingleProps`,
|
|
22
|
+
`KptSelectMultipleProps`, `KptSelectProps` (the wide one)
|
|
19
23
|
- `open` / `defaultOpen` / `onOpenChange` - the panel state, so the list can be opened from outside
|
|
20
24
|
- `placeholder`, `multiple`, `filter`, `disabled`, `invalid`, `touched`
|
|
21
25
|
- `onTouch()` - fires when the panel closes or focus leaves the closed trigger
|
package/dist/select/select.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ComponentPropsWithRef } from 'react';
|
|
1
|
+
import { type ComponentPropsWithRef, type JSX } from 'react';
|
|
2
2
|
import { type KptI18nOverrides } from '../i18n/i18n-context.tsx';
|
|
3
3
|
import type { KptOption } from '../shared/types.ts';
|
|
4
4
|
export interface KptSelectProps<T> extends Omit<ComponentPropsWithRef<'div'>, 'children' | 'onChange' | 'defaultValue'>, KptI18nOverrides {
|
|
@@ -28,6 +28,23 @@ export interface KptSelectProps<T> extends Omit<ComponentPropsWithRef<'div'>, 'c
|
|
|
28
28
|
/** Wywoływane, gdy panel się zamyka albo trigger traci fokus. */
|
|
29
29
|
onTouch?: () => void;
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Pojedynczy wybór: `onValueChange` dostaje zawsze wybraną opcję - nigdy tablicę ani `null`, bo
|
|
33
|
+
* lista bez `multiple` nie ma jak wyczyścić wyboru. Rodzic trzyma więc zwykłe `T`.
|
|
34
|
+
*/
|
|
35
|
+
export interface KptSelectSingleProps<T> extends Omit<KptSelectProps<T>, 'multiple' | 'value' | 'defaultValue' | 'onValueChange'> {
|
|
36
|
+
multiple?: false;
|
|
37
|
+
value?: T | null;
|
|
38
|
+
defaultValue?: T | null;
|
|
39
|
+
onValueChange?: (value: T) => void;
|
|
40
|
+
}
|
|
41
|
+
/** Wybór wielokrotny: wartością jest zawsze tablica (pusta, gdy nic nie wybrano). */
|
|
42
|
+
export interface KptSelectMultipleProps<T> extends Omit<KptSelectProps<T>, 'multiple' | 'value' | 'defaultValue' | 'onValueChange'> {
|
|
43
|
+
multiple: true;
|
|
44
|
+
value?: T[] | null;
|
|
45
|
+
defaultValue?: T[] | null;
|
|
46
|
+
onValueChange?: (value: T[]) => void;
|
|
47
|
+
}
|
|
31
48
|
/**
|
|
32
49
|
* Lista wyboru (select) na własnym panelu. Warianty: pojedynczy, z wyszukiwarką (`filter`)
|
|
33
50
|
* i wielokrotny (`multiple`, chipy).
|
|
@@ -46,5 +63,10 @@ export interface KptSelectProps<T> extends Omit<ComponentPropsWithRef<'div'>, 'c
|
|
|
46
63
|
* @example
|
|
47
64
|
* <KptSelect options={roles} value={role} onValueChange={setRole} placeholder="Wybierz..." />
|
|
48
65
|
* <KptSelect options={tags} multiple filter value={tags} onValueChange={setTags} />
|
|
66
|
+
*
|
|
67
|
+
* Typ `onValueChange` idzie za `multiple`: bez niego `(value: T)`, z `multiple` - `(value: T[])`.
|
|
68
|
+
* `multiple` liczone w locie (`multiple={flag}`) zostawia szeroki typ `T | T[] | null`.
|
|
49
69
|
*/
|
|
50
|
-
export declare function KptSelect<T>(
|
|
70
|
+
export declare function KptSelect<T>(props: Readonly<KptSelectSingleProps<T>>): JSX.Element;
|
|
71
|
+
export declare function KptSelect<T>(props: Readonly<KptSelectMultipleProps<T>>): JSX.Element;
|
|
72
|
+
export declare function KptSelect<T>(props: Readonly<KptSelectProps<T>>): JSX.Element;
|
package/dist/select/select.js
CHANGED
|
@@ -18,25 +18,6 @@ function filterSelectOptions(options, text) {
|
|
|
18
18
|
const q = foldMentionText(text.trim());
|
|
19
19
|
return q ? options.filter((option) => foldMentionText(option.label).includes(q)) : options;
|
|
20
20
|
}
|
|
21
|
-
/**
|
|
22
|
-
* Lista wyboru (select) na własnym panelu. Warianty: pojedynczy, z wyszukiwarką (`filter`)
|
|
23
|
-
* i wielokrotny (`multiple`, chipy).
|
|
24
|
-
*
|
|
25
|
-
* Klawiatura według wzorca WAI-ARIA "select-only combobox": fokus zostaje na polu, a aktywna opcja
|
|
26
|
-
* idzie przez `aria-activedescendant`. `ArrowDown`/`ArrowUp`/`Enter`/`Spacja` otwierają listę na wybranej opcji,
|
|
27
|
-
* strzałki, `Home`/`End`, `PageUp`/`PageDown` chodzą po opcjach (z pominięciem wyłączonych),
|
|
28
|
-
* wpisywane litery skaczą do pasującej opcji (bez polskich znaków), `Enter` wybiera, `Escape`
|
|
29
|
-
* zamyka. Z `filter` fokus przechodzi do pola wyszukiwania, które obsługuje te same klawisze.
|
|
30
|
-
* W trybie `multiple` `Backspace` na zamkniętym polu usuwa ostatni wybór.
|
|
31
|
-
*
|
|
32
|
-
* Wewnątrz `KptFormField` zgłasza wypełnienie hookiem `useKptFormFieldFilled`, więc etykieta
|
|
33
|
-
* Float Label unosi się nad wybraną wartością - natywnego pola, z którego wrapper mógłby to
|
|
34
|
-
* odczytać, tu nie ma.
|
|
35
|
-
*
|
|
36
|
-
* @example
|
|
37
|
-
* <KptSelect options={roles} value={role} onValueChange={setRole} placeholder="Wybierz..." />
|
|
38
|
-
* <KptSelect options={tags} multiple filter value={tags} onValueChange={setTags} />
|
|
39
|
-
*/
|
|
40
21
|
export function KptSelect({ options = [], placeholder = '', multiple = false, filter = false, disabled = false, readOnly = false, invalid = false, touched, value, defaultValue = null, onValueChange, open, defaultOpen = false, onOpenChange, onTouch, locale, dictionary, className, onBlur, id, required = false, 'aria-label': ariaLabel, 'aria-describedby': ariaDescribedBy, ...rest }) {
|
|
41
22
|
/** `div` z rolą nie dostaje nazwy z `label[for]` - stąd `aria-labelledby` na etykietę wrappera. */
|
|
42
23
|
const field = useKptFieldA11y(id, 'kpt-select-trigger');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@konce-pt/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.1",
|
|
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/
|
|
69
|
-
"@konce-pt/
|
|
70
|
-
"@konce-pt/
|
|
71
|
-
"@konce-pt/
|
|
72
|
-
"@konce-pt/
|
|
73
|
-
"@konce-pt/
|
|
74
|
-
"@konce-pt/
|
|
75
|
-
"@konce-pt/
|
|
76
|
-
"@konce-pt/
|
|
77
|
-
"@konce-pt/
|
|
78
|
-
"@konce-pt/
|
|
79
|
-
"@konce-pt/
|
|
80
|
-
"@konce-pt/
|
|
68
|
+
"@konce-pt/backdrop": "0.18.1",
|
|
69
|
+
"@konce-pt/icons": "0.18.1",
|
|
70
|
+
"@konce-pt/chart": "0.18.1",
|
|
71
|
+
"@konce-pt/roadmap": "0.18.1",
|
|
72
|
+
"@konce-pt/mention": "0.18.1",
|
|
73
|
+
"@konce-pt/speech": "0.18.1",
|
|
74
|
+
"@konce-pt/motion": "0.18.1",
|
|
75
|
+
"@konce-pt/i18n": "0.18.1",
|
|
76
|
+
"@konce-pt/map": "0.18.1",
|
|
77
|
+
"@konce-pt/chat": "0.18.1",
|
|
78
|
+
"@konce-pt/tokens": "0.18.1",
|
|
79
|
+
"@konce-pt/datetime": "0.18.1",
|
|
80
|
+
"@konce-pt/table": "0.18.1"
|
|
81
81
|
},
|
|
82
82
|
"devDependencies": {
|
|
83
83
|
"@types/react": "^19.2.0",
|