@konce-pt/react 0.8.3 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.md +4 -4
  2. package/dist/backdrop/backdrop.d.ts +79 -0
  3. package/dist/backdrop/backdrop.js +195 -0
  4. package/dist/backdrop/llms.txt +132 -0
  5. package/dist/button/button.d.ts +23 -7
  6. package/dist/button/button.js +33 -9
  7. package/dist/button/llms.txt +48 -12
  8. package/dist/button-group/button-group.d.ts +7 -1
  9. package/dist/button-group/button-group.js +9 -4
  10. package/dist/button-group/llms.txt +30 -5
  11. package/dist/chat/chat.d.ts +53 -0
  12. package/dist/chat/chat.js +160 -0
  13. package/dist/chat/llms.txt +78 -0
  14. package/dist/chat-composer/chat-composer.d.ts +41 -0
  15. package/dist/chat-composer/chat-composer.js +81 -0
  16. package/dist/chat-composer/llms.txt +56 -0
  17. package/dist/chat-dock/chat-dock.d.ts +40 -0
  18. package/dist/chat-dock/chat-dock.js +67 -0
  19. package/dist/chat-dock/llms.txt +71 -0
  20. package/dist/chat-entry/public-api.d.ts +12 -0
  21. package/dist/chat-entry/public-api.js +10 -0
  22. package/dist/chat-message/chat-message.d.ts +53 -0
  23. package/dist/chat-message/chat-message.js +112 -0
  24. package/dist/chat-message/llms.txt +73 -0
  25. package/dist/chat-typing/chat-typing.d.ts +23 -0
  26. package/dist/chat-typing/chat-typing.js +30 -0
  27. package/dist/chat-typing/llms.txt +41 -0
  28. package/dist/chip/chip.d.ts +10 -1
  29. package/dist/chip/chip.js +3 -2
  30. package/dist/chip/llms.txt +9 -0
  31. package/dist/data-view/data-view.d.ts +15 -3
  32. package/dist/data-view/data-view.js +22 -5
  33. package/dist/data-view/llms.txt +20 -7
  34. package/dist/dialog/dialog.d.ts +9 -1
  35. package/dist/dialog/dialog.js +2 -2
  36. package/dist/internal/dev.d.ts +1 -0
  37. package/dist/internal/dev.js +3 -0
  38. package/dist/internal/overlay.d.ts +6 -1
  39. package/dist/internal/overlay.js +5 -3
  40. package/dist/mention/llms.txt +60 -0
  41. package/dist/mention/mention.d.ts +38 -0
  42. package/dist/mention/mention.js +29 -0
  43. package/dist/mention/mentions.d.ts +31 -0
  44. package/dist/mention/mentions.js +24 -0
  45. package/dist/order-list/llms.txt +16 -5
  46. package/dist/order-list/order-list.d.ts +5 -3
  47. package/dist/order-list/order-list.js +69 -4
  48. package/dist/peek/llms.txt +42 -0
  49. package/dist/peek/peek.d.ts +35 -0
  50. package/dist/peek/peek.js +127 -0
  51. package/dist/pick-list/llms.txt +19 -5
  52. package/dist/pick-list/pick-list.d.ts +7 -2
  53. package/dist/pick-list/pick-list.js +123 -17
  54. package/dist/progress/llms.txt +70 -22
  55. package/dist/progress/progress.d.ts +34 -7
  56. package/dist/progress/progress.js +46 -9
  57. package/dist/public-api.d.ts +15 -3
  58. package/dist/public-api.js +6 -0
  59. package/dist/rich-text/llms.txt +10 -0
  60. package/dist/rich-text/rich-text.d.ts +11 -1
  61. package/dist/rich-text/rich-text.js +157 -5
  62. package/dist/rich-text/sanitize.js +10 -1
  63. package/dist/roadmap/roadmap.js +1 -3
  64. package/dist/shared/types.d.ts +2 -0
  65. package/package.json +16 -9
@@ -1,47 +1,95 @@
1
1
  # KptProgress (@konce-pt/react)
2
2
 
3
- A progress indicator — linear or circular, determinate (0–100) or indeterminate (`value` = null).
3
+ A progress indicator — linear or circular, determinate (`min`–`max`) or indeterminate (`value` = null).
4
4
  Import: `import { KptProgress } from '@konce-pt/react';`
5
5
 
6
6
  ## DOM
7
- <!-- linear -->
8
- <div class="kpt-progress-host">
9
- <div class="kpt-progress__linear" data-variant="primary" role="progressbar" aria-valuenow="65">
10
- <div class="kpt-progress__bar" style="width: 65%"></div>
7
+ <!-- linear, with a label, a buffer and a value -->
8
+ <div class="kpt-progress-host" data-type="linear" data-size="md" data-variant="primary" data-label-position="top">
9
+ <span class="kpt-progress__label">Loading…</span>
10
+ <div class="kpt-progress__linear" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="80">
11
+ <div class="kpt-progress__buffer" style="width: 92%"></div>
12
+ <div class="kpt-progress__bar" style="width: 80%"></div>
11
13
  </div>
14
+ <span class="kpt-progress__value" aria-hidden="true">80%</span>
12
15
  </div>
13
16
 
14
17
  <!-- circular -->
15
- <svg class="kpt-progress__circular" data-variant="success" viewBox="0 0 40 40" role="progressbar" aria-valuenow="75">
16
- <circle class="kpt-progress__track" cx="20" cy="20" r="16"></circle>
17
- <circle class="kpt-progress__bar" cx="20" cy="20" r="16" style="stroke-dasharray: 100.531; stroke-dashoffset: 25.1327"></circle>
18
- </svg>
18
+ <div class="kpt-progress-host" data-type="circular" data-size="md" data-variant="success">
19
+ <svg class="kpt-progress__circular" viewBox="0 0 40 40" role="progressbar" aria-valuenow="75">
20
+ <circle class="kpt-progress__track" cx="20" cy="20" r="16"></circle>
21
+ <circle class="kpt-progress__bar" cx="20" cy="20" r="16" style="stroke-dasharray: 100.531; stroke-dashoffset: 25.1327"></circle>
22
+ </svg>
23
+ <span class="kpt-progress__value" aria-hidden="true">75%</span>
24
+ </div>
25
+
26
+ The label, the track and the value are siblings straight under the host — there is no wrapper element.
27
+ `data-variant`, `data-type`, `data-size` and `data-label-position` all sit on the host, which is also
28
+ where the private `--_color` and `--_thickness` are resolved. This markup matches the Angular port
29
+ element for element, so both share one stylesheet.
19
30
 
20
31
  ## Props
21
- - `value`: number | null (default null) — 0–100, or null for indeterminate
32
+ - `value`: number | null (default null) — a value on the `min`–`max` scale, or null for indeterminate
33
+ - `min`: number (default 0); `max`: number (default 100)
22
34
  - `variant`: 'primary' | 'success' | 'danger' | 'warning' | 'info' (default 'primary')
23
35
  - `type`: 'linear' | 'circular' (default 'linear')
36
+ - `size`: 'sm' | 'md' | 'lg' (default 'md') — track thickness, and arc width on the circular variant
24
37
  - `diameter`: number (default 40) — the circular variant's size in pixels
38
+ - `bufferValue`: number | null (default null) — a second, dimmed bar behind the main one; linear only
39
+ - `showValue`: boolean (default false) — turns the value area on; `children` needs it too
40
+ - `valueFormat`: 'percent' | 'fraction' | 'none' (default 'percent')
41
+ - `label`: string — descriptive text next to the bar; it also becomes the `aria-label`
42
+ - `labelPosition`: 'top' | 'bottom' | 'end' (default 'top') — ignored by the circular variant
43
+ - `locale`, `dictionary` — per-instance dictionary overrides
25
44
  - `className`, `ref` and every other `<div>` prop go to the host
26
45
 
46
+ ## Value label
47
+ `showValue` opens the value area; `valueFormat` decides what goes in it — `percent` renders `80%`,
48
+ `fraction` renders `152 / 160` from `value` and `max`, `none` leaves it empty. Both strings come from
49
+ the `KptMessages` dictionary (`progress.percent`, `progress.fraction`), so a new locale can reshape them.
50
+
51
+ `children` replaces the formatted value instead of adding to it, which is how a circle shows a headline
52
+ number over a caption. It still requires `showValue`, because that prop is what renders the element the
53
+ content lands in.
54
+
55
+ The circular variant always centres the value over the arc; `label` is not drawn there, it only feeds
56
+ the accessible name.
57
+
27
58
  ## How the arc is drawn
28
- The circle lives in a fixed `viewBox="0 0 40 40"` with `r=16`, so the circumference is computed
29
- from that radius, not from `diameter` — the SVG scales, the maths does not change. The arc is the
30
- dash offset: full circumference means empty, zero means complete. An indeterminate circle is pinned
31
- at a quarter of the circumference and spun by CSS.
59
+ The circle lives in a fixed `viewBox="0 0 40 40"` with `r=16`, so the circumference is computed from
60
+ that radius, not from `diameter` — the SVG scales, the maths does not change. The arc is the dash
61
+ offset: full circumference means empty, zero means complete. `size` only changes `stroke-width`, and
62
+ the widest step still fits inside the box, so no arc is ever clipped.
32
63
 
33
- Values outside 0–100 are clamped rather than rejected, so a computed percentage that briefly
34
- overshoots cannot break the drawing.
64
+ Values outside the range are clamped rather than rejected, `max` ≤ `min` collapses to zero, and the
65
+ buffer is never allowed behind the main bar — a computed percentage that briefly overshoots cannot
66
+ break the drawing.
35
67
 
36
68
  ## Examples
37
- <KptProgress value={65} />
38
- <KptProgress value={null} variant="info" />
39
- <KptProgress type="circular" value={75} variant="success" diameter={64} />
69
+ <KptProgress value={60} />
70
+ <KptProgress value={80} label="Loading…" showValue />
71
+ <KptProgress value={152} max={160} size="lg" showValue valueFormat="fraction" labelPosition="bottom" />
72
+ <KptProgress value={65} showValue labelPosition="end" />
73
+ <KptProgress value={38} bufferValue={62} />
74
+ <KptProgress type="circular" value={80} showValue />
75
+ <KptProgress type="circular" value={72} diameter={140} size="lg" showValue>
76
+ <strong>29</strong>
77
+ <small>Excellent</small>
78
+ </KptProgress>
79
+ <KptProgress value={null} />
40
80
 
41
81
  ## Tokens
42
82
  The semantic colour of each variant (`--kpt-color-primary` / `-success` / `-danger` / `-warning` /
43
- `-info`), `--kpt-color-muted` for the track.
83
+ `-info`), plus the component layer: `--kpt-progress-track-bg`, `--kpt-progress-thickness-sm`,
84
+ `--kpt-progress-thickness-md`, `--kpt-progress-thickness-lg`, `--kpt-progress-buffer-opacity`,
85
+ `--kpt-progress-label-fg`, `--kpt-progress-value-fg`, `--kpt-progress-gap`.
44
86
 
45
87
  ## Accessibility
46
- `role="progressbar"` with `aria-valuenow`; an indeterminate bar leaves the value out, which is how
47
- a screen reader learns the progress is unknown rather than zero.
88
+ `role="progressbar"` with `aria-valuemin`, `aria-valuemax` and `aria-valuenow`; an indeterminate bar
89
+ leaves the value out, which is how a screen reader learns the progress is unknown rather than zero.
90
+ `valueFormat="fraction"` also sets `aria-valuetext`, so the reader says "152 / 160" instead of
91
+ announcing the raw number as a percentage. The visible value is `aria-hidden`, so it is never read
92
+ twice. The accessible name comes from `label`, or from the dictionary when there is none.
93
+
94
+ Under `prefers-reduced-motion: reduce` the indeterminate animation slows down instead of stopping —
95
+ a frozen progress indicator reads as a hung application.
@@ -1,19 +1,46 @@
1
- import type { ComponentPropsWithRef } from 'react';
1
+ import type { ComponentPropsWithRef, ReactNode } from 'react';
2
+ import { type KptI18nOverrides } from '../i18n/i18n-context.tsx';
2
3
  export type KptProgressVariant = 'primary' | 'success' | 'danger' | 'warning' | 'info';
3
- export interface KptProgressProps extends Omit<ComponentPropsWithRef<'div'>, 'children'> {
4
- /** 0–100 albo `null` = postęp nieokreślony. */
4
+ export type KptProgressType = 'linear' | 'circular';
5
+ export type KptProgressSize = 'sm' | 'md' | 'lg';
6
+ /** Postać etykiety wartości: procent, „ile z ilu" albo brak. */
7
+ export type KptProgressValueFormat = 'percent' | 'fraction' | 'none';
8
+ /** Gdzie siada wiersz etykiety względem paska (tylko `type="linear"`). */
9
+ export type KptProgressLabelPosition = 'top' | 'bottom' | 'end';
10
+ export interface KptProgressProps extends ComponentPropsWithRef<'div'>, KptI18nOverrides {
11
+ /** Wartość w skali `min`–`max` albo `null` = postęp nieokreślony. */
5
12
  value?: number | null;
13
+ min?: number;
14
+ max?: number;
6
15
  variant?: KptProgressVariant;
7
- type?: 'linear' | 'circular';
16
+ type?: KptProgressType;
17
+ /** Grubość toru: `sm` 0.25rem, `md` 0.5rem, `lg` 0.75rem. */
18
+ size?: KptProgressSize;
8
19
  /** Średnica wariantu kołowego w pikselach. */
9
20
  diameter?: number;
21
+ /** Postęp buforowania — drugi, przygaszony pasek pod głównym (tylko liniowy). */
22
+ bufferValue?: number | null;
23
+ /** Włącza obszar etykiety wartości — razem z nim renderuje się `children`. */
24
+ showValue?: boolean;
25
+ valueFormat?: KptProgressValueFormat;
26
+ /** Tekst opisowy obok paska; służy też za `aria-label`. */
27
+ label?: string;
28
+ labelPosition?: KptProgressLabelPosition;
29
+ /** Zastępuje sformatowaną wartość — wymaga `showValue`. */
30
+ children?: ReactNode;
10
31
  }
11
32
  /**
12
- * Wskaźnik postępu — liniowy lub kołowy, określony (0–100) albo nieokreślony (`value` = null).
33
+ * Wskaźnik postępu — liniowy lub kołowy, określony (`min`–`max`) albo nieokreślony (`value` = null).
34
+ *
35
+ * Etykietę wartości włącza `showValue`; `children` zastępuje sformatowaną wartość,
36
+ * więc w środku koła można pokazać cokolwiek.
13
37
  *
14
38
  * @example
15
39
  * <KptProgress value={60} />
16
- * <KptProgress type="circular" value={75} variant="success" />
40
+ * <KptProgress value={80} label="Ładowanie…" showValue />
41
+ * <KptProgress value={152} max={160} showValue valueFormat="fraction" labelPosition="bottom" />
42
+ * <KptProgress value={38} bufferValue={62} />
43
+ * <KptProgress type="circular" value={75} variant="success" showValue />
17
44
  * <KptProgress value={null} />
18
45
  */
19
- export declare function KptProgress({ value, variant, type, diameter, className, ...rest }: Readonly<KptProgressProps>): import("react").JSX.Element;
46
+ export declare function KptProgress({ value, min, max, variant, type, size, diameter, bufferValue, showValue, valueFormat, label, labelPosition, locale, dictionary, className, children, ...rest }: Readonly<KptProgressProps>): import("react").JSX.Element;
@@ -1,21 +1,58 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { cn } from "../internal/cn.js";
3
+ import { useKptMessages } from "../i18n/i18n-context.js";
3
4
  /** Promień okręgu w viewBox 0 0 40 40 — obwód liczymy z niego, nie ze średnicy w pikselach. */
4
5
  const RADIUS = 16;
5
6
  const CIRCUMFERENCE = 2 * Math.PI * RADIUS;
6
7
  /**
7
- * Wskaźnik postępu — liniowy lub kołowy, określony (0–100) albo nieokreślony (`value` = null).
8
+ * Wskaźnik postępu — liniowy lub kołowy, określony (`min`–`max`) albo nieokreślony (`value` = null).
9
+ *
10
+ * Etykietę wartości włącza `showValue`; `children` zastępuje sformatowaną wartość,
11
+ * więc w środku koła można pokazać cokolwiek.
8
12
  *
9
13
  * @example
10
14
  * <KptProgress value={60} />
11
- * <KptProgress type="circular" value={75} variant="success" />
15
+ * <KptProgress value={80} label="Ładowanie…" showValue />
16
+ * <KptProgress value={152} max={160} showValue valueFormat="fraction" labelPosition="bottom" />
17
+ * <KptProgress value={38} bufferValue={62} />
18
+ * <KptProgress type="circular" value={75} variant="success" showValue />
12
19
  * <KptProgress value={null} />
13
20
  */
14
- export function KptProgress({ value = null, variant = 'primary', type = 'linear', diameter = 40, className, ...rest }) {
21
+ export function KptProgress({ value = null, min = 0, max = 100, variant = 'primary', type = 'linear', size = 'md', diameter = 40, bufferValue = null, showValue = false, valueFormat = 'percent', label, labelPosition = 'top', locale, dictionary, className, children, ...rest }) {
22
+ const { t } = useKptMessages({ locale, dictionary });
15
23
  const indeterminate = value == null;
16
- // Nieokreślony postęp pokazuje wycinek 25% obwodu, który obraca CSS — stąd stały offset.
17
- const offset = indeterminate
18
- ? CIRCUMFERENCE * 0.75
19
- : CIRCUMFERENCE * (1 - Math.min(100, Math.max(0, value)) / 100);
20
- return (_jsx("div", { className: cn('kpt-progress-host', className), ...rest, children: type === 'circular' ? (_jsxs("svg", { className: cn('kpt-progress__circular', indeterminate && 'kpt-progress--indeterminate'), "data-variant": variant, width: diameter, height: diameter, viewBox: "0 0 40 40", role: "progressbar", "aria-valuenow": value ?? undefined, children: [_jsx("circle", { className: "kpt-progress__track", cx: "20", cy: "20", r: RADIUS }), _jsx("circle", { className: "kpt-progress__bar", cx: "20", cy: "20", r: RADIUS, style: { strokeDasharray: CIRCUMFERENCE, strokeDashoffset: offset } })] })) : (_jsx("div", { className: "kpt-progress__linear", "data-variant": variant, role: "progressbar", "aria-valuenow": value ?? undefined, children: _jsx("div", { className: cn('kpt-progress__bar', indeterminate && 'kpt-progress--indeterminate'), style: { width: `${value ?? 0}%` } }) })) }));
24
+ /** Ułamek 0–1. Odporny na `max` ≤ `min` i na wartości spoza zakresu. */
25
+ const toRatio = (v) => {
26
+ if (v == null)
27
+ return 0;
28
+ const span = max - min;
29
+ if (span <= 0)
30
+ return 0;
31
+ return Math.min(1, Math.max(0, (v - min) / span));
32
+ };
33
+ const ratio = toRatio(value);
34
+ const percent = Math.round(ratio * 100);
35
+ // Bufor nigdy nie cofa się za pasek główny — inaczej wyglądałby jak błąd renderowania.
36
+ const bufferRatio = Math.max(ratio, toRatio(bufferValue));
37
+ const offset = CIRCUMFERENCE * (1 - ratio);
38
+ const fractionText = () => t('progress.fraction', { value: value ?? 0, max });
39
+ let valueText = '';
40
+ if (!indeterminate && valueFormat !== 'none') {
41
+ valueText = valueFormat === 'fraction' ? fractionText() : t('progress.percent', { value: percent });
42
+ }
43
+ /**
44
+ * Czytnik ekranu domyślnie odczytuje `aria-valuenow` jako procent, więc przy formacie „ile z ilu"
45
+ * podajemy mu ten sam napis, który widzi użytkownik.
46
+ */
47
+ const ariaValueText = indeterminate || valueFormat !== 'fraction' ? undefined : fractionText();
48
+ const ariaLabel = label ?? t(indeterminate ? 'progress.loading' : 'progress.label');
49
+ const barAria = {
50
+ role: 'progressbar',
51
+ 'aria-label': ariaLabel,
52
+ 'aria-valuemin': min,
53
+ 'aria-valuemax': max,
54
+ 'aria-valuenow': indeterminate ? undefined : (value ?? undefined),
55
+ 'aria-valuetext': ariaValueText,
56
+ };
57
+ return (_jsxs("div", { className: cn('kpt-progress-host', className), "data-type": type, "data-size": size, "data-variant": variant, "data-label-position": labelPosition, ...rest, children: [type === 'circular' ? (_jsxs("svg", { className: cn('kpt-progress__circular', indeterminate && 'kpt-progress--indeterminate'), width: diameter, height: diameter, viewBox: "0 0 40 40", ...barAria, children: [_jsx("circle", { className: "kpt-progress__track", cx: "20", cy: "20", r: RADIUS }), _jsx("circle", { className: "kpt-progress__bar", cx: "20", cy: "20", r: RADIUS, style: indeterminate ? undefined : { strokeDasharray: CIRCUMFERENCE, strokeDashoffset: offset } })] })) : (_jsxs(_Fragment, { children: [label ? _jsx("span", { className: "kpt-progress__label", children: label }) : null, _jsxs("div", { className: "kpt-progress__linear", ...barAria, children: [bufferValue != null && !indeterminate ? (_jsx("div", { className: "kpt-progress__buffer", style: { width: `${bufferRatio * 100}%` } })) : null, _jsx("div", { className: cn('kpt-progress__bar', indeterminate && 'kpt-progress--indeterminate'), style: indeterminate ? undefined : { width: `${ratio * 100}%` } })] })] })), showValue ? (_jsx("span", { className: "kpt-progress__value", "aria-hidden": "true", children: children ?? valueText })) : null] }));
21
58
  }
@@ -23,7 +23,7 @@ export type { KptDividerOrientation, KptDividerProps } from './divider/divider.t
23
23
  export { KptEmpty } from './empty/empty.tsx';
24
24
  export type { KptEmptyProps } from './empty/empty.tsx';
25
25
  export { KptProgress } from './progress/progress.tsx';
26
- export type { KptProgressProps, KptProgressVariant } from './progress/progress.tsx';
26
+ export type { KptProgressProps, KptProgressVariant, KptProgressType, KptProgressSize, KptProgressValueFormat, KptProgressLabelPosition, } from './progress/progress.tsx';
27
27
  export { KptSpinner } from './spinner/spinner.tsx';
28
28
  export type { KptSpinnerProps } from './spinner/spinner.tsx';
29
29
  export { KptSkeleton } from './skeleton/skeleton.tsx';
@@ -36,10 +36,14 @@ export { KptAvatar } from './avatar/avatar.tsx';
36
36
  export type { KptAvatarProps, KptAvatarShape, KptAvatarSize } from './avatar/avatar.tsx';
37
37
  export { KptBadge } from './badge/badge.tsx';
38
38
  export type { KptBadgeProps, KptBadgeSize, KptBadgeVariant } from './badge/badge.tsx';
39
+ export { KptBackdrop } from './backdrop/backdrop.tsx';
40
+ export type { KptBackdropAnchor, KptBackdropEffect, KptBackdropProps } from './backdrop/backdrop.tsx';
41
+ export type { KptBackdropRenderOptions, KptBackdropRenderer, KptOklab, KptRgb, KptSignalGridCell, KptSignalGridFrame, KptSignalGridOptions, KptWaveOptions, KptWaveSettings, } from '@konce-pt/backdrop';
42
+ export { KPT_SIGNAL_GRID_CELL, KPT_SIGNAL_GRID_DENSITY, KPT_SIGNAL_GRID_FALLOFF, KPT_SIGNAL_GRID_GAP, KPT_WAVE_AMPLITUDE, KPT_WAVE_BAND, KPT_WAVE_RESOLUTION, KPT_WAVE_ROTATION, buildSignalGrid, createBackdropRenderer, mixOklch, readCssColor, resolveWave, shiftHue, signalGridAlpha, signalGridDensity, waveIntensity, waveRamp, } from '@konce-pt/backdrop';
39
43
  export { KptChip } from './chip/chip.tsx';
40
- export type { KptChipProps, KptChipVariant } from './chip/chip.tsx';
44
+ export type { KptChipProps, KptChipTextTransform, KptChipVariant } from './chip/chip.tsx';
41
45
  export { KptDialog, KptDialogFooter } from './dialog/dialog.tsx';
42
- export type { KptDialogProps } from './dialog/dialog.tsx';
46
+ export type { KptDialogProps, KptDialogSize } from './dialog/dialog.tsx';
43
47
  export { KptDrawer } from './drawer/drawer.tsx';
44
48
  export type { KptDrawerPosition, KptDrawerProps } from './drawer/drawer.tsx';
45
49
  export { KptBottomSheet } from './bottom-sheet/bottom-sheet.tsx';
@@ -102,6 +106,14 @@ export { KptTooltip } from './tooltip/tooltip.tsx';
102
106
  export type { KptTooltipPosition, KptTooltipProps } from './tooltip/tooltip.tsx';
103
107
  export { KptPopover } from './popover/popover.tsx';
104
108
  export type { KptPopoverProps } from './popover/popover.tsx';
109
+ export { KptPeek } from './peek/peek.tsx';
110
+ export type { KptPeekPlacement, KptPeekProps } from './peek/peek.tsx';
111
+ export { KptMention } from './mention/mention.tsx';
112
+ export type { KptMentionProps } from './mention/mention.tsx';
113
+ export { KptMentions } from './mention/mentions.tsx';
114
+ export type { KptMentionsProps } from './mention/mentions.tsx';
115
+ export type { KptMentionItem, KptMentionQuery, KptMentionSegment } from '@konce-pt/mention';
116
+ export { KPT_MENTION_LIMIT, applyMention, collectMentions, filterMentions, foldMentionText, mentionQuery, mentionToken, parseMentionText, } from '@konce-pt/mention';
105
117
  export { KptOverlay } from './internal/overlay.tsx';
106
118
  export type { KptOverlayProps } from './internal/overlay.tsx';
107
119
  export { KptButtonGroup } from './button-group/button-group.tsx';
@@ -27,6 +27,8 @@ export { KptIcon } from "./icon/icon.js";
27
27
  export { getKptIcon, hasKptIcon, registerKptIcons } from "./icon/icon-registry.js";
28
28
  export { KptAvatar } from "./avatar/avatar.js";
29
29
  export { KptBadge } from "./badge/badge.js";
30
+ export { KptBackdrop } from "./backdrop/backdrop.js";
31
+ export { KPT_SIGNAL_GRID_CELL, KPT_SIGNAL_GRID_DENSITY, KPT_SIGNAL_GRID_FALLOFF, KPT_SIGNAL_GRID_GAP, KPT_WAVE_AMPLITUDE, KPT_WAVE_BAND, KPT_WAVE_RESOLUTION, KPT_WAVE_ROTATION, buildSignalGrid, createBackdropRenderer, mixOklch, readCssColor, resolveWave, shiftHue, signalGridAlpha, signalGridDensity, waveIntensity, waveRamp, } from '@konce-pt/backdrop';
30
32
  export { KptChip } from "./chip/chip.js";
31
33
  // Komponenty — nakładki
32
34
  export { KptDialog, KptDialogFooter } from "./dialog/dialog.js";
@@ -60,6 +62,10 @@ export { KptBreadcrumb } from "./breadcrumb/breadcrumb.js";
60
62
  export { KptContextMenu } from "./context-menu/context-menu.js";
61
63
  export { KptTooltip } from "./tooltip/tooltip.js";
62
64
  export { KptPopover } from "./popover/popover.js";
65
+ export { KptPeek } from "./peek/peek.js";
66
+ export { KptMention } from "./mention/mention.js";
67
+ export { KptMentions } from "./mention/mentions.js";
68
+ export { KPT_MENTION_LIMIT, applyMention, collectMentions, filterMentions, foldMentionText, mentionQuery, mentionToken, parseMentionText, } from '@konce-pt/mention';
63
69
  export { KptOverlay } from "./internal/overlay.js";
64
70
  // Komponenty — formularze
65
71
  export { KptButtonGroup } from "./button-group/button-group.js";
@@ -9,8 +9,18 @@ Import: `import { KptRichText, type KptRichTool } from '@konce-pt/react';`
9
9
  - `toolbar`: `KptRichTool[] | KptRichTool[][]` — a flat list, or groups = sections separated by a
10
10
  divider. Leaving a tool or a group out disables it; the full toolbar in 5 sections by default.
11
11
  - `placeholder`, `maxListLevel` (6), `disabled`, `invalid`, `touched`, `onTouch()`
12
+ - `mentions`: `readonly KptMentionItem[]`, `mentionLimit` (8), `onMention(item)` — suggestions
13
+ for `@`; an empty list turns the mechanism off, so `@` stays an ordinary character
12
14
  - `className`, `ref` and every other `<div>` prop go to the host
13
15
 
16
+ ## Mentions (`@`)
17
+ With `mentions` set, typing `@` opens a suggestion list anchored at the caret: arrows move through
18
+ it, Enter or Tab inserts, Escape closes. The query is read from the **text node** at the caret and
19
+ recognised by `mentionQuery()` from `@konce-pt/mention` — the same rules as in the Angular port.
20
+ An inserted mention is `<span class="kpt-mention" data-mention="id" contenteditable="false">@Name`
21
+ plus a non-breaking space: the id survives saving, the flag keeps the mention in one piece and the
22
+ space gives the caret a place to stand after it.
23
+
14
24
  ## Tools (KptRichTool)
15
25
  Clipboard: `copy`, `paste` (sanitized), `pasteText`. Style: `heading` (a panel: Normal + H1–H6).
16
26
  Format: `bold`, `italic`, `underline`, `strike`, `ul`, `ol`, `indent`, `outdent`.
@@ -1,4 +1,5 @@
1
1
  import { type ComponentPropsWithRef } from 'react';
2
+ import { type KptMentionItem } from '@konce-pt/mention';
2
3
  import { type KptI18nOverrides } from '../i18n/i18n-context.tsx';
3
4
  export type KptRichTool = 'copy' | 'paste' | 'pasteText' | 'heading' | 'bold' | 'italic' | 'underline' | 'strike' | 'ul' | 'ol' | 'indent' | 'outdent' | 'foreColor' | 'backColor' | 'alignLeft' | 'alignCenter' | 'alignRight' | 'alignJustify' | 'link' | 'table' | 'clear' | 'html';
4
5
  /** Pasek: płaska lista narzędzi albo grupy — sekcje rozdzielone separatorem. */
@@ -16,6 +17,15 @@ export interface KptRichTextProps extends Omit<ComponentPropsWithRef<'div'>, 'on
16
17
  invalid?: boolean;
17
18
  touched?: boolean;
18
19
  onTouch?: () => void;
20
+ /**
21
+ * Pozycje do podpowiedzi wzmianek. Pusta lista wyłącza mechanizm: `@` zostaje wtedy zwykłym
22
+ * znakiem, bo nie ma czego podpowiadać.
23
+ */
24
+ mentions?: readonly KptMentionItem[];
25
+ /** Ile podpowiedzi naraz. */
26
+ mentionLimit?: number;
27
+ /** Wstawiona wzmianka — moment na powiadomienie osoby albo dopięcie powiązania. */
28
+ onMention?: (item: KptMentionItem) => void;
19
29
  }
20
30
  /**
21
31
  * Lekki edytor tekstu sformatowanego na `contenteditable`, z konfigurowalnym paskiem sekcji
@@ -25,4 +35,4 @@ export interface KptRichTextProps extends Omit<ComponentPropsWithRef<'div'>, 'on
25
35
  * <KptRichText value={html} onValueChange={setHtml} />
26
36
  * <KptRichText value={html} onValueChange={setHtml} toolbar={[['bold', 'italic'], ['link']]} />
27
37
  */
28
- export declare function KptRichText({ value, defaultValue, onValueChange, toolbar, placeholder, maxListLevel, disabled, invalid, touched, onTouch, locale, dictionary, className, ...rest }: Readonly<KptRichTextProps>): import("react").JSX.Element;
38
+ export declare function KptRichText({ value, defaultValue, onValueChange, toolbar, placeholder, maxListLevel, disabled, invalid, touched, onTouch, mentions, mentionLimit, onMention, locale, dictionary, className, ...rest }: Readonly<KptRichTextProps>): import("react").JSX.Element;
@@ -3,6 +3,7 @@ import { useCallback, useEffect, useRef, useState, } from 'react';
3
3
  import { createPortal } from 'react-dom';
4
4
  import { cn } from "../internal/cn.js";
5
5
  import { useControllableState } from "../internal/use-controllable-state.js";
6
+ import { KPT_MENTION_LIMIT, filterMentions, mentionQuery } from '@konce-pt/mention';
6
7
  import { useKptOverlayContainer } from "../internal/overlay-container.js";
7
8
  import { KptOverlay } from "../internal/overlay.js";
8
9
  import { useKptMessages } from "../i18n/i18n-context.js";
@@ -85,7 +86,7 @@ function legacyState(cmd) {
85
86
  * <KptRichText value={html} onValueChange={setHtml} />
86
87
  * <KptRichText value={html} onValueChange={setHtml} toolbar={[['bold', 'italic'], ['link']]} />
87
88
  */
88
- export function KptRichText({ value, defaultValue = '', onValueChange, toolbar = DEFAULT_TOOLBAR, placeholder = '', maxListLevel = 6, disabled = false, invalid = false, touched, onTouch, locale, dictionary, className, ...rest }) {
89
+ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar = DEFAULT_TOOLBAR, placeholder = '', maxListLevel = 6, disabled = false, invalid = false, touched, onTouch, mentions = [], mentionLimit = KPT_MENTION_LIMIT, onMention, locale, dictionary, className, ...rest }) {
89
90
  const { t } = useKptMessages({ locale, dictionary });
90
91
  const container = useKptOverlayContainer();
91
92
  const editorRef = useRef(null);
@@ -105,6 +106,14 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
105
106
  const [sourceView, setSourceView] = useState(false);
106
107
  const [menuOpen, setMenuOpen] = useState(false);
107
108
  const [menuPoint, setMenuPoint] = useState({ x: 0, y: 0 });
109
+ // ── Wzmianki (`@`) ──
110
+ const [mentionOpen, setMentionOpen] = useState(false);
111
+ const [mentionMatches, setMentionMatches] = useState([]);
112
+ const [mentionActive, setMentionActive] = useState(0);
113
+ /** Zaczepienie listy w miejscu kursora — punkt, nie element, bo `@` nie ma własnego elementu. */
114
+ const [mentionPoint, setMentionPoint] = useState({ x: 0, y: 0 });
115
+ /** Węzeł tekstowy i zakres zapytania `@…`, który podmieni wybrana pozycja. */
116
+ const mentionRange = useRef(null);
108
117
  const [borderOpen, setBorderOpen] = useState(false);
109
118
  const [borderTarget, setBorderTarget] = useState('table');
110
119
  const [activeCmds, setActiveCmds] = useState(new Set());
@@ -175,13 +184,106 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
175
184
  selCells.current = new Set();
176
185
  setSelCount(0);
177
186
  }, []);
187
+ /** Zamknięcie podpowiedzi — czyści też zapamiętany zakres zapytania. */
188
+ const closeMention = useCallback(() => {
189
+ mentionRange.current = null;
190
+ setMentionMatches([]);
191
+ setMentionOpen(false);
192
+ }, []);
193
+ /** Punkt pod kursorem — lista podpowiedzi zaczepia się o niego, bo `@` nie ma swojego elementu. */
194
+ const caretPoint = useCallback((node, offset) => {
195
+ const range = document.createRange();
196
+ range.setStart(node, Math.min(offset, node.length));
197
+ range.collapse(true);
198
+ const rect = range.getBoundingClientRect();
199
+ // Zwinięty zakres na początku wiersza potrafi dać zerowy prostokąt — wtedy bierzemy
200
+ // krawędź samego edytora, żeby lista nie wylądowała w rogu ekranu.
201
+ if (rect.width === 0 && rect.height === 0) {
202
+ const fallback = editorRef.current?.getBoundingClientRect();
203
+ return { x: fallback?.left ?? 0, y: fallback?.top ?? 0 };
204
+ }
205
+ return { x: rect.left, y: rect.bottom };
206
+ }, []);
207
+ /**
208
+ * Sprawdza, czy kursor stoi w zapytaniu `@…`, i ustawia listę podpowiedzi.
209
+ *
210
+ * Zapytanie liczymy z **węzła tekstowego**, nie z całego HTML-a: w treści siedzą tagi, a `@`
211
+ * ma widzieć tylko to, co użytkownik naprawdę napisał w tym miejscu. Rozpoznanie zostawiamy
212
+ * `mentionQuery()` z `@konce-pt/mention` — te same reguły działają w obu portach.
213
+ */
214
+ const syncMention = useCallback(() => {
215
+ if (disabled || !mentions.length) {
216
+ closeMention();
217
+ return;
218
+ }
219
+ const selection = window.getSelection();
220
+ const node = selection?.anchorNode;
221
+ if (!selection?.isCollapsed || !(node instanceof Text) || !editorRef.current?.contains(node)) {
222
+ closeMention();
223
+ return;
224
+ }
225
+ const query = mentionQuery(node.data, selection.anchorOffset);
226
+ if (!query) {
227
+ closeMention();
228
+ return;
229
+ }
230
+ const matches = filterMentions(mentions, query.query, mentionLimit);
231
+ if (!matches.length) {
232
+ closeMention();
233
+ return;
234
+ }
235
+ mentionRange.current = { node, start: query.start, end: query.end };
236
+ setMentionMatches(matches);
237
+ setMentionActive(0);
238
+ setMentionPoint(caretPoint(node, query.end));
239
+ setMentionOpen(true);
240
+ }, [caretPoint, closeMention, disabled, mentionLimit, mentions]);
241
+ /**
242
+ * Wstawia wzmiankę w miejsce zapytania `@…`.
243
+ *
244
+ * Wzmianka jest elementem, nie samym tekstem: `data-mention` niesie identyfikator, który
245
+ * przeżyje zmianę nazwy, a `contenteditable="false"` trzyma ją w jednym kawałku — bez tego
246
+ * kursor wchodziłby w środek nazwiska i psuł zapis. Spacja po wzmiance daje gdzie postawić
247
+ * kursor: bez niej nie da się wyjść poza element, który nie jest edytowalny.
248
+ */
249
+ const insertMention = useCallback((item) => {
250
+ const target = mentionRange.current;
251
+ if (!target)
252
+ return;
253
+ const range = document.createRange();
254
+ range.setStart(target.node, target.start);
255
+ range.setEnd(target.node, Math.min(target.end, target.node.length));
256
+ range.deleteContents();
257
+ const element = document.createElement('span');
258
+ element.className = 'kpt-mention';
259
+ element.dataset['mention'] = item.id;
260
+ element.setAttribute('contenteditable', 'false');
261
+ element.textContent = '@' + item.label;
262
+ const space = document.createTextNode(' ');
263
+ range.insertNode(space);
264
+ range.insertNode(element);
265
+ const after = document.createRange();
266
+ after.setStartAfter(space);
267
+ after.collapse(true);
268
+ const selection = window.getSelection();
269
+ selection?.removeAllRanges();
270
+ selection?.addRange(after);
271
+ closeMention();
272
+ pushValue();
273
+ onMention?.(item);
274
+ }, [closeMention, onMention, pushValue]);
178
275
  // Stan narzędzi zależy od zaznaczenia, a to zmienia się także poza naszymi zdarzeniami
179
276
  // (myszą, klawiaturą, przez inne okno), więc nasłuchujemy `selectionchange` na dokumencie.
180
277
  useEffect(() => {
181
- const onSelectionChange = () => refreshState();
278
+ const onSelectionChange = () => {
279
+ refreshState();
280
+ // Kursor może odjechać od `@` także bez pisania (klik, strzałki) — wtedy podpowiedzi
281
+ // muszą zniknąć, inaczej Enter wstawiłby wzmiankę w zupełnie innym miejscu.
282
+ syncMention();
283
+ };
182
284
  document.addEventListener('selectionchange', onSelectionChange);
183
285
  return () => document.removeEventListener('selectionchange', onSelectionChange);
184
- }, [refreshState]);
286
+ }, [refreshState, syncMention]);
185
287
  // Przeciąganie krawędzi kolumny i zaznaczanie komórek biegną na całym dokumencie: kursor
186
288
  // wyjeżdża poza tabelę praktycznie zawsze.
187
289
  useEffect(() => {
@@ -344,8 +446,43 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
344
446
  * Tab: wcięcie w liście, przeskok komórki w tabeli. Poza nimi i na krawędziach zostawiamy Tab
345
447
  * natywny — inaczej edytor byłby pułapką klawiaturową (WCAG 2.1.2).
346
448
  */
449
+ /** Klawiatura listy podpowiedzi; `true` = zdarzenie obsłużone i edytor go już nie zobaczy. */
450
+ const onMentionKeyDown = (event) => {
451
+ const count = mentionMatches.length;
452
+ if (!count)
453
+ return false;
454
+ switch (event.key) {
455
+ case 'ArrowDown':
456
+ setMentionActive((index) => (index + 1) % count);
457
+ break;
458
+ case 'ArrowUp':
459
+ setMentionActive((index) => (index - 1 + count) % count);
460
+ break;
461
+ case 'Enter':
462
+ case 'Tab': {
463
+ const item = mentionMatches[mentionActive];
464
+ if (!item)
465
+ return false;
466
+ insertMention(item);
467
+ break;
468
+ }
469
+ case 'Escape':
470
+ closeMention();
471
+ break;
472
+ default:
473
+ return false;
474
+ }
475
+ event.preventDefault();
476
+ return true;
477
+ };
347
478
  const onKeyDown = (event) => {
348
- if (disabled || event.key !== 'Tab' || event.ctrlKey || event.altKey || event.metaKey)
479
+ if (disabled)
480
+ return;
481
+ // Podpowiedzi wzmianek przechwytują klawiaturę jako pierwsze: Tab i Enter znaczą w nich
482
+ // co innego niż w samym edytorze.
483
+ if (mentionOpen && onMentionKeyDown(event))
484
+ return;
485
+ if (event.key !== 'Tab' || event.ctrlKey || event.altKey || event.metaKey)
349
486
  return;
350
487
  if (shiftItems(editorRef.current, event.shiftKey ? 'outdent' : 'indent', maxListLevel)) {
351
488
  event.preventDefault();
@@ -570,7 +707,22 @@ export function KptRichText({ value, defaultValue = '', onValueChange, toolbar =
570
707
  return (_jsx("button", { ref: setTableAnchor, type: "button", className: cn('kpt-rich-text__tool', tableOpen && 'is-active'), "aria-label": t('richText.table'), title: t('richText.table'), disabled: disabled, onMouseDown: (event) => event.preventDefault(), onClick: () => exec(item), children: _jsx(KptIcon, { name: ICON[item] }) }, item));
571
708
  }
572
709
  return toolButton(item);
573
- })] }, index))) }), sourceView ? (_jsx("textarea", { className: "kpt-rich-text__source", value: current, disabled: disabled, spellCheck: false, "aria-label": t('richText.htmlSource'), onChange: (event) => setCurrent(event.target.value), onBlur: () => onTouch?.() })) : (_jsx("div", { ref: editorRef, className: "kpt-rich-text__editor", contentEditable: !disabled, suppressContentEditableWarning: true, "data-placeholder": placeholder, "aria-invalid": showInvalid || undefined, role: "textbox", "aria-multiline": "true", onInput: pushValue, onKeyDown: onKeyDown, onPaste: onPaste, onContextMenu: onContextMenu, onMouseDown: onEditorMouseDown, onMouseMove: onEditorMouseMove, onBlur: () => onTouch?.() }))] }), _jsx(KptOverlay, { open: styleOpen, anchor: styleAnchor, onClose: () => setStyleOpen(false), offset: 4, children: _jsx("ul", { className: "kpt-rich-text__style-panel", role: "listbox", children: styleOptions.map((option) => (_jsx("li", { className: cn(`kpt-rich-text__style-opt kpt-rich-text__style-opt--${option.tag}`, currentBlock === option.tag && 'is-selected'), role: "option", "aria-selected": currentBlock === option.tag, onClick: () => {
710
+ })] }, index))) }), sourceView ? (_jsx("textarea", { className: "kpt-rich-text__source", value: current, disabled: disabled, spellCheck: false, "aria-label": t('richText.htmlSource'), onChange: (event) => setCurrent(event.target.value), onBlur: () => onTouch?.() })) : (_jsx("div", { ref: editorRef, className: "kpt-rich-text__editor", contentEditable: !disabled, suppressContentEditableWarning: true, "data-placeholder": placeholder, "aria-invalid": showInvalid || undefined, role: "textbox", "aria-multiline": "true", onInput: () => {
711
+ pushValue();
712
+ syncMention();
713
+ }, onKeyDown: onKeyDown, onPaste: onPaste, onContextMenu: onContextMenu, onMouseDown: onEditorMouseDown, onMouseMove: onEditorMouseMove, onBlur: () => onTouch?.() }))] }), mentionOpen && container
714
+ ? createPortal(_jsx("ul", { className: "kpt-rt-mentions", role: "listbox", "aria-label": t('richText.mentions'), style: {
715
+ position: 'fixed',
716
+ left: mentionPoint.x,
717
+ top: mentionPoint.y,
718
+ zIndex: 'var(--kpt-z-overlay, 1100)',
719
+ }, children: mentionMatches.map((item, index) => (_jsxs("li", { className: "kpt-rt-mentions__item", role: "option", "aria-selected": index === mentionActive, "data-active": index === mentionActive ? '' : undefined, onMouseDown: (event) => {
720
+ // `mousedown` zamiast `click`: kliknięcie nie może zabrać fokusu edytorowi,
721
+ // bo razem z nim zniknąłby zakres, w który wstawiamy wzmiankę.
722
+ event.preventDefault();
723
+ insertMention(item);
724
+ }, onMouseEnter: () => setMentionActive(index), children: [_jsx("span", { className: "kpt-rt-mentions__label", children: item.label }), item.kind ? _jsx("span", { className: "kpt-rt-mentions__kind", children: item.kind }) : null] }, item.id))) }), container)
725
+ : null, _jsx(KptOverlay, { open: styleOpen, anchor: styleAnchor, onClose: () => setStyleOpen(false), offset: 4, children: _jsx("ul", { className: "kpt-rich-text__style-panel", role: "listbox", children: styleOptions.map((option) => (_jsx("li", { className: cn(`kpt-rich-text__style-opt kpt-rich-text__style-opt--${option.tag}`, currentBlock === option.tag && 'is-selected'), role: "option", "aria-selected": currentBlock === option.tag, onClick: () => {
574
726
  setStyleOpen(false);
575
727
  focusEditor();
576
728
  restoreSelection();
@@ -13,7 +13,16 @@ const ALLOWED_STYLES = new Set([
13
13
  'color', 'background-color', 'text-align', 'font-weight', 'font-style', 'text-decoration',
14
14
  'width', 'height', 'border', 'border-color', 'border-width', 'border-style', 'vertical-align',
15
15
  ]);
16
- const ALLOWED_ATTRS = new Set(['colspan', 'rowspan', 'span', 'width', 'class', 'target', 'rel', 'title']);
16
+ /*
17
+ * `data-mention` i `contenteditable` są tu dla wzmianek: identyfikator musi przeżyć zapis treści,
18
+ * a `contenteditable="false"` trzyma wzmiankę jako jeden kawałek, w którego środku nie da się
19
+ * pisać. Oba atrybuty są bierne — nie wykonują niczego i nie wskazują zasobów, więc wpuszczenie
20
+ * ich nie otwiera nowej furtki.
21
+ */
22
+ const ALLOWED_ATTRS = new Set([
23
+ 'colspan', 'rowspan', 'span', 'width', 'class', 'target', 'rel', 'title',
24
+ 'data-mention', 'contenteditable',
25
+ ]);
17
26
  // Schematy dopuszczone w `href` narzędzia „link"; blocklista byłaby obchodzalna.
18
27
  const SAFE_URL_SCHEME = /^(https?:|mailto:|tel:)/i;
19
28
  const HAS_URL_SCHEME = /^[a-z][a-z0-9+.-]*:/i;
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, } from 'react';
3
3
  import { buildRows, buildTimeline, linkGeometry, resolveLinks, resolveRange, sliceRows, } from '@konce-pt/roadmap';
4
4
  import { cn } from "../internal/cn.js";
5
+ import { isDevBuild } from "../internal/dev.js";
5
6
  import { useControllableState } from "../internal/use-controllable-state.js";
6
7
  import { useKptMessages } from "../i18n/i18n-context.js";
7
8
  import { KptTooltip } from "../tooltip/tooltip.js";
@@ -262,9 +263,6 @@ export function KptRoadmap({ config, scrollHeight = null, labelWidth = '14rem',
262
263
  }, tabIndex: 0, onClick: () => handleItemClick(bar.item), onKeyDown: (event) => handleItemKeydown(event, bar.item), children: [bar.progress !== null && (_jsx("span", { className: "kpt-roadmap__bar-progress", style: { inlineSize: `${bar.progress * 100}%` } })), _jsx("span", { className: "kpt-roadmap__bar-label", children: bar.label })] }) }, bar.item.id))) })] }, row.key));
263
264
  })] }))] }) }));
264
265
  }
265
- function isDevBuild() {
266
- return typeof process !== 'undefined' && process?.env?.['NODE_ENV'] !== 'production';
267
- }
268
266
  /** Identyfikatory grup zwiniętych deklaratywnie w danych. */
269
267
  function declaredCollapsed(groups) {
270
268
  return (groups ?? []).filter((group) => group.collapsed).map((group) => group.id);
@@ -3,6 +3,8 @@ export interface KptOption<T = unknown> {
3
3
  label: string;
4
4
  value: T;
5
5
  disabled?: boolean;
6
+ /** Nazwa ikony z rejestru. Renderuje ją dziś wyłącznie `kpt-button-group`. */
7
+ icon?: string;
6
8
  }
7
9
  /** Wariant komunikatu — wspólny dla alertu i toastów. */
8
10
  export type KptAlertVariant = 'info' | 'success' | 'warning' | 'danger';