@konce-pt/react 0.9.1 → 0.11.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 (200) hide show
  1. package/dist/accordion/accordion.js +7 -1
  2. package/dist/accordion/llms.txt +8 -4
  3. package/dist/alert/alert.d.ts +10 -4
  4. package/dist/alert/alert.js +13 -6
  5. package/dist/alert/llms.txt +8 -6
  6. package/dist/app-shell/app-shell.js +28 -2
  7. package/dist/app-shell/llms.txt +3 -0
  8. package/dist/autocomplete/autocomplete.d.ts +7 -2
  9. package/dist/autocomplete/autocomplete.js +46 -9
  10. package/dist/autocomplete/llms.txt +12 -0
  11. package/dist/avatar/avatar.d.ts +4 -2
  12. package/dist/avatar/avatar.js +8 -3
  13. package/dist/avatar/llms.txt +9 -5
  14. package/dist/avatar-group/avatar-group.d.ts +5 -2
  15. package/dist/avatar-group/avatar-group.js +10 -2
  16. package/dist/avatar-group/llms.txt +7 -4
  17. package/dist/badge/badge.d.ts +8 -1
  18. package/dist/badge/badge.js +3 -2
  19. package/dist/badge/llms.txt +11 -5
  20. package/dist/bottom-sheet/bottom-sheet.d.ts +1 -1
  21. package/dist/bottom-sheet/bottom-sheet.js +8 -2
  22. package/dist/bottom-sheet/llms.txt +8 -2
  23. package/dist/breadcrumb/breadcrumb.js +4 -4
  24. package/dist/breadcrumb/llms.txt +12 -5
  25. package/dist/card/card.d.ts +9 -1
  26. package/dist/card/card.js +2 -2
  27. package/dist/card/llms.txt +6 -2
  28. package/dist/carousel/carousel.d.ts +8 -1
  29. package/dist/carousel/carousel.js +9 -2
  30. package/dist/carousel/llms.txt +24 -10
  31. package/dist/checkbox/checkbox.d.ts +6 -1
  32. package/dist/checkbox/checkbox.js +11 -3
  33. package/dist/checkbox/llms.txt +11 -0
  34. package/dist/chip/chip.js +10 -1
  35. package/dist/chip/llms.txt +7 -4
  36. package/dist/chips-input/chips-input.d.ts +7 -1
  37. package/dist/chips-input/chips-input.js +21 -5
  38. package/dist/chips-input/llms.txt +12 -0
  39. package/dist/color-picker/color-picker.d.ts +18 -5
  40. package/dist/color-picker/color-picker.js +48 -14
  41. package/dist/color-picker/llms.txt +14 -0
  42. package/dist/confirm/confirm-container.d.ts +5 -1
  43. package/dist/confirm/confirm-container.js +15 -1
  44. package/dist/confirm/llms.txt +4 -2
  45. package/dist/context-menu/context-menu.d.ts +5 -0
  46. package/dist/context-menu/context-menu.js +48 -4
  47. package/dist/context-menu/llms.txt +11 -4
  48. package/dist/date-range/date-range.d.ts +14 -1
  49. package/dist/date-range/date-range.js +133 -17
  50. package/dist/date-range/llms.txt +26 -3
  51. package/dist/datepicker/datepicker.d.ts +10 -1
  52. package/dist/datepicker/datepicker.js +147 -14
  53. package/dist/datepicker/llms.txt +27 -3
  54. package/dist/dialog/dialog.d.ts +4 -0
  55. package/dist/dialog/dialog.js +7 -1
  56. package/dist/dialog/llms.txt +2 -0
  57. package/dist/drawer/drawer.js +7 -2
  58. package/dist/drawer/llms.txt +10 -5
  59. package/dist/empty/empty.d.ts +6 -1
  60. package/dist/empty/empty.js +2 -2
  61. package/dist/empty/llms.txt +4 -3
  62. package/dist/fab/llms.txt +2 -1
  63. package/dist/file-upload/file-upload.d.ts +5 -0
  64. package/dist/file-upload/file-upload.js +24 -3
  65. package/dist/file-upload/llms.txt +15 -4
  66. package/dist/form-field/form-field-context.d.ts +30 -1
  67. package/dist/form-field/form-field-context.js +27 -0
  68. package/dist/form-field/form-field.js +16 -2
  69. package/dist/form-field/llms.txt +10 -1
  70. package/dist/galleria/galleria.d.ts +6 -1
  71. package/dist/galleria/galleria.js +40 -1
  72. package/dist/galleria/llms.txt +17 -8
  73. package/dist/icon/icon.d.ts +7 -1
  74. package/dist/icon/icon.js +18 -4
  75. package/dist/icon/llms.txt +5 -2
  76. package/dist/image/image.d.ts +5 -0
  77. package/dist/image/image.js +38 -2
  78. package/dist/image/llms.txt +24 -17
  79. package/dist/input/input.d.ts +1 -1
  80. package/dist/input/input.js +4 -6
  81. package/dist/input/llms.txt +5 -0
  82. package/dist/input-mask/input-mask.d.ts +1 -1
  83. package/dist/input-mask/input-mask.js +8 -6
  84. package/dist/input-mask/llms.txt +11 -0
  85. package/dist/input-number/input-number.d.ts +13 -1
  86. package/dist/input-number/input-number.js +49 -9
  87. package/dist/input-number/llms.txt +21 -3
  88. package/dist/input-otp/input-otp.d.ts +7 -2
  89. package/dist/input-otp/input-otp.js +36 -7
  90. package/dist/input-otp/llms.txt +14 -0
  91. package/dist/internal/calendar-focus.d.ts +8 -0
  92. package/dist/internal/calendar-focus.js +38 -0
  93. package/dist/internal/option-nav.d.ts +24 -0
  94. package/dist/internal/option-nav.js +80 -0
  95. package/dist/internal/overlay.d.ts +7 -2
  96. package/dist/internal/overlay.js +4 -2
  97. package/dist/internal/roving.d.ts +9 -0
  98. package/dist/internal/roving.js +43 -0
  99. package/dist/internal/tween.d.ts +17 -0
  100. package/dist/internal/tween.js +40 -0
  101. package/dist/internal/use-modal-focus.d.ts +10 -0
  102. package/dist/internal/use-modal-focus.js +57 -0
  103. package/dist/knob/knob.d.ts +9 -1
  104. package/dist/knob/knob.js +41 -5
  105. package/dist/knob/llms.txt +15 -0
  106. package/dist/listbox/listbox.d.ts +13 -1
  107. package/dist/listbox/listbox.js +89 -6
  108. package/dist/listbox/llms.txt +21 -5
  109. package/dist/megamenu/llms.txt +28 -10
  110. package/dist/megamenu/megamenu.d.ts +10 -1
  111. package/dist/megamenu/megamenu.js +116 -5
  112. package/dist/menu/llms.txt +12 -1
  113. package/dist/menu/menu-keys.d.ts +15 -0
  114. package/dist/menu/menu-keys.js +53 -0
  115. package/dist/menu/menu-rows.js +1 -1
  116. package/dist/menu/menu.js +52 -4
  117. package/dist/menubar/llms.txt +24 -7
  118. package/dist/menubar/menubar.d.ts +8 -1
  119. package/dist/menubar/menubar.js +92 -6
  120. package/dist/meter-group/llms.txt +10 -3
  121. package/dist/meter-group/meter-group.d.ts +9 -2
  122. package/dist/meter-group/meter-group.js +8 -2
  123. package/dist/order-list/llms.txt +11 -3
  124. package/dist/order-list/order-list.d.ts +5 -1
  125. package/dist/order-list/order-list.js +46 -8
  126. package/dist/panel/llms.txt +9 -6
  127. package/dist/panel/panel.js +3 -1
  128. package/dist/password/llms.txt +8 -0
  129. package/dist/password/password.d.ts +6 -1
  130. package/dist/password/password.js +6 -5
  131. package/dist/pick-list/llms.txt +12 -3
  132. package/dist/pick-list/pick-list.d.ts +5 -1
  133. package/dist/pick-list/pick-list.js +52 -5
  134. package/dist/popover/llms.txt +9 -2
  135. package/dist/popover/popover.d.ts +9 -3
  136. package/dist/popover/popover.js +41 -6
  137. package/dist/public-api.d.ts +6 -6
  138. package/dist/public-api.js +1 -1
  139. package/dist/radio-group/llms.txt +11 -0
  140. package/dist/radio-group/radio-group.d.ts +9 -1
  141. package/dist/radio-group/radio-group.js +5 -2
  142. package/dist/rating/llms.txt +18 -5
  143. package/dist/rating/rating.d.ts +21 -3
  144. package/dist/rating/rating.js +87 -12
  145. package/dist/rich-text/llms.txt +20 -0
  146. package/dist/rich-text/rich-text.d.ts +7 -1
  147. package/dist/rich-text/rich-text.js +155 -28
  148. package/dist/scroll-top/llms.txt +5 -1
  149. package/dist/scroll-top/scroll-top.js +3 -1
  150. package/dist/select/llms.txt +21 -7
  151. package/dist/select/select.d.ts +12 -1
  152. package/dist/select/select.js +110 -38
  153. package/dist/sidenav/llms.txt +6 -0
  154. package/dist/skeleton/llms.txt +3 -2
  155. package/dist/skeleton/skeleton.d.ts +1 -1
  156. package/dist/skeleton/skeleton.js +2 -2
  157. package/dist/slider/llms.txt +14 -1
  158. package/dist/slider/slider.d.ts +11 -2
  159. package/dist/slider/slider.js +15 -2
  160. package/dist/speed-dial/llms.txt +6 -2
  161. package/dist/speed-dial/speed-dial.d.ts +1 -1
  162. package/dist/speed-dial/speed-dial.js +14 -3
  163. package/dist/spinner/llms.txt +12 -8
  164. package/dist/spinner/spinner.d.ts +5 -1
  165. package/dist/spinner/spinner.js +12 -2
  166. package/dist/split-button/llms.txt +11 -3
  167. package/dist/split-button/split-button.d.ts +3 -1
  168. package/dist/split-button/split-button.js +43 -3
  169. package/dist/splitter/llms.txt +14 -4
  170. package/dist/splitter/splitter.d.ts +9 -2
  171. package/dist/splitter/splitter.js +15 -2
  172. package/dist/stepper/llms.txt +8 -4
  173. package/dist/stepper/stepper.d.ts +3 -2
  174. package/dist/stepper/stepper.js +7 -2
  175. package/dist/switch/llms.txt +10 -0
  176. package/dist/switch/switch.d.ts +1 -1
  177. package/dist/switch/switch.js +4 -2
  178. package/dist/switch-group/llms.txt +11 -0
  179. package/dist/switch-group/switch-group.d.ts +5 -1
  180. package/dist/switch-group/switch-group.js +7 -2
  181. package/dist/tabs/llms.txt +10 -4
  182. package/dist/tabs/tabs.d.ts +4 -0
  183. package/dist/tabs/tabs.js +21 -2
  184. package/dist/textarea/llms.txt +10 -0
  185. package/dist/textarea/textarea.d.ts +3 -1
  186. package/dist/textarea/textarea.js +13 -8
  187. package/dist/toast/llms.txt +21 -7
  188. package/dist/toast/toast-container.d.ts +6 -1
  189. package/dist/toast/toast-container.js +52 -1
  190. package/dist/toast/toast-store.d.ts +12 -0
  191. package/dist/toast/toast-store.js +46 -7
  192. package/dist/tooltip/llms.txt +10 -3
  193. package/dist/tooltip/tooltip.d.ts +5 -0
  194. package/dist/tooltip/tooltip.js +80 -7
  195. package/dist/tree/llms.txt +20 -8
  196. package/dist/tree/tree-keys.d.ts +27 -0
  197. package/dist/tree/tree-keys.js +39 -0
  198. package/dist/tree/tree.d.ts +12 -5
  199. package/dist/tree/tree.js +75 -29
  200. package/package.json +12 -12
@@ -71,7 +71,7 @@ export { KptOverlay } from "./internal/overlay.js";
71
71
  export { KptButtonGroup } from "./button-group/button-group.js";
72
72
  export { KptSelect } from "./select/select.js";
73
73
  export { KptFormField } from "./form-field/form-field.js";
74
- export { useKptFormFieldContext, useKptFormFieldFilled } from "./form-field/form-field-context.js";
74
+ export { useKptFieldA11y, useKptFormFieldContext, useKptFormFieldFilled } from "./form-field/form-field-context.js";
75
75
  export { KptCheckbox } from "./checkbox/checkbox.js";
76
76
  export { KptAutocomplete } from "./autocomplete/autocomplete.js";
77
77
  export { KptListbox } from "./listbox/listbox.js";
@@ -31,6 +31,17 @@ interfere.
31
31
  <KptRadioGroup options={plans} value={plan} onValueChange={setPlan} />
32
32
  <KptRadioGroup name="plan" options={plans} defaultValue="pro" />
33
33
 
34
+ ## Screen reader and keyboard
35
+ `orientation`: 'vertical' (default) | 'horizontal' — options in a column or in a wrapping row; also
36
+ sets `aria-orientation`.
37
+
38
+ ## Label and description
39
+ `id` goes to the focusable element, not the host; without one it comes from `KptFormField` or is
40
+ generated. A `div` with a role is not named by `label[for]`, so inside `KptFormField` it gets
41
+ `aria-labelledby` pointing at the wrapper's label (unless `ariaLabel` is set), and the hint or error
42
+ is added to `aria-describedby`. New: `id`, `ariaLabel` and `required` (→ `aria-required`) on the
43
+ `radiogroup`.
44
+
34
45
  ## Tokens
35
46
  `--kpt-color-primary` (the checked dot), `--kpt-form-field-border`,
36
47
  `--kpt-form-field-border-error`.
@@ -7,6 +7,14 @@ export interface KptRadioGroupProps<T> extends Omit<ComponentPropsWithRef<'div'>
7
7
  onValueChange?: (value: T) => void;
8
8
  /** Nazwa grupy w formularzu; pominięta = generowana. */
9
9
  name?: string;
10
+ /** Id elementu fokusowalnego (nie hosta); bez niego generowane, w `KptFormField` — z wrappera. */
11
+ id?: string;
12
+ /** Nazwa dostępna, gdy kontrolki nie opisuje etykieta (`<label for>` albo `KptFormField`). */
13
+ ariaLabel?: string | null;
14
+ /** Oznacza grupę jako wymaganą atrybutem `aria-required`. */
15
+ required?: boolean;
16
+ /** Układ opcji: pod sobą (domyślnie) albo w rzędzie, z zawijaniem. */
17
+ orientation?: 'vertical' | 'horizontal';
10
18
  disabled?: boolean;
11
19
  invalid?: boolean;
12
20
  touched?: boolean;
@@ -21,4 +29,4 @@ export interface KptRadioGroupProps<T> extends Omit<ComponentPropsWithRef<'div'>
21
29
  * @example
22
30
  * <KptRadioGroup options={plans} value={plan} onValueChange={setPlan} />
23
31
  */
24
- export declare function KptRadioGroup<T>({ options, value, defaultValue, onValueChange, name, disabled, invalid, touched, onTouch, className, ...rest }: Readonly<KptRadioGroupProps<T>>): import("react").JSX.Element;
32
+ export declare function KptRadioGroup<T>({ options, value, defaultValue, onValueChange, name, id, ariaLabel, required, orientation, disabled, invalid, touched, onTouch, className, ...rest }: Readonly<KptRadioGroupProps<T>>): import("react").JSX.Element;
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { cn } from "../internal/cn.js";
3
3
  import { useControllableState } from "../internal/use-controllable-state.js";
4
4
  import { useKptId } from "../internal/use-id.js";
5
+ import { useKptFieldA11y } from "../form-field/form-field-context.js";
5
6
  /**
6
7
  * Grupa przycisków opcji (radio). Opcje przez `options`.
7
8
  *
@@ -11,7 +12,9 @@ import { useKptId } from "../internal/use-id.js";
11
12
  * @example
12
13
  * <KptRadioGroup options={plans} value={plan} onValueChange={setPlan} />
13
14
  */
14
- export function KptRadioGroup({ options = [], value, defaultValue, onValueChange, name, disabled = false, invalid = false, touched, onTouch, className, ...rest }) {
15
+ export function KptRadioGroup({ options = [], value, defaultValue, onValueChange, name, id, ariaLabel = null, required = false, orientation = 'vertical', disabled = false, invalid = false, touched, onTouch, className, ...rest }) {
16
+ /** Grupa nie dostaje nazwy z `label[for]` — stąd `aria-labelledby` na etykietę wrappera. */
17
+ const field = useKptFieldA11y(id, 'kpt-radio-group');
15
18
  const groupName = useKptId(name, 'kpt-radio');
16
19
  const [current, setCurrent] = useControllableState({
17
20
  value,
@@ -19,5 +22,5 @@ export function KptRadioGroup({ options = [], value, defaultValue, onValueChange
19
22
  onChange: onValueChange,
20
23
  });
21
24
  const showInvalid = invalid && (touched ?? true);
22
- return (_jsx("div", { className: cn('kpt-radio-group-host', className), ...rest, children: _jsx("div", { className: "kpt-radio-group", role: "radiogroup", "aria-invalid": showInvalid || undefined, children: options.map((option) => (_jsxs("label", { className: cn('kpt-radio', (disabled || option.disabled) && 'kpt-radio--disabled'), children: [_jsx("input", { type: "radio", className: "kpt-radio__input", name: groupName, checked: option.value === current, disabled: disabled || option.disabled, onChange: () => setCurrent(option.value), onBlur: () => onTouch?.() }), _jsx("span", { className: "kpt-radio__dot", "aria-hidden": "true" }), _jsx("span", { className: "kpt-radio__label", children: option.label })] }, String(option.value)))) }) }));
25
+ return (_jsx("div", { className: cn('kpt-radio-group-host', className), ...rest, children: _jsx("div", { className: cn('kpt-radio-group', orientation === 'horizontal' && 'kpt-radio-group--horizontal'), role: "radiogroup", "aria-orientation": orientation, "aria-invalid": showInvalid || undefined, id: field.id, "aria-label": ariaLabel ?? undefined, "aria-labelledby": ariaLabel ? undefined : field.labelledBy, "aria-describedby": field.describedBy, "aria-required": required || undefined, children: options.map((option) => (_jsxs("label", { className: cn('kpt-radio', (disabled || option.disabled) && 'kpt-radio--disabled'), children: [_jsx("input", { type: "radio", className: "kpt-radio__input", name: groupName, checked: option.value === current, disabled: disabled || option.disabled, onChange: () => setCurrent(option.value), onBlur: () => onTouch?.() }), _jsx("span", { className: "kpt-radio__dot", "aria-hidden": "true" }), _jsx("span", { className: "kpt-radio__label", children: option.label })] }, String(option.value)))) }) }));
23
26
  }
@@ -5,8 +5,8 @@ Import: `import { KptRating } from '@konce-pt/react';`
5
5
 
6
6
  ## DOM
7
7
  <div class="kpt-rating-host">
8
- <div class="kpt-rating" role="slider" aria-valuenow="3" aria-valuemax="5">
9
- <button class="kpt-rating__star is-filled" aria-label="1 of 5">…</button>
8
+ <div class="kpt-rating" role="radiogroup" aria-label="…">
9
+ <button class="kpt-rating__star is-filled" role="radio" aria-checked="false" tabindex="-1" aria-label="1 of 5">…</button>
10
10
  …count buttons…
11
11
  </div>
12
12
  </div>
@@ -14,9 +14,11 @@ Import: `import { KptRating } from '@konce-pt/react';`
14
14
  ## Props
15
15
  - `value` / `defaultValue` / `onValueChange` — the score as a number, `0` means unrated
16
16
  - `count`: number (default 5)
17
+ - `allowHalf`: boolean — half stars, the value goes in steps of 0.5
17
18
  - `readOnly`: boolean — display only, no clicking and no hover preview
18
19
  - `disabled`: boolean
19
- - `onTouch()` — fired together with a pick
20
+ - `onTouch()` — when focus leaves the group
21
+ - `id`, `ariaLabel`, `required`, `invalid`, `touched`
20
22
  - `className`, `ref` and every other `<div>` prop go to the host
21
23
 
22
24
  ## Clicking a lit star clears the score
@@ -35,9 +37,20 @@ per instance with the `locale` / `dictionary` props.
35
37
  <KptRating value={score} onValueChange={setScore} count={5} />
36
38
  <KptRating value={4} readOnly />
37
39
 
40
+ ## Half stars
41
+ `allowHalf` splits every star into two halves, each its own `role="radio"` ("3.5 of 5" — the number
42
+ follows the page language, PL "3,5 z 5"), so the group keeps the radio pattern: clicking the left half
43
+ gives x.5, and the arrows step by 0.5. Read-only stars show the value rounded to a half (4.3 → four
44
+ and a half stars) while the name carries the exact score ("4.3 of 5"), so an average can be shown
45
+ as it is.
46
+
38
47
  ## Tokens
39
48
  Lit stars `--kpt-color-warning`, empty ones `--kpt-color-border-strong`.
40
49
 
41
50
  ## Accessibility
42
- `role="slider"` with `aria-valuenow` / `aria-valuemax`; every star is a real `<button>`, so the
43
- row is reachable with Tab and each value can be picked from the keyboard.
51
+ `role="radiogroup"` named by `ariaLabel` or the `KptFormField` label; each star is
52
+ `role="radio"` with `aria-checked`, labelled from `rating.star` (`{value}`, `{max}`) — EN `1 of 5`,
53
+ PL `1 z 5`. One Tab stop: `→`/`↑` raise the score by one, `←`/`↓` lower it (mirrored in RTL),
54
+ `Home`/`End` set the extremes, `Delete`/`Backspace` clear it; clicking the chosen star also clears it.
55
+ `readOnly` renders `role="img"` named "4 of 5" with no buttons, so a screen reader does not announce
56
+ a row of disabled controls. `touch` fires when focus leaves the group.
@@ -6,17 +6,35 @@ export interface KptRatingProps extends Omit<ComponentPropsWithRef<'div'>, 'onCh
6
6
  onValueChange?: (value: number) => void;
7
7
  /** Liczba gwiazdek. */
8
8
  count?: number;
9
+ /** Połówki gwiazdek: wartość co 0,5, każda połówka to osobne `radio`. */
10
+ allowHalf?: boolean;
9
11
  /** Ocena tylko do odczytu — bez klikania i bez podglądu pod kursorem. */
10
12
  readOnly?: boolean;
13
+ /** Id grupy (nie hosta); bez niego generowane, w `KptFormField` — z wrappera. */
14
+ id?: string;
15
+ /** Nazwa dostępna grupy, gdy nie opisuje jej etykieta (`KptFormField`). */
16
+ ariaLabel?: string | null;
17
+ /** Oznacza ocenę jako wymaganą atrybutem `aria-required`. */
18
+ required?: boolean;
11
19
  disabled?: boolean;
20
+ invalid?: boolean;
21
+ touched?: boolean;
12
22
  onTouch?: () => void;
13
23
  }
14
24
  /**
15
25
  * Ocena gwiazdkami. Kliknięcie zapalonej gwiazdki zeruje ocenę, więc wystawioną można cofnąć
16
26
  * bez osobnego przycisku „wyczyść".
17
27
  *
28
+ * Do edycji to grupa radiowa (`role="radiogroup"`, gwiazdki jako `role="radio"`): jeden przystanek
29
+ * Tab, strzałki zmieniają ocenę o krok (w RTL odwrócone w poziomie), `Home`/`End` ustawiają
30
+ * skrajne, `Delete`/`Backspace` zeruje. Z `allowHalf` każda gwiazdka ma dwie połówki — dwa osobne
31
+ * `radio` („3,5 z 5”), a krok strzałek to 0,5. Tylko do odczytu (`readOnly`) to obrazek z nazwą
32
+ * „4,3 z 5” — bez przycisków, które czytnik ogłaszałby jako wyłączone; gwiazdki pokazują wartość
33
+ * zaokrągloną do połówki.
34
+ *
18
35
  * @example
19
- * <KptRating value={score} onValueChange={setScore} count={5} />
20
- * <KptRating value={4} readOnly />
36
+ * <KptRating value={score} onValueChange={setScore} count={5} ariaLabel="Ocena" />
37
+ * <KptRating value={score} onValueChange={setScore} allowHalf ariaLabel="Ocena" />
38
+ * <KptRating value={4.3} readOnly />
21
39
  */
22
- export declare function KptRating({ value, defaultValue, onValueChange, count, readOnly, disabled, onTouch, locale, dictionary, className, ...rest }: Readonly<KptRatingProps>): import("react").JSX.Element;
40
+ export declare function KptRating({ value, defaultValue, onValueChange, count, allowHalf, readOnly, id, ariaLabel, required, disabled, invalid, touched, onTouch, locale, dictionary, className, ...rest }: Readonly<KptRatingProps>): import("react").JSX.Element;
@@ -1,30 +1,105 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { useState } from 'react';
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState } from 'react';
3
3
  import { cn } from "../internal/cn.js";
4
4
  import { useControllableState } from "../internal/use-controllable-state.js";
5
5
  import { useKptMessages } from "../i18n/i18n-context.js";
6
+ import { useKptFieldA11y } from "../form-field/form-field-context.js";
6
7
  import { KptIcon } from "../icon/icon.js";
8
+ const fillOf = (star, shown) => shown >= star ? 'full' : shown >= star - 0.5 ? 'half' : 'empty';
7
9
  /**
8
10
  * Ocena gwiazdkami. Kliknięcie zapalonej gwiazdki zeruje ocenę, więc wystawioną można cofnąć
9
11
  * bez osobnego przycisku „wyczyść".
10
12
  *
13
+ * Do edycji to grupa radiowa (`role="radiogroup"`, gwiazdki jako `role="radio"`): jeden przystanek
14
+ * Tab, strzałki zmieniają ocenę o krok (w RTL odwrócone w poziomie), `Home`/`End` ustawiają
15
+ * skrajne, `Delete`/`Backspace` zeruje. Z `allowHalf` każda gwiazdka ma dwie połówki — dwa osobne
16
+ * `radio` („3,5 z 5”), a krok strzałek to 0,5. Tylko do odczytu (`readOnly`) to obrazek z nazwą
17
+ * „4,3 z 5” — bez przycisków, które czytnik ogłaszałby jako wyłączone; gwiazdki pokazują wartość
18
+ * zaokrągloną do połówki.
19
+ *
11
20
  * @example
12
- * <KptRating value={score} onValueChange={setScore} count={5} />
13
- * <KptRating value={4} readOnly />
21
+ * <KptRating value={score} onValueChange={setScore} count={5} ariaLabel="Ocena" />
22
+ * <KptRating value={score} onValueChange={setScore} allowHalf ariaLabel="Ocena" />
23
+ * <KptRating value={4.3} readOnly />
14
24
  */
15
- export function KptRating({ value, defaultValue = 0, onValueChange, count = 5, readOnly = false, disabled = false, onTouch, locale, dictionary, className, ...rest }) {
16
- const { t } = useKptMessages({ locale, dictionary });
25
+ export function KptRating({ value, defaultValue = 0, onValueChange, count = 5, allowHalf = false, readOnly = false, id, ariaLabel = null, required = false, disabled = false, invalid = false, touched, onTouch, locale, dictionary, className, ...rest }) {
26
+ const { t, locale: activeLocale } = useKptMessages({ locale, dictionary });
27
+ const field = useKptFieldA11y(id, 'kpt-rating');
17
28
  const [current, setCurrent] = useControllableState({ value, defaultValue, onChange: onValueChange });
18
29
  const [hover, setHover] = useState(0);
19
- const locked = readOnly || disabled;
30
+ const groupRef = useRef(null);
31
+ /** Prośba o fokus na przystanek Tab po najbliższym renderze (zmiana oceny klawiszem). */
32
+ const wantFocus = useRef(false);
33
+ const showInvalid = invalid && (touched ?? true);
34
+ const stars = Array.from({ length: count }, (_, index) => index + 1);
35
+ const step = allowHalf ? 0.5 : 1;
20
36
  // Podgląd pod kursorem wygrywa z oceną, ale tylko dopóki kursor jest nad gwiazdkami —
21
37
  // po `mouseleave` wraca 0 i zapalona zostaje sama wartość.
22
38
  const shown = hover || current;
23
- const pick = (star) => {
24
- if (locked)
39
+ /** Przystanek Tab: wybrana wartość, a bez oceny — pierwsza możliwa. */
40
+ const tabStop = current >= step && current <= count ? current : step;
41
+ /** Liczba w zapisie języka strony („3,5” po polsku). */
42
+ const numberFormat = new Intl.NumberFormat(activeLocale, { maximumFractionDigits: 2 });
43
+ const starLabel = (score) => t('rating.star', { value: numberFormat.format(score), max: count });
44
+ useEffect(() => {
45
+ if (!wantFocus.current)
46
+ return;
47
+ wantFocus.current = false;
48
+ groupRef.current?.querySelector(`[data-value="${tabStop}"]`)?.focus();
49
+ });
50
+ const starVisual = (star, fill, children, hidden = false) => (_jsxs("span", { className: "kpt-rating__star", "data-fill": fill, "aria-hidden": hidden || undefined, children: [_jsx(KptIcon, { name: "star" }), _jsx("span", { className: "kpt-rating__fill", "aria-hidden": "true", children: _jsx(KptIcon, { name: "star" }) }), children] }, star));
51
+ if (readOnly) {
52
+ const score = starLabel(current);
53
+ // Gwiazdki pokazują wartość zaokrągloną do połówki (4,3 → 4,5), nazwa — dokładną.
54
+ const display = Math.round(current * 2) / 2;
55
+ return (_jsx("div", { className: cn('kpt-rating-host', className), ...rest, children: _jsx("div", { className: "kpt-rating kpt-rating--readonly", role: "img", id: field.id, "aria-label": ariaLabel ? `${ariaLabel}: ${score}` : score, "aria-describedby": field.describedBy, children: stars.map((star) => starVisual(star, fillOf(star, display), null, true)) }) }));
56
+ }
57
+ const pick = (score) => {
58
+ if (disabled)
59
+ return;
60
+ setCurrent(current === score ? 0 : score);
61
+ };
62
+ const onKeyDown = (event) => {
63
+ if (disabled)
64
+ return;
65
+ const rtl = getComputedStyle(event.currentTarget).direction === 'rtl';
66
+ const forward = rtl ? 'ArrowLeft' : 'ArrowRight';
67
+ const back = rtl ? 'ArrowRight' : 'ArrowLeft';
68
+ let next = null;
69
+ if (event.key === forward || event.key === 'ArrowUp')
70
+ next = Math.min(count, Math.max(step, current + step));
71
+ else if (event.key === back || event.key === 'ArrowDown')
72
+ next = Math.max(step, current - step);
73
+ else if (event.key === 'Home')
74
+ next = step;
75
+ else if (event.key === 'End')
76
+ next = count;
77
+ else if (event.key === 'Delete' || event.key === 'Backspace')
78
+ next = 0;
79
+ else if (event.key === ' ' || event.key === 'Enter') {
80
+ // Spacja na gwiazdce wybiera ją (jak radio); ponowna nie zeruje — od tego jest Delete.
81
+ const score = Number(event.target.dataset['value']);
82
+ if (score)
83
+ next = score;
84
+ }
85
+ if (next === null)
25
86
  return;
26
- setCurrent(current === star ? 0 : star);
27
- onTouch?.();
87
+ event.preventDefault();
88
+ setCurrent(next);
89
+ wantFocus.current = true;
90
+ };
91
+ /** „Dotknięcie” dopiero przy wyjściu fokusu z całej grupy, nie przy przejściu między gwiazdkami. */
92
+ const onBlur = (event) => {
93
+ const next = event.relatedTarget;
94
+ if (!next || !event.currentTarget.contains(next))
95
+ onTouch?.();
28
96
  };
29
- return (_jsx("div", { className: cn('kpt-rating-host', className), ...rest, children: _jsx("div", { className: "kpt-rating", role: "slider", "aria-valuenow": current, "aria-valuemax": count, children: Array.from({ length: count }, (_, index) => index + 1).map((star) => (_jsx("button", { type: "button", className: cn('kpt-rating__star', star <= shown && 'is-filled'), disabled: locked, onClick: () => pick(star), onMouseEnter: () => setHover(star), onMouseLeave: () => setHover(0), "aria-label": t('rating.star', { value: star, max: count }), children: _jsx(KptIcon, { name: "star" }) }, star))) }) }));
97
+ /** Pola wyboru jednej gwiazdki: całość albo — z `allowHalf` — dwie połówki. */
98
+ const optionsOf = (star) => allowHalf
99
+ ? [
100
+ { score: star - 0.5, part: 'start' },
101
+ { score: star, part: 'end' },
102
+ ]
103
+ : [{ score: star, part: 'full' }];
104
+ return (_jsx("div", { className: cn('kpt-rating-host', className), ...rest, children: _jsx("div", { ref: groupRef, className: cn('kpt-rating', disabled && 'kpt-rating--disabled'), role: "radiogroup", id: field.id, "aria-label": ariaLabel ?? undefined, "aria-labelledby": ariaLabel ? undefined : field.labelledBy, "aria-describedby": field.describedBy, "aria-required": required || undefined, "aria-invalid": showInvalid || undefined, "aria-disabled": disabled || undefined, onKeyDown: onKeyDown, onBlur: onBlur, onMouseLeave: () => setHover(0), children: stars.map((star) => starVisual(star, fillOf(star, shown), optionsOf(star).map((option) => (_jsx("button", { type: "button", role: "radio", className: cn('kpt-rating__hit', `kpt-rating__hit--${option.part}`), "aria-checked": option.score === current, tabIndex: !disabled && option.score === tabStop ? 0 : -1, "aria-disabled": disabled || undefined, "data-value": option.score, "aria-label": starLabel(option.score), onClick: () => pick(option.score), onMouseEnter: () => !disabled && setHover(option.score) }, option.part))))) }) }));
30
105
  }
@@ -45,6 +45,11 @@ only when the operation actually changed something — otherwise Tab stays nativ
45
45
  never becomes a keyboard trap (WCAG 2.1.2). In a table cell Tab moves to the next cell; a list
46
46
  inside the cell takes precedence.
47
47
 
48
+ ## The toolbar is one Tab stop
49
+ The toolbar is a named `role="toolbar"` ("Formatting", `richText.toolbar`) with a roving tabindex:
50
+ `←`/`→` (mirrored in RTL) and `Home`/`End` move between tools, skipping disabled ones; the stop
51
+ remembers the last tool, so Tab from the toolbar goes straight to the editor.
52
+
48
53
  ## Tables
49
54
  Insert from an 8×8 grid popover. Dragging across cells makes a rectangular selection that grows
50
55
  until it fully covers merged cells on its edges. Right-click opens the menu: insert and delete rows
@@ -69,3 +74,18 @@ Every label comes from the `richText` namespace, including `richText.menu.*` and
69
74
  Formatting goes through `document.execCommand`. The API is formally deprecated, but it is the only
70
75
  route into the browser's native undo stack (Ctrl+Z); replacing it would mean writing our own
71
76
  history, that is, a different editor.
77
+
78
+ ## Screen reader and keyboard
79
+ `link` opens a small dialog instead of the browser's `prompt()`: a labelled URL field (prefilled
80
+ with the current link), `Enter` inserts, `Escape` returns to the text with the selection restored.
81
+ The table context menu is a `role="menu"` of `menuitem`s: it also opens from the keyboard (the menu
82
+ key or `Shift+F10` in a cell), the arrows and `Home`/`End` move, `Escape`/`Tab` close it, and focus
83
+ goes back to the cell. Colour swatches are named ("Dark grey", `richText.swatches.*`) instead of
84
+ their hex codes. The table size grid is still mouse-only.
85
+
86
+ ## Label and description
87
+ `id` goes to the focusable element, not the host; without one it comes from `KptFormField` or is
88
+ generated. A `div` with a role is not named by `label[for]`, so inside `KptFormField` it gets
89
+ `aria-labelledby` pointing at the wrapper's label (unless `ariaLabel` is set), and the hint or error
90
+ is added to `aria-describedby`. New: `id`, `ariaLabel` and `required` (→ `aria-required`) on the
91
+ `role="textbox"` area.
@@ -13,6 +13,12 @@ export interface KptRichTextProps extends Omit<ComponentPropsWithRef<'div'>, 'on
13
13
  placeholder?: string;
14
14
  /** Maksymalna głębokość zagnieżdżenia list (Tab, „Dodaj wcięcie"). */
15
15
  maxListLevel?: number;
16
+ /** Id elementu fokusowalnego (nie hosta); bez niego generowane, w `KptFormField` — z wrappera. */
17
+ id?: string;
18
+ /** Nazwa dostępna, gdy kontrolki nie opisuje etykieta (`<label for>` albo `KptFormField`). */
19
+ ariaLabel?: string | null;
20
+ /** Oznacza pole jako wymagane atrybutem `aria-required`. */
21
+ required?: boolean;
16
22
  disabled?: boolean;
17
23
  invalid?: boolean;
18
24
  touched?: boolean;
@@ -35,4 +41,4 @@ export interface KptRichTextProps extends Omit<ComponentPropsWithRef<'div'>, 'on
35
41
  * <KptRichText value={html} onValueChange={setHtml} />
36
42
  * <KptRichText value={html} onValueChange={setHtml} toolbar={[['bold', 'italic'], ['link']]} />
37
43
  */
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;
44
+ export declare function KptRichText({ value, defaultValue, onValueChange, toolbar, placeholder, maxListLevel, id, ariaLabel, required, disabled, invalid, touched, onTouch, mentions, mentionLimit, onMention, locale, dictionary, className, ...rest }: Readonly<KptRichTextProps>): import("react").JSX.Element;