@konce-pt/react 0.11.2 → 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 +14 -1
  24. package/dist/button-group/button-group.js +30 -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 +90 -31
  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
@@ -61,3 +61,6 @@ to the single input or the lower thumb.
61
61
  ## Accessibility
62
62
  Native range inputs carry their own role and value semantics. In `range` mode each handle gets an
63
63
  `aria-label` telling the two apart, because "slider" twice would say nothing.
64
+
65
+ ## Read-only and required
66
+ `readOnly` keeps the thumb focusable but undoes every move (`aria-readonly`). The range thumbs can no longer cross: the clamped value is written back to the native input, so the thumb stops at the other one. `slider` does not accept `aria-required` — the field asterisk and the validator carry it.
@@ -22,6 +22,8 @@ export interface KptSliderProps extends Omit<ComponentPropsWithRef<'div'>, 'onCh
22
22
  /** Nazwa dostępna, gdy kontrolki nie opisuje etykieta (`<label for>` albo `KptFormField`). */
23
23
  ariaLabel?: string | null;
24
24
  disabled?: boolean;
25
+ /** Tylko do odczytu — uchwyt można skupić, ale nie przesunąć (`aria-readonly`). */
26
+ readOnly?: boolean;
25
27
  onTouch?: () => void;
26
28
  }
27
29
  /**
@@ -34,4 +36,4 @@ export interface KptSliderProps extends Omit<ComponentPropsWithRef<'div'>, 'onCh
34
36
  * <KptSlider value={volume} onValueChange={setVolume} min={0} max={100} step={5} showValue />
35
37
  * <KptSlider range value={range} onValueChange={setRange} showValue />
36
38
  */
37
- export declare function KptSlider({ value, defaultValue, onValueChange, range, min, max, step, showValue, suffix, invalid, touched, id, ariaLabel, disabled, onTouch, locale, dictionary, className, ...rest }: Readonly<KptSliderProps>): import("react").JSX.Element;
39
+ export declare function KptSlider({ value, defaultValue, onValueChange, range, min, max, step, showValue, suffix, invalid, touched, id, ariaLabel, disabled, readOnly, onTouch, locale, dictionary, className, ...rest }: Readonly<KptSliderProps>): import("react").JSX.Element;
@@ -1,3 +1,4 @@
1
+ 'use client';
1
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
3
  import { cn } from "../internal/cn.js";
3
4
  import { useControllableState } from "../internal/use-controllable-state.js";
@@ -13,7 +14,7 @@ import { useKptMessages } from "../i18n/i18n-context.js";
13
14
  * <KptSlider value={volume} onValueChange={setVolume} min={0} max={100} step={5} showValue />
14
15
  * <KptSlider range value={range} onValueChange={setRange} showValue />
15
16
  */
16
- export function KptSlider({ value, defaultValue, onValueChange, range = false, min = 0, max = 100, step = 1, showValue = false, suffix = '', invalid = false, touched, id, ariaLabel = null, disabled = false, onTouch, locale, dictionary, className, ...rest }) {
17
+ export function KptSlider({ value, defaultValue, onValueChange, range = false, min = 0, max = 100, step = 1, showValue = false, suffix = '', invalid = false, touched, id, ariaLabel = null, disabled = false, readOnly = false, onTouch, locale, dictionary, className, ...rest }) {
17
18
  const { t } = useKptMessages({ locale, dictionary });
18
19
  const field = useKptFieldA11y(id, 'kpt-slider');
19
20
  /** Uchwyt zakresu bez etykiety pola: „Cena, Minimum” z `ariaLabel` albo samo „Minimum”. */
@@ -37,7 +38,17 @@ export function KptSlider({ value, defaultValue, onValueChange, range = false, m
37
38
  return span <= 0 ? 0 : ((val - min) / span) * 100;
38
39
  };
39
40
  // Uchwyty nie mogą się wyminąć — dolny nie przekracza górnego i odwrotnie.
40
- const setLow = (next) => setCurrent([Math.min(next, high), high]);
41
- const setHigh = (next) => setCurrent([low, Math.max(next, low)]);
42
- return (_jsx("div", { className: cn('kpt-slider-host', className), ...rest, children: range ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "kpt-slider__range", children: [_jsx("span", { className: "kpt-slider__sr", id: `${field.id}-min-name`, children: t('slider.min') }), _jsx("span", { className: "kpt-slider__sr", id: `${field.id}-max-name`, children: t('slider.max') }), _jsx("div", { className: "kpt-slider__rail" }), _jsx("div", { className: "kpt-slider__fill", style: { left: `${pct(low)}%`, right: `${100 - pct(high)}%` } }), _jsx("input", { className: "kpt-slider__thumb kpt-slider__thumb--low", type: "range", min: min, max: max, step: step, value: low, "aria-valuetext": suffix ? `${low}${suffix}` : undefined, "aria-invalid": invalid && (touched ?? true) ? true : undefined, id: field.id, disabled: disabled, "aria-label": thumbLabel('min'), "aria-labelledby": thumbLabelledBy('min'), "aria-describedby": field.describedBy, onChange: (event) => setLow(Number(event.target.value)), onBlur: () => onTouch?.() }), _jsx("input", { className: "kpt-slider__thumb kpt-slider__thumb--high", type: "range", min: min, max: max, step: step, value: high, "aria-valuetext": suffix ? `${high}${suffix}` : undefined, "aria-invalid": invalid && (touched ?? true) ? true : undefined, disabled: disabled, "aria-label": thumbLabel('max'), "aria-labelledby": thumbLabelledBy('max'), "aria-describedby": field.describedBy, onChange: (event) => setHigh(Number(event.target.value)), onBlur: () => onTouch?.() })] }), showValue ? (_jsxs("span", { className: "kpt-slider__value", children: [low, suffix, "\u2013", high, suffix] })) : null] })) : (_jsxs(_Fragment, { children: [_jsx("input", { className: "kpt-slider__input", type: "range", min: min, max: max, step: step, value: single, "aria-valuetext": suffix ? `${single}${suffix}` : undefined, "aria-invalid": invalid && (touched ?? true) ? true : undefined, id: field.id, disabled: disabled, "aria-label": ariaLabel ?? undefined, "aria-describedby": field.describedBy, style: { ['--_pct']: `${pct(single)}%` }, onChange: (event) => setCurrent(Number(event.target.value)), onBlur: () => onTouch?.() }), showValue ? (_jsxs("span", { className: "kpt-slider__value", children: [single, suffix] })) : null] })) }));
41
+ // Suwaki są kontrolowane: przy `readOnly` stan się nie zmienia, więc React cofa uchwyt sam.
42
+ const setLow = (next) => {
43
+ if (!readOnly)
44
+ setCurrent([Math.min(next, high), high]);
45
+ };
46
+ const setHigh = (next) => {
47
+ if (!readOnly)
48
+ setCurrent([low, Math.max(next, low)]);
49
+ };
50
+ return (_jsx("div", { className: cn('kpt-slider-host', className), ...rest, children: range ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "kpt-slider__range", children: [_jsx("span", { className: "kpt-slider__sr", id: `${field.id}-min-name`, children: t('slider.min') }), _jsx("span", { className: "kpt-slider__sr", id: `${field.id}-max-name`, children: t('slider.max') }), _jsx("div", { className: "kpt-slider__rail" }), _jsx("div", { className: "kpt-slider__fill", style: { left: `${pct(low)}%`, right: `${100 - pct(high)}%` } }), _jsx("input", { className: "kpt-slider__thumb kpt-slider__thumb--low", type: "range", min: min, max: max, step: step, value: low, "aria-valuetext": suffix ? `${low}${suffix}` : undefined, "aria-invalid": invalid && (touched ?? true) ? true : undefined, id: field.id, disabled: disabled, "aria-label": thumbLabel('min'), "aria-labelledby": thumbLabelledBy('min'), "aria-describedby": field.describedBy, "aria-readonly": readOnly || undefined, onChange: (event) => setLow(Number(event.target.value)), onBlur: () => onTouch?.() }), _jsx("input", { className: "kpt-slider__thumb kpt-slider__thumb--high", type: "range", min: min, max: max, step: step, value: high, "aria-valuetext": suffix ? `${high}${suffix}` : undefined, "aria-invalid": invalid && (touched ?? true) ? true : undefined, disabled: disabled, "aria-label": thumbLabel('max'), "aria-labelledby": thumbLabelledBy('max'), "aria-describedby": field.describedBy, "aria-readonly": readOnly || undefined, onChange: (event) => setHigh(Number(event.target.value)), onBlur: () => onTouch?.() })] }), showValue ? (_jsxs("span", { className: "kpt-slider__value", children: [low, suffix, "\u2013", high, suffix] })) : null] })) : (_jsxs(_Fragment, { children: [_jsx("input", { className: "kpt-slider__input", type: "range", min: min, max: max, step: step, value: single, "aria-valuetext": suffix ? `${single}${suffix}` : undefined, "aria-invalid": invalid && (touched ?? true) ? true : undefined, id: field.id, disabled: disabled, "aria-label": ariaLabel ?? undefined, "aria-describedby": field.describedBy, style: { ['--_pct']: `${pct(single)}%` }, "aria-readonly": readOnly || undefined, onChange: (event) => {
51
+ if (!readOnly)
52
+ setCurrent(Number(event.target.value));
53
+ }, onBlur: () => onTouch?.() }), showValue ? (_jsxs("span", { className: "kpt-slider__value", children: [single, suffix] })) : null] })) }));
43
54
  }
@@ -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";
@@ -30,5 +31,5 @@ export function KptSpeedDial({ items = [], direction = 'up', icon = 'plus', aria
30
31
  onSelect?.(item);
31
32
  setOpen(false);
32
33
  };
33
- return (_jsxs("div", { ref: hostRef, className: cn('kpt-speed-dial-host', className), onKeyDown: onKeyDown, ...rest, children: [_jsxs("div", { className: cn('kpt-speed-dial', open && 'is-open'), "data-direction": direction, children: [_jsx("div", { className: "kpt-speed-dial__actions", id: actionsId, children: items.map((item, index) => (_jsxs("button", { type: "button", className: "kpt-speed-dial__action", disabled: item.disabled, "aria-label": item.label, onClick: () => pick(item), children: [item.icon ? _jsx(KptIcon, { name: item.icon }) : null, item.label ? _jsx("span", { className: "kpt-speed-dial__label", children: item.label }) : null] }, index))) }), _jsx(KptFab, { "aria-label": open ? t('common.close') : (ariaLabel ?? t('speedDial.actions')), "aria-expanded": open, "aria-controls": actionsId, onClick: () => setOpen(!open), children: _jsx("span", { className: "kpt-speed-dial__trigger-icon", children: _jsx(KptIcon, { name: icon }) }) })] }), open ? _jsx("div", { className: "kpt-speed-dial__backdrop", onClick: () => setOpen(false) }) : null] }));
34
+ return (_jsxs("div", { ref: hostRef, className: cn('kpt-speed-dial-host', className), onKeyDown: onKeyDown, ...rest, children: [_jsxs("div", { className: cn('kpt-speed-dial', open && 'is-open'), "data-direction": direction, children: [_jsx("div", { className: "kpt-speed-dial__actions", id: actionsId, children: items.map((item, index) => (_jsxs("button", { type: "button", className: "kpt-speed-dial__action", disabled: item.disabled, "aria-label": item.label, onClick: () => pick(item), children: [item.icon ? _jsx(KptIcon, { name: item.icon }) : null, item.label ? _jsx("span", { className: "kpt-speed-dial__label", children: item.label }) : null] }, index))) }), _jsx(KptFab, { "aria-label": open ? t('common.close') : (ariaLabel ?? t('speedDial.actions')), "aria-expanded": open, "aria-controls": actionsId, onClick: () => setOpen(!open), children: _jsx("span", { className: "kpt-speed-dial__trigger-icon", children: _jsx(KptIcon, { name: icon }) }) })] }), open ? (_jsx("div", { className: "kpt-speed-dial__backdrop", onClick: () => setOpen(false) })) : null] }));
34
35
  }
@@ -1,3 +1,4 @@
1
+ 'use client';
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
3
  import { useEffect, useState } from 'react';
3
4
  import { cn } from "../internal/cn.js";
@@ -1,5 +1,6 @@
1
+ 'use client';
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useCallback, useEffect, useRef } from 'react';
3
+ import { useCallback, useEffect, useRef, useState } from 'react';
3
4
  import { cn } from "../internal/cn.js";
4
5
  import { KptOverlay } from "../internal/overlay.js";
5
6
  import { useControllableState } from "../internal/use-controllable-state.js";
@@ -20,6 +21,12 @@ import { focusMenuEdge, handleMenuKeydown } from "../menu/menu-keys.js";
20
21
  export function KptSplitButton({ variant = 'filled', items = [], disabled = false, onPrimaryClick, onSelect, open, defaultOpen = false, onOpenChange, locale, dictionary, className, children, ...rest }) {
21
22
  const { t } = useKptMessages({ locale, dictionary });
22
23
  const toggleRef = useRef(null);
24
+ /** Kotwica panelu — w stanie, bo czyta ją render; ref zostaje dla przywracania fokusu. */
25
+ const [toggleEl, setToggleEl] = useState(null);
26
+ const setToggle = useCallback((element) => {
27
+ toggleRef.current = element;
28
+ setToggleEl(element);
29
+ }, []);
23
30
  const panelRef = useRef(null);
24
31
  /** Którą pozycję skupić po otwarciu — strzałka w górę na przycisku otwiera „od końca”. */
25
32
  const focusEdge = useRef('first');
@@ -67,8 +74,8 @@ export function KptSplitButton({ variant = 'filled', items = [], disabled = fals
67
74
  onSelect?.(item);
68
75
  closeAndRestoreFocus();
69
76
  };
70
- return (_jsxs("span", { className: cn('kpt-split-button-host', className), ...rest, children: [_jsxs("div", { className: "kpt-split-button", "data-variant": variant, children: [_jsx("button", { className: "kpt-split-button__main", type: "button", disabled: disabled, onClick: onPrimaryClick, children: children }), _jsx("button", { ref: toggleRef, className: "kpt-split-button__toggle", type: "button", disabled: disabled, onClick: () => {
77
+ return (_jsxs("span", { className: cn('kpt-split-button-host', className), ...rest, children: [_jsxs("div", { className: "kpt-split-button", "data-variant": variant, children: [_jsx("button", { className: "kpt-split-button__main", type: "button", disabled: disabled, onClick: onPrimaryClick, children: children }), _jsx("button", { ref: setToggle, className: "kpt-split-button__toggle", type: "button", disabled: disabled, onClick: () => {
71
78
  focusEdge.current = 'first';
72
79
  setOpen(!isOpen);
73
- }, onKeyDown: onToggleKeyDown, "aria-haspopup": "menu", "aria-expanded": isOpen, "aria-controls": isOpen ? panelId : undefined, "aria-label": t('splitButton.more'), children: _jsx(KptIcon, { name: "chevron-down" }) })] }), _jsx(KptOverlay, { open: isOpen, anchor: toggleRef.current, onClose: close, align: "end", children: _jsx("div", { ref: panelRef, id: panelId, className: "kpt-split-button__panel", role: "menu", onKeyDown: onPanelKeyDown, children: _jsx(MenuRows, { items: items, block: "kpt-split-button", onPick: pick }) }) })] }));
80
+ }, onKeyDown: onToggleKeyDown, "aria-haspopup": "menu", "aria-expanded": isOpen, "aria-controls": isOpen ? panelId : undefined, "aria-label": t('splitButton.more'), children: _jsx(KptIcon, { name: "chevron-down" }) })] }), _jsx(KptOverlay, { open: isOpen, anchor: toggleEl, onClose: close, align: "end", children: _jsx("div", { ref: panelRef, id: panelId, className: "kpt-split-button__panel", role: "menu", tabIndex: -1, onKeyDown: onPanelKeyDown, children: _jsx(MenuRows, { items: items, block: "kpt-split-button", onPick: pick }) }) })] }));
74
81
  }
@@ -1,6 +1,8 @@
1
+ 'use client';
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Children, isValidElement, useRef, } from 'react';
3
+ import { isValidElement, useRef, } from 'react';
3
4
  import { cn } from "../internal/cn.js";
5
+ import { slotChildren, warnUnslottedChild } from "../internal/slot-children.js";
4
6
  import { useControllableState } from "../internal/use-controllable-state.js";
5
7
  import { useKptId } from "../internal/use-id.js";
6
8
  import { useKptMessages } from "../i18n/i18n-context.js";
@@ -40,16 +42,18 @@ export function KptSplitter({ orientation = 'horizontal', startSize, defaultStar
40
42
  });
41
43
  let start = null;
42
44
  let end = null;
43
- Children.forEach(children, (child) => {
44
- if (!isValidElement(child))
45
- return;
45
+ for (const child of slotChildren(children)) {
46
+ if (!isValidElement(child)) {
47
+ warnUnslottedChild('KptSplitter', child, '<KptSplitter.Start> / <KptSplitter.End>');
48
+ continue;
49
+ }
46
50
  const slot = child.type?.kptSplitterSlot;
47
51
  const content = child.props.children;
48
52
  if (slot === 'start')
49
53
  start = content;
50
54
  else if (slot === 'end')
51
55
  end = content;
52
- });
56
+ }
53
57
  const clamp = (pct) => Math.min(100 - minSize, Math.max(minSize, Math.round(pct)));
54
58
  const onPointerDown = (event) => {
55
59
  event.preventDefault();
@@ -58,18 +62,24 @@ export function KptSplitter({ orientation = 'horizontal', startSize, defaultStar
58
62
  return;
59
63
  // Nasłuch na oknie, nie na uchwycie: przy szybkim ruchu kursor wyprzedza wąski pasek
60
64
  // i bez tego przeciąganie urywałoby się po kilku pikselach.
65
+ // W RTL panel „start” stoi po prawej (flex idzie za kierunkiem pisma), więc mierzymy od prawej.
66
+ const rtl = getComputedStyle(container).direction === 'rtl';
61
67
  const move = (e) => {
62
68
  const rect = container.getBoundingClientRect();
63
69
  setSize(clamp(orientation === 'horizontal'
64
- ? ((e.clientX - rect.left) / rect.width) * 100
70
+ ? ((rtl ? rect.right - e.clientX : e.clientX - rect.left) / rect.width) * 100
65
71
  : ((e.clientY - rect.top) / rect.height) * 100));
66
72
  };
73
+ // `pointercancel`: dotyk przerwany przez system nie wysyła `pointerup` — bez sprzątania
74
+ // `pointermove` dalej przesuwałby uchwyt.
67
75
  const up = () => {
68
76
  window.removeEventListener('pointermove', move);
69
77
  window.removeEventListener('pointerup', up);
78
+ window.removeEventListener('pointercancel', up);
70
79
  };
71
80
  window.addEventListener('pointermove', move);
72
81
  window.addEventListener('pointerup', up);
82
+ window.addEventListener('pointercancel', up);
73
83
  };
74
84
  const onKeyDown = (event) => {
75
85
  if (event.key === 'Home' || event.key === 'End') {
@@ -85,9 +95,14 @@ export function KptSplitter({ orientation = 'horizontal', startSize, defaultStar
85
95
  if (!dir)
86
96
  return;
87
97
  event.preventDefault();
88
- setSize(clamp(size + dir));
98
+ // W RTL ← przesuwa uchwyt w lewo, czyli powiększa prawy panel „start” — odwrotnie niż w LTR.
99
+ const horizontal = event.key === 'ArrowLeft' || event.key === 'ArrowRight';
100
+ const rtl = horizontal && getComputedStyle(event.currentTarget).direction === 'rtl';
101
+ setSize(clamp(size + (rtl ? -dir : dir)));
89
102
  };
90
- return (_jsx("div", { className: cn('kpt-splitter-host', className), ...rest, children: _jsxs("div", { ref: containerRef, className: "kpt-splitter", "data-orientation": orientation, children: [_jsx("div", { className: "kpt-splitter__pane", id: startId, style: { flexBasis: `${size}%` }, children: start }), _jsx("div", { className: "kpt-splitter__gutter", role: "separator", tabIndex: 0, "aria-orientation": orientation === 'horizontal' ? 'vertical' : 'horizontal', "aria-valuenow": size, "aria-valuemin": minSize, "aria-valuemax": 100 - minSize, "aria-controls": startId, "aria-label": ariaLabel || t('splitter.resize'), onPointerDown: onPointerDown, onKeyDown: onKeyDown }), _jsx("div", { className: "kpt-splitter__pane", style: { flexBasis: `${100 - size}%` }, children: end })] }) }));
103
+ return (_jsx("div", { className: cn('kpt-splitter-host', className), ...rest, children: _jsxs("div", { ref: containerRef, className: "kpt-splitter", "data-orientation": orientation, children: [_jsx("div", { className: "kpt-splitter__pane", id: startId, style: { flexBasis: `${size}%` }, children: start }), _jsx("div", { className: "kpt-splitter__gutter", role: "separator",
104
+ // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex -- separator okna (APG Window Splitter) to fokusowalny widżet ze strzałkami
105
+ tabIndex: 0, "aria-orientation": orientation === 'horizontal' ? 'vertical' : 'horizontal', "aria-valuenow": size, "aria-valuemin": minSize, "aria-valuemax": 100 - minSize, "aria-controls": startId, "aria-label": ariaLabel || t('splitter.resize'), onPointerDown: onPointerDown, onKeyDown: onKeyDown }), _jsx("div", { className: "kpt-splitter__pane", style: { flexBasis: `${100 - size}%` }, children: end })] }) }));
91
106
  }
92
107
  KptSplitter.Start = KptSplitterStart;
93
108
  KptSplitter.End = KptSplitterEnd;
@@ -66,3 +66,6 @@ announces how many there are. Each marker is named from the dictionary — `step
66
66
  number or a check; the visible label is `aria-hidden` so it is not read twice. The active marker
67
67
  carries `aria-current="step"`. After `next()` focus stays where it was — move it into the new step
68
68
  yourself when the step is long.
69
+
70
+ ## Inactive content is destroyed
71
+ Switching renders the new panel from scratch (that is what the enter animation starts from), so local state inside the previous one — an unsent file upload, expanded sections, scroll position — is lost. Keep such state in your own model, outside the panel.
@@ -1,6 +1,8 @@
1
+ 'use client';
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Children, createContext, isValidElement, useContext, useState, } from 'react';
3
+ import { createContext, isValidElement, useContext, useState, } from 'react';
3
4
  import { cn } from "../internal/cn.js";
5
+ import { slotChildren, warnUnslottedChild } from "../internal/slot-children.js";
4
6
  import { useControllableState } from "../internal/use-controllable-state.js";
5
7
  import { useKptMessages } from "../i18n/i18n-context.js";
6
8
  import { KptIcon } from "../icon/icon.js";
@@ -46,11 +48,13 @@ export function KptStepper({ activeIndex, defaultActiveIndex = 0, onActiveIndexC
46
48
  });
47
49
  const [direction, setDirection] = useState(1);
48
50
  const steps = [];
49
- Children.forEach(children, (child) => {
50
- if (!isValidElement(child) || child.type?.kptStepperSlot !== 'step')
51
- return;
51
+ for (const child of slotChildren(children)) {
52
+ if (!isValidElement(child) || child.type?.kptStepperSlot !== 'step') {
53
+ warnUnslottedChild('KptStepper', child, '<KptStep>');
54
+ continue;
55
+ }
52
56
  steps.push(child.props);
53
- });
57
+ }
54
58
  const goTo = (index) => {
55
59
  const next = Math.max(0, Math.min(index, steps.length - 1));
56
60
  // Kierunek karmi `--kpt-anim-dir`, więc panel wjeżdża z tej strony, z której się cofnąłeś.
@@ -50,3 +50,6 @@ Inside `kpt-form-field` the switch gets both names: the wrapper label and its ow
50
50
  ## Accessibility
51
51
  `role="switch"` on a native checkbox — a screen reader announces on/off instead of
52
52
  checked/unchecked. The track and the thumb are `aria-hidden`.
53
+
54
+ ## Read-only and required
55
+ `readOnly` keeps the switch focusable but blocks toggling (`aria-readonly`); `required` sets `aria-required`.
@@ -36,4 +36,4 @@ export interface KptSwitchProps extends Omit<ComponentPropsWithRef<'input'>, 'ty
36
36
  * <KptSwitch name="notifications" label="Powiadomienia" />
37
37
  * <KptSwitch checked={on} onCheckedChange={setOn} onLabel="Włączony" offLabel="Wyłączony" />
38
38
  */
39
- export declare function KptSwitch({ label, labelPosition, onLabel, offLabel, required, invalid, touched, onCheckedChange, onTouch, hostProps, className, disabled, checked, onChange, onBlur, children, 'aria-invalid': ariaInvalid, 'aria-describedby': ariaDescribedBy, id, ...rest }: Readonly<KptSwitchProps>): import("react").JSX.Element;
39
+ export declare function KptSwitch({ label, labelPosition, onLabel, offLabel, required, invalid, touched, onCheckedChange, onTouch, hostProps, className, disabled, readOnly, onClick, checked, onChange, onBlur, children, 'aria-invalid': ariaInvalid, 'aria-describedby': ariaDescribedBy, id, ...rest }: Readonly<KptSwitchProps>): 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 } from 'react';
3
4
  import { cn } from "../internal/cn.js";
@@ -15,7 +16,7 @@ import { joinIds, useKptFieldA11y } from "../form-field/form-field-context.js";
15
16
  * <KptSwitch name="notifications" label="Powiadomienia" />
16
17
  * <KptSwitch checked={on} onCheckedChange={setOn} onLabel="Włączony" offLabel="Wyłączony" />
17
18
  */
18
- export function KptSwitch({ label = '', labelPosition = 'end', onLabel = '', offLabel = '', required = false, invalid = false, touched, onCheckedChange, onTouch, hostProps, className, disabled = false, checked, onChange, onBlur, children, 'aria-invalid': ariaInvalid, 'aria-describedby': ariaDescribedBy, id, ...rest }) {
19
+ export function KptSwitch({ label = '', labelPosition = 'end', onLabel = '', offLabel = '', required = false, invalid = false, touched, onCheckedChange, onTouch, hostProps, className, disabled = false, readOnly = false, onClick, checked, onChange, onBlur, children, 'aria-invalid': ariaInvalid, 'aria-describedby': ariaDescribedBy, id, ...rest }) {
19
20
  const field = useKptFieldA11y(id, 'kpt-switch');
20
21
  const { className: hostClassName, ...hostRest } = hostProps ?? {};
21
22
  const showInvalid = invalid && (touched ?? true);
@@ -28,5 +29,11 @@ export function KptSwitch({ label = '', labelPosition = 'end', onLabel = '', off
28
29
  onBlur?.(event);
29
30
  onTouch?.();
30
31
  }, [onBlur, onTouch]);
31
- return (_jsx("span", { className: cn('kpt-switch-host', hostClassName), ...hostRest, children: _jsxs("label", { className: cn('kpt-switch', disabled && 'kpt-switch--disabled', className), children: [label && labelPosition === 'start' ? (_jsx("span", { className: "kpt-switch__label kpt-switch__label--start", children: label })) : null, _jsx("input", { type: "checkbox", role: "switch", className: "kpt-switch__input", id: field.id, "aria-describedby": joinIds(ariaDescribedBy, field.describedBy), checked: checked, disabled: disabled, "aria-required": required || undefined, "aria-invalid": ariaInvalid ?? (showInvalid || undefined), onChange: handleChange, onBlur: handleBlur, ...rest }), _jsx("span", { className: "kpt-switch__track", "aria-hidden": "true", children: _jsx("span", { className: "kpt-switch__thumb" }) }), label && labelPosition === 'end' ? _jsx("span", { className: "kpt-switch__label", children: label }) : null, hasStateLabels ? (_jsx("span", { className: cn('kpt-switch__state', checked && 'kpt-switch__state--on'), "aria-hidden": "true", children: checked ? onLabel : offLabel })) : null, children] }) }));
32
+ return (_jsx("span", { className: cn('kpt-switch-host', hostClassName), ...hostRest, children: _jsxs("label", { className: cn('kpt-switch', disabled && 'kpt-switch--disabled', className), children: [label && labelPosition === 'start' ? (_jsx("span", { className: "kpt-switch__label kpt-switch__label--start", children: label })) : null, _jsx("input", { type: "checkbox", role: "switch", className: "kpt-switch__input", id: field.id, "aria-describedby": joinIds(ariaDescribedBy, field.describedBy), checked: checked, disabled: disabled, "aria-required": required || undefined, "aria-readonly": readOnly || undefined,
33
+ // Natywne `readonly` nie działa na polach wyboru — klik (i Spacja) jest blokowany ręcznie.
34
+ onClick: (event) => {
35
+ if (readOnly)
36
+ event.preventDefault();
37
+ onClick?.(event);
38
+ }, "aria-invalid": ariaInvalid ?? (showInvalid || undefined), onChange: handleChange, onBlur: handleBlur, ...rest }), _jsx("span", { className: "kpt-switch__track", "aria-hidden": "true", children: _jsx("span", { className: "kpt-switch__thumb" }) }), label && labelPosition === 'end' ? _jsx("span", { className: "kpt-switch__label", children: label }) : null, hasStateLabels ? (_jsx("span", { className: cn('kpt-switch__state', checked && 'kpt-switch__state--on'), "aria-hidden": "true", children: checked ? onLabel : offLabel })) : null, children] }) }));
32
39
  }
@@ -52,3 +52,6 @@ Layout only: `--kpt-space-2` (vertical gap) and `--kpt-space-4` (horizontal). Th
52
52
  ## Accessibility
53
53
  `role="group"` with `aria-invalid` once the field is touched; every entry keeps the native
54
54
  checkbox of `KptSwitch`, so keyboard and screen readers work with no extra handling.
55
+
56
+ ## Read-only and required
57
+ `readOnly` is passed to every switch. With `allowDeselect=false` turning the active switch off puts it back on — before, the switch looked off while the group value said it was on.
@@ -16,6 +16,8 @@ export interface KptSwitchGroupProps<T> extends Omit<ComponentPropsWithRef<'div'
16
16
  /** Nazwa dostępna, gdy kontrolki nie opisuje etykieta (`<label for>` albo `KptFormField`). */
17
17
  ariaLabel?: string | null;
18
18
  disabled?: boolean;
19
+ /** Tylko do odczytu — przełączniki widać, nie da się ich zmienić. */
20
+ readOnly?: boolean;
19
21
  invalid?: boolean;
20
22
  touched?: boolean;
21
23
  onTouch?: () => void;
@@ -30,4 +32,4 @@ export interface KptSwitchGroupProps<T> extends Omit<ComponentPropsWithRef<'div'
30
32
  * <KptSwitchGroup options={plans} value={plan} onValueChange={setPlan} />
31
33
  * <KptSwitchGroup multiple options={tags} value={selected} onValueChange={setSelected} />
32
34
  */
33
- export declare function KptSwitchGroup<T>({ options, value, defaultValue, onValueChange, multiple, orientation, allowDeselect, id, ariaLabel, disabled, invalid, touched, onTouch, className, ...rest }: Readonly<KptSwitchGroupProps<T>>): import("react").JSX.Element;
35
+ export declare function KptSwitchGroup<T>({ options, value, defaultValue, onValueChange, multiple, orientation, allowDeselect, id, ariaLabel, disabled, readOnly, invalid, touched, onTouch, className, ...rest }: Readonly<KptSwitchGroupProps<T>>): import("react").JSX.Element;
@@ -1,5 +1,7 @@
1
+ 'use client';
1
2
  import { jsx as _jsx } from "react/jsx-runtime";
2
3
  import { cn } from "../internal/cn.js";
4
+ import { optionKey } from "../internal/option-key.js";
3
5
  import { useControllableState } from "../internal/use-controllable-state.js";
4
6
  import { KptSwitch } from "../switch/switch.js";
5
7
  import { useKptFieldA11y } from "../form-field/form-field-context.js";
@@ -13,7 +15,7 @@ import { useKptFieldA11y } from "../form-field/form-field-context.js";
13
15
  * <KptSwitchGroup options={plans} value={plan} onValueChange={setPlan} />
14
16
  * <KptSwitchGroup multiple options={tags} value={selected} onValueChange={setSelected} />
15
17
  */
16
- export function KptSwitchGroup({ options = [], value, defaultValue = null, onValueChange, multiple = false, orientation = 'vertical', allowDeselect = true, id, ariaLabel = null, disabled = false, invalid = false, touched, onTouch, className, ...rest }) {
18
+ export function KptSwitchGroup({ options = [], value, defaultValue = null, onValueChange, multiple = false, orientation = 'vertical', allowDeselect = true, id, ariaLabel = null, disabled = false, readOnly = false, invalid = false, touched, onTouch, className, ...rest }) {
17
19
  /** Grupa nie dostaje nazwy z `label[for]` — stąd `aria-labelledby` na etykietę wrappera. */
18
20
  const field = useKptFieldA11y(id, 'kpt-switch-group');
19
21
  const [current, setCurrent] = useControllableState({
@@ -46,7 +48,7 @@ export function KptSwitchGroup({ options = [], value, defaultValue = null, onVal
46
48
  // Bez `allowDeselect` nie robimy nic: `checked` jest wyliczane z wartości, a ta się nie
47
49
  // zmieniła, więc przełącznik sam wraca do włączonego przy najbliższym renderze.
48
50
  };
49
- return (_jsx("div", { className: cn('kpt-switch-group-host', className), ...rest, children: _jsx("div", { className: cn('kpt-switch-group', orientation === 'horizontal' && 'kpt-switch-group--horizontal'), role: "group", id: field.id, "aria-label": ariaLabel ?? undefined, "aria-labelledby": ariaLabel ? undefined : field.labelledBy, "aria-describedby": field.describedBy, children: options.map((option) => (_jsx(KptSwitch, { hostProps: { className: 'kpt-switch-group__item' }, checked: isChecked(option.value), label: option.label, disabled: disabled || option.disabled,
51
+ return (_jsx("div", { className: cn('kpt-switch-group-host', className), ...rest, children: _jsx("div", { className: cn('kpt-switch-group', orientation === 'horizontal' && 'kpt-switch-group--horizontal'), role: "group", id: field.id, "aria-label": ariaLabel ?? undefined, "aria-labelledby": ariaLabel ? undefined : field.labelledBy, "aria-describedby": field.describedBy, children: options.map((option, index) => (_jsx(KptSwitch, { hostProps: { className: 'kpt-switch-group__item' }, checked: isChecked(option.value), label: option.label, disabled: disabled || option.disabled, readOnly: readOnly,
50
52
  // `aria-invalid` nie działa na `role="group"` — stan błędu niesie każdy przełącznik.
51
- invalid: showInvalid, onCheckedChange: (checked) => toggle(option, checked), onTouch: onTouch }, String(option.value)))) }) }));
53
+ invalid: showInvalid, onCheckedChange: (checked) => toggle(option, checked), onTouch: onTouch }, optionKey(option.value, index)))) }) }));
52
54
  }
@@ -52,3 +52,6 @@ The header is `role="tablist"` with `role="tab"` buttons carrying `aria-selected
52
52
  `aria-controls`. The tab row is a single `Tab` stop (roving `tabindex`): `ArrowLeft`/`ArrowRight`
53
53
  move between tabs, wrapping at the ends, `Home`/`End` jump to the first/last one, and disabled tabs
54
54
  are skipped. Moving activates the tab straight away; the next `Tab` goes to the panel.
55
+
56
+ ## Inactive content is destroyed
57
+ Switching renders the new panel from scratch (that is what the enter animation starts from), so local state inside the previous one — an unsent file upload, expanded sections, scroll position — is lost. Keep such state in your own model, outside the panel.
package/dist/tabs/tabs.js CHANGED
@@ -1,6 +1,8 @@
1
+ 'use client';
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Children, isValidElement, useCallback, useLayoutEffect, useRef, useState, } from 'react';
3
+ import { isValidElement, useCallback, useLayoutEffect, useRef, useState, } from 'react';
3
4
  import { cn } from "../internal/cn.js";
5
+ import { slotChildren, warnUnslottedChild } from "../internal/slot-children.js";
4
6
  import { nextRovingIndex, rovingStop } from "../internal/roving.js";
5
7
  import { useControllableState } from "../internal/use-controllable-state.js";
6
8
  import { useKptId } from "../internal/use-id.js";
@@ -42,15 +44,23 @@ export function KptTabs({ activeIndex, defaultActiveIndex = 0, onActiveIndexChan
42
44
  const baseId = useKptId(undefined, 'kpt-tabs');
43
45
  const tabs = [];
44
46
  const contents = [];
45
- Children.forEach(children, (child) => {
46
- if (!isValidElement(child) || child.type?.kptTabsSlot !== 'tab')
47
- return;
47
+ for (const child of slotChildren(children)) {
48
+ if (!isValidElement(child) || child.type?.kptTabsSlot !== 'tab') {
49
+ warnUnslottedChild('KptTabs', child, '<KptTab>');
50
+ continue;
51
+ }
48
52
  const props = child.props;
49
53
  tabs.push(props);
50
54
  contents.push(props.children);
51
- });
55
+ }
56
+ const disabled = tabs.map((tab) => !!tab.disabled);
57
+ /**
58
+ * Zakładka pokazywana i jedyna w kolejności Tab: aktywna, a gdy ta jest wyłączona — pierwsza
59
+ * dostępna. Wyłączona zakładka nie może wyglądać na wybraną (ani mieć swojego panelu).
60
+ */
61
+ const shown = rovingStop(disabled, active);
52
62
  const measureInk = useCallback(() => {
53
- const button = buttonsRef.current[active];
63
+ const button = buttonsRef.current[shown];
54
64
  if (!button)
55
65
  return;
56
66
  const next = { x: button.offsetLeft, w: button.offsetWidth };
@@ -58,24 +68,30 @@ export function KptTabs({ activeIndex, defaultActiveIndex = 0, onActiveIndexChan
58
68
  // się zmierzyć przed montażem). Bez porównania `setInk` wywoływałby kolejny render w kółko —
59
69
  // React przerywa aktualizację dopiero wtedy, gdy dostanie ten sam obiekt stanu.
60
70
  setInk((prev) => (prev.x === next.x && prev.w === next.w ? prev : next));
61
- }, [active]);
71
+ }, [shown]);
62
72
  useLayoutEffect(measureInk);
73
+ // Szerokość paska zakładek zmienia się też bez zmiany okna (panel boczny, splitter) — mierzymy
74
+ // kontener, a `resize` okna zostaje tylko tam, gdzie nie ma ResizeObservera.
75
+ const headerRef = useRef(null);
63
76
  useLayoutEffect(() => {
77
+ const header = headerRef.current;
78
+ if (header && typeof ResizeObserver !== 'undefined') {
79
+ const observer = new ResizeObserver(measureInk);
80
+ observer.observe(header);
81
+ return () => observer.disconnect();
82
+ }
64
83
  window.addEventListener('resize', measureInk);
65
84
  return () => window.removeEventListener('resize', measureInk);
66
85
  }, [measureInk]);
67
- const disabled = tabs.map((tab) => !!tab.disabled);
68
- // Jedyna zakładka w kolejności Tab: aktywna, a gdy ta jest wyłączona — pierwsza dostępna.
69
- const tabStop = rovingStop(disabled, active);
70
86
  const select = (index) => {
71
87
  if (tabs[index]?.disabled)
72
88
  return;
73
- setDirection(index >= active ? 1 : -1);
89
+ setDirection(index >= shown ? 1 : -1);
74
90
  setActive(index);
75
91
  };
76
92
  const onKeyDown = (event) => {
77
93
  const current = buttonsRef.current.indexOf(document.activeElement);
78
- const next = nextRovingIndex(disabled, current < 0 ? tabStop : current, event.key);
94
+ const next = nextRovingIndex(disabled, current < 0 ? shown : current, event.key);
79
95
  if (next === null)
80
96
  return;
81
97
  event.preventDefault();
@@ -86,8 +102,8 @@ export function KptTabs({ activeIndex, defaultActiveIndex = 0, onActiveIndexChan
86
102
  ['--kpt-anim-duration']: `${animated ? animationDuration : 0}ms`,
87
103
  ['--kpt-anim-dir']: direction,
88
104
  ...style,
89
- }, ...rest, children: [_jsxs("div", { className: "kpt-tabs__header", role: "tablist", onKeyDown: onKeyDown, children: [tabs.map((tab, index) => (_jsx("button", { ref: (el) => {
105
+ }, ...rest, children: [_jsxs("div", { ref: headerRef, className: "kpt-tabs__header", role: "tablist", onKeyDown: onKeyDown, children: [tabs.map((tab, index) => (_jsx("button", { ref: (el) => {
90
106
  buttonsRef.current[index] = el;
91
- }, type: "button", className: cn('kpt-tabs__tab', index === active && 'is-active'), role: "tab", id: `${baseId}-tab-${index}`, disabled: tab.disabled, "aria-selected": index === active, "aria-controls": index === active ? `${baseId}-panel` : undefined, tabIndex: index === tabStop ? 0 : -1, onClick: () => select(index), children: tab.label }, index))), _jsx("span", { className: "kpt-tabs__ink", style: { width: ink.w, transform: `translateX(${ink.x}px)` } })] }), _jsx("div", { className: "kpt-tabs__body", role: "tabpanel", tabIndex: 0, id: `${baseId}-panel`, "aria-labelledby": `${baseId}-tab-${active}`, children: contents[active] !== undefined ? (_jsx("div", { className: "kpt-tabs__panel", children: contents[active] }, active)) : null })] }));
107
+ }, type: "button", className: cn('kpt-tabs__tab', index === shown && 'is-active'), role: "tab", id: `${baseId}-tab-${index}`, disabled: tab.disabled, "aria-selected": index === shown, "aria-controls": index === shown ? `${baseId}-panel` : undefined, tabIndex: index === shown ? 0 : -1, onClick: () => select(index), children: tab.label }, index))), _jsx("span", { className: "kpt-tabs__ink", style: { width: ink.w, transform: `translateX(${ink.x}px)` } })] }), _jsx("div", { className: "kpt-tabs__body", role: "tabpanel", tabIndex: 0, id: `${baseId}-panel`, "aria-labelledby": `${baseId}-tab-${shown}`, children: contents[shown] !== undefined ? (_jsx("div", { className: "kpt-tabs__panel", children: contents[shown] }, shown)) : null })] }));
92
108
  }
93
109
  KptTabs.Tab = KptTab;
@@ -1,3 +1,4 @@
1
+ 'use client';
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
3
  import { useCallback, useLayoutEffect, useRef, useState } from 'react';
3
4
  import { cn } from "../internal/cn.js";
@@ -64,3 +64,6 @@ text twice is announced twice, and a dismissed toast never re-announces an older
64
64
  are toasts the stack is `role="region"` named `toast.region` (“Notifications”). Pointer or focus on
65
65
  the stack pauses every timer (WCAG 2.2.1); the timers resume with the time that was left. The close
66
66
  button is labelled from `common.close`.
67
+
68
+ ## Toasts with an action
69
+ A toast with `action` does not close by itself (`duration` defaults to 0): a keyboard or screen-reader user cannot reach the "Undo" button at the end of the page within four seconds (WCAG 2.2.1). An explicit `duration` still wins.
@@ -1,3 +1,4 @@
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";
@@ -9,7 +9,11 @@ export interface KptToast {
9
9
  message: string;
10
10
  title?: string;
11
11
  variant: KptAlertVariant;
12
- /** Czas życia w ms; 0 = bez auto-zamykania. */
12
+ /**
13
+ * Czas życia w ms; 0 = bez auto-zamykania. Domyślnie 4000 ms, a dla toastu z `action` — 0:
14
+ * użytkownik klawiatury albo czytnika nie zdąży dojść do przycisku „Cofnij” w kontenerze na końcu
15
+ * strony, zanim toast zniknie (WCAG 2.2.1). Jawne `duration` zawsze wygrywa.
16
+ */
13
17
  duration: number;
14
18
  action?: KptToastAction;
15
19
  }
@@ -10,7 +10,7 @@ function emit() {
10
10
  }
11
11
  function show(message, options = {}) {
12
12
  const id = ++seq;
13
- const toast = { id, message, variant: 'info', duration: 4000, ...options };
13
+ const toast = { id, message, variant: 'info', duration: options.action ? 0 : 4000, ...options };
14
14
  toasts = [...toasts, toast];
15
15
  emit();
16
16
  if (toast.duration > 0) {
@@ -24,9 +24,8 @@ change, with no observers. It measures the host AND its direct children, because
24
24
  available width instead of stretching its parent, so measuring the host alone would find nothing.
25
25
 
26
26
  ## Styling
27
- The bubble carries inline styles built from tokens rather than a class from the shared stylesheet,
28
- exactly like the Angular directive — it is created outside React's tree, so there is nothing to
29
- attach a stylesheet rule to reliably.
27
+ The bubble is created outside React's tree, so its look comes from `.kpt-tooltip` in `@konce-pt/styles`
28
+ (see "Styling and the top layer" below), the same rule the Angular directive uses.
30
29
 
31
30
  ## Examples
32
31
  <KptTooltip text="Save changes" position="bottom"><KptButton>Save</KptButton></KptTooltip>
@@ -47,3 +46,6 @@ attach a stylesheet rule to reliably.
47
46
  - `Escape` closes the bubble, and the pointer can move onto it without it disappearing (a 120 ms
48
47
  grace period) — WCAG 1.4.13.
49
48
  - It does NOT replace an accessible name — an icon-only button still needs its own label.
49
+
50
+ ## Styling and the top layer
51
+ The bubble is styled by `.kpt-tooltip` from `@konce-pt/styles`, in the `kpt.components` layer — override it from your own CSS without `!important`; only `position: fixed` and the coordinates stay inline. Inside an open `<dialog>` or popover the bubble is attached to that element, because a modal dialog lives in the top layer and anything outside it is hidden behind it regardless of `z-index`. A hover shows the bubble after 300 ms (sliding across a toolbar does not light every icon); focus shows it at once. Changing the text while the bubble is visible updates it.
@@ -13,8 +13,9 @@ export interface KptTooltipProps {
13
13
  children: ReactNode;
14
14
  }
15
15
  /**
16
- * Dymek podpowiedzi pokazywany na hover i focus. Lekki — element dodawany do `body`
17
- * i pozycjonowany względem dziecka.
16
+ * Dymek podpowiedzi pokazywany na hover i focus. Lekki — element dodawany do `body` (albo do
17
+ * otwartego `<dialog>`/popovera dziecka, żeby był widoczny w warstwie szczytowej) i pozycjonowany
18
+ * względem dziecka. Wygląd daje klasa `.kpt-tooltip` z `@konce-pt/styles` w warstwie `kpt.components`.
18
19
  *
19
20
  * Port dyrektywy `[kptTooltip]`: nie renderuje własnego opakowania, tylko dokleja handlery
20
21
  * i ref do przekazanego elementu, więc drzewo DOM zostaje takie samo jak bez tooltipa.