@konce-pt/react 0.11.1 → 0.12.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 (203) hide show
  1. package/README.md +8 -2
  2. package/dist/accordion/accordion.js +11 -10
  3. package/dist/alert/alert.js +1 -0
  4. package/dist/alert/llms.txt +1 -0
  5. package/dist/app-shell/app-shell.js +12 -5
  6. package/dist/app-shell/llms.txt +5 -4
  7. package/dist/auth/auth.js +17 -8
  8. package/dist/auth/llms.txt +3 -2
  9. package/dist/autocomplete/autocomplete.d.ts +1 -1
  10. package/dist/autocomplete/autocomplete.js +12 -4
  11. package/dist/autocomplete/llms.txt +3 -0
  12. package/dist/avatar/avatar.js +1 -0
  13. package/dist/avatar-group/avatar-group.js +1 -0
  14. package/dist/backdrop/backdrop.d.ts +0 -19
  15. package/dist/backdrop/backdrop.js +18 -13
  16. package/dist/bottom-sheet/bottom-sheet.d.ts +6 -1
  17. package/dist/bottom-sheet/bottom-sheet.js +3 -2
  18. package/dist/bottom-sheet/llms.txt +3 -0
  19. package/dist/breadcrumb/breadcrumb.d.ts +7 -1
  20. package/dist/breadcrumb/breadcrumb.js +10 -2
  21. package/dist/breadcrumb/llms.txt +8 -0
  22. package/dist/button/button.js +2 -1
  23. package/dist/button-group/button-group.d.ts +15 -2
  24. package/dist/button-group/button-group.js +31 -4
  25. package/dist/button-group/llms.txt +9 -6
  26. package/dist/card/card.js +4 -3
  27. package/dist/carousel/carousel.js +4 -1
  28. package/dist/charts/chart-legend.js +1 -0
  29. package/dist/charts/chart.d.ts +3 -2
  30. package/dist/charts/chart.js +12 -4
  31. package/dist/charts/llms.txt +6 -3
  32. package/dist/chat/chat.js +39 -9
  33. package/dist/chat/llms.txt +9 -0
  34. package/dist/chat-composer/chat-composer.d.ts +5 -2
  35. package/dist/chat-composer/chat-composer.js +10 -1
  36. package/dist/chat-dock/chat-dock.js +1 -0
  37. package/dist/chat-message/chat-message.js +2 -1
  38. package/dist/chat-typing/chat-typing.js +1 -0
  39. package/dist/checkbox/checkbox.d.ts +1 -1
  40. package/dist/checkbox/checkbox.js +9 -2
  41. package/dist/checkbox/llms.txt +3 -0
  42. package/dist/chip/chip.js +1 -0
  43. package/dist/chips-input/chips-input.d.ts +3 -1
  44. package/dist/chips-input/chips-input.js +14 -5
  45. package/dist/chips-input/llms.txt +3 -0
  46. package/dist/clock/clock.d.ts +9 -4
  47. package/dist/clock/clock.js +35 -5
  48. package/dist/clock/llms.txt +6 -2
  49. package/dist/color-picker/color-picker.d.ts +5 -1
  50. package/dist/color-picker/color-picker.js +36 -17
  51. package/dist/color-picker/llms.txt +3 -0
  52. package/dist/confirm/confirm-container.js +1 -0
  53. package/dist/confirm/confirm-store.js +6 -0
  54. package/dist/confirm/llms.txt +2 -1
  55. package/dist/context-menu/context-menu.js +35 -12
  56. package/dist/context-menu/llms.txt +3 -0
  57. package/dist/data-table/data-table.d.ts +12 -1
  58. package/dist/data-table/data-table.js +113 -51
  59. package/dist/data-table/export-csv.d.ts +2 -2
  60. package/dist/data-table/export-csv.js +7 -9
  61. package/dist/data-table/llms.txt +19 -4
  62. package/dist/data-view/data-view.js +1 -0
  63. package/dist/date-range/date-range.d.ts +16 -4
  64. package/dist/date-range/date-range.js +69 -46
  65. package/dist/date-range/llms.txt +17 -3
  66. package/dist/datepicker/datepicker.d.ts +17 -3
  67. package/dist/datepicker/datepicker.js +86 -54
  68. package/dist/datepicker/llms.txt +17 -3
  69. package/dist/dialog/dialog.js +29 -8
  70. package/dist/dialog/llms.txt +3 -0
  71. package/dist/divider/divider.d.ts +7 -1
  72. package/dist/divider/divider.js +3 -2
  73. package/dist/divider/llms.txt +1 -0
  74. package/dist/drawer/drawer.d.ts +6 -1
  75. package/dist/drawer/drawer.js +3 -2
  76. package/dist/drawer/llms.txt +3 -0
  77. package/dist/fieldset/fieldset.js +4 -1
  78. package/dist/file-upload/file-upload.d.ts +7 -1
  79. package/dist/file-upload/file-upload.js +18 -7
  80. package/dist/file-upload/llms.txt +6 -3
  81. package/dist/form-field/form-field.js +1 -0
  82. package/dist/galleria/galleria.js +4 -1
  83. package/dist/i18n/i18n-context.d.ts +3 -3
  84. package/dist/i18n/i18n-context.js +25 -3
  85. package/dist/icon/icon.js +4 -1
  86. package/dist/image/image.js +4 -4
  87. package/dist/input/input.js +1 -0
  88. package/dist/input-mask/input-mask.d.ts +7 -2
  89. package/dist/input-mask/input-mask.js +15 -41
  90. package/dist/input-mask/llms.txt +3 -0
  91. package/dist/input-number/input-number.d.ts +3 -1
  92. package/dist/input-number/input-number.js +15 -11
  93. package/dist/input-number/llms.txt +5 -2
  94. package/dist/input-otp/input-otp.d.ts +16 -3
  95. package/dist/input-otp/input-otp.js +43 -19
  96. package/dist/input-otp/llms.txt +11 -0
  97. package/dist/internal/mask.d.ts +14 -0
  98. package/dist/internal/mask.js +62 -0
  99. package/dist/internal/number-group.d.ts +22 -0
  100. package/dist/internal/number-group.js +53 -0
  101. package/dist/internal/option-key.d.ts +6 -0
  102. package/dist/internal/option-key.js +8 -0
  103. package/dist/internal/overlay.js +37 -7
  104. package/dist/internal/password-strength.d.ts +6 -0
  105. package/dist/internal/password-strength.js +74 -0
  106. package/dist/internal/scroll-lock.d.ts +6 -0
  107. package/dist/internal/scroll-lock.js +35 -0
  108. package/dist/internal/slot-children.d.ts +13 -0
  109. package/dist/internal/slot-children.js +31 -0
  110. package/dist/internal/step.d.ts +12 -0
  111. package/dist/internal/step.js +31 -0
  112. package/dist/internal/tween.js +2 -3
  113. package/dist/internal/use-controllable-state.d.ts +5 -0
  114. package/dist/internal/use-controllable-state.js +25 -4
  115. package/dist/internal/use-modal-focus.d.ts +1 -0
  116. package/dist/internal/use-modal-focus.js +12 -1
  117. package/dist/knob/knob.d.ts +5 -2
  118. package/dist/knob/knob.js +15 -14
  119. package/dist/knob/llms.txt +4 -2
  120. package/dist/listbox/listbox.d.ts +3 -1
  121. package/dist/listbox/listbox.js +30 -10
  122. package/dist/listbox/llms.txt +3 -0
  123. package/dist/map/llms.txt +5 -1
  124. package/dist/map/map-create-dialog.js +30 -22
  125. package/dist/map/map-hours-field.js +1 -0
  126. package/dist/map/map-image-field.js +2 -1
  127. package/dist/map/map-legend.js +1 -0
  128. package/dist/map/map-poi-card.js +2 -1
  129. package/dist/map/map-search.js +54 -30
  130. package/dist/map/map.d.ts +6 -1
  131. package/dist/map/map.js +62 -34
  132. package/dist/megamenu/llms.txt +11 -0
  133. package/dist/megamenu/megamenu.d.ts +7 -1
  134. package/dist/megamenu/megamenu.js +15 -6
  135. package/dist/menu/menu-rows.js +1 -0
  136. package/dist/menu/menu.js +10 -3
  137. package/dist/menubar/llms.txt +11 -0
  138. package/dist/menubar/menubar.d.ts +7 -1
  139. package/dist/menubar/menubar.js +15 -3
  140. package/dist/meter-group/meter-group.js +1 -0
  141. package/dist/order-list/llms.txt +3 -0
  142. package/dist/order-list/order-list.js +21 -6
  143. package/dist/paginator/paginator.js +7 -4
  144. package/dist/panel/panel.js +5 -3
  145. package/dist/password/llms.txt +6 -2
  146. package/dist/password/password.js +4 -14
  147. package/dist/peek/llms.txt +1 -1
  148. package/dist/peek/peek.js +37 -15
  149. package/dist/pick-list/llms.txt +3 -0
  150. package/dist/pick-list/pick-list.js +19 -3
  151. package/dist/popover/llms.txt +3 -0
  152. package/dist/popover/popover.d.ts +3 -1
  153. package/dist/popover/popover.js +10 -3
  154. package/dist/progress/progress.js +1 -0
  155. package/dist/public-api.d.ts +1 -1
  156. package/dist/radio-group/llms.txt +3 -0
  157. package/dist/radio-group/radio-group.d.ts +3 -1
  158. package/dist/radio-group/radio-group.js +13 -2
  159. package/dist/rating/rating.js +1 -0
  160. package/dist/rich-text/llms.txt +14 -0
  161. package/dist/rich-text/rich-text.d.ts +3 -9
  162. package/dist/rich-text/rich-text.js +241 -96
  163. package/dist/rich-text/sanitize.d.ts +9 -0
  164. package/dist/rich-text/sanitize.js +77 -29
  165. package/dist/roadmap/llms.txt +9 -3
  166. package/dist/roadmap/roadmap.js +88 -11
  167. package/dist/scroll-top/scroll-top.js +1 -0
  168. package/dist/select/llms.txt +3 -0
  169. package/dist/select/select.d.ts +3 -1
  170. package/dist/select/select.js +46 -32
  171. package/dist/shared/types.d.ts +21 -0
  172. package/dist/shared/types.js +9 -1
  173. package/dist/sidenav/sidenav.d.ts +1 -2
  174. package/dist/sidenav/sidenav.js +4 -3
  175. package/dist/slider/llms.txt +3 -0
  176. package/dist/slider/slider.d.ts +3 -1
  177. package/dist/slider/slider.js +15 -4
  178. package/dist/speed-dial/speed-dial.js +2 -1
  179. package/dist/spinner/spinner.js +1 -0
  180. package/dist/split-button/split-button.js +10 -3
  181. package/dist/splitter/splitter.js +23 -8
  182. package/dist/stepper/llms.txt +3 -0
  183. package/dist/stepper/stepper.js +9 -5
  184. package/dist/switch/llms.txt +3 -0
  185. package/dist/switch/switch.d.ts +1 -1
  186. package/dist/switch/switch.js +9 -2
  187. package/dist/switch-group/llms.txt +3 -0
  188. package/dist/switch-group/switch-group.d.ts +3 -1
  189. package/dist/switch-group/switch-group.js +5 -3
  190. package/dist/tabs/llms.txt +3 -0
  191. package/dist/tabs/tabs.js +30 -14
  192. package/dist/textarea/textarea.js +1 -0
  193. package/dist/toast/llms.txt +3 -0
  194. package/dist/toast/toast-container.js +1 -0
  195. package/dist/toast/toast-store.d.ts +5 -1
  196. package/dist/toast/toast-store.js +1 -1
  197. package/dist/tooltip/llms.txt +5 -3
  198. package/dist/tooltip/tooltip.d.ts +3 -2
  199. package/dist/tooltip/tooltip.js +148 -55
  200. package/dist/tree/llms.txt +1 -0
  201. package/dist/tree/tree.d.ts +6 -1
  202. package/dist/tree/tree.js +14 -7
  203. package/package.json +15 -14
@@ -1,9 +1,11 @@
1
+ 'use client';
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
3
  import { useRef, useState } from 'react';
3
4
  import { cn } from "../internal/cn.js";
4
5
  import { useControllableState } from "../internal/use-controllable-state.js";
5
6
  import { useKptMessages } from "../i18n/i18n-context.js";
6
7
  import { KptIcon } from "../icon/icon.js";
8
+ import { useKptFieldA11y } from "../form-field/form-field-context.js";
7
9
  const EMPTY = [];
8
10
  /** Rozmiar pliku w KB albo MB — bez bibliotek, bo to jedna reguła. */
9
11
  function formatSize(file) {
@@ -26,14 +28,20 @@ function fileIcon(file) {
26
28
  * @example
27
29
  * <KptFileUpload value={files} onValueChange={setFiles} accept="image/*" multiple />
28
30
  */
29
- export function KptFileUpload({ value, defaultValue = EMPTY, onValueChange, accept = '', multiple = false, disabled = false, invalid = false, touched, onTouch, locale, dictionary, className, ...rest }) {
31
+ export function KptFileUpload({ value, defaultValue = EMPTY, onValueChange, accept = '', multiple = false, disabled = false, readOnly = false, id, ariaLabel = null, invalid = false, touched, onTouch, locale, dictionary, className, ...rest }) {
30
32
  const { t } = useKptMessages({ locale, dictionary });
31
33
  const inputRef = useRef(null);
34
+ const zoneRef = useRef(null);
35
+ /** `div` z rolą nie dostaje nazwy z `label[for]` — stąd `aria-labelledby` na etykietę wrappera. */
36
+ const field = useKptFieldA11y(id, 'kpt-file-upload');
37
+ // Podpowiedź rozcięta w miejscu `{action}` — pogrubienie robi JSX, nie HTML ze słownika.
38
+ const [hintBefore = '', hintAfter = ''] = t('fileUpload.hint').split('{action}');
39
+ const locked = disabled || readOnly;
32
40
  const [current, setCurrent] = useControllableState({ value, defaultValue, onChange: onValueChange });
33
41
  const [dragover, setDragover] = useState(false);
34
42
  const showError = invalid && (touched ?? true);
35
43
  const openPicker = () => {
36
- if (!disabled)
44
+ if (!locked)
37
45
  inputRef.current?.click();
38
46
  };
39
47
  /** `Enter` / spacja na strefie — spacja bez `preventDefault` przewinęłaby stronę. */
@@ -55,17 +63,20 @@ export function KptFileUpload({ value, defaultValue = EMPTY, onValueChange, acce
55
63
  // Czyszczenie pola pozwala wybrać ten sam plik drugi raz — inaczej `change` by nie poleciał.
56
64
  event.target.value = '';
57
65
  };
58
- return (_jsxs("div", { className: cn('kpt-file-upload-host', className), ...rest, children: [_jsxs("div", { className: cn('kpt-file-upload__zone', dragover && 'is-dragover', disabled && 'is-disabled'), role: "button", tabIndex: disabled ? -1 : 0, "aria-disabled": disabled || undefined, "aria-invalid": showError || undefined, "data-invalid": showError ? '' : undefined, onClick: openPicker, onKeyDown: onZoneKeyDown, onDragOver: (event) => {
66
+ return (_jsxs("div", { className: cn('kpt-file-upload-host', className), ...rest, children: [_jsxs("div", { className: cn('kpt-file-upload__zone', dragover && 'is-dragover', disabled && 'is-disabled'), ref: zoneRef, role: "button", id: field.id, tabIndex: disabled ? -1 : 0, "aria-disabled": locked || undefined, "aria-label": ariaLabel ?? undefined, "aria-labelledby": ariaLabel ? undefined : field.labelledBy, "aria-describedby": field.describedBy, "data-invalid": showError ? '' : undefined, onClick: openPicker, onKeyDown: onZoneKeyDown, onDragOver: (event) => {
59
67
  event.preventDefault();
60
- if (!disabled)
68
+ if (!locked)
61
69
  setDragover(true);
62
70
  }, onDragLeave: () => setDragover(false), onDrop: (event) => {
63
71
  event.preventDefault();
64
72
  setDragover(false);
65
- if (!disabled)
73
+ if (!locked)
66
74
  addFiles(event.dataTransfer?.files);
67
- }, children: [_jsx("input", { ref: inputRef, type: "file", hidden: true, accept: accept, multiple: multiple, onChange: onSelect }), _jsx(KptIcon, { name: "upload", className: "kpt-file-upload__icon" }), _jsx("div", { className: "kpt-file-upload__text", dangerouslySetInnerHTML: { __html: t('fileUpload.hint') } }), accept ? _jsx("div", { className: "kpt-file-upload__hint", children: accept }) : null] }), current.length ? (_jsx("ul", { className: "kpt-file-upload__list", children: current.map((file, index) => (_jsxs("li", { className: "kpt-file-upload__file", children: [_jsx(KptIcon, { name: fileIcon(file) }), _jsx("span", { className: "kpt-file-upload__name", children: file.name }), _jsx("span", { className: "kpt-file-upload__size", children: formatSize(file) }), _jsx("button", { type: "button", className: "kpt-file-upload__remove", "aria-label": t('fileUpload.remove'), onClick: (event) => {
75
+ }, children: [_jsx("input", { ref: inputRef, type: "file", hidden: true, accept: accept, multiple: multiple, onChange: onSelect }), _jsx(KptIcon, { name: "upload", className: "kpt-file-upload__icon" }), _jsxs("div", { className: "kpt-file-upload__text", children: [hintBefore, _jsx("strong", { children: t('fileUpload.hintAction') }), hintAfter] }), accept ? _jsx("div", { className: "kpt-file-upload__hint", children: accept }) : null] }), current.length ? (_jsx("ul", { className: "kpt-file-upload__list", children: current.map((file, index) => (_jsxs("li", { className: "kpt-file-upload__file", children: [_jsx(KptIcon, { name: fileIcon(file) }), _jsx("span", { className: "kpt-file-upload__name", children: file.name }), _jsx("span", { className: "kpt-file-upload__size", children: formatSize(file) }), readOnly ? null : (_jsx("button", { type: "button", className: "kpt-file-upload__remove", "aria-label": `${t('fileUpload.remove')}: ${file.name}`, onClick: (event) => {
68
76
  event.stopPropagation();
69
77
  setCurrent(current.filter((item) => item !== file));
70
- }, children: _jsx(KptIcon, { name: "trash" }) })] }, `${index}-${file.name}`))) })) : null] }));
78
+ onTouch?.();
79
+ // Przycisk znika razem z pozycją — fokus wraca na strefę, a nie spada na <body>.
80
+ zoneRef.current?.focus();
81
+ }, children: _jsx(KptIcon, { name: "trash" }) }))] }, `${index}-${file.name}`))) })) : null] }));
71
82
  }
@@ -28,9 +28,9 @@ Import: `import { KptFileUpload } from '@konce-pt/react';`
28
28
  `event.target.value = ''` after reading the files, otherwise picking the same file twice in a row
29
29
  would fire no `change` and the second pick would be silently lost.
30
30
 
31
- ## The hint carries markup
32
- `fileUpload.hint` holds a `<strong>`, so it is rendered with `dangerouslySetInnerHTML` — the text
33
- comes from the dictionary (ours or the app's), never from user input.
31
+ ## The hint is plain text
32
+ `fileUpload.hint` holds an `{action}` placeholder that is replaced by `fileUpload.hintAction` in
33
+ `<strong>`. Dictionaries carry no HTML, so a translation from a CMS cannot inject markup.
34
34
 
35
35
  ## Examples
36
36
  <KptFileUpload value={files} onValueChange={setFiles} accept="image/*" multiple />
@@ -57,3 +57,6 @@ focus ring `--kpt-color-focus-ring`.
57
57
  ## Accessibility
58
58
  The remove button is labelled from the dictionary. The zone itself is the keyboard path to the
59
59
  system picker — see "Keyboard and states" above.
60
+
61
+ ## Read-only and required
62
+ `readOnly` shows the list without the remove buttons and does not open the picker nor accept a drop. The zone takes `id` and `ariaLabel` and is linked to `kpt-form-field` (label and description); `button` accepts neither `aria-invalid` nor `aria-required`, so the error is the linked message. The hint is plain text: `fileUpload.hint` with `{action}` and `fileUpload.hintAction` (bold) — dictionaries no longer carry HTML. The remove button names the file.
@@ -1,3 +1,4 @@
1
+ 'use client';
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
3
  import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, } from 'react';
3
4
  import { cn } from "../internal/cn.js";
@@ -1,3 +1,4 @@
1
+ 'use client';
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
3
  import { useRef, useState } from 'react';
3
4
  import { cn } from "../internal/cn.js";
@@ -19,11 +20,13 @@ const NAV_KEYS = new Set(['ArrowLeft', 'ArrowRight', 'Home', 'End']);
19
20
  */
20
21
  export function KptGalleria({ items = [], activeIndex, defaultActiveIndex = 0, onActiveIndexChange, showThumbnails = true, locale, dictionary, className, ...rest }) {
21
22
  const { t } = useKptMessages({ locale, dictionary });
22
- const [active, setActive] = useControllableState({
23
+ const [requested, setActive] = useControllableState({
23
24
  value: activeIndex,
24
25
  defaultValue: defaultActiveIndex,
25
26
  onChange: onActiveIndexChange,
26
27
  });
28
+ // Indeks przycięty do listy: po skróceniu `items` (filtr) widok nie zostaje pusty.
29
+ const active = Math.min(Math.max(requested, 0), Math.max(items.length - 1, 0));
27
30
  // Ogłoszenie dopiero po działaniu użytkownika — przy starcie region ma milczeć.
28
31
  const [userMoved, setUserMoved] = useState(false);
29
32
  const thumbsRef = useRef([]);
@@ -1,6 +1,5 @@
1
1
  import { type ReactNode } from 'react';
2
- import { type DeepPartial, type KptLocale, type KptMessages } from '@konce-pt/i18n';
3
- /** Kontrakt zwracany przez `useKptI18n()`. */
2
+ import { type DeepPartial, type KptLocale, type KptMessageKey, type KptMessages } from '@konce-pt/i18n';
4
3
  export interface KptI18nContextValue {
5
4
  /** Bieżący język. */
6
5
  locale: KptLocale;
@@ -11,7 +10,8 @@ export interface KptI18nContextValue {
11
10
  /** Słownik zarejestrowany dla danego języka; nieznany język spada na EN. */
12
11
  messagesFor: (locale: KptLocale) => KptMessages;
13
12
  /** Tłumaczy klucz w notacji kropkowej (`'paginator.range'`) z interpolacją `{name}`. */
14
- t: (key: string, params?: Record<string, string | number>) => string;
13
+ /** Tłumaczenie klucza; `KptMessageKey` daje podpowiedzi kluczy bazowych, dowolny napis też przechodzi. */
14
+ t: (key: KptMessageKey | (string & {}), params?: Record<string, string | number>) => string;
15
15
  }
16
16
  export interface KptI18nProviderProps {
17
17
  /** Język początkowy (domyślnie 'en'). */
@@ -1,12 +1,34 @@
1
+ 'use client';
1
2
  import { jsx as _jsx } from "react/jsx-runtime";
2
3
  import { createContext, useCallback, useContext, useMemo, useState } from 'react';
3
4
  import { KPT_MESSAGES_EN, KPT_MESSAGES_PL, deepMerge, translate, } from '@konce-pt/i18n';
5
+ import { isDevBuild } from "../internal/dev.js";
6
+ /*
7
+ * Wiązanie i18n dla Reacta. Odpowiednik `provideKptI18n` + `KptI18n` z Angulara,
8
+ * na tym samym rdzeniu z `@konce-pt/i18n` — te same klucze, ten sam fallback
9
+ * (słownik → EN → sam klucz) i ta sama interpolacja `{name}`.
10
+ */
11
+ /** Kontrakt zwracany przez `useKptI18n()`. */
12
+ /** Klucze, o których brak już ostrzegaliśmy — jedno ostrzeżenie na klucz. */
13
+ const warnedKeys = new Set();
14
+ /**
15
+ * `translate` z ostrzeżeniem w buildzie deweloperskim, gdy klucza nie ma w żadnym słowniku —
16
+ * inaczej literówka daje po cichu sam klucz zamiast tekstu.
17
+ */
18
+ function translateChecked(dict, key, params) {
19
+ const text = translate(dict, key, params);
20
+ if (text === key && isDevBuild() && !warnedKeys.has(key)) {
21
+ warnedKeys.add(key);
22
+ console.warn(`KptI18n: brak tłumaczenia dla klucza „${key}”.`);
23
+ }
24
+ return text;
25
+ }
4
26
  const FALLBACK = {
5
27
  locale: 'en',
6
28
  setLocale: () => { },
7
29
  messages: KPT_MESSAGES_EN,
8
30
  messagesFor: () => KPT_MESSAGES_EN,
9
- t: (key, params) => translate(KPT_MESSAGES_EN, key, params),
31
+ t: (key, params) => translateChecked(KPT_MESSAGES_EN, key, params),
10
32
  };
11
33
  const KptI18nContext = createContext(null);
12
34
  /**
@@ -33,7 +55,7 @@ export function KptI18nProvider({ locale = 'en', messages, children }) {
33
55
  setLocale: setCurrent,
34
56
  messages: active,
35
57
  messagesFor,
36
- t: (key, params) => translate(active, key, params),
58
+ t: (key, params) => translateChecked(active, key, params),
37
59
  };
38
60
  }, [current, messagesFor]);
39
61
  return _jsx(KptI18nContext.Provider, { value: value, children: children });
@@ -59,7 +81,7 @@ export function useKptMessages(overrides) {
59
81
  ...context,
60
82
  locale: effectiveLocale,
61
83
  messages: merged,
62
- t: (key, params) => translate(merged, key, params),
84
+ t: (key, params) => translateChecked(merged, key, params),
63
85
  };
64
86
  }, [context, locale, dictionary]);
65
87
  }
package/dist/icon/icon.js CHANGED
@@ -1,3 +1,4 @@
1
+ 'use client';
1
2
  import { jsx as _jsx } from "react/jsx-runtime";
2
3
  import { renderKptIconSvg } from '@konce-pt/icons';
3
4
  import { useEffect, useSyncExternalStore } from 'react';
@@ -33,5 +34,7 @@ export function KptIcon({ name, size = '', label = '', className, style, ...rest
33
34
  warned.add(name);
34
35
  console.warn(`KptIcon: ikony „${name}” nie ma (jeszcze) w rejestrze — sprawdź nazwę albo zarejestruj ją (registerKptIcons / registerKptTablerIcons).`);
35
36
  }, [svg, name]);
36
- return (_jsx("span", { className: cn('kpt-icon-host', className), "aria-hidden": label ? undefined : true, role: label ? 'img' : undefined, "aria-label": label || undefined, style: size ? { fontSize: size, ...style } : style, dangerouslySetInnerHTML: { __html: html }, ...rest }));
37
+ return (_jsx("span", { className: cn('kpt-icon-host', className), "aria-hidden": label ? undefined : true, role: label ? 'img' : undefined, "aria-label": label || undefined, style: size ? { fontSize: size, ...style } : style,
38
+ // eslint-disable-next-line react/no-danger -- treść z rejestru, zsanityzowana przy `register()` (sanitizeSvgFragment)
39
+ dangerouslySetInnerHTML: { __html: html }, ...rest }));
37
40
  }
@@ -1,6 +1,8 @@
1
+ 'use client';
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
3
  import { useEffect, useRef, useState } from 'react';
3
4
  import { cn } from "../internal/cn.js";
5
+ import { kptLockScroll } from "../internal/scroll-lock.js";
4
6
  import { useKptMessages } from "../i18n/i18n-context.js";
5
7
  import { KptIcon } from "../icon/icon.js";
6
8
  /**
@@ -25,11 +27,9 @@ export function KptImage({ src, alt = '', width = null, preview = false, locale,
25
27
  return;
26
28
  if (!el.open)
27
29
  el.showModal();
28
- const root = document.documentElement;
29
- const saved = root.style.overflow;
30
- root.style.overflow = 'hidden';
30
+ const releaseScroll = kptLockScroll();
31
31
  return () => {
32
- root.style.overflow = saved;
32
+ releaseScroll();
33
33
  if (el.open)
34
34
  el.close();
35
35
  };
@@ -1,3 +1,4 @@
1
+ 'use client';
1
2
  import { jsx as _jsx } from "react/jsx-runtime";
2
3
  import { useCallback } from 'react';
3
4
  import { cn } from "../internal/cn.js";
@@ -1,10 +1,15 @@
1
1
  import type { ComponentPropsWithRef } from 'react';
2
- export interface KptInputMaskProps extends Omit<ComponentPropsWithRef<'input'>, 'value' | 'defaultValue' | 'onChange'> {
2
+ export interface KptInputMaskProps extends Omit<ComponentPropsWithRef<'input'>, 'value' | 'defaultValue' | 'onChange' | 'required'> {
3
3
  value?: string;
4
4
  defaultValue?: string;
5
5
  onValueChange?: (value: string) => void;
6
6
  /** Wzorzec maski; pozostałe znaki to literały (spacje, nawiasy, myślniki). */
7
7
  mask?: string;
8
+ /**
9
+ * Pole wymagane — `aria-required`, a nie natywne `required` (jak w `KptInput`): natywne
10
+ * blokowałoby wysyłkę i uruchamiało walidację przeglądarki obok Twojej.
11
+ */
12
+ required?: boolean;
8
13
  invalid?: boolean;
9
14
  touched?: boolean;
10
15
  onTouch?: () => void;
@@ -16,4 +21,4 @@ export interface KptInputMaskProps extends Omit<ComponentPropsWithRef<'input'>,
16
21
  * @example
17
22
  * <KptInputMask mask="(999) 999-99-99" value={phone} onValueChange={setPhone} />
18
23
  */
19
- export declare function KptInputMask({ value, defaultValue, onValueChange, mask, placeholder, invalid, touched, onTouch, disabled, className, id, onBlur, 'aria-describedby': ariaDescribedBy, inputMode, ...rest }: Readonly<KptInputMaskProps>): import("react").JSX.Element;
24
+ export declare function KptInputMask({ value, defaultValue, onValueChange, mask, placeholder, required, invalid, touched, onTouch, disabled, className, id, onBlur, 'aria-describedby': ariaDescribedBy, inputMode, ...rest }: Readonly<KptInputMaskProps>): import("react").JSX.Element;
@@ -1,13 +1,9 @@
1
+ 'use client';
1
2
  import { jsx as _jsx } from "react/jsx-runtime";
2
3
  import { cn } from "../internal/cn.js";
3
4
  import { useControllableState } from "../internal/use-controllable-state.js";
4
5
  import { joinIds, useKptFieldA11y } from "../form-field/form-field-context.js";
5
- /** Tokeny maski: `9` = cyfra, `a` = litera, `*` = znak alfanumeryczny. */
6
- const TOKENS = {
7
- '9': /\d/,
8
- a: /[a-zA-Z]/,
9
- '*': /[a-zA-Z0-9]/,
10
- };
6
+ import { KPT_MASK_TOKENS, kptApplyMask, kptMaskCaret } from "../internal/mask.js";
11
7
  /**
12
8
  * Pole z maską znakową. Tokeny: `9` = cyfra, `a` = litera, `*` = alfanumeryczny; reszta wzorca
13
9
  * to literały wstawiane automatycznie.
@@ -15,52 +11,30 @@ const TOKENS = {
15
11
  * @example
16
12
  * <KptInputMask mask="(999) 999-99-99" value={phone} onValueChange={setPhone} />
17
13
  */
18
- export function KptInputMask({ value, defaultValue = '', onValueChange, mask = '', placeholder, invalid = false, touched, onTouch, disabled = false, className, id, onBlur, 'aria-describedby': ariaDescribedBy, inputMode, ...rest }) {
14
+ export function KptInputMask({ value, defaultValue = '', onValueChange, mask = '', placeholder, required = false, invalid = false, touched, onTouch, disabled = false, className, id, onBlur, 'aria-describedby': ariaDescribedBy, inputMode, ...rest }) {
19
15
  const field = useKptFieldA11y(id, 'kpt-input-mask');
20
16
  // Klawiatura ekranowa z maski: same tokeny `9` (telefon, kod pocztowy) → numeryczna.
21
- const tokens = [...mask].filter((char) => char in TOKENS);
17
+ const tokens = [...mask].filter((char) => char in KPT_MASK_TOKENS);
22
18
  const autoInputMode = tokens.length && tokens.every((char) => char === '9') ? 'numeric' : 'text';
23
19
  const inputId = field.id;
24
20
  const [current, setCurrent] = useControllableState({ value, defaultValue, onChange: onValueChange });
25
21
  const showInvalid = invalid && (touched ?? true);
26
- /*
27
- * Maska przepisuje wejście od zera: z tekstu bierzemy wyłącznie znaki pasujące do kolejnych
28
- * tokenów, a literały wstawiamy same. Dzięki temu wklejenie „48123456789" i wpisanie
29
- * „(48) 123…" dają ten sam wynik, a usunięcie literału nie zostawia dziury we wzorcu.
30
- */
31
- const applyMask = (raw) => {
32
- if (!mask)
33
- return raw;
34
- const chars = raw.split('');
35
- let charIndex = 0;
36
- let out = '';
37
- for (let maskIndex = 0; maskIndex < mask.length && charIndex < chars.length;) {
38
- const token = mask[maskIndex];
39
- const rule = TOKENS[token];
40
- if (rule) {
41
- while (charIndex < chars.length && !rule.test(chars[charIndex]))
42
- charIndex++;
43
- if (charIndex >= chars.length)
44
- break;
45
- out += chars[charIndex];
46
- charIndex++;
47
- maskIndex++;
48
- }
49
- else {
50
- out += token;
51
- maskIndex++;
52
- }
53
- }
54
- return out;
55
- };
56
22
  const onInput = (event) => {
57
- const formatted = applyMask(event.target.value);
23
+ const el = event.target;
24
+ const raw = el.value;
25
+ const caret = el.selectionStart ?? raw.length;
26
+ const formatted = kptApplyMask(raw, mask);
58
27
  setCurrent(formatted);
59
28
  // Gdy maska odrzuci znak, wartość się nie zmienia i React nie przerysuje pola —
60
29
  // wpisany znak zostałby na ekranie, więc DOM zrównujemy ręcznie.
61
- event.target.value = formatted;
30
+ el.value = formatted;
31
+ // Przepisanie `value` przenosi kursor na koniec; wraca za tyle samo znaczących znaków co przed zmianą.
32
+ if (document.activeElement === el) {
33
+ const at = kptMaskCaret(raw, caret, mask);
34
+ el.setSelectionRange(at, at);
35
+ }
62
36
  };
63
- return (_jsx("span", { className: "kpt-input-mask-host", children: _jsx("input", { className: cn('kpt-input-mask__input', showInvalid && 'kpt-input-mask--invalid', className), id: inputId, "aria-invalid": showInvalid || undefined, "aria-describedby": joinIds(ariaDescribedBy, field.describedBy), inputMode: inputMode ?? autoInputMode, type: "text", value: current, disabled: disabled, placeholder: placeholder || mask, onChange: onInput, onBlur: (event) => {
37
+ return (_jsx("span", { className: "kpt-input-mask-host", children: _jsx("input", { className: cn('kpt-input-mask__input', showInvalid && 'kpt-input-mask--invalid', className), id: inputId, "aria-invalid": showInvalid || undefined, "aria-required": required || undefined, "aria-describedby": joinIds(ariaDescribedBy, field.describedBy), inputMode: inputMode ?? autoInputMode, type: "text", value: current, disabled: disabled, placeholder: placeholder || mask, onChange: onInput, onBlur: (event) => {
64
38
  onBlur?.(event);
65
39
  onTouch?.();
66
40
  }, ...rest }) }));
@@ -46,3 +46,6 @@ New: `aria-invalid` after touch.
46
46
  ## Accessibility
47
47
  A native text input; the mask doubles as the placeholder, so the expected shape is visible before
48
48
  typing starts.
49
+
50
+ ## Read-only and required
51
+ `readOnly` is the native read-only field. The caret stays where you typed: fixing a digit in the middle of a phone number no longer throws it to the end. The `a` and `*` tokens accept every letter (`ą`, `ł`, `ß`), not only ASCII.
@@ -19,6 +19,8 @@ export interface KptInputNumberProps extends Omit<ComponentPropsWithRef<'div'>,
19
19
  /** Oznacza pole jako wymagane atrybutem `aria-required`. */
20
20
  required?: boolean;
21
21
  disabled?: boolean;
22
+ /** Tylko do odczytu — wartość widać i da się ją zaznaczyć, ale nie zmienić (także strzałkami). */
23
+ readOnly?: boolean;
22
24
  invalid?: boolean;
23
25
  touched?: boolean;
24
26
  onTouch?: () => void;
@@ -37,4 +39,4 @@ export interface KptInputNumberProps extends Omit<ComponentPropsWithRef<'div'>,
37
39
  * <KptInputNumber value={qty} onValueChange={setQty} min={0} max={99} suffix=" szt." />
38
40
  * <KptInputNumber value={from} onValueChange={setFrom} showButtons={false} suffix=" zł" />
39
41
  */
40
- export declare function KptInputNumber({ value, defaultValue, onValueChange, min, max, step, placeholder, suffix, showButtons, id, ariaLabel, required, disabled, invalid, touched, onTouch, locale, dictionary, className, ...rest }: Readonly<KptInputNumberProps>): import("react").JSX.Element;
42
+ export declare function KptInputNumber({ value, defaultValue, onValueChange, min, max, step, placeholder, suffix, showButtons, id, ariaLabel, required, disabled, readOnly, invalid, touched, onTouch, locale, dictionary, className, ...rest }: Readonly<KptInputNumberProps>): import("react").JSX.Element;
@@ -1,3 +1,4 @@
1
+ 'use client';
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
3
  import { useCallback, useState } from 'react';
3
4
  import { cn } from "../internal/cn.js";
@@ -5,6 +6,9 @@ import { useControllableState } from "../internal/use-controllable-state.js";
5
6
  import { useKptMessages } from "../i18n/i18n-context.js";
6
7
  import { KptIcon } from "../icon/icon.js";
7
8
  import { useKptFieldA11y } from "../form-field/form-field-context.js";
9
+ import { kptRoundTo } from "../internal/step.js";
10
+ /** Liczba dziesiętna wpisana przez człowieka: bez `0x1A`, `1e3` i `Infinity`, które `Number()` też przyjmuje. */
11
+ const DECIMAL = /^[-+]?(?:\d+\.?\d*|\.\d+)$/;
8
12
  /**
9
13
  * Pole liczbowe ze strzałkami krokowania. Wartość to `number | null` — puste pole ma znaczenie,
10
14
  * bo „brak wartości" to nie to samo co zero.
@@ -19,7 +23,7 @@ import { useKptFieldA11y } from "../form-field/form-field-context.js";
19
23
  * <KptInputNumber value={qty} onValueChange={setQty} min={0} max={99} suffix=" szt." />
20
24
  * <KptInputNumber value={from} onValueChange={setFrom} showButtons={false} suffix=" zł" />
21
25
  */
22
- export function KptInputNumber({ value, defaultValue = null, onValueChange, min, max, step = 1, placeholder = '', suffix = '', showButtons = true, id, ariaLabel = null, required = false, disabled = false, invalid = false, touched, onTouch, locale, dictionary, className, ...rest }) {
26
+ export function KptInputNumber({ value, defaultValue = null, onValueChange, min, max, step = 1, placeholder = '', suffix = '', showButtons = true, id, ariaLabel = null, required = false, disabled = false, readOnly = false, invalid = false, touched, onTouch, locale, dictionary, className, ...rest }) {
23
27
  const { t, locale: activeLocale } = useKptMessages({ locale, dictionary });
24
28
  const field = useKptFieldA11y(id, 'kpt-input-number');
25
29
  const [current, setCurrent] = useControllableState({
@@ -39,7 +43,9 @@ export function KptInputNumber({ value, defaultValue = null, onValueChange, min,
39
43
  }, [min, max]);
40
44
  // Zapis liczby wg aktywnego języka (PL: `12,5`), bez separatorów tysięcy — pole przyjmuje i kropkę,
41
45
  // i przecinek, a grupowanie cyfr rozjechałoby się z tym, co user wpisuje.
42
- const format = new Intl.NumberFormat(activeLocale, { useGrouping: false, maximumFractionDigits: 20 });
46
+ // `latn`: cyfry łacińskie także w locale z własnymi (np. `ar`) — inaczej pole pokazywałoby zapis,
47
+ // którego parser niżej nie przeczyta.
48
+ const format = new Intl.NumberFormat(activeLocale, { useGrouping: false, maximumFractionDigits: 20, numberingSystem: 'latn' });
43
49
  const display = current === null || current === undefined || Number.isNaN(current) ? '' : `${format.format(current)}${suffix}`;
44
50
  const canIncrement = max === undefined || (current ?? -Infinity) < max;
45
51
  const canDecrement = min === undefined || (current ?? Infinity) > min;
@@ -53,21 +59,19 @@ export function KptInputNumber({ value, defaultValue = null, onValueChange, min,
53
59
  return;
54
60
  }
55
61
  // Bez przycinania: wartość spoza granic zostaje, a walidacja formularza ją zgłosi.
56
- const parsed = Number(raw);
57
- if (!Number.isNaN(parsed))
58
- setCurrent(parsed);
62
+ if (DECIMAL.test(raw))
63
+ setCurrent(Number(raw));
59
64
  };
60
65
  const stepBy = (count) => {
61
66
  const base = current ?? min ?? 0;
62
- // Zaokrąglenie do precyzji kroku — 0.1 + 0.2 nie może dać 0.30000000000000004.
63
- const decimals = (String(step).split('.')[1] ?? '').length;
67
+ // Precyzja kroku ALBO wartości, zależnie od tego, która jest drobniejsza (1.25 + 1 = 2.25).
64
68
  setDraft(null);
65
- setCurrent(clamp(Number((base + count * step).toFixed(decimals))));
69
+ setCurrent(clamp(kptRoundTo(base + count * step, step, base)));
66
70
  };
67
71
  const onKeyDown = (event) => {
68
72
  const steps = { ArrowUp: 1, ArrowDown: -1, PageUp: 10, PageDown: -10 };
69
73
  const count = steps[event.key];
70
- if (count === undefined || disabled)
74
+ if (count === undefined || disabled || readOnly)
71
75
  return;
72
76
  event.preventDefault();
73
77
  stepBy(count);
@@ -82,10 +86,10 @@ export function KptInputNumber({ value, defaultValue = null, onValueChange, min,
82
86
  setDraft(null);
83
87
  onTouch?.();
84
88
  };
85
- return (_jsx("div", { className: cn('kpt-input-number-host', className), ...rest, children: _jsxs("div", { className: cn('kpt-input-number', disabled && 'kpt-input-number--disabled', showInvalid && 'kpt-input-number--invalid', !showButtons && 'kpt-input-number--plain'), children: [showButtons ? (_jsx("button", { type: "button", className: "kpt-input-number__btn", tabIndex: -1, disabled: disabled || !canDecrement, onMouseDown: (event) => event.preventDefault(), onClick: () => {
89
+ return (_jsx("div", { className: cn('kpt-input-number-host', className), ...rest, children: _jsxs("div", { className: cn('kpt-input-number', disabled && 'kpt-input-number--disabled', showInvalid && 'kpt-input-number--invalid', !showButtons && 'kpt-input-number--plain'), children: [showButtons ? (_jsx("button", { type: "button", className: "kpt-input-number__btn", tabIndex: -1, disabled: disabled || readOnly || !canDecrement, onMouseDown: (event) => event.preventDefault(), onClick: () => {
86
90
  stepBy(-1);
87
91
  onTouch?.();
88
- }, "aria-label": t('inputNumber.decrement'), children: _jsx(KptIcon, { name: "minus" }) })) : null, _jsx("input", { className: "kpt-input-number__input", id: field.id, "aria-label": ariaLabel ?? undefined, "aria-required": required || undefined, "aria-invalid": showInvalid || undefined, "aria-describedby": field.describedBy, type: "text", inputMode: "decimal", role: "spinbutton", "aria-valuemin": min, "aria-valuemax": max, "aria-valuenow": current ?? undefined, "aria-valuetext": suffix && current !== null ? display : undefined, value: draft ?? display, disabled: disabled, placeholder: placeholder, onChange: onInput, onKeyDown: onKeyDown, onBlur: onBlur }), showButtons ? (_jsx("button", { type: "button", className: "kpt-input-number__btn", tabIndex: -1, disabled: disabled || !canIncrement, onMouseDown: (event) => event.preventDefault(), onClick: () => {
92
+ }, "aria-label": t('inputNumber.decrement'), children: _jsx(KptIcon, { name: "minus" }) })) : null, _jsx("input", { className: "kpt-input-number__input", id: field.id, "aria-label": ariaLabel ?? undefined, "aria-required": required || undefined, "aria-invalid": showInvalid || undefined, "aria-describedby": field.describedBy, type: "text", inputMode: "decimal", role: "spinbutton", "aria-valuemin": min, "aria-valuemax": max, "aria-valuenow": current ?? undefined, "aria-valuetext": suffix && current !== null ? display : undefined, value: draft ?? display, disabled: disabled, readOnly: readOnly, "aria-readonly": readOnly || undefined, placeholder: placeholder, onChange: onInput, onKeyDown: onKeyDown, onBlur: onBlur }), showButtons ? (_jsx("button", { type: "button", className: "kpt-input-number__btn", tabIndex: -1, disabled: disabled || readOnly || !canIncrement, onMouseDown: (event) => event.preventDefault(), onClick: () => {
89
93
  stepBy(1);
90
94
  onTouch?.();
91
95
  }, "aria-label": t('inputNumber.increment'), children: _jsx(KptIcon, { name: "plus" }) })) : null] }) }));
@@ -16,7 +16,8 @@ A number field with stepper arrows. Import: `import { KptInputNumber } from '@ko
16
16
  - `min`, `max`, `step` (default 1)
17
17
  - `placeholder`, `suffix` — text shown after the number, e.g. ' szt.' or 'ms'
18
18
  - `showButtons`: boolean (default true) — `false` leaves the bare field
19
- - `disabled`, `invalid`, `touched`, `onTouch()`
19
+ - `disabled`, `readOnly`, `invalid`, `touched`, `onTouch()` — `readOnly` leaves the value selectable but
20
+ blocks typing, the arrows and the buttons (`aria-readonly`)
20
21
  - `locale`, `dictionary` — i18n overrides for this component only
21
22
 
22
23
  ## Null is a value
@@ -39,7 +40,9 @@ The stepper labels come from `inputNumber.decrement` and `inputNumber.increment`
39
40
  ## Keyboard
40
41
  The field is `role="spinbutton"` with `aria-valuemin`/`aria-valuemax`/`aria-valuenow` (and
41
42
  `aria-valuetext` with the `suffix`, "5 pcs"). `↑`/`↓` change the value by `step`,
42
- `PageUp`/`PageDown` by ten steps; stepping is rounded to the precision of `step`. A typed value is
43
+ `PageUp`/`PageDown` by ten steps; stepping is rounded to the precision of `step` or of the current
44
+ value, whichever is finer (`1.25` + 1 gives `2.25`, not `2`). Only a plain decimal is accepted while
45
+ typing — `0x1A`, `1e3` and `Infinity` are ignored. A typed value is
43
46
  **not** clamped while typing ("150" with `max` 99 stays "150", so the form's `max()` can report it) —
44
47
  it is clamped on blur. The `−`/`+` buttons sit outside the Tab order and do not take focus from the
45
48
  field, so focus never drops to `<body>` when a button goes disabled at a limit.
@@ -1,10 +1,17 @@
1
- import type { ComponentPropsWithRef } from 'react';
1
+ import { type ComponentPropsWithRef } from 'react';
2
2
  import { type KptI18nOverrides } from '../i18n/i18n-context.tsx';
3
3
  export interface KptInputOtpProps extends Omit<ComponentPropsWithRef<'div'>, 'onChange' | 'defaultValue'>, KptI18nOverrides {
4
4
  value?: string;
5
5
  defaultValue?: string;
6
6
  onValueChange?: (value: string) => void;
7
7
  length?: number;
8
+ /**
9
+ * Co ile komórek wstawić separator, licząc od lewej (`3` przy `length` 9 → `[][][]–[][][]–[][][]`).
10
+ * `0` (domyślnie) — bez grup. Separator jest wyłącznie wizualny: wartość to same znaki.
11
+ */
12
+ groupSize?: number;
13
+ /** Znak między grupami komórek. */
14
+ groupSeparator?: string;
8
15
  /** Dopuszcza wyłącznie cyfry i przełącza klawiaturę mobilną na numeryczną. */
9
16
  integerOnly?: boolean;
10
17
  /** Id pierwszej komórki (nie hosta) — klik w etykietę zaczyna wpisywanie od początku. */
@@ -12,15 +19,21 @@ export interface KptInputOtpProps extends Omit<ComponentPropsWithRef<'div'>, 'on
12
19
  /** Nazwa grupy komórek, gdy nie opisuje jej etykieta (`KptFormField`). */
13
20
  ariaLabel?: string | null;
14
21
  disabled?: boolean;
22
+ /** Tylko do odczytu — kod widać, nie da się go zmienić. */
23
+ readOnly?: boolean;
24
+ /** Pole wymagane — `aria-required` na komórkach. */
25
+ required?: boolean;
15
26
  invalid?: boolean;
16
27
  touched?: boolean;
17
28
  onTouch?: () => void;
18
29
  }
19
30
  /**
20
31
  * Pole kodu jednorazowego (OTP/PIN) — N osobnych pól, automatyczne przejście do następnego
21
- * i wklejanie całego kodu naraz. Wartością jest sklejony string.
32
+ * i wklejanie całego kodu naraz. Wartością są wpisane znaki **bez dziur** (`'13'`, nie `'1 3'`), więc
33
+ * walidator długości i backend widzą tyle znaków, ile naprawdę wpisano.
22
34
  *
23
35
  * @example
24
36
  * <KptInputOtp value={code} onValueChange={setCode} length={6} integerOnly />
37
+ * <KptInputOtp value={code} onValueChange={setCode} length={9} groupSize={3} />
25
38
  */
26
- export declare function KptInputOtp({ value, defaultValue, onValueChange, length, integerOnly, id, ariaLabel, disabled, invalid, touched, onTouch, locale, dictionary, className, ...rest }: Readonly<KptInputOtpProps>): import("react").JSX.Element;
39
+ export declare function KptInputOtp({ value, defaultValue, onValueChange, length, groupSize, groupSeparator, integerOnly, id, ariaLabel, disabled, readOnly, required, invalid, touched, onTouch, locale, dictionary, className, ...rest }: Readonly<KptInputOtpProps>): import("react").JSX.Element;