@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.
- package/README.md +4 -4
- package/dist/backdrop/backdrop.d.ts +79 -0
- package/dist/backdrop/backdrop.js +195 -0
- package/dist/backdrop/llms.txt +132 -0
- package/dist/button/button.d.ts +23 -7
- package/dist/button/button.js +33 -9
- package/dist/button/llms.txt +48 -12
- package/dist/button-group/button-group.d.ts +7 -1
- package/dist/button-group/button-group.js +9 -4
- package/dist/button-group/llms.txt +30 -5
- package/dist/chat/chat.d.ts +53 -0
- package/dist/chat/chat.js +160 -0
- package/dist/chat/llms.txt +78 -0
- package/dist/chat-composer/chat-composer.d.ts +41 -0
- package/dist/chat-composer/chat-composer.js +81 -0
- package/dist/chat-composer/llms.txt +56 -0
- package/dist/chat-dock/chat-dock.d.ts +40 -0
- package/dist/chat-dock/chat-dock.js +67 -0
- package/dist/chat-dock/llms.txt +71 -0
- package/dist/chat-entry/public-api.d.ts +12 -0
- package/dist/chat-entry/public-api.js +10 -0
- package/dist/chat-message/chat-message.d.ts +53 -0
- package/dist/chat-message/chat-message.js +112 -0
- package/dist/chat-message/llms.txt +73 -0
- package/dist/chat-typing/chat-typing.d.ts +23 -0
- package/dist/chat-typing/chat-typing.js +30 -0
- package/dist/chat-typing/llms.txt +41 -0
- package/dist/chip/chip.d.ts +10 -1
- package/dist/chip/chip.js +3 -2
- package/dist/chip/llms.txt +9 -0
- package/dist/data-view/data-view.d.ts +15 -3
- package/dist/data-view/data-view.js +22 -5
- package/dist/data-view/llms.txt +20 -7
- package/dist/dialog/dialog.d.ts +9 -1
- package/dist/dialog/dialog.js +2 -2
- package/dist/internal/dev.d.ts +1 -0
- package/dist/internal/dev.js +3 -0
- package/dist/internal/overlay.d.ts +6 -1
- package/dist/internal/overlay.js +5 -3
- package/dist/mention/llms.txt +60 -0
- package/dist/mention/mention.d.ts +38 -0
- package/dist/mention/mention.js +29 -0
- package/dist/mention/mentions.d.ts +31 -0
- package/dist/mention/mentions.js +24 -0
- package/dist/order-list/llms.txt +16 -5
- package/dist/order-list/order-list.d.ts +5 -3
- package/dist/order-list/order-list.js +69 -4
- package/dist/peek/llms.txt +42 -0
- package/dist/peek/peek.d.ts +35 -0
- package/dist/peek/peek.js +127 -0
- package/dist/pick-list/llms.txt +19 -5
- package/dist/pick-list/pick-list.d.ts +7 -2
- package/dist/pick-list/pick-list.js +123 -17
- package/dist/progress/llms.txt +70 -22
- package/dist/progress/progress.d.ts +34 -7
- package/dist/progress/progress.js +46 -9
- package/dist/public-api.d.ts +15 -3
- package/dist/public-api.js +6 -0
- package/dist/rich-text/llms.txt +10 -0
- package/dist/rich-text/rich-text.d.ts +11 -1
- package/dist/rich-text/rich-text.js +157 -5
- package/dist/rich-text/sanitize.js +10 -1
- package/dist/roadmap/roadmap.js +1 -3
- package/dist/shared/types.d.ts +2 -0
- package/package.json +16 -9
package/dist/progress/llms.txt
CHANGED
|
@@ -1,47 +1,95 @@
|
|
|
1
1
|
# KptProgress (@konce-pt/react)
|
|
2
2
|
|
|
3
|
-
A progress indicator — linear or circular, determinate (
|
|
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
|
-
<
|
|
10
|
-
|
|
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
|
-
<
|
|
16
|
-
<
|
|
17
|
-
|
|
18
|
-
|
|
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) —
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
|
34
|
-
|
|
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={
|
|
38
|
-
<KptProgress value={
|
|
39
|
-
<KptProgress
|
|
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-
|
|
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
|
|
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
|
|
4
|
-
|
|
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?:
|
|
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 (
|
|
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
|
|
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 (
|
|
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
|
|
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
|
-
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
}
|
package/dist/public-api.d.ts
CHANGED
|
@@ -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';
|
package/dist/public-api.js
CHANGED
|
@@ -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";
|
package/dist/rich-text/llms.txt
CHANGED
|
@@ -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 = () =>
|
|
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
|
|
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:
|
|
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
|
-
|
|
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;
|
package/dist/roadmap/roadmap.js
CHANGED
|
@@ -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);
|
package/dist/shared/types.d.ts
CHANGED
|
@@ -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';
|