@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,6 +1,7 @@
1
+ 'use client';
1
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useEffect, useRef, useState } from 'react';
3
- import { buildRangeGrid, clampVisibleMonths, datePart, formatDate, formatTime, initialFocusDate, moveFocusDate, pad, parseDate, parseTime, pickRangeDay, timePart, toIso, } from '@konce-pt/datetime';
3
+ import { useEffect, useEffectEvent, useRef, useState, } from 'react';
4
+ import { buildRangeGrid, clampVisibleMonths, datePart, dayPeriodLabels, displayDate, formatTime, kptFirstDayOfWeek, orderWeekdays, initialFocusDate, moveFocusDate, pad, parseDate, parseTime, pickRangeDay, timePart, toIso, } from '@konce-pt/datetime';
4
5
  import { monthNames, weekdayNames } from '@konce-pt/i18n';
5
6
  import { cn } from "../internal/cn.js";
6
7
  import { useControllableState } from "../internal/use-controllable-state.js";
@@ -21,18 +22,20 @@ const EMPTY_RANGE = { start: '', end: '' };
21
22
  * Klawiatura jak w `KptDatepicker`: `Enter`/`Spacja`/`↓` otwierają panel z fokusem na początku
22
23
  * zakresu (albo dziś), strzałki, `Home`/`End` i `PageUp`/`PageDown` (z `Shift` — rok) chodzą po
23
24
  * dniach przez wszystkie panele, `Tab` krąży w panelu, `Escape` i wybór końca oddają fokus polu.
24
- * Krańce i środek zakresu są w nazwie dnia („…, początek zakresu”), a wybór ogłasza region `polite`.
25
+ * Krańce i środek zakresu są w nazwie dnia („…, początek zakresu”), a wybór i przewinięcie miesiąca
26
+ * ogłasza region `polite`. Pole to `role="combobox"` z `aria-haspopup="dialog"`, więc niesie
27
+ * `aria-required`, `aria-readonly` i `aria-invalid`. Tydzień zaczyna się wg aktywnego języka.
25
28
  *
26
29
  * @example
27
30
  * <KptDateRange value={period} onValueChange={setPeriod} visibleMonths={2} />
28
31
  * <KptDateRange mode="datetime" hourFormat={12} value={span} onValueChange={setSpan} />
29
32
  */
30
- export function KptDateRange({ value, defaultValue = EMPTY_RANGE, onValueChange, mode = 'date', visibleMonths = 1, hourFormat = 24, minuteStep = 1, placeholder, displayFormat = 'pl', minDate = null, maxDate = null, ariaLabel = null, id, disabled = false, invalid = false, touched, onTouch, locale, dictionary, className, ...rest }) {
33
+ export function KptDateRange({ value, defaultValue = EMPTY_RANGE, onValueChange, mode = 'date', visibleMonths = 1, hourFormat = 24, minuteStep = 1, placeholder, displayFormat = 'pl', firstDayOfWeek = null, open, onOpenChange, minDate = null, maxDate = null, ariaLabel = null, id, disabled = false, readOnly = false, required = false, invalid = false, touched, onTouch, locale, dictionary, className, ...rest }) {
31
34
  const { t, locale: activeLocale } = useKptMessages({ locale, dictionary });
32
35
  /** `div` z rolą nie dostaje nazwy z `label[for]` — stąd `aria-labelledby` na etykietę wrappera. */
33
36
  const field = useKptFieldA11y(id, 'kpt-date-range');
34
37
  const [current, setCurrent] = useControllableState({ value, defaultValue, onChange: onValueChange });
35
- const [isOpen, setOpen] = useState(false);
38
+ const [isOpen, setOpen] = useControllableState({ value: open, defaultValue: false, onChange: onOpenChange });
36
39
  const [anchor, setAnchor] = useState(null);
37
40
  const triggerRef = useRef(null);
38
41
  const panelRef = useRef(null);
@@ -41,10 +44,14 @@ export function KptDateRange({ value, defaultValue = EMPTY_RANGE, onValueChange,
41
44
  const [focusIso, setFocusIso] = useState('');
42
45
  /** Komunikat regionu `polite` po wyborze początku i całego zakresu. */
43
46
  const [announcement, setAnnouncement] = useState('');
44
- /** Prośba o przeniesienie fokusu po najbliższym renderze (otwarcie, ruch klawiszem). */
45
- const wantFocus = useRef(false);
46
- const today = useRef(new Date()).current;
47
+ /** Prośba o przeniesienie fokusu po najbliższym renderze (otwarcie, ruch klawiszem) — licznik w stanie. */
48
+ const [focusRequest, setFocusRequest] = useState(0);
49
+ const requestFocus = () => setFocusRequest((n) => n + 1);
50
+ /** „Dziś” liczone przy każdym otwarciu — panel otwarty po północy nie pokazuje wczorajszego dnia. */
51
+ const [today, setToday] = useState(() => new Date());
47
52
  const todayIso = toIso(today);
53
+ const firstDay = firstDayOfWeek ?? kptFirstDayOfWeek(activeLocale);
54
+ const periods = dayPeriodLabels(activeLocale);
48
55
  const showInvalid = invalid && (touched ?? true);
49
56
  const [page, setPage] = useState(() => ({ y: today.getFullYear(), m: today.getMonth() }));
50
57
  const [startTime, setStartTime] = useState('');
@@ -54,7 +61,7 @@ export function KptDateRange({ value, defaultValue = EMPTY_RANGE, onValueChange,
54
61
  const [startDate, setStartDate] = useState('');
55
62
  const [endDate, setEndDate] = useState('');
56
63
  const months = monthNames(activeLocale);
57
- const weekdays = weekdayNames(activeLocale);
64
+ const weekdays = orderWeekdays(weekdayNames(activeLocale), firstDay);
58
65
  const panelCount = mode === 'time' ? 1 : clampVisibleMonths(visibleMonths);
59
66
  const multi = panelCount > 1;
60
67
  const dayFormat = new Intl.DateTimeFormat(activeLocale, { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' });
@@ -75,7 +82,7 @@ export function KptDateRange({ value, defaultValue = EMPTY_RANGE, onValueChange,
75
82
  };
76
83
  const fmtTime = (hhmm) => {
77
84
  const parsed = parseTime(hhmm);
78
- return parsed ? formatTime(parsed.h, parsed.m, hourFormat) : '';
85
+ return parsed ? formatTime(parsed.h, parsed.m, hourFormat, periods) : '';
79
86
  };
80
87
  const display = (() => {
81
88
  const { start, end } = current;
@@ -86,38 +93,47 @@ export function KptDateRange({ value, defaultValue = EMPTY_RANGE, onValueChange,
86
93
  }
87
94
  if (mode === 'datetime') {
88
95
  const one = (v) => {
89
- const date = formatDate(datePart(v), displayFormat);
96
+ const date = displayDate(datePart(v), displayFormat, activeLocale);
90
97
  const time = fmtTime(timePart(v));
91
98
  return time ? `${date} ${time}` : date;
92
99
  };
93
100
  return end ? `${one(start)} – ${one(end)}` : `${one(start)} – …`;
94
101
  }
95
- return end
96
- ? `${formatDate(start, displayFormat)} – ${formatDate(end, displayFormat)}`
97
- : `${formatDate(start, displayFormat)} – …`;
102
+ const fmt = (iso) => displayDate(iso, displayFormat, activeLocale);
103
+ return end ? `${fmt(start)} – ${fmt(end)}` : `${fmt(start)} – …`;
98
104
  })();
105
+ // Panel otwiera też `open` z zewnątrz — stan początkowy liczymy przy każdym przejściu w stan
106
+ // otwarty, wzorcem „poprzednia wartość w stanie” (bez efektu i klatki ze starym widokiem).
107
+ const [wasOpen, setWasOpen] = useState(isOpen);
108
+ if (wasOpen !== isOpen) {
109
+ setWasOpen(isOpen);
110
+ if (isOpen)
111
+ prepareOpen();
112
+ }
113
+ function prepareOpen() {
114
+ const now = new Date();
115
+ setToday(now);
116
+ const { start, end } = current;
117
+ const focus = initialFocusDate(datePart(start), toIso(now), minDate, maxDate);
118
+ setFocusIso(focus);
119
+ setAnnouncement('');
120
+ setPage({ y: +focus.slice(0, 4), m: +focus.slice(5, 7) - 1 });
121
+ requestFocus();
122
+ if (mode === 'datetime') {
123
+ setStartDate(datePart(start));
124
+ setEndDate(datePart(end));
125
+ setStartTime(timePart(start) || '00:00');
126
+ setEndTime(timePart(end) || '00:00');
127
+ }
128
+ else if (mode === 'time') {
129
+ setStartTime(/^\d{2}:\d{2}$/.test(start) ? start : '00:00');
130
+ setEndTime(/^\d{2}:\d{2}$/.test(end) ? end : '00:00');
131
+ }
132
+ setTimeEnd('start');
133
+ }
99
134
  const toggleOpen = () => {
100
- if (disabled)
135
+ if (disabled || readOnly)
101
136
  return;
102
- if (!isOpen) {
103
- const { start, end } = current;
104
- const focus = initialFocusDate(datePart(start), todayIso, minDate, maxDate);
105
- setFocusIso(focus);
106
- setAnnouncement('');
107
- setPage({ y: +focus.slice(0, 4), m: +focus.slice(5, 7) - 1 });
108
- wantFocus.current = true;
109
- if (mode === 'datetime') {
110
- setStartDate(datePart(start));
111
- setEndDate(datePart(end));
112
- setStartTime(timePart(start) || '00:00');
113
- setEndTime(timePart(end) || '00:00');
114
- }
115
- else if (mode === 'time') {
116
- setStartTime(/^\d{2}:\d{2}$/.test(start) ? start : '00:00');
117
- setEndTime(/^\d{2}:\d{2}$/.test(end) ? end : '00:00');
118
- }
119
- setTimeEnd('start');
120
- }
121
137
  setOpen(!isOpen);
122
138
  };
123
139
  const close = () => {
@@ -132,15 +148,13 @@ export function KptDateRange({ value, defaultValue = EMPTY_RANGE, onValueChange,
132
148
  triggerRef.current?.focus();
133
149
  };
134
150
  // Fokus po renderze. Panel w nakładce jest niewidoczny do pierwszego pomiaru — stąd rAF.
151
+ const focusActive = useEffectEvent(() => focusCalendarActive(panelRef.current, focusIso));
135
152
  useEffect(() => {
136
- if (!isOpen || !wantFocus.current)
153
+ if (!isOpen || focusRequest === 0)
137
154
  return;
138
- const frame = requestAnimationFrame(() => {
139
- wantFocus.current = false;
140
- focusCalendarActive(panelRef.current, focusIso);
141
- });
155
+ const frame = requestAnimationFrame(() => focusActive());
142
156
  return () => cancelAnimationFrame(frame);
143
- });
157
+ }, [focusRequest, isOpen]);
144
158
  /** Przesuwa okno paneli tak, żeby miesiąc dnia `iso` był widoczny — z najmniejszym skokiem. */
145
159
  const revealMonth = (iso) => {
146
160
  const target = +iso.slice(0, 4) * 12 + (+iso.slice(5, 7) - 1);
@@ -154,19 +168,24 @@ export function KptDateRange({ value, defaultValue = EMPTY_RANGE, onValueChange,
154
168
  };
155
169
  const onDayKeyDown = (event) => {
156
170
  const rtl = !!panelRef.current && getComputedStyle(panelRef.current).direction === 'rtl';
157
- const next = moveFocusDate(focusIso, event.key, { shift: event.shiftKey, rtl, minDate, maxDate });
171
+ const next = moveFocusDate(focusIso, event.key, { shift: event.shiftKey, rtl, minDate, maxDate, firstDay });
158
172
  if (next === null)
159
173
  return;
160
174
  event.preventDefault();
161
175
  setFocusIso(next);
162
176
  revealMonth(next);
163
- wantFocus.current = true;
177
+ requestFocus();
164
178
  };
165
179
  /** Strzałki ‹ ›: fokus zostaje na strzałce, ale przystanek siatki idzie za stroną. */
166
180
  const turnPage = (dir) => {
167
181
  const first = page.y * 12 + page.m + dir;
168
182
  setPage({ y: Math.floor(first / 12), m: ((first % 12) + 12) % 12 });
169
- const moved = moveFocusDate(focusIso, dir === 1 ? 'PageDown' : 'PageUp', { minDate, maxDate });
183
+ // Przewinięcie ogłasza widoczne miesiące (APG: `aria-live` na nagłówku).
184
+ setAnnouncement(Array.from({ length: panelCount }, (_, i) => {
185
+ const month = first + i;
186
+ return `${months[((month % 12) + 12) % 12]} ${Math.floor(month / 12)}`;
187
+ }).join(' – '));
188
+ const moved = moveFocusDate(focusIso, dir === 1 ? 'PageDown' : 'PageUp', { minDate, maxDate, firstDay });
170
189
  if (moved) {
171
190
  const target = +moved.slice(0, 4) * 12 + (+moved.slice(5, 7) - 1);
172
191
  // Nowe okno zaczyna się od `first` — dzień spoza niego nie dostałby przystanku Tab.
@@ -194,6 +213,7 @@ export function KptDateRange({ value, defaultValue = EMPTY_RANGE, onValueChange,
194
213
  hideOutside: multi,
195
214
  minDate,
196
215
  maxDate,
216
+ firstDay,
197
217
  }),
198
218
  };
199
219
  });
@@ -232,17 +252,20 @@ export function KptDateRange({ value, defaultValue = EMPTY_RANGE, onValueChange,
232
252
  };
233
253
  const clear = (event) => {
234
254
  event.stopPropagation();
255
+ if (readOnly)
256
+ return;
235
257
  setCurrent(EMPTY_RANGE);
236
258
  setStartDate('');
237
259
  setEndDate('');
260
+ onTouch?.();
238
261
  };
239
262
  const clockValue = timeEnd === 'start' ? startTime : endTime;
240
263
  const setClockValue = timeEnd === 'start' ? setStartTime : setEndTime;
241
264
  return (_jsxs("div", { className: cn('kpt-date-range-host', className), ...rest, children: [_jsxs("div", { ref: (el) => {
242
265
  triggerRef.current = el;
243
266
  setAnchor(el);
244
- }, className: cn('kpt-date-range', isOpen && 'kpt-date-range--open', disabled && 'kpt-date-range--disabled', showInvalid && 'kpt-date-range--invalid'), role: "button", tabIndex: 0, "aria-haspopup": "dialog", "aria-expanded": isOpen, "aria-controls": isOpen ? panelId : undefined, id: field.id, "aria-labelledby": ariaLabel ? undefined : field.labelledBy, "aria-describedby": field.describedBy, "aria-disabled": disabled || undefined, "aria-invalid": showInvalid || undefined, "aria-label": ariaLabel ?? undefined, onClick: toggleOpen, onKeyDown: (event) => {
245
- if (disabled)
267
+ }, className: cn('kpt-date-range', isOpen && 'kpt-date-range--open', disabled && 'kpt-date-range--disabled', readOnly && 'kpt-date-range--readonly', showInvalid && 'kpt-date-range--invalid'), role: "combobox", tabIndex: 0, "aria-keyshortcuts": "Delete Backspace", "aria-required": required || undefined, "aria-readonly": readOnly || undefined, "aria-haspopup": "dialog", "aria-expanded": isOpen, "aria-controls": isOpen ? panelId : undefined, id: field.id, "aria-labelledby": ariaLabel ? undefined : field.labelledBy, "aria-describedby": field.describedBy, "aria-disabled": disabled || undefined, "aria-invalid": showInvalid || undefined, "aria-label": ariaLabel ?? undefined, onClick: toggleOpen, onKeyDown: (event) => {
268
+ if (disabled || readOnly)
246
269
  return;
247
270
  if (event.key === 'Enter' || event.key === ' ' || event.key === 'ArrowDown') {
248
271
  event.preventDefault();
@@ -255,7 +278,7 @@ export function KptDateRange({ value, defaultValue = EMPTY_RANGE, onValueChange,
255
278
  }
256
279
  },
257
280
  // Przy otwartym panelu fokus idzie do kalendarza — „dotknięcie” wyśle dopiero zamknięcie.
258
- onBlur: () => !isOpen && onTouch?.(), children: [display ? (_jsx("span", { className: "kpt-date-range__value", children: display })) : (_jsx("span", { className: "kpt-date-range__placeholder", children: placeholder ?? t('dateRange.placeholder') })), _jsx("span", { className: "kpt-date-range__spacer" }), display ? (_jsx("button", { type: "button", className: "kpt-date-range__clear", tabIndex: -1, onClick: clear, "aria-label": t('common.clear'), children: _jsx(KptIcon, { name: "x" }) })) : null, _jsx(KptIcon, { className: "kpt-date-range__icon", name: "calendar" })] }), _jsx(KptOverlay, { open: isOpen, anchor: anchor, onClose: close, offset: 4, children: _jsxs("div", { ref: panelRef, id: panelId, role: "dialog", "aria-label": t('dateRange.dialog'), className: cn('kpt-calendar', mode === 'time' && 'kpt-calendar--clock', mode !== 'time' && 'kpt-calendar--range', multi && mode !== 'time' && 'kpt-calendar--multi'), onKeyDown: (event) => {
281
+ onBlur: () => !isOpen && onTouch?.(), children: [display ? (_jsx("span", { className: "kpt-date-range__value", children: display })) : (_jsx("span", { className: "kpt-date-range__placeholder", children: placeholder ?? t('dateRange.placeholder') })), _jsx("span", { className: "kpt-date-range__spacer" }), display && !readOnly ? (_jsx("button", { type: "button", className: "kpt-date-range__clear", tabIndex: -1, onClick: clear, "aria-label": t('common.clear'), children: _jsx(KptIcon, { name: "x" }) })) : null, _jsx(KptIcon, { className: "kpt-date-range__icon", name: "calendar" })] }), _jsx(KptOverlay, { open: isOpen, anchor: anchor, onClose: close, offset: 4, children: _jsxs("div", { ref: panelRef, id: panelId, role: "dialog", "aria-label": t('dateRange.dialog'), className: cn('kpt-calendar', mode === 'time' && 'kpt-calendar--clock', mode !== 'time' && 'kpt-calendar--range', multi && mode !== 'time' && 'kpt-calendar--multi'), onKeyDown: (event) => {
259
282
  if (event.key === 'Escape') {
260
283
  event.preventDefault();
261
284
  event.stopPropagation();
@@ -5,7 +5,7 @@ Import: `import { KptDateRange, type KptDateRangeValue } from '@konce-pt/react';
5
5
 
6
6
  ## DOM
7
7
  <div class="kpt-date-range-host">
8
- <div class="kpt-date-range" role="button" tabindex="0">
8
+ <div class="kpt-date-range" role="combobox" tabindex="0" aria-haspopup="dialog">
9
9
  <span class="kpt-date-range__value">08.09.2026 – 14.09.2026</span>
10
10
  …clear, icon…
11
11
  </div>
@@ -19,7 +19,7 @@ Import: `import { KptDateRange, type KptDateRangeValue } from '@konce-pt/react';
19
19
  - `visibleMonths`: number (default 1, clamped to 1–3) — months side by side; ignored in 'time' mode
20
20
  - `hourFormat`: 12 | 24 (default 24); `minuteStep`: number (default 1)
21
21
  - `placeholder` — without one it falls back to `dateRange.placeholder`
22
- - `displayFormat`: `'pl'` (default) | `'iso'`
22
+ - `displayFormat`: `'pl'` (default) | `'iso'` | `'locale'`
23
23
  - `minDate`, `maxDate`: 'YYYY-MM-DD' bounds (inclusive); days outside are disabled
24
24
  - `ariaLabel` — the accessible name when no `<label for>` describes the field
25
25
  - `disabled`, `invalid`, `touched`
@@ -60,6 +60,20 @@ in Angular that came for free from two separate template branches.
60
60
  Day and month names come from `Intl`; "From/To", the navigation, "Done" and the placeholder come
61
61
  from the `dateRange`, `datepicker` and `common` namespaces.
62
62
 
63
+ ## Week start, display format and field state
64
+ - The week starts by the active language (`Intl.Locale#getWeekInfo`, with a fallback list of regions):
65
+ en / en-US on Sunday, pl, de, en-GB on Monday. `firstDayOfWeek` (1 = Monday … 7 = Sunday) overrides it;
66
+ the headers, the grid and `Home`/`End` follow it.
67
+ - `displayFormat`: `'pl'` (DD.MM.YYYY, default), `'iso'` (YYYY-MM-DD) or `'locale'` — order and separators
68
+ from `Intl` (en-US: MM/DD/YYYY). The placeholder follows the format and its letters follow the language
69
+ (EN `YYYY`, PL `RRRR`, the `datepicker.yearToken`/`monthToken`/`dayToken` keys). AM/PM come from `Intl`.
70
+ - The field is `role="combobox"` with `aria-haspopup="dialog"` (the APG select-only combobox), so it carries
71
+ `aria-required` (`required`), `aria-readonly` (`readOnly` — the value shows, the panel does not open,
72
+ there is no clear button) and `aria-invalid`.
73
+ - Turning the page with ‹ › is announced (`aria-live="polite"`); the title buttons are named
74
+ "October, Choose month" — the visible text first (WCAG 2.5.3). Clearing the value counts as a touch.
75
+ - "Today" is computed on every open, so a panel opened after midnight marks the right day.
76
+
63
77
  ## Label and description
64
78
  `id` goes to the focusable element, not the host; without one it comes from `KptFormField` or is
65
79
  generated. A `div` with a role is not named by `label[for]`, so inside `KptFormField` it gets
@@ -71,7 +85,7 @@ The field `--kpt-form-field-*`; the calendar `--kpt-color-surface-raised`, the r
71
85
  `--kpt-color-primary`.
72
86
 
73
87
  ## Accessibility
74
- The trigger is `role="button"` with `aria-haspopup="dialog"`, `aria-expanded` and `aria-controls`;
88
+ The trigger is `role="combobox"` with `aria-haspopup="dialog"`, `aria-expanded` and `aria-controls`;
75
89
  the panel is a named `role="dialog"`; the days are real buttons named with the date and their range
76
90
  role, and the time ends are switched with a two-button toggle rather than a custom control. The
77
91
  keyboard is described above.
@@ -1,5 +1,5 @@
1
1
  import { type ComponentPropsWithRef } from 'react';
2
- import { type KptCalendarView, type KptDateSelectionMode } from '@konce-pt/datetime';
2
+ import { type KptCalendarView, type KptDateDisplayFormat, type KptDateSelectionMode, type KptFirstDayOfWeek } from '@konce-pt/datetime';
3
3
  import { type KptI18nOverrides } from '../i18n/i18n-context.tsx';
4
4
  export type { KptDateSelectionMode as KptDatepickerMode, KptCalendarView as KptDatepickerView };
5
5
  export interface KptDatepickerProps extends Omit<ComponentPropsWithRef<'div'>, 'onChange' | 'defaultValue'>, KptI18nOverrides {
@@ -15,7 +15,13 @@ export interface KptDatepickerProps extends Omit<ComponentPropsWithRef<'div'>, '
15
15
  placeholder?: string;
16
16
  /** Prezentacja miesiąca w trybie 'month' i w etykietach: słownie czy liczbą. */
17
17
  monthDisplay?: 'long' | 'number';
18
- displayFormat?: 'pl' | 'iso';
18
+ /**
19
+ * Zapis daty: `'pl'` (DD.MM.RRRR, domyślnie), `'iso'` albo `'locale'` — kolejność i separatory
20
+ * z `Intl` dla aktywnego języka. Placeholder idzie za formatem, a jego litery za językiem.
21
+ */
22
+ displayFormat?: KptDateDisplayFormat;
23
+ /** Pierwszy dzień tygodnia (1 = poniedziałek … 7 = niedziela); `null` = wg aktywnego języka. */
24
+ firstDayOfWeek?: KptFirstDayOfWeek | null;
19
25
  hourFormat?: 12 | 24;
20
26
  minuteStep?: number;
21
27
  /** Stan panelu — pozwala otworzyć kalendarz z zewnątrz i wiedzieć, kiedy wybór się skończył. */
@@ -26,6 +32,10 @@ export interface KptDatepickerProps extends Omit<ComponentPropsWithRef<'div'>, '
26
32
  /** Id pola (nie hosta); bez niego generowane, w `KptFormField` — z wrappera. */
27
33
  id?: string;
28
34
  disabled?: boolean;
35
+ /** Tylko do odczytu — wartość widać, panel się nie otwiera (`aria-readonly`). */
36
+ readOnly?: boolean;
37
+ /** Pole wymagane — `aria-required`. */
38
+ required?: boolean;
29
39
  invalid?: boolean;
30
40
  touched?: boolean;
31
41
  onTouch?: () => void;
@@ -41,8 +51,12 @@ export interface KptDatepickerProps extends Omit<ComponentPropsWithRef<'div'>, '
41
51
  * w panelu, `Escape` zamyka go, a wybór i zamknięcie oddają fokus polu. `Delete`/`Backspace` na polu
42
52
  * czyści wartość.
43
53
  *
54
+ * Pole to `role="combobox"` z `aria-haspopup="dialog"`, więc niesie `aria-required`,
55
+ * `aria-readonly` i `aria-invalid`. Tydzień zaczyna się wg aktywnego języka, chyba że podano
56
+ * `firstDayOfWeek`; przewinięcie strony strzałką ‹ › jest ogłaszane.
57
+ *
44
58
  * @example
45
59
  * <KptDatepicker value={date} onValueChange={setDate} startView="decade" minDate="1920-01-01" />
46
60
  * <KptDatepicker selectionMode="datetime" hourFormat={12} value={at} onValueChange={setAt} />
47
61
  */
48
- export declare function KptDatepicker({ value, defaultValue, onValueChange, selectionMode, startView, minDate, maxDate, placeholder, monthDisplay, displayFormat, hourFormat, minuteStep, open, onOpenChange, ariaLabel, id, disabled, invalid, touched, onTouch, locale, dictionary, className, ...rest }: Readonly<KptDatepickerProps>): import("react").JSX.Element;
62
+ export declare function KptDatepicker({ value, defaultValue, onValueChange, selectionMode, startView, minDate, maxDate, placeholder, monthDisplay, displayFormat, firstDayOfWeek, hourFormat, minuteStep, open, onOpenChange, ariaLabel, id, disabled, readOnly, required, invalid, touched, onTouch, locale, dictionary, className, ...rest }: Readonly<KptDatepickerProps>): import("react").JSX.Element;