@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
@@ -1,18 +1,39 @@
1
- import { useCallback, useState } from 'react';
1
+ import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
2
+ import { isDevBuild } from "./dev.js";
2
3
  /**
3
4
  * Stan działający w obu trybach naraz: kontrolowanym (`value` podane z zewnątrz)
4
5
  * i niekontrolowanym (`defaultValue` + stan wewnętrzny). Kontrakt jest ten sam
5
6
  * co w natywnych polach formularza, więc komponenty współpracują z natywnym
6
7
  * `<form>`, React 19 Actions i React Hook Form bez adapterów.
8
+ *
9
+ * `setValue` ma stałą tożsamość: rodzic przekazujący `onChange` jako funkcję inline nie restartuje
10
+ * efektów, które mają `setValue` w zależnościach. Przejście między trybami (`value` raz podane,
11
+ * raz `undefined`) działa jak w Reakcie — po cichu przełącza źródło — ale w buildzie deweloperskim
12
+ * ostrzega, tak jak React robi to dla `<input>`.
7
13
  */
8
14
  export function useControllableState(options) {
9
15
  const { value, defaultValue, onChange } = options;
10
16
  const isControlled = value !== undefined;
11
17
  const [uncontrolled, setUncontrolled] = useState(defaultValue);
18
+ // Najświeższy tryb i callback dla stabilnego `setValue` — odświeżane po zatwierdzeniu renderu.
19
+ const latest = useRef({ isControlled, onChange });
20
+ useLayoutEffect(() => {
21
+ latest.current = { isControlled, onChange };
22
+ });
12
23
  const setValue = useCallback((next) => {
13
- if (!isControlled)
24
+ if (!latest.current.isControlled)
14
25
  setUncontrolled(next);
15
- onChange?.(next);
16
- }, [isControlled, onChange]);
26
+ latest.current.onChange?.(next);
27
+ }, []);
28
+ const [initiallyControlled] = useState(isControlled);
29
+ const warned = useRef(false);
30
+ useEffect(() => {
31
+ if (warned.current || isControlled === initiallyControlled || !isDevBuild())
32
+ return;
33
+ warned.current = true;
34
+ console.warn(isControlled
35
+ ? '[koncept-ui] Komponent przeszedł z trybu niekontrolowanego w kontrolowany (`value` było undefined). Wybierz jeden tryb na całe życie komponentu.'
36
+ : '[koncept-ui] Komponent przeszedł z trybu kontrolowanego w niekontrolowany (`value` stało się undefined). Do „braku wartości” użyj null, nie undefined.');
37
+ }, [isControlled, initiallyControlled]);
17
38
  return [isControlled ? value : uncontrolled, setValue];
18
39
  }
@@ -4,6 +4,7 @@ import { type RefObject } from 'react';
4
4
  * z fokusem, przenosi fokus do panelu (pierwszy element z kolejki Tab albo sam panel) i zawija
5
5
  * `Tab` / `Shift+Tab` w jego obrębie; przy zamknięciu oddaje fokus zapamiętanemu elementowi.
6
6
  * Bez tego `aria-modal="true"` obiecywał czytnikowi okno, z którego Tab po cichu wychodził.
7
+ * Otwarty panel blokuje też przewijanie strony pod spodem (wspólny licznik z `KptDialog`).
7
8
  *
8
9
  * Port Angulara robi to samo na pułapce CDK (`shared/modal-focus.ts`) — zmiana zachowania w obu.
9
10
  */
@@ -1,4 +1,10 @@
1
1
  import { useEffect } from 'react';
2
+ import { kptLockScroll } from "./scroll-lock.js";
3
+ /**
4
+ * Stos otwartych paneli modalnych. Pułapki słuchają `keydown` na dokumencie, więc przy dwóch
5
+ * otwartych naraz (szuflada → dialog) walczyłyby o ten sam Tab — klawisz obsługuje tylko najwyższa.
6
+ */
7
+ const stack = [];
2
8
  /** Elementy z kolejki Tab — na nich zawija się pułapka. */
3
9
  const TABBABLE = 'a[href], button:not(:disabled), input:not(:disabled), select:not(:disabled), textarea:not(:disabled), [tabindex]:not([tabindex="-1"])';
4
10
  function tabbables(panel) {
@@ -9,6 +15,7 @@ function tabbables(panel) {
9
15
  * z fokusem, przenosi fokus do panelu (pierwszy element z kolejki Tab albo sam panel) i zawija
10
16
  * `Tab` / `Shift+Tab` w jego obrębie; przy zamknięciu oddaje fokus zapamiętanemu elementowi.
11
17
  * Bez tego `aria-modal="true"` obiecywał czytnikowi okno, z którego Tab po cichu wychodził.
18
+ * Otwarty panel blokuje też przewijanie strony pod spodem (wspólny licznik z `KptDialog`).
12
19
  *
13
20
  * Port Angulara robi to samo na pułapce CDK (`shared/modal-focus.ts`) — zmiana zachowania w obu.
14
21
  */
@@ -19,6 +26,8 @@ export function useModalFocus(open, panelRef) {
19
26
  return;
20
27
  const active = document.activeElement;
21
28
  const returnTo = active instanceof HTMLElement && !panel.contains(active) ? active : null;
29
+ stack.push(panel);
30
+ const releaseScroll = kptLockScroll();
22
31
  // Klatkę później: panel dostaje `is-open` (i `visibility: visible`) w tym samym commicie,
23
32
  // a przeglądarka nie skupi elementu, który w chwili wywołania jest jeszcze niewidoczny.
24
33
  const frame = requestAnimationFrame(() => {
@@ -26,7 +35,7 @@ export function useModalFocus(open, panelRef) {
26
35
  (first ?? panel).focus();
27
36
  });
28
37
  const onKeyDown = (event) => {
29
- if (event.key !== 'Tab')
38
+ if (event.key !== 'Tab' || stack[stack.length - 1] !== panel)
30
39
  return;
31
40
  const items = tabbables(panel);
32
41
  if (!items.length) {
@@ -50,6 +59,8 @@ export function useModalFocus(open, panelRef) {
50
59
  return () => {
51
60
  cancelAnimationFrame(frame);
52
61
  document.removeEventListener('keydown', onKeyDown);
62
+ stack.splice(stack.indexOf(panel), 1);
63
+ releaseScroll();
53
64
  if (returnTo?.isConnected)
54
65
  returnTo.focus();
55
66
  };
@@ -18,6 +18,8 @@ export interface KptKnobProps extends Omit<ComponentPropsWithRef<'div'>, 'onChan
18
18
  /** Nazwa dostępna, gdy kontrolki nie opisuje etykieta (`<label for>` albo `KptFormField`). */
19
19
  ariaLabel?: string | null;
20
20
  disabled?: boolean;
21
+ /** Tylko do odczytu — pokrętło widać i da się na nim stanąć, ale nie zmienić wartości. */
22
+ readOnly?: boolean;
21
23
  invalid?: boolean;
22
24
  touched?: boolean;
23
25
  onTouch?: () => void;
@@ -26,10 +28,11 @@ export interface KptKnobProps extends Omit<ComponentPropsWithRef<'div'>, 'onChan
26
28
  * Pokrętło — wybór wartości po łuku, przeciąganiem albo strzałkami.
27
29
  *
28
30
  * Skok wartości (strzałka, `Home`/`End`, klik w łuk, zmiana z zewnątrz) łuk i licznik pokonują
29
- * płynnie; przy przeciąganiu idą za kursorem bez opóźnienia. `prefers-reduced-motion` wyłącza
31
+ * płynnie; przy przeciąganiu idą za kursorem bez opóźnienia. `PageUp`/`PageDown` skaczą o dziesięć
32
+ * kroków, a wartości leżą na siatce `min + k·step` dla myszy i klawiatury. `prefers-reduced-motion` wyłącza
30
33
  * animację, a `aria-valuenow` zawsze niesie wartość docelową, nie pośrednią.
31
34
  *
32
35
  * @example
33
36
  * <KptKnob value={level} onValueChange={setLevel} min={0} max={100} step={5} suffix="%" />
34
37
  */
35
- export declare function KptKnob({ value, defaultValue, onValueChange, min, max, step, size, stroke, suffix, showValue, id, ariaLabel, disabled, invalid, touched, onTouch, className, ...rest }: Readonly<KptKnobProps>): import("react").JSX.Element;
38
+ export declare function KptKnob({ value, defaultValue, onValueChange, min, max, step, size, stroke, suffix, showValue, id, ariaLabel, disabled, readOnly, invalid, touched, onTouch, className, ...rest }: Readonly<KptKnobProps>): import("react").JSX.Element;
package/dist/knob/knob.js CHANGED
@@ -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 { kptSnapToStep } from "../internal/step.js";
4
6
  import { kptStepDecimals, kptTween } from "../internal/tween.js";
5
7
  import { useControllableState } from "../internal/use-controllable-state.js";
6
8
  import { useKptFieldA11y } from "../form-field/form-field-context.js";
@@ -15,13 +17,14 @@ const TWEEN_MS = 220;
15
17
  * Pokrętło — wybór wartości po łuku, przeciąganiem albo strzałkami.
16
18
  *
17
19
  * Skok wartości (strzałka, `Home`/`End`, klik w łuk, zmiana z zewnątrz) łuk i licznik pokonują
18
- * płynnie; przy przeciąganiu idą za kursorem bez opóźnienia. `prefers-reduced-motion` wyłącza
20
+ * płynnie; przy przeciąganiu idą za kursorem bez opóźnienia. `PageUp`/`PageDown` skaczą o dziesięć
21
+ * kroków, a wartości leżą na siatce `min + k·step` dla myszy i klawiatury. `prefers-reduced-motion` wyłącza
19
22
  * animację, a `aria-valuenow` zawsze niesie wartość docelową, nie pośrednią.
20
23
  *
21
24
  * @example
22
25
  * <KptKnob value={level} onValueChange={setLevel} min={0} max={100} step={5} suffix="%" />
23
26
  */
24
- export function KptKnob({ value, defaultValue = 0, onValueChange, min = 0, max = 100, step = 1, size = 96, stroke = 10, suffix = '', showValue = true, id, ariaLabel = null, disabled = false, invalid = false, touched, onTouch, className, ...rest }) {
27
+ export function KptKnob({ value, defaultValue = 0, onValueChange, min = 0, max = 100, step = 1, size = 96, stroke = 10, suffix = '', showValue = true, id, ariaLabel = null, disabled = false, readOnly = false, invalid = false, touched, onTouch, className, ...rest }) {
25
28
  const field = useKptFieldA11y(id, 'kpt-knob');
26
29
  const svgRef = useRef(null);
27
30
  const [current, setCurrent] = useControllableState({ value, defaultValue, onChange: onValueChange });
@@ -58,7 +61,6 @@ export function KptKnob({ value, defaultValue = 0, onValueChange, min = 0, max =
58
61
  return `M ${x0} ${y0}`;
59
62
  return `M ${x0} ${y0} A ${r} ${r} 0 ${a1 - a0 > Math.PI ? 1 : 0} 1 ${x1} ${y1}`;
60
63
  };
61
- const clamp = (raw) => Math.min(max, Math.max(min, Math.round(raw / step) * step));
62
64
  const setFromPoint = (clientX, clientY) => {
63
65
  const svg = svgRef.current;
64
66
  if (!svg)
@@ -72,10 +74,10 @@ export function KptKnob({ value, defaultValue = 0, onValueChange, min = 0, max =
72
74
  // inaczej przeciągnięcie przez dół przeskakiwałoby z maksimum na minimum.
73
75
  if (angle > SWEEP + GAP)
74
76
  angle = angle > SWEEP + GAP / 2 ? 0 : SWEEP;
75
- setCurrent(clamp(min + Math.min(1, Math.max(0, angle / SWEEP)) * range));
77
+ setCurrent(kptSnapToStep(min + Math.min(1, Math.max(0, angle / SWEEP)) * range, min, max, step));
76
78
  };
77
79
  const onPointerDown = (event) => {
78
- if (disabled)
80
+ if (disabled || readOnly)
79
81
  return;
80
82
  // Bez tego przeciąganie zaznacza tekst: licznik w środku i — przez SVG — całe koło, jak po
81
83
  // `Ctrl+A`. Zablokowanie domyślnej akcji zabiera też fokus, więc oddajemy go ręcznie.
@@ -101,22 +103,21 @@ export function KptKnob({ value, defaultValue = 0, onValueChange, min = 0, max =
101
103
  window.addEventListener('pointercancel', up);
102
104
  };
103
105
  const onKeyDown = (event) => {
104
- if (disabled)
106
+ if (disabled || readOnly)
105
107
  return;
106
- let next = current;
107
- if (event.key === 'ArrowUp' || event.key === 'ArrowRight')
108
- next += step;
109
- else if (event.key === 'ArrowDown' || event.key === 'ArrowLeft')
110
- next -= step;
111
- else if (event.key === 'Home')
108
+ const steps = { ArrowUp: 1, ArrowRight: 1, ArrowDown: -1, ArrowLeft: -1, PageUp: 10, PageDown: -10 };
109
+ let next;
110
+ if (event.key === 'Home')
112
111
  next = min;
113
112
  else if (event.key === 'End')
114
113
  next = max;
114
+ else if (steps[event.key] !== undefined)
115
+ next = current + steps[event.key] * step;
115
116
  else
116
117
  return;
117
118
  event.preventDefault();
118
- setCurrent(Math.min(max, Math.max(min, next)));
119
+ setCurrent(kptSnapToStep(next, min, max, step));
119
120
  onTouch?.();
120
121
  };
121
- return (_jsx("div", { className: cn('kpt-knob-host', className), ...rest, children: _jsx("div", { className: cn('kpt-knob', showInvalid && 'kpt-knob--invalid'), role: "slider", id: field.id, tabIndex: disabled ? -1 : 0, "aria-valuemin": min, "aria-valuemax": max, "aria-valuenow": current, "aria-valuetext": suffix ? `${current}${suffix}` : undefined, "aria-disabled": disabled || undefined, "aria-invalid": showInvalid || undefined, "aria-label": ariaLabel ?? undefined, "aria-labelledby": ariaLabel ? undefined : field.labelledBy, "aria-describedby": field.describedBy, onPointerDown: onPointerDown, onKeyDown: onKeyDown, children: _jsxs("svg", { ref: svgRef, viewBox: `0 0 ${size} ${size}`, style: { width: size, height: size }, children: [_jsx("path", { className: "kpt-knob__range", d: arc(0, 1), fill: "none", strokeWidth: stroke, strokeLinecap: "round" }), _jsx("path", { className: "kpt-knob__value", d: arc(0, ratio), fill: "none", strokeWidth: stroke, strokeLinecap: "round" }), showValue ? (_jsxs("text", { className: "kpt-knob__text", x: size / 2, y: size / 2, textAnchor: "middle", dominantBaseline: "central", children: [shown.toFixed(kptStepDecimals(step)), suffix] })) : null] }) }) }));
122
+ return (_jsx("div", { className: cn('kpt-knob-host', className), ...rest, children: _jsx("div", { className: cn('kpt-knob', showInvalid && 'kpt-knob--invalid'), role: "slider", id: field.id, tabIndex: disabled ? -1 : 0, "aria-valuemin": min, "aria-valuemax": max, "aria-valuenow": current, "aria-valuetext": suffix ? `${current}${suffix}` : undefined, "aria-disabled": disabled || undefined, "aria-readonly": readOnly || undefined, "aria-invalid": showInvalid || undefined, "aria-label": ariaLabel ?? undefined, "aria-labelledby": ariaLabel ? undefined : field.labelledBy, "aria-describedby": field.describedBy, onPointerDown: onPointerDown, onKeyDown: onKeyDown, children: _jsxs("svg", { ref: svgRef, viewBox: `0 0 ${size} ${size}`, style: { width: size, height: size }, children: [_jsx("path", { className: "kpt-knob__range", d: arc(0, 1), fill: "none", strokeWidth: stroke, strokeLinecap: "round" }), _jsx("path", { className: "kpt-knob__value", d: arc(0, ratio), fill: "none", strokeWidth: stroke, strokeLinecap: "round" }), showValue ? (_jsxs("text", { className: "kpt-knob__text", x: size / 2, y: size / 2, textAnchor: "middle", dominantBaseline: "central", children: [shown.toFixed(kptStepDecimals(step)), suffix] })) : null] }) }) }));
122
123
  }
@@ -19,7 +19,8 @@ Import: `import { KptKnob } from '@konce-pt/react';`
19
19
  - `min` (0), `max` (100), `step` (1)
20
20
  - `size`: px (96) — the side of the SVG square; `stroke`: px (10) — arc thickness
21
21
  - `suffix`: string appended to the value in the middle; `showValue`: boolean (default true)
22
- - `disabled`, `invalid`, `touched`, `onTouch()`
22
+ - `disabled`, `readOnly`, `invalid`, `touched`, `onTouch()` — `readOnly` keeps the knob focusable but ignores
23
+ the pointer and the keys (`aria-readonly`)
23
24
  - `className`, `ref` and every other `<div>` prop go to the host
24
25
 
25
26
  ## The gap at the bottom
@@ -57,5 +58,6 @@ select the number or the arc.
57
58
 
58
59
  ## Accessibility
59
60
  `role="slider"` with `aria-valuemin` / `aria-valuemax` / `aria-valuenow`; arrows step by `step`,
60
- Home and End jump to the bounds. `disabled` sets `aria-disabled` and drops the knob out of the
61
+ PageUp/PageDown by ten steps, Home and End jump to the bounds. Values sit on the grid `min + k·step` for
62
+ both the pointer and the keys — with `min={1} step={2}` that is 1, 3, 5…. `disabled` sets `aria-disabled` and drops the knob out of the
61
63
  tab order.
@@ -16,6 +16,8 @@ export interface KptListboxProps<T> extends Omit<ComponentPropsWithRef<'div'>, '
16
16
  /** Oznacza listę jako wymaganą atrybutem `aria-required`. */
17
17
  required?: boolean;
18
18
  disabled?: boolean;
19
+ /** Tylko do odczytu — po liście da się chodzić i czytać wybór, ale nie da się go zmienić. */
20
+ readOnly?: boolean;
19
21
  invalid?: boolean;
20
22
  touched?: boolean;
21
23
  onTouch?: () => void;
@@ -37,4 +39,4 @@ export interface KptListboxProps<T> extends Omit<ComponentPropsWithRef<'div'>, '
37
39
  * <KptListbox options={roles} value={role} onValueChange={setRole} filter />
38
40
  * <KptListbox options={tags} value={tags} onValueChange={setTags} multiple />
39
41
  */
40
- export declare function KptListbox<T>({ options, value, defaultValue, onValueChange, multiple, filter, id, ariaLabel, required, disabled, invalid, touched, onTouch, locale, dictionary, className, ...rest }: Readonly<KptListboxProps<T>>): import("react").JSX.Element;
42
+ export declare function KptListbox<T>({ options, value, defaultValue, onValueChange, multiple, filter, id, ariaLabel, required, disabled, readOnly, invalid, touched, onTouch, locale, dictionary, className, ...rest }: Readonly<KptListboxProps<T>>): 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 { useEffect, useMemo, useRef, useState } from 'react';
3
4
  import { foldMentionText } from '@konce-pt/mention';
@@ -24,7 +25,7 @@ import { KptIcon } from "../icon/icon.js";
24
25
  * <KptListbox options={roles} value={role} onValueChange={setRole} filter />
25
26
  * <KptListbox options={tags} value={tags} onValueChange={setTags} multiple />
26
27
  */
27
- export function KptListbox({ options = [], value, defaultValue = null, onValueChange, multiple = false, filter = false, id, ariaLabel = null, required = false, disabled = false, invalid = false, touched, onTouch, locale, dictionary, className, ...rest }) {
28
+ export function KptListbox({ options = [], value, defaultValue = null, onValueChange, multiple = false, filter = false, id, ariaLabel = null, required = false, disabled = false, readOnly = false, invalid = false, touched, onTouch, locale, dictionary, className, ...rest }) {
28
29
  const { t } = useKptMessages({ locale, dictionary });
29
30
  /** `div` z rolą nie dostaje nazwy z `label[for]` — stąd `aria-labelledby` na etykietę wrappera. */
30
31
  const field = useKptFieldA11y(id, 'kpt-listbox');
@@ -55,18 +56,33 @@ export function KptListbox({ options = [], value, defaultValue = null, onValueCh
55
56
  const isSelected = (candidate) => multiple ? Array.isArray(current) && current.includes(candidate) : current === candidate;
56
57
  const toggle = (candidate) => {
57
58
  if (multiple) {
58
- const list = Array.isArray(current) ? current : [];
59
+ const list = Array.isArray(current) ? (current) : [];
59
60
  setCurrent(list.includes(candidate) ? list.filter((item) => item !== candidate) : [...list, candidate]);
60
61
  }
61
62
  else {
62
63
  setCurrent(current === candidate ? null : candidate);
63
64
  }
64
65
  };
65
- const pick = (index) => {
66
+ /** Ostatnio kliknięta opcja — początek zakresu dla `Shift+klik` w trybie `multiple`. */
67
+ const anchorIndex = useRef(-1);
68
+ const pick = (index, extend = false) => {
66
69
  const option = visible[index];
67
- if (!option || disabledMap[index])
70
+ if (!option || disabledMap[index] || readOnly)
68
71
  return;
69
72
  setActiveIndex(index);
73
+ if (extend && multiple && anchorIndex.current >= 0) {
74
+ // Zaznacza (dokłada) dozwolone opcje między kotwicą a klikniętą — `Shift+klik` (APG).
75
+ const [lo, hi] = anchorIndex.current <= index ? [anchorIndex.current, index] : [index, anchorIndex.current];
76
+ const list = Array.isArray(current) ? current : [];
77
+ const add = visible
78
+ .slice(lo, hi + 1)
79
+ .filter((_, offset) => !disabledMap[lo + offset])
80
+ .map((item) => item.value)
81
+ .filter((item) => !list.includes(item));
82
+ setCurrent([...list, ...add]);
83
+ return;
84
+ }
85
+ anchorIndex.current = index;
70
86
  toggle(option.value);
71
87
  };
72
88
  /** Wejście fokusem: aktywna zostaje wybrana opcja, a bez niej pierwsza dozwolona. */
@@ -88,8 +104,12 @@ export function KptListbox({ options = [], value, defaultValue = null, onValueCh
88
104
  if (OPTION_NAV_KEYS.has(event.key)) {
89
105
  event.preventDefault();
90
106
  const next = nextOptionIndex(disabledMap, activeIndex, event.key);
91
- if (next !== null)
92
- setActiveIndex(next);
107
+ if (next === null)
108
+ return;
109
+ setActiveIndex(next);
110
+ // `Shift+strzałka` w wyborze wielokrotnym zaznacza opcję, na którą przeszedł fokus (APG listbox).
111
+ if (event.shiftKey && multiple && !readOnly && !isSelected(visible[next].value))
112
+ toggle(visible[next].value);
93
113
  return;
94
114
  }
95
115
  if (event.key === ' ' || event.key === 'Enter') {
@@ -99,10 +119,10 @@ export function KptListbox({ options = [], value, defaultValue = null, onValueCh
99
119
  pick(activeIndex);
100
120
  return;
101
121
  }
102
- if (multiple && (event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'a') {
122
+ if (multiple && !readOnly && (event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'a') {
103
123
  event.preventDefault();
104
124
  const all = visible.filter((_, index) => !disabledMap[index]).map((option) => option.value);
105
- const list = Array.isArray(current) ? current : [];
125
+ const list = Array.isArray(current) ? (current) : [];
106
126
  // Drugi raz `Ctrl+A` przy wszystkich zaznaczonych — odznacza widoczne.
107
127
  const allOn = all.every((item) => list.includes(item));
108
128
  setCurrent(allOn ? list.filter((item) => !all.includes(item)) : [...list, ...all.filter((item) => !list.includes(item))]);
@@ -119,8 +139,8 @@ export function KptListbox({ options = [], value, defaultValue = null, onValueCh
119
139
  return;
120
140
  event.preventDefault();
121
141
  listRef.current?.focus();
122
- } })] })) : null, _jsx("div", { ref: listRef, className: "kpt-listbox__items", role: "listbox", id: field.id, tabIndex: disabled ? -1 : 0, "aria-multiselectable": multiple || undefined, "aria-activedescendant": activeOptionId, "aria-disabled": disabled || undefined, "aria-invalid": showInvalid || undefined, "aria-required": required || undefined, "aria-label": ariaLabel ?? undefined, "aria-labelledby": ariaLabel ? undefined : field.labelledBy, "aria-describedby": field.describedBy, onKeyDown: onKeyDown, onFocus: onFocus, onBlur: () => onTouch?.(), children: visible.length ? (visible.map((option, index) => (_jsxs("div", { role: "option", id: optionId(index), className: cn('kpt-listbox__item', isSelected(option.value) && 'is-selected', index === activeIndex && 'is-active'), "aria-selected": isSelected(option.value), "aria-disabled": disabledMap[index] || undefined, onMouseDown: (event) => {
142
+ } })] })) : null, _jsx("div", { ref: listRef, className: "kpt-listbox__items", role: "listbox", id: field.id, tabIndex: disabled ? -1 : 0, "aria-multiselectable": multiple || undefined, "aria-activedescendant": activeOptionId, "aria-disabled": disabled || undefined, "aria-invalid": showInvalid || undefined, "aria-required": required || undefined, "aria-readonly": readOnly || undefined, "aria-label": ariaLabel ?? undefined, "aria-labelledby": ariaLabel ? undefined : field.labelledBy, "aria-describedby": field.describedBy, onKeyDown: onKeyDown, onFocus: onFocus, onBlur: () => onTouch?.(), children: visible.map((option, index) => (_jsxs("div", { role: "option", id: optionId(index), className: cn('kpt-listbox__item', isSelected(option.value) && 'is-selected', index === activeIndex && 'is-active'), "aria-selected": isSelected(option.value), "aria-disabled": disabledMap[index] || undefined, onMouseDown: (event) => {
123
143
  event.preventDefault();
124
144
  listRef.current?.focus();
125
- }, onClick: () => pick(index), children: [_jsx("span", { children: option.label }), isSelected(option.value) ? _jsx(KptIcon, { name: "check" }) : null] }, String(option.value))))) : (_jsx("div", { className: "kpt-listbox__empty", role: "status", children: t('listbox.empty') })) })] }) }));
145
+ }, onClick: (event) => pick(index, event.shiftKey), children: [_jsx("span", { children: option.label }), isSelected(option.value) ? _jsx(KptIcon, { name: "check" }) : null] }, typeof option.value === 'object' && option.value !== null ? `#${index}` : String(option.value)))) }), visible.length === 0 ? (_jsx("div", { className: "kpt-listbox__empty", role: "status", children: t('listbox.empty') })) : null] }) }));
126
146
  }
@@ -53,3 +53,6 @@ label (unless `ariaLabel` is set) and the hint or error in `aria-describedby`. N
53
53
  The list is `role="listbox"` with `aria-multiselectable`, `aria-activedescendant` and a name from
54
54
  `ariaLabel` or `KptFormField`; options are `role="option"` with `aria-selected`/`aria-disabled`.
55
55
  The keyboard is described above.
56
+
57
+ ## Read-only and required
58
+ `readOnly` lets you move through the list and read the choice but not change it (`aria-readonly`). With `multiple`, `Shift+↑/↓` selects the option focus moves to and `Shift+click` adds the range from the last clicked option (APG). The empty message sits next to the list.
package/dist/map/llms.txt CHANGED
@@ -41,7 +41,11 @@ a readable message.
41
41
 
42
42
  ## Events
43
43
  `onLocationClick`, `onClusterClick`, `onLocationCreate`, `onSearchSelect`, `onViewChange`,
44
- `onUserPositionChange`, `onUserLocationError`.
44
+ `onUserPositionChange`, `onUserLocationError`, `onEngineError`.
45
+
46
+ `onEngineError(error)` fires when the engine fails to start (no `leaflet` package, a rejected Google key);
47
+ the error is also logged to the console. `followUser` and the position callbacks are read fresh on every
48
+ fix, so changing them does not restart the geolocation watch.
45
49
 
46
50
  `onLocationCreate` gives `{ at, categoryId, value }` — the component appends **nothing** to
47
51
  `locations`; the application is the source of truth.
@@ -1,3 +1,4 @@
1
+ 'use client';
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
3
  import { emptyWeek, parseTime, weekdayName } from '@konce-pt/map';
3
4
  import { useEffect, useMemo, useState } from 'react';
@@ -40,6 +41,10 @@ function initialModel(fields) {
40
41
  out[def.key] = initialValue(def);
41
42
  return out;
42
43
  }
44
+ /** Wartość pola tekstowego z modelu `unknown` — liczba zostaje liczbą zapisaną tekstem, reszta pustym napisem. */
45
+ function asText(value) {
46
+ return typeof value === 'string' || typeof value === 'number' ? String(value) : '';
47
+ }
43
48
  /**
44
49
  * Treść dialogu „dodaj lokalizację" budowana ze schemy kategorii (`category.fields`).
45
50
  *
@@ -55,42 +60,48 @@ export function KptMapCreateDialog({ category, at, geocoder = null, onSave, onCa
55
60
  const [touched, setTouched] = useState({});
56
61
  /** Po nieudanej próbie zapisu pokazujemy błędy wszystkich pól, nie tylko dotkniętych. */
57
62
  const [submitted, setSubmitted] = useState(false);
58
- /** Trwa zapytanie do geokodera — pokazujemy notkę zamiast ciszy. */
59
- const [geocoding, setGeocoding] = useState(false);
60
63
  /** Ostatnio ustalony adres; `address` bywa czytelniejszy niż suma pól. */
61
64
  const [address, setAddress] = useState(null);
62
- // Inna kategoria to inny formularz — model wraca do wartości początkowych.
63
- const schemaRef = useMemo(() => fields, [fields]);
64
- useEffect(() => {
65
- setModel(initialModel(schemaRef));
65
+ // Inna kategoria to inny formularz — model wraca do wartości początkowych. Liczone w renderze
66
+ // (wzorzec poprzedniej wartości): efekt pokazałby jedną klatkę pól starej kategorii.
67
+ const [modelFields, setModelFields] = useState(fields);
68
+ if (modelFields !== fields) {
69
+ setModelFields(fields);
70
+ setModel(initialModel(fields));
66
71
  setTouched({});
67
72
  setSubmitted(false);
68
- }, [schemaRef]);
73
+ }
69
74
  /** Czy kategoria ma pola do podpowiedzenia i jest czym je podpowiedzieć. */
70
75
  const autofillFields = useMemo(() => fields.filter((def) => def.autofill && TEXTUAL.has(def.type)), [fields]);
71
76
  const hasAutofill = !!geocoder && autofillFields.length > 0;
77
+ /** Tożsamość bieżącego zapytania do geokodera — nowa przy każdej zmianie jego wejść. */
78
+ const geocodeRequest = useMemo(() => (geocoder && autofillFields.length > 0 ? { geocoder, at, locale, autofillFields } : null), [geocoder, at, locale, autofillFields]);
79
+ /** Zapytanie, na które przyszła już odpowiedź (albo błąd). */
80
+ const [settledRequest, setSettledRequest] = useState(null);
81
+ /** Trwa zapytanie do geokodera — pokazujemy notkę zamiast ciszy. */
82
+ const geocoding = geocodeRequest !== null && settledRequest !== geocodeRequest;
72
83
  /**
73
84
  * Podpowiada adres z geokodowania wstecznego współrzędnych kliknięcia. Wypełnia wyłącznie
74
85
  * pola z `autofill` i wyłącznie te, które są jeszcze puste — podpowiedź nigdy nie kasuje
75
86
  * tego, co użytkownik zdążył wpisać.
76
87
  */
77
88
  useEffect(() => {
78
- if (!geocoder || autofillFields.length === 0)
89
+ if (!geocodeRequest)
79
90
  return;
91
+ const request = geocodeRequest;
80
92
  const controller = new AbortController();
81
- setGeocoding(true);
82
- geocoder
83
- .reverse(at, { language: locale, signal: controller.signal })
93
+ request.geocoder
94
+ .reverse(request.at, { language: request.locale, signal: controller.signal })
84
95
  .then((found) => {
85
96
  if (controller.signal.aborted)
86
97
  return;
87
- setGeocoding(false);
98
+ setSettledRequest(request);
88
99
  setAddress(found);
89
100
  if (!found)
90
101
  return;
91
102
  setModel((current) => {
92
103
  const next = { ...current };
93
- for (const def of autofillFields) {
104
+ for (const def of request.autofillFields) {
94
105
  const value = found[def.autofill];
95
106
  // Puste pole = to, czego użytkownik nie tknął.
96
107
  if (typeof value === 'string' && value && !next[def.key])
@@ -101,13 +112,10 @@ export function KptMapCreateDialog({ category, at, geocoder = null, onSave, onCa
101
112
  })
102
113
  .catch(() => {
103
114
  if (!controller.signal.aborted)
104
- setGeocoding(false);
115
+ setSettledRequest(request);
105
116
  });
106
- return () => {
107
- controller.abort();
108
- setGeocoding(false);
109
- };
110
- }, [geocoder, at, locale, autofillFields]);
117
+ return () => controller.abort();
118
+ }, [geocodeRequest]);
111
119
  /**
112
120
  * Komunikat błędu pola albo `null`. Dzień otwarty musi mieć obie godziny i różne od siebie;
113
121
  * dzień oznaczony jako zamknięty przechodzi bez zastrzeżeń — to prawidłowa odpowiedź,
@@ -174,7 +182,7 @@ export function KptMapCreateDialog({ category, at, geocoder = null, onSave, onCa
174
182
  const common = { onTouch: touch(def.key), invalid: !!visibleError(def), touched: !!touched[def.key] };
175
183
  switch (def.type) {
176
184
  case 'textarea':
177
- return (_jsx(KptTextarea, { value: String(value ?? ''), onValueChange: (next) => set(def.key, next), placeholder: def.placeholder ?? '', ...common }));
185
+ return (_jsx(KptTextarea, { value: asText(value), onValueChange: (next) => set(def.key, next), placeholder: def.placeholder ?? '', ...common }));
178
186
  case 'number':
179
187
  return (_jsx(KptInputNumber, { value: value, onValueChange: (next) => set(def.key, next), min: typeof def.min === 'number' ? def.min : undefined, max: typeof def.max === 'number' ? def.max : undefined, placeholder: def.placeholder ?? '', ...common }));
180
188
  case 'date':
@@ -186,11 +194,11 @@ export function KptMapCreateDialog({ category, at, geocoder = null, onSave, onCa
186
194
  case 'checkbox':
187
195
  return (_jsx(KptCheckbox, { checked: !!value, onCheckedChange: (next) => set(def.key, next), label: def.placeholder ?? '', onTouch: touch(def.key) }));
188
196
  case 'image':
189
- return (_jsx(KptMapImageField, { value: String(value ?? ''), onValueChange: (next) => set(def.key, next), invalid: !!visibleError(def), touched: !!touched[def.key], onTouch: touch(def.key) }));
197
+ return (_jsx(KptMapImageField, { value: asText(value), onValueChange: (next) => set(def.key, next), invalid: !!visibleError(def), touched: !!touched[def.key], onTouch: touch(def.key) }));
190
198
  case 'hours':
191
199
  return (_jsx(KptMapHoursField, { value: value ?? emptyWeek(), onValueChange: (next) => set(def.key, next), onTouch: touch(def.key) }));
192
200
  default:
193
- return (_jsx(KptInput, { id: controlId(def), type: def.type === 'tel' || def.type === 'email' || def.type === 'url' ? def.type : 'text', value: String(value ?? ''), onValueChange: (next) => set(def.key, next), placeholder: def.placeholder ?? (def.type === 'url' ? 'https://' : ''), ...common }));
201
+ return (_jsx(KptInput, { id: controlId(def), type: def.type === 'tel' || def.type === 'email' || def.type === 'url' ? def.type : 'text', value: asText(value), onValueChange: (next) => set(def.key, next), placeholder: def.placeholder ?? (def.type === 'url' ? 'https://' : ''), ...common }));
194
202
  }
195
203
  }
196
204
  }
@@ -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 { emptyWeek, weekdayName } from '@konce-pt/map';
3
4
  import { useState } from 'react';
@@ -1,3 +1,4 @@
1
+ 'use client';
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
3
  import { useState } from 'react';
3
4
  import { cn } from "../internal/cn.js";
@@ -42,7 +43,7 @@ export function KptMapImageField({ value, onValueChange, disabled = false, readO
42
43
  return;
43
44
  }
44
45
  const reader = new FileReader();
45
- reader.onload = () => onValueChange(String(reader.result ?? ''));
46
+ reader.onload = () => onValueChange((typeof reader.result === 'string' ? reader.result : ''));
46
47
  reader.onerror = () => setSizeError(t('map.field.imageTooLarge', { max: maxLabel }));
47
48
  reader.readAsDataURL(file);
48
49
  };
@@ -1,3 +1,4 @@
1
+ 'use client';
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
3
  import { colorVarOf, contrastVarOf } from '@konce-pt/map';
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 { foregroundVarOf, formatDistance, formatLatLng, groupWeek, isAlwaysOpen, isOpenAt, nextChange, normalizeUrl, prettyUrl, } from '@konce-pt/map';
3
4
  import { useId, useRef, useState } from 'react';
@@ -148,5 +149,5 @@ export function KptMapPoiCard({ poi, distance = null, category = null, primaryLa
148
149
  // Odświeżamy zegar przy rozwinięciu — karta bywa otwarta dłużej niż minutę.
149
150
  setNow(Date.now());
150
151
  setExpanded(!expanded);
151
- }, children: [_jsx(KptIcon, { className: "kpt-map-poi__row-icon", name: "clock" }), _jsx("span", { className: "kpt-map-poi__status", "data-open": open || alwaysOpen, children: statusLabel }), statusHint ? _jsxs("span", { className: "kpt-map-poi__status-hint", children: ["\u00B7 ", statusHint] }) : null, _jsx(KptIcon, { className: "kpt-map-poi__chevron", name: "chevron-down" })] }), expanded ? (_jsx("dl", { className: "kpt-map-poi__week", id: hoursId, children: hoursGroups.map((group) => (_jsxs("div", { style: { display: 'contents' }, children: [_jsx("dt", { className: "kpt-map-poi__week-day", children: group.label }), _jsx("dd", { className: "kpt-map-poi__week-hours", children: group.closed ? (_jsx("span", { className: "kpt-map-poi__week-closed", children: t('map.poi.closedDay') })) : (group.ranges.map((range) => (_jsx("span", { className: "kpt-map-poi__week-range", children: range }, range)))) })] }, group.label))) })) : null] })) : null, rows.length || poi.facts?.length ? (_jsxs("ul", { className: "kpt-map-poi__rows", children: [rows.map((row) => (_jsxs("li", { className: "kpt-map-poi__row", children: [_jsx(KptIcon, { className: "kpt-map-poi__row-icon", name: row.icon }), row.href ? (_jsx("a", { className: "kpt-map-poi__row-value kpt-map-poi__row-link", href: row.href, target: row.external ? '_blank' : undefined, rel: row.external ? 'noopener' : undefined, children: row.text })) : (_jsx("span", { className: "kpt-map-poi__row-value", children: row.text })), _jsxs("span", { className: "kpt-map-poi__row-actions", children: [row.href ? (_jsx("a", { className: "kpt-map-poi__row-action", href: row.href, target: row.external ? '_blank' : undefined, rel: row.external ? 'noopener' : undefined, "aria-label": row.actionLabel, title: row.actionLabel, children: _jsx(KptIcon, { name: row.actionIcon }) })) : null, canCopy ? (_jsx("button", { className: "kpt-map-poi__row-action", type: "button", "aria-label": t('map.poi.copy'), title: t('map.poi.copy'), onClick: () => void copy(row), children: _jsx(KptIcon, { name: justCopied === row.kind ? 'check' : 'copy' }) })) : null] })] }, row.kind))), (poi.facts ?? []).map((fact) => (_jsxs("li", { className: "kpt-map-poi__row kpt-map-poi__row--fact", children: [fact.icon ? (_jsx(KptIcon, { className: "kpt-map-poi__row-icon", name: fact.icon })) : (_jsx("span", { className: "kpt-map-poi__row-icon", "aria-hidden": "true" })), _jsxs("span", { className: "kpt-map-poi__row-value", children: [_jsx("span", { className: "kpt-map-poi__fact-label", children: fact.label }), " ", fact.value] })] }, fact.label)))] })) : null, _jsx("p", { className: "kpt-map-poi__live", "aria-live": "polite", children: justCopied ? t('map.poi.copied') : '' })] })] }));
152
+ }, children: [_jsx(KptIcon, { className: "kpt-map-poi__row-icon", name: "clock" }), _jsx("span", { className: "kpt-map-poi__status", "data-open": open || alwaysOpen, children: statusLabel }), statusHint ? _jsxs("span", { className: "kpt-map-poi__status-hint", children: ["\u00B7 ", statusHint] }) : null, _jsx(KptIcon, { className: "kpt-map-poi__chevron", name: "chevron-down" })] }), expanded ? (_jsx("dl", { className: "kpt-map-poi__week", id: hoursId, children: hoursGroups.map((group) => (_jsxs("div", { style: { display: 'contents' }, children: [_jsx("dt", { className: "kpt-map-poi__week-day", children: group.label }), _jsx("dd", { className: "kpt-map-poi__week-hours", children: group.closed ? (_jsx("span", { className: "kpt-map-poi__week-closed", children: t('map.poi.closedDay') })) : (group.ranges.map((range) => (_jsx("span", { className: "kpt-map-poi__week-range", children: range }, range)))) })] }, group.label))) })) : null] })) : null, rows.length || poi.facts?.length ? (_jsxs("ul", { className: "kpt-map-poi__rows", children: [rows.map((row) => (_jsxs("li", { className: "kpt-map-poi__row", children: [_jsx(KptIcon, { className: "kpt-map-poi__row-icon", name: row.icon }), row.href ? (_jsx("a", { className: "kpt-map-poi__row-value kpt-map-poi__row-link", href: row.href, target: row.external ? '_blank' : undefined, rel: row.external ? 'noopener noreferrer' : undefined, children: row.text })) : (_jsx("span", { className: "kpt-map-poi__row-value", children: row.text })), _jsxs("span", { className: "kpt-map-poi__row-actions", children: [row.href ? (_jsx("a", { className: "kpt-map-poi__row-action", href: row.href, target: row.external ? '_blank' : undefined, rel: row.external ? 'noopener noreferrer' : undefined, "aria-label": row.actionLabel, title: row.actionLabel, children: _jsx(KptIcon, { name: row.actionIcon }) })) : null, canCopy ? (_jsx("button", { className: "kpt-map-poi__row-action", type: "button", "aria-label": t('map.poi.copy'), title: t('map.poi.copy'), onClick: () => void copy(row), children: _jsx(KptIcon, { name: justCopied === row.kind ? 'check' : 'copy' }) })) : null] })] }, row.kind))), (poi.facts ?? []).map((fact) => (_jsxs("li", { className: "kpt-map-poi__row kpt-map-poi__row--fact", children: [fact.icon ? (_jsx(KptIcon, { className: "kpt-map-poi__row-icon", name: fact.icon })) : (_jsx("span", { className: "kpt-map-poi__row-icon", "aria-hidden": "true" })), _jsxs("span", { className: "kpt-map-poi__row-value", children: [_jsx("span", { className: "kpt-map-poi__fact-label", children: fact.label }), " ", fact.value] })] }, fact.label)))] })) : null, _jsx("p", { className: "kpt-map-poi__live", "aria-live": "polite", children: justCopied ? t('map.poi.copied') : '' })] })] }));
152
153
  }