@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.
- package/README.md +8 -2
- package/dist/accordion/accordion.js +11 -10
- package/dist/alert/alert.js +1 -0
- package/dist/alert/llms.txt +1 -0
- package/dist/app-shell/app-shell.js +12 -5
- package/dist/app-shell/llms.txt +5 -4
- package/dist/auth/auth.js +17 -8
- package/dist/auth/llms.txt +3 -2
- package/dist/autocomplete/autocomplete.d.ts +1 -1
- package/dist/autocomplete/autocomplete.js +12 -4
- package/dist/autocomplete/llms.txt +3 -0
- package/dist/avatar/avatar.js +1 -0
- package/dist/avatar-group/avatar-group.js +1 -0
- package/dist/backdrop/backdrop.d.ts +0 -19
- package/dist/backdrop/backdrop.js +18 -13
- package/dist/bottom-sheet/bottom-sheet.d.ts +6 -1
- package/dist/bottom-sheet/bottom-sheet.js +3 -2
- package/dist/bottom-sheet/llms.txt +3 -0
- package/dist/breadcrumb/breadcrumb.d.ts +7 -1
- package/dist/breadcrumb/breadcrumb.js +10 -2
- package/dist/breadcrumb/llms.txt +8 -0
- package/dist/button/button.js +2 -1
- package/dist/button-group/button-group.d.ts +14 -1
- package/dist/button-group/button-group.js +30 -4
- package/dist/button-group/llms.txt +9 -6
- package/dist/card/card.js +4 -3
- package/dist/carousel/carousel.js +4 -1
- package/dist/charts/chart-legend.js +1 -0
- package/dist/charts/chart.d.ts +3 -2
- package/dist/charts/chart.js +12 -4
- package/dist/charts/llms.txt +6 -3
- package/dist/chat/chat.js +39 -9
- package/dist/chat/llms.txt +9 -0
- package/dist/chat-composer/chat-composer.d.ts +5 -2
- package/dist/chat-composer/chat-composer.js +10 -1
- package/dist/chat-dock/chat-dock.js +1 -0
- package/dist/chat-message/chat-message.js +2 -1
- package/dist/chat-typing/chat-typing.js +1 -0
- package/dist/checkbox/checkbox.d.ts +1 -1
- package/dist/checkbox/checkbox.js +9 -2
- package/dist/checkbox/llms.txt +3 -0
- package/dist/chip/chip.js +1 -0
- package/dist/chips-input/chips-input.d.ts +3 -1
- package/dist/chips-input/chips-input.js +14 -5
- package/dist/chips-input/llms.txt +3 -0
- package/dist/clock/clock.d.ts +9 -4
- package/dist/clock/clock.js +35 -5
- package/dist/clock/llms.txt +6 -2
- package/dist/color-picker/color-picker.d.ts +5 -1
- package/dist/color-picker/color-picker.js +36 -17
- package/dist/color-picker/llms.txt +3 -0
- package/dist/confirm/confirm-container.js +1 -0
- package/dist/confirm/confirm-store.js +6 -0
- package/dist/confirm/llms.txt +2 -1
- package/dist/context-menu/context-menu.js +35 -12
- package/dist/context-menu/llms.txt +3 -0
- package/dist/data-table/data-table.d.ts +12 -1
- package/dist/data-table/data-table.js +113 -51
- package/dist/data-table/export-csv.d.ts +2 -2
- package/dist/data-table/export-csv.js +7 -9
- package/dist/data-table/llms.txt +19 -4
- package/dist/data-view/data-view.js +1 -0
- package/dist/date-range/date-range.d.ts +16 -4
- package/dist/date-range/date-range.js +69 -46
- package/dist/date-range/llms.txt +17 -3
- package/dist/datepicker/datepicker.d.ts +17 -3
- package/dist/datepicker/datepicker.js +86 -54
- package/dist/datepicker/llms.txt +17 -3
- package/dist/dialog/dialog.js +29 -8
- package/dist/dialog/llms.txt +3 -0
- package/dist/divider/divider.d.ts +7 -1
- package/dist/divider/divider.js +3 -2
- package/dist/divider/llms.txt +1 -0
- package/dist/drawer/drawer.d.ts +6 -1
- package/dist/drawer/drawer.js +3 -2
- package/dist/drawer/llms.txt +3 -0
- package/dist/fieldset/fieldset.js +4 -1
- package/dist/file-upload/file-upload.d.ts +7 -1
- package/dist/file-upload/file-upload.js +18 -7
- package/dist/file-upload/llms.txt +6 -3
- package/dist/form-field/form-field.js +1 -0
- package/dist/galleria/galleria.js +4 -1
- package/dist/i18n/i18n-context.d.ts +3 -3
- package/dist/i18n/i18n-context.js +25 -3
- package/dist/icon/icon.js +4 -1
- package/dist/image/image.js +4 -4
- package/dist/input/input.js +1 -0
- package/dist/input-mask/input-mask.d.ts +7 -2
- package/dist/input-mask/input-mask.js +15 -41
- package/dist/input-mask/llms.txt +3 -0
- package/dist/input-number/input-number.d.ts +3 -1
- package/dist/input-number/input-number.js +15 -11
- package/dist/input-number/llms.txt +5 -2
- package/dist/input-otp/input-otp.d.ts +16 -3
- package/dist/input-otp/input-otp.js +43 -19
- package/dist/input-otp/llms.txt +11 -0
- package/dist/internal/mask.d.ts +14 -0
- package/dist/internal/mask.js +62 -0
- package/dist/internal/number-group.d.ts +22 -0
- package/dist/internal/number-group.js +53 -0
- package/dist/internal/option-key.d.ts +6 -0
- package/dist/internal/option-key.js +8 -0
- package/dist/internal/overlay.js +37 -7
- package/dist/internal/password-strength.d.ts +6 -0
- package/dist/internal/password-strength.js +74 -0
- package/dist/internal/scroll-lock.d.ts +6 -0
- package/dist/internal/scroll-lock.js +35 -0
- package/dist/internal/slot-children.d.ts +13 -0
- package/dist/internal/slot-children.js +31 -0
- package/dist/internal/step.d.ts +12 -0
- package/dist/internal/step.js +31 -0
- package/dist/internal/tween.js +2 -3
- package/dist/internal/use-controllable-state.d.ts +5 -0
- package/dist/internal/use-controllable-state.js +25 -4
- package/dist/internal/use-modal-focus.d.ts +1 -0
- package/dist/internal/use-modal-focus.js +12 -1
- package/dist/knob/knob.d.ts +5 -2
- package/dist/knob/knob.js +15 -14
- package/dist/knob/llms.txt +4 -2
- package/dist/listbox/listbox.d.ts +3 -1
- package/dist/listbox/listbox.js +30 -10
- package/dist/listbox/llms.txt +3 -0
- package/dist/map/llms.txt +5 -1
- package/dist/map/map-create-dialog.js +30 -22
- package/dist/map/map-hours-field.js +1 -0
- package/dist/map/map-image-field.js +2 -1
- package/dist/map/map-legend.js +1 -0
- package/dist/map/map-poi-card.js +2 -1
- package/dist/map/map-search.js +54 -30
- package/dist/map/map.d.ts +6 -1
- package/dist/map/map.js +62 -34
- package/dist/megamenu/llms.txt +11 -0
- package/dist/megamenu/megamenu.d.ts +7 -1
- package/dist/megamenu/megamenu.js +15 -6
- package/dist/menu/menu-rows.js +1 -0
- package/dist/menu/menu.js +10 -3
- package/dist/menubar/llms.txt +11 -0
- package/dist/menubar/menubar.d.ts +7 -1
- package/dist/menubar/menubar.js +15 -3
- package/dist/meter-group/meter-group.js +1 -0
- package/dist/order-list/llms.txt +3 -0
- package/dist/order-list/order-list.js +21 -6
- package/dist/paginator/paginator.js +7 -4
- package/dist/panel/panel.js +5 -3
- package/dist/password/llms.txt +6 -2
- package/dist/password/password.js +4 -14
- package/dist/peek/llms.txt +1 -1
- package/dist/peek/peek.js +37 -15
- package/dist/pick-list/llms.txt +3 -0
- package/dist/pick-list/pick-list.js +19 -3
- package/dist/popover/llms.txt +3 -0
- package/dist/popover/popover.d.ts +3 -1
- package/dist/popover/popover.js +10 -3
- package/dist/progress/progress.js +1 -0
- package/dist/public-api.d.ts +1 -1
- package/dist/radio-group/llms.txt +3 -0
- package/dist/radio-group/radio-group.d.ts +3 -1
- package/dist/radio-group/radio-group.js +13 -2
- package/dist/rating/rating.js +1 -0
- package/dist/rich-text/llms.txt +14 -0
- package/dist/rich-text/rich-text.d.ts +3 -9
- package/dist/rich-text/rich-text.js +241 -96
- package/dist/rich-text/sanitize.d.ts +9 -0
- package/dist/rich-text/sanitize.js +77 -29
- package/dist/roadmap/llms.txt +9 -3
- package/dist/roadmap/roadmap.js +88 -11
- package/dist/scroll-top/scroll-top.js +1 -0
- package/dist/select/llms.txt +3 -0
- package/dist/select/select.d.ts +3 -1
- package/dist/select/select.js +46 -32
- package/dist/shared/types.d.ts +21 -0
- package/dist/shared/types.js +9 -1
- package/dist/sidenav/sidenav.d.ts +1 -2
- package/dist/sidenav/sidenav.js +4 -3
- package/dist/slider/llms.txt +3 -0
- package/dist/slider/slider.d.ts +3 -1
- package/dist/slider/slider.js +15 -4
- package/dist/speed-dial/speed-dial.js +2 -1
- package/dist/spinner/spinner.js +1 -0
- package/dist/split-button/split-button.js +10 -3
- package/dist/splitter/splitter.js +23 -8
- package/dist/stepper/llms.txt +3 -0
- package/dist/stepper/stepper.js +9 -5
- package/dist/switch/llms.txt +3 -0
- package/dist/switch/switch.d.ts +1 -1
- package/dist/switch/switch.js +9 -2
- package/dist/switch-group/llms.txt +3 -0
- package/dist/switch-group/switch-group.d.ts +3 -1
- package/dist/switch-group/switch-group.js +5 -3
- package/dist/tabs/llms.txt +3 -0
- package/dist/tabs/tabs.js +30 -14
- package/dist/textarea/textarea.js +1 -0
- package/dist/toast/llms.txt +3 -0
- package/dist/toast/toast-container.js +1 -0
- package/dist/toast/toast-store.d.ts +5 -1
- package/dist/toast/toast-store.js +1 -1
- package/dist/tooltip/llms.txt +5 -3
- package/dist/tooltip/tooltip.d.ts +3 -2
- package/dist/tooltip/tooltip.js +90 -31
- package/dist/tree/llms.txt +1 -0
- package/dist/tree/tree.d.ts +6 -1
- package/dist/tree/tree.js +14 -7
- 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 { KPT_CALENDAR_LEVELS, buildDayGrid, buildDecadeCells, buildMonthCells, buildYearCells, canPageBack, canPageForward, centuryStart, commitLevelFor, decadeStart,
|
|
3
|
+
import { useEffect, useEffectEvent, useRef, useState, } from 'react';
|
|
4
|
+
import { KPT_CALENDAR_LEVELS, buildDayGrid, buildDecadeCells, buildMonthCells, buildYearCells, canPageBack, canPageForward, centuryStart, commitLevelFor, datePlaceholder, dayPeriodLabels, decadeStart, displayDate, displayMonthYear, formatTime, initialFocusDate, kptFirstDayOfWeek, orderWeekdays, initialPage, moveCellIndex, moveFocusDate, pad, pageBy, parseDate, parseDateTime, parseTime, resolveStartView, selectedYearMonth, 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";
|
|
@@ -23,11 +24,15 @@ import { focusCalendarActive, trapTab } from "../internal/calendar-focus.js";
|
|
|
23
24
|
* w panelu, `Escape` zamyka go, a wybór i zamknięcie oddają fokus polu. `Delete`/`Backspace` na polu
|
|
24
25
|
* czyści wartość.
|
|
25
26
|
*
|
|
27
|
+
* Pole to `role="combobox"` z `aria-haspopup="dialog"`, więc niesie `aria-required`,
|
|
28
|
+
* `aria-readonly` i `aria-invalid`. Tydzień zaczyna się wg aktywnego języka, chyba że podano
|
|
29
|
+
* `firstDayOfWeek`; przewinięcie strony strzałką ‹ › jest ogłaszane.
|
|
30
|
+
*
|
|
26
31
|
* @example
|
|
27
32
|
* <KptDatepicker value={date} onValueChange={setDate} startView="decade" minDate="1920-01-01" />
|
|
28
33
|
* <KptDatepicker selectionMode="datetime" hourFormat={12} value={at} onValueChange={setAt} />
|
|
29
34
|
*/
|
|
30
|
-
export function KptDatepicker({ value, defaultValue = '', onValueChange, selectionMode = 'date', startView = null, minDate = null, maxDate = null, placeholder = '', monthDisplay = 'long', displayFormat = 'pl', hourFormat = 24, minuteStep = 1, open, onOpenChange, ariaLabel = null, id, disabled = false, invalid = false, touched, onTouch, locale, dictionary, className, ...rest }) {
|
|
35
|
+
export function KptDatepicker({ value, defaultValue = '', onValueChange, selectionMode = 'date', startView = null, minDate = null, maxDate = null, placeholder = '', monthDisplay = 'long', displayFormat = 'pl', firstDayOfWeek = null, hourFormat = 24, minuteStep = 1, open, onOpenChange, ariaLabel = null, id, disabled = false, readOnly = false, required = false, invalid = false, touched, onTouch, locale, dictionary, className, ...rest }) {
|
|
31
36
|
const { t, locale: activeLocale } = useKptMessages({ locale, dictionary });
|
|
32
37
|
/** `div` z rolą nie dostaje nazwy z `label[for]` — stąd `aria-labelledby` na etykietę wrappera. */
|
|
33
38
|
const field = useKptFieldA11y(id, 'kpt-datepicker');
|
|
@@ -35,8 +40,13 @@ export function KptDatepicker({ value, defaultValue = '', onValueChange, selecti
|
|
|
35
40
|
const [isOpen, setOpen] = useControllableState({ value: open, defaultValue: false, onChange: onOpenChange });
|
|
36
41
|
const [anchor, setAnchor] = useState(null);
|
|
37
42
|
const triggerRef = useRef(null);
|
|
38
|
-
|
|
43
|
+
/** „Dziś” liczone przy każdym otwarciu — panel otwarty po północy nie pokazuje wczorajszego dnia. */
|
|
44
|
+
const [today, setToday] = useState(() => new Date());
|
|
39
45
|
const todayIso = toIso(today);
|
|
46
|
+
const firstDay = firstDayOfWeek ?? kptFirstDayOfWeek(activeLocale);
|
|
47
|
+
const periods = dayPeriodLabels(activeLocale);
|
|
48
|
+
/** Ogłoszenie strony kalendarza po przewinięciu strzałką ‹ ›. */
|
|
49
|
+
const [pageAnnouncement, setPageAnnouncement] = useState('');
|
|
40
50
|
const showInvalid = invalid && (touched ?? true);
|
|
41
51
|
const [page, setPage] = useState(() => initialPage(current, selectionMode, today));
|
|
42
52
|
const [panelView, setPanelView] = useState(() => resolveStartView(selectionMode, startView));
|
|
@@ -49,17 +59,30 @@ export function KptDatepicker({ value, defaultValue = '', onValueChange, selecti
|
|
|
49
59
|
const [focusCell, setFocusCell] = useState(0);
|
|
50
60
|
const panelId = useKptId(undefined, 'kpt-datepicker-panel');
|
|
51
61
|
const panelRef = useRef(null);
|
|
52
|
-
/**
|
|
53
|
-
|
|
62
|
+
/**
|
|
63
|
+
* Prośba o przeniesienie fokusu po najbliższym renderze (otwarcie, ruch klawiszem, zmiana
|
|
64
|
+
* poziomu) — licznik w stanie, nie ref zapisywany w renderze.
|
|
65
|
+
*/
|
|
66
|
+
const [focusRequest, setFocusRequest] = useState(0);
|
|
67
|
+
const requestFocus = () => setFocusRequest((n) => n + 1);
|
|
54
68
|
const months = monthNames(activeLocale);
|
|
55
|
-
const weekdays = weekdayNames(activeLocale);
|
|
69
|
+
const weekdays = orderWeekdays(weekdayNames(activeLocale), firstDay);
|
|
56
70
|
const commitLevel = commitLevelFor(selectionMode);
|
|
57
|
-
// Panel można otworzyć także z zewnątrz (`open`), nie tylko klikiem w trigger — widok
|
|
58
|
-
//
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
71
|
+
// Panel można otworzyć także z zewnątrz (`open`), nie tylko klikiem w trigger — widok i bufor
|
|
72
|
+
// czasu ustawiamy więc przy każdym przejściu w stan otwarty. Wzorzec „poprzednia wartość
|
|
73
|
+
// w stanie” z react.dev: stan liczy się w tym samym renderze, bez efektu i bez klatki ze starym
|
|
74
|
+
// widokiem. Późniejsze zmiany wartości w otwartym panelu go nie resetują.
|
|
75
|
+
const [wasOpen, setWasOpen] = useState(isOpen);
|
|
76
|
+
if (wasOpen !== isOpen) {
|
|
77
|
+
setWasOpen(isOpen);
|
|
78
|
+
if (isOpen)
|
|
79
|
+
prepareOpen();
|
|
80
|
+
}
|
|
81
|
+
function prepareOpen() {
|
|
82
|
+
const now = new Date();
|
|
83
|
+
setToday(now);
|
|
84
|
+
setPageAnnouncement('');
|
|
85
|
+
setPage(initialPage(current, selectionMode, now));
|
|
63
86
|
setPanelView(resolveStartView(selectionMode, startView));
|
|
64
87
|
setDtView(selectionMode === 'time' ? 'clock' : 'calendar');
|
|
65
88
|
if (selectionMode === 'time') {
|
|
@@ -73,18 +96,15 @@ export function KptDatepicker({ value, defaultValue = '', onValueChange, selecti
|
|
|
73
96
|
}
|
|
74
97
|
const startLevel = resolveStartView(selectionMode, startView);
|
|
75
98
|
const selected = selectionMode === 'datetime' ? (parseDateTime(current)?.date ?? '') : current;
|
|
76
|
-
const focus = initialFocusDate(selected,
|
|
99
|
+
const focus = initialFocusDate(selected, toIso(now), minDate, maxDate);
|
|
77
100
|
setFocusIso(focus);
|
|
78
101
|
if (startLevel === 'day' && selectionMode !== 'time') {
|
|
79
102
|
const date = parseDate(focus);
|
|
80
103
|
setPage({ y: date.getFullYear(), m: date.getMonth() });
|
|
81
104
|
}
|
|
82
105
|
setFocusCell(-1); // -1 = domyślna komórka nowej siatki, wyliczana przy renderze
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
// zmiany wartości w otwartym panelu nie mogą go zresetować pod palcami użytkownika.
|
|
86
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
87
|
-
}, [isOpen]);
|
|
106
|
+
requestFocus();
|
|
107
|
+
}
|
|
88
108
|
const showClock = selectionMode === 'time' || (selectionMode === 'datetime' && dtView === 'clock');
|
|
89
109
|
const hasFooter = selectionMode === 'time' || selectionMode === 'datetime';
|
|
90
110
|
const hasNav = selectionMode !== 'month';
|
|
@@ -100,48 +120,42 @@ export function KptDatepicker({ value, defaultValue = '', onValueChange, selecti
|
|
|
100
120
|
triggerRef.current?.focus();
|
|
101
121
|
};
|
|
102
122
|
// Fokus po renderze. Panel w nakładce jest niewidoczny do pierwszego pomiaru — stąd rAF.
|
|
123
|
+
// `focusIso` czytamy przez zdarzenie efektu: liczy się prośba, nie każda zmiana dnia.
|
|
124
|
+
const focusActive = useEffectEvent(() => focusCalendarActive(panelRef.current, focusIso));
|
|
103
125
|
useEffect(() => {
|
|
104
|
-
if (!isOpen ||
|
|
126
|
+
if (!isOpen || focusRequest === 0)
|
|
105
127
|
return;
|
|
106
|
-
const frame = requestAnimationFrame(() =>
|
|
107
|
-
wantFocus.current = false;
|
|
108
|
-
focusCalendarActive(panelRef.current, focusIso);
|
|
109
|
-
});
|
|
128
|
+
const frame = requestAnimationFrame(() => focusActive());
|
|
110
129
|
return () => cancelAnimationFrame(frame);
|
|
111
|
-
});
|
|
130
|
+
}, [focusRequest, isOpen]);
|
|
112
131
|
// ── Prezentacja w triggerze ──
|
|
113
132
|
const effectivePlaceholder = (() => {
|
|
114
133
|
if (placeholder)
|
|
115
134
|
return placeholder;
|
|
116
|
-
const
|
|
135
|
+
const tokens = { year: t('datepicker.yearToken'), month: t('datepicker.monthToken'), day: t('datepicker.dayToken') };
|
|
117
136
|
const time = hourFormat === 12 ? '--:-- --' : '--:--';
|
|
118
137
|
switch (selectionMode) {
|
|
119
138
|
case 'monthYear':
|
|
120
|
-
return
|
|
139
|
+
return datePlaceholder(displayFormat, tokens, 'monthYear', activeLocale);
|
|
121
140
|
case 'year':
|
|
122
|
-
return
|
|
141
|
+
return tokens.year;
|
|
123
142
|
case 'month':
|
|
124
|
-
return monthDisplay === 'number' ?
|
|
143
|
+
return monthDisplay === 'number' ? tokens.month : t('datepicker.month');
|
|
125
144
|
case 'time':
|
|
126
145
|
return time;
|
|
127
146
|
case 'datetime':
|
|
128
|
-
return `${
|
|
147
|
+
return `${datePlaceholder(displayFormat, tokens, 'date', activeLocale)} ${time}`;
|
|
129
148
|
default:
|
|
130
|
-
return
|
|
149
|
+
return datePlaceholder(displayFormat, tokens, 'date', activeLocale);
|
|
131
150
|
}
|
|
132
151
|
})();
|
|
133
152
|
/** Wartość niepasująca do formatu trybu daje pusty tekst — trigger nie pokazuje śmieci. */
|
|
134
153
|
const display = (() => {
|
|
135
154
|
if (!current)
|
|
136
155
|
return '';
|
|
137
|
-
const iso = displayFormat === 'iso';
|
|
138
156
|
switch (selectionMode) {
|
|
139
|
-
case 'monthYear':
|
|
140
|
-
|
|
141
|
-
if (!match)
|
|
142
|
-
return '';
|
|
143
|
-
return iso ? current : `${match[2]}.${match[1]}`;
|
|
144
|
-
}
|
|
157
|
+
case 'monthYear':
|
|
158
|
+
return displayMonthYear(current, displayFormat, activeLocale);
|
|
145
159
|
case 'year':
|
|
146
160
|
return /^\d{4}$/.test(current) ? current : '';
|
|
147
161
|
case 'month': {
|
|
@@ -152,22 +166,22 @@ export function KptDatepicker({ value, defaultValue = '', onValueChange, selecti
|
|
|
152
166
|
}
|
|
153
167
|
case 'time': {
|
|
154
168
|
const parsed = parseTime(current);
|
|
155
|
-
return parsed ? formatTime(parsed.h, parsed.m, hourFormat) : '';
|
|
169
|
+
return parsed ? formatTime(parsed.h, parsed.m, hourFormat, periods) : '';
|
|
156
170
|
}
|
|
157
171
|
case 'datetime': {
|
|
158
172
|
const parsed = parseDateTime(current);
|
|
159
173
|
if (!parsed)
|
|
160
174
|
return '';
|
|
161
|
-
const sep = iso ? 'T' : ' ';
|
|
162
|
-
return `${
|
|
175
|
+
const sep = displayFormat === 'iso' ? 'T' : ' ';
|
|
176
|
+
return `${displayDate(parsed.date, displayFormat, activeLocale)}${sep}${formatTime(parsed.h, parsed.m, hourFormat, periods)}`;
|
|
163
177
|
}
|
|
164
178
|
default:
|
|
165
|
-
return
|
|
179
|
+
return displayDate(current, displayFormat, activeLocale);
|
|
166
180
|
}
|
|
167
181
|
})();
|
|
168
182
|
// ── Siatki ──
|
|
169
183
|
const selectedDateIso = selectionMode === 'datetime' ? pendingDate : current;
|
|
170
|
-
const weeks = buildDayGrid({ page, selectedIso: selectedDateIso, todayIso, minDate, maxDate });
|
|
184
|
+
const weeks = buildDayGrid({ page, selectedIso: selectedDateIso, todayIso, minDate, maxDate, firstDay });
|
|
171
185
|
const selectedYM = selectedYearMonth(selectionMode === 'datetime' ? pendingDate : current, selectionMode);
|
|
172
186
|
const levelCells = panelView === 'year'
|
|
173
187
|
? buildYearCells({ year: page.y, selected: selectedYM, today, minDate, maxDate })
|
|
@@ -199,7 +213,7 @@ export function KptDatepicker({ value, defaultValue = '', onValueChange, selecti
|
|
|
199
213
|
};
|
|
200
214
|
const isRtl = () => !!panelRef.current && getComputedStyle(panelRef.current).direction === 'rtl';
|
|
201
215
|
const onDayKeyDown = (event) => {
|
|
202
|
-
const next = moveFocusDate(focusIso, event.key, { shift: event.shiftKey, rtl: isRtl(), minDate, maxDate });
|
|
216
|
+
const next = moveFocusDate(focusIso, event.key, { shift: event.shiftKey, rtl: isRtl(), minDate, maxDate, firstDay });
|
|
203
217
|
if (next === null)
|
|
204
218
|
return;
|
|
205
219
|
event.preventDefault();
|
|
@@ -207,7 +221,7 @@ export function KptDatepicker({ value, defaultValue = '', onValueChange, selecti
|
|
|
207
221
|
const date = parseDate(next);
|
|
208
222
|
if (date.getFullYear() !== page.y || date.getMonth() !== page.m)
|
|
209
223
|
setPage({ y: date.getFullYear(), m: date.getMonth() });
|
|
210
|
-
|
|
224
|
+
requestFocus();
|
|
211
225
|
};
|
|
212
226
|
const onCellKeyDown = (event) => {
|
|
213
227
|
const next = moveCellIndex(levelCells.map((c) => c.disabled), activeCell, event.key, 3, isRtl());
|
|
@@ -215,23 +229,24 @@ export function KptDatepicker({ value, defaultValue = '', onValueChange, selecti
|
|
|
215
229
|
return;
|
|
216
230
|
event.preventDefault();
|
|
217
231
|
setFocusCell(next);
|
|
218
|
-
|
|
232
|
+
requestFocus();
|
|
219
233
|
};
|
|
220
234
|
/** Tytuł, który kliknięto, znika razem z poziomem — fokus idzie do nowej siatki. */
|
|
221
235
|
const goTo = (level) => {
|
|
222
236
|
setPanelView(level);
|
|
223
237
|
setFocusCell(-1);
|
|
224
|
-
|
|
238
|
+
requestFocus();
|
|
225
239
|
};
|
|
226
240
|
/** Strzałki ‹ ›: fokus zostaje na strzałce, ale przystanek siatki dni idzie za stroną. */
|
|
227
241
|
const turnPage = (dir) => {
|
|
228
242
|
const next = pageBy(page, panelView, dir);
|
|
229
243
|
setPage(next);
|
|
244
|
+
setPageAnnouncement(pageTitle(next));
|
|
230
245
|
if (panelView === 'day') {
|
|
231
|
-
const moved = moveFocusDate(focusIso, dir === 1 ? 'PageDown' : 'PageUp', { minDate, maxDate });
|
|
246
|
+
const moved = moveFocusDate(focusIso, dir === 1 ? 'PageDown' : 'PageUp', { minDate, maxDate, firstDay });
|
|
232
247
|
// Dzień przycięty do granicy może leżeć poza nową stroną — wtedy pierwszy dozwolony dzień miesiąca.
|
|
233
248
|
const inPage = moved?.startsWith(`${next.y}-${pad(next.m + 1)}`);
|
|
234
|
-
const fallback = buildDayGrid({ page: next, selectedIso: selectedDateIso, todayIso, minDate, maxDate })
|
|
249
|
+
const fallback = buildDayGrid({ page: next, selectedIso: selectedDateIso, todayIso, minDate, maxDate, firstDay })
|
|
235
250
|
.flat()
|
|
236
251
|
.find((c) => c.inMonth && !c.disabled)?.iso;
|
|
237
252
|
const target = inPage ? moved : fallback;
|
|
@@ -242,6 +257,19 @@ export function KptDatepicker({ value, defaultValue = '', onValueChange, selecti
|
|
|
242
257
|
setFocusCell(-1);
|
|
243
258
|
}
|
|
244
259
|
};
|
|
260
|
+
/** Tytuł strony do ogłoszenia — miesiąc z rokiem, rok albo zakres lat. */
|
|
261
|
+
const pageTitle = (target) => {
|
|
262
|
+
switch (panelView) {
|
|
263
|
+
case 'day':
|
|
264
|
+
return `${months[target.m]} ${target.y}`;
|
|
265
|
+
case 'month':
|
|
266
|
+
return String(target.y);
|
|
267
|
+
case 'year':
|
|
268
|
+
return `${decadeStart(target.y)} – ${decadeStart(target.y) + 9}`;
|
|
269
|
+
default:
|
|
270
|
+
return `${centuryStart(target.y)} – ${centuryStart(target.y) + 119}`;
|
|
271
|
+
}
|
|
272
|
+
};
|
|
245
273
|
const navLabel = (dir) => {
|
|
246
274
|
switch (panelView) {
|
|
247
275
|
case 'month':
|
|
@@ -286,7 +314,7 @@ export function KptDatepicker({ value, defaultValue = '', onValueChange, selecti
|
|
|
286
314
|
else {
|
|
287
315
|
setFocusCell(-1);
|
|
288
316
|
}
|
|
289
|
-
|
|
317
|
+
requestFocus();
|
|
290
318
|
};
|
|
291
319
|
const commit = () => {
|
|
292
320
|
const time = timeStr || '00:00';
|
|
@@ -301,15 +329,19 @@ export function KptDatepicker({ value, defaultValue = '', onValueChange, selecti
|
|
|
301
329
|
};
|
|
302
330
|
const clear = (event) => {
|
|
303
331
|
event.stopPropagation();
|
|
332
|
+
if (readOnly)
|
|
333
|
+
return;
|
|
304
334
|
setCurrent('');
|
|
305
335
|
setPendingDate('');
|
|
336
|
+
// Wyczyszczenie to interakcja z polem — błąd „wymagane” ma się pokazać od razu.
|
|
337
|
+
onTouch?.();
|
|
306
338
|
};
|
|
307
339
|
const timeParts = parseTime(timeStr);
|
|
308
340
|
return (_jsxs("div", { className: cn('kpt-datepicker-host', className), ...rest, children: [_jsxs("div", { ref: (el) => {
|
|
309
341
|
triggerRef.current = el;
|
|
310
342
|
setAnchor(el);
|
|
311
|
-
}, className: cn('kpt-datepicker', isOpen && 'kpt-datepicker--open', disabled && 'kpt-datepicker--disabled', showInvalid && 'kpt-datepicker--invalid'), role: "
|
|
312
|
-
if (disabled)
|
|
343
|
+
}, className: cn('kpt-datepicker', isOpen && 'kpt-datepicker--open', disabled && 'kpt-datepicker--disabled', readOnly && 'kpt-datepicker--readonly', showInvalid && 'kpt-datepicker--invalid'), role: "combobox", tabIndex: 0, "aria-haspopup": "dialog", "aria-keyshortcuts": "Delete Backspace", "aria-required": required || undefined, "aria-readonly": readOnly || undefined, "aria-invalid": showInvalid || undefined, "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-label": ariaLabel ?? undefined, onClick: () => !disabled && !readOnly && setOpen(!isOpen), onKeyDown: (event) => {
|
|
344
|
+
if (disabled || readOnly)
|
|
313
345
|
return;
|
|
314
346
|
if (event.key === 'Enter' || event.key === ' ' || event.key === 'ArrowDown') {
|
|
315
347
|
event.preventDefault();
|
|
@@ -322,7 +354,7 @@ export function KptDatepicker({ value, defaultValue = '', onValueChange, selecti
|
|
|
322
354
|
}
|
|
323
355
|
},
|
|
324
356
|
// Przy otwartym panelu fokus idzie do kalendarza — „dotknięcie” wyśle dopiero zamknięcie.
|
|
325
|
-
onBlur: () => !isOpen && onTouch?.(), children: [display ? (_jsx("span", { className: "kpt-datepicker__value", children: display })) : (_jsx("span", { className: "kpt-datepicker__placeholder", children: effectivePlaceholder })), _jsx("span", { className: "kpt-datepicker__spacer" }), display ? (_jsx("button", { type: "button", className: "kpt-datepicker__clear", tabIndex: -1, onClick: clear, "aria-label": t('common.clear'), children: _jsx(KptIcon, { name: "x" }) })) : null, _jsx(KptIcon, { className: "kpt-datepicker__icon", name: selectionMode === 'time' ? 'clock' : 'calendar' })] }), _jsx(KptOverlay, { open: isOpen, anchor: anchor, onClose: close, offset: 4, children: _jsxs("div", { ref: panelRef, id: panelId, role: "dialog", "aria-label": t('datepicker.dialog'), className: cn('kpt-calendar', showClock && 'kpt-calendar--clock'), onKeyDown: (event) => {
|
|
357
|
+
onBlur: () => !isOpen && onTouch?.(), children: [display ? (_jsx("span", { className: "kpt-datepicker__value", children: display })) : (_jsx("span", { className: "kpt-datepicker__placeholder", children: effectivePlaceholder })), _jsx("span", { className: "kpt-datepicker__spacer" }), display && !readOnly ? (_jsx("button", { type: "button", className: "kpt-datepicker__clear", tabIndex: -1, onClick: clear, "aria-label": t('common.clear'), children: _jsx(KptIcon, { name: "x" }) })) : null, _jsx(KptIcon, { className: "kpt-datepicker__icon", name: selectionMode === 'time' ? 'clock' : 'calendar' })] }), _jsx(KptOverlay, { open: isOpen, anchor: anchor, onClose: close, offset: 4, children: _jsxs("div", { ref: panelRef, id: panelId, role: "dialog", "aria-label": t('datepicker.dialog'), className: cn('kpt-calendar', showClock && 'kpt-calendar--clock'), onKeyDown: (event) => {
|
|
326
358
|
if (event.key === 'Escape') {
|
|
327
359
|
event.preventDefault();
|
|
328
360
|
event.stopPropagation();
|
|
@@ -331,5 +363,5 @@ export function KptDatepicker({ value, defaultValue = '', onValueChange, selecti
|
|
|
331
363
|
else if (event.key === 'Tab') {
|
|
332
364
|
trapTab(event.currentTarget, event.nativeEvent);
|
|
333
365
|
}
|
|
334
|
-
}, children: [showClock ? (_jsxs(_Fragment, { children: [selectionMode === 'datetime' ? (_jsxs("div", { className: "kpt-calendar__header", children: [_jsx("button", { type: "button", className: "kpt-calendar__nav", onClick: () => setDtView('calendar'), "aria-label": t('datepicker.calendar'), children: _jsx(KptIcon, { name: "chevron-left" }) }), _jsx("span", { className: "kpt-calendar__title", children: t('datepicker.time') })] })) : null, _jsx(KptClock, { value: timeStr, onValueChange: setTimeStr, hourFormat: hourFormat, minuteStep: minuteStep })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: "kpt-calendar__header", children: [hasNav ? (_jsx("button", { type: "button", className: "kpt-calendar__nav", disabled: !canPageBack(page, panelView, minDate), onClick: () => turnPage(-1), "aria-label": navLabel('prev'), children: _jsx(KptIcon, { name: "chevron-left" }) })) : null, _jsxs("span", { className: "kpt-calendar__titles", children: [panelView === 'day' ? (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: "kpt-calendar__title-btn", onClick: () => goTo('month'), "aria-label": t('datepicker.chooseMonth')
|
|
366
|
+
}, children: [showClock ? (_jsxs(_Fragment, { children: [selectionMode === 'datetime' ? (_jsxs("div", { className: "kpt-calendar__header", children: [_jsx("button", { type: "button", className: "kpt-calendar__nav", onClick: () => setDtView('calendar'), "aria-label": t('datepicker.calendar'), children: _jsx(KptIcon, { name: "chevron-left" }) }), _jsx("span", { className: "kpt-calendar__title", children: t('datepicker.time') })] })) : null, _jsx(KptClock, { value: timeStr, onValueChange: setTimeStr, hourFormat: hourFormat, minuteStep: minuteStep })] })) : (_jsxs(_Fragment, { children: [_jsx("span", { className: "kpt-calendar__live", "aria-live": "polite", children: pageAnnouncement }), _jsxs("div", { className: "kpt-calendar__header", children: [hasNav ? (_jsx("button", { type: "button", className: "kpt-calendar__nav", disabled: !canPageBack(page, panelView, minDate), onClick: () => turnPage(-1), "aria-label": navLabel('prev'), children: _jsx(KptIcon, { name: "chevron-left" }) })) : null, _jsxs("span", { className: "kpt-calendar__titles", children: [panelView === 'day' ? (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: "kpt-calendar__title-btn", onClick: () => goTo('month'), "aria-label": `${months[page.m]}, ${t('datepicker.chooseMonth')}`, children: months[page.m] }), _jsx("button", { type: "button", className: "kpt-calendar__title-btn", onClick: () => goTo('year'), "aria-label": `${page.y}, ${t('datepicker.chooseYear')}`, children: page.y })] })) : null, panelView === 'month' ? (selectionMode === 'month' ? (_jsx("span", { className: "kpt-calendar__title", children: t('datepicker.month') })) : (_jsx("button", { type: "button", className: "kpt-calendar__title-btn", onClick: () => goTo('year'), "aria-label": `${page.y}, ${t('datepicker.chooseYear')}`, children: page.y }))) : null, panelView === 'year' ? (_jsx("button", { type: "button", className: "kpt-calendar__title-btn", onClick: () => goTo('decade'), "aria-label": `${decadeStart(page.y)} – ${decadeStart(page.y) + 9}, ${t('datepicker.chooseDecade')}`, children: `${decadeStart(page.y)} – ${decadeStart(page.y) + 9}` })) : null, panelView === 'decade' ? (_jsx("span", { className: "kpt-calendar__title", children: `${centuryStart(page.y)} – ${centuryStart(page.y) + 119}` })) : null] }), hasNav ? (_jsx("button", { type: "button", className: "kpt-calendar__nav", disabled: !canPageForward(page, panelView, maxDate), onClick: () => turnPage(1), "aria-label": navLabel('next'), children: _jsx(KptIcon, { name: "chevron-right" }) })) : null] }), panelView === 'day' ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "kpt-calendar__weekdays", children: weekdays.map((weekday) => (_jsx("span", { className: "kpt-calendar__weekday", children: weekday }, weekday))) }), _jsx("div", { className: "kpt-calendar__grid", onKeyDown: onDayKeyDown, children: weeks.flat().map((cell) => (_jsx("button", { type: "button", className: cn('kpt-calendar__day', !cell.inMonth && 'kpt-calendar__day--muted', cell.today && 'kpt-calendar__day--today', cell.selected && 'kpt-calendar__day--selected'), "data-iso": cell.iso, tabIndex: cell.iso === focusIso ? 0 : -1, "aria-label": dayName(cell.iso), "aria-current": cell.today ? 'date' : undefined, disabled: cell.disabled, "aria-pressed": cell.selected, onClick: () => selectDay(cell.iso, cell.disabled), children: cell.day }, cell.iso))) })] })) : (_jsx("div", { className: cn('kpt-calendar__grid kpt-calendar__grid--cells', panelView === 'decade' && 'kpt-calendar__grid--decades'), onKeyDown: onCellKeyDown, children: levelCells.map((cell, index) => (_jsx("button", { type: "button", className: cn('kpt-calendar__cell', cell.selected && 'kpt-calendar__cell--selected', cell.current && 'kpt-calendar__cell--current'), tabIndex: index === activeCell ? 0 : -1, disabled: cell.disabled, "aria-pressed": cell.selected, onClick: () => selectCell(cell), children: cell.label }, cell.label))) })), selectionMode === 'datetime' ? (_jsxs("button", { type: "button", className: "kpt-calendar__timebar", onClick: () => setDtView('clock'), children: [_jsx(KptIcon, { name: "clock" }), _jsx("span", { children: formatTime(timeParts?.h ?? 0, timeParts?.m ?? 0, hourFormat, periods) })] })) : null] })), hasFooter ? (_jsx("div", { className: "kpt-calendar__footer", children: _jsx(KptButton, { size: "sm", onClick: commit, children: t('common.done') }) })) : null] }) })] }));
|
|
335
367
|
}
|
package/dist/datepicker/llms.txt
CHANGED
|
@@ -5,7 +5,7 @@ Import: `import { KptDatepicker } from '@konce-pt/react';`
|
|
|
5
5
|
|
|
6
6
|
## DOM
|
|
7
7
|
<div class="kpt-datepicker-host">
|
|
8
|
-
<div class="kpt-datepicker" role="
|
|
8
|
+
<div class="kpt-datepicker" role="combobox" tabindex="0" aria-haspopup="dialog">
|
|
9
9
|
<span class="kpt-datepicker__value">06.09.2026</span>
|
|
10
10
|
<span class="kpt-datepicker__spacer"></span>
|
|
11
11
|
<button class="kpt-datepicker__clear">…</button>
|
|
@@ -22,7 +22,7 @@ Import: `import { KptDatepicker } from '@konce-pt/react';`
|
|
|
22
22
|
means the level where the mode commits. It is never lowered below that level, so
|
|
23
23
|
`startView="day"` in 'year' mode is raised to 'year'.
|
|
24
24
|
- `minDate`, `maxDate`: 'YYYY-MM-DD' strings
|
|
25
|
-
- `monthDisplay`: `'long'` (default) | `'number'`; `displayFormat`: `'pl'` (default) | `'iso'`
|
|
25
|
+
- `monthDisplay`: `'long'` (default) | `'number'`; `displayFormat`: `'pl'` (default) | `'iso'` | `'locale'`
|
|
26
26
|
- `hourFormat`: 12 | 24 (default 24) — the dial; the stored value is always 24h
|
|
27
27
|
- `minuteStep`: number (default 1) — minute snapping on the dial
|
|
28
28
|
- `open` / `onOpenChange` — the panel state, so it can be opened from outside
|
|
@@ -81,6 +81,20 @@ Weekday and month names come from `Intl` for the active locale, not from the dic
|
|
|
81
81
|
navigation labels and "Done" come from the `datepicker` and `common` namespaces; `locale` and
|
|
82
82
|
`dictionary` override them per instance.
|
|
83
83
|
|
|
84
|
+
## Week start, display format and field state
|
|
85
|
+
- The week starts by the active language (`Intl.Locale#getWeekInfo`, with a fallback list of regions):
|
|
86
|
+
en / en-US on Sunday, pl, de, en-GB on Monday. `firstDayOfWeek` (1 = Monday … 7 = Sunday) overrides it;
|
|
87
|
+
the headers, the grid and `Home`/`End` follow it.
|
|
88
|
+
- `displayFormat`: `'pl'` (DD.MM.YYYY, default), `'iso'` (YYYY-MM-DD) or `'locale'` — order and separators
|
|
89
|
+
from `Intl` (en-US: MM/DD/YYYY). The placeholder follows the format and its letters follow the language
|
|
90
|
+
(EN `YYYY`, PL `RRRR`, the `datepicker.yearToken`/`monthToken`/`dayToken` keys). AM/PM come from `Intl`.
|
|
91
|
+
- The field is `role="combobox"` with `aria-haspopup="dialog"` (the APG select-only combobox), so it carries
|
|
92
|
+
`aria-required` (`required`), `aria-readonly` (`readOnly` — the value shows, the panel does not open,
|
|
93
|
+
there is no clear button) and `aria-invalid`.
|
|
94
|
+
- Turning the page with ‹ › is announced (`aria-live="polite"`); the title buttons are named
|
|
95
|
+
"October, Choose month" — the visible text first (WCAG 2.5.3). Clearing the value counts as a touch.
|
|
96
|
+
- "Today" is computed on every open, so a panel opened after midnight marks the right day.
|
|
97
|
+
|
|
84
98
|
## Label and description
|
|
85
99
|
`id` goes to the focusable element, not the host; without one it comes from `KptFormField` or is
|
|
86
100
|
generated. A `div` with a role is not named by `label[for]`, so inside `KptFormField` it gets
|
|
@@ -92,7 +106,7 @@ is added to `aria-describedby`. New: `id`; the trigger is the element that carri
|
|
|
92
106
|
`--kpt-color-surface-raised`, the selection `--kpt-color-primary`.
|
|
93
107
|
|
|
94
108
|
## Accessibility
|
|
95
|
-
The trigger is `role="
|
|
109
|
+
The trigger is `role="combobox"` with `aria-haspopup="dialog"`, `aria-expanded` and `aria-controls`;
|
|
96
110
|
the panel is a named `role="dialog"`; every cell is a real `<button>` with `aria-pressed`, days carry
|
|
97
111
|
the full date and `aria-current="date"`; `ariaLabel` names the field when no `<label for>` describes
|
|
98
112
|
it. The keyboard is described above.
|
package/dist/dialog/dialog.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
3
|
+
import { isValidElement, useEffect, useRef, useState, } from 'react';
|
|
3
4
|
import { cn } from "../internal/cn.js";
|
|
5
|
+
import { slotChildren } from "../internal/slot-children.js";
|
|
6
|
+
import { kptLockScroll } from "../internal/scroll-lock.js";
|
|
4
7
|
import { composeRefs } from "../internal/compose-refs.js";
|
|
5
8
|
import { useKptId } from "../internal/use-id.js";
|
|
6
9
|
import { KptOverlayContainerContext } from "../internal/overlay-container.js";
|
|
@@ -31,19 +34,37 @@ export function KptDialog({ open, onOpenChange, header = '', size = 'md', dismis
|
|
|
31
34
|
const [dialogEl, setDialogEl] = useState(null);
|
|
32
35
|
useEffect(() => {
|
|
33
36
|
const el = dialogRef.current;
|
|
34
|
-
if (!el)
|
|
37
|
+
if (!el || !open)
|
|
35
38
|
return;
|
|
36
|
-
if (
|
|
39
|
+
if (!el.open)
|
|
37
40
|
el.showModal();
|
|
38
|
-
|
|
39
|
-
|
|
41
|
+
// `showModal()` blokuje interakcję z tłem, ale nie jego przewijanie kółkiem.
|
|
42
|
+
const releaseScroll = kptLockScroll();
|
|
43
|
+
return () => {
|
|
44
|
+
releaseScroll();
|
|
45
|
+
if (el.open)
|
|
46
|
+
el.close();
|
|
47
|
+
};
|
|
40
48
|
}, [open]);
|
|
49
|
+
/**
|
|
50
|
+
* `close` przychodzi z każdej drogi. Przy `dismissible=false` zamknąć wolno tylko z kodu:
|
|
51
|
+
* Chromium z CloseWatcherem pozwala anulować `cancel` jedynie po aktywacji użytkownika, więc drugi
|
|
52
|
+
* Escape z rzędu zamyka okno mimo `preventDefault` — wtedy otwieramy je z powrotem.
|
|
53
|
+
*/
|
|
54
|
+
const onClose = () => {
|
|
55
|
+
const el = dialogRef.current;
|
|
56
|
+
if (!dismissible && open && el && !el.open) {
|
|
57
|
+
el.showModal();
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
onOpenChange(false);
|
|
61
|
+
};
|
|
41
62
|
const footers = [];
|
|
42
63
|
const body = [];
|
|
43
|
-
|
|
64
|
+
for (const child of slotChildren(children)) {
|
|
44
65
|
const isFooter = isValidElement(child) && child.type?.kptDialogSlot === 'footer';
|
|
45
66
|
(isFooter ? footers : body).push(child);
|
|
46
|
-
}
|
|
67
|
+
}
|
|
47
68
|
const onCancel = (event) => {
|
|
48
69
|
// Esc idzie przez `cancel`; blokujemy go, gdy okna nie wolno zamknąć.
|
|
49
70
|
if (!dismissible) {
|
|
@@ -52,7 +73,7 @@ export function KptDialog({ open, onOpenChange, header = '', size = 'md', dismis
|
|
|
52
73
|
}
|
|
53
74
|
onOpenChange(false);
|
|
54
75
|
};
|
|
55
|
-
return (_jsx(KptOverlayContainerContext.Provider, { value: open ? dialogEl : null, children: _jsxs("dialog", { ref: composeRefs(dialogRef, setDialogEl, ref), className: cn('kpt-dialog', className), "data-size": size, "aria-labelledby": header ? titleId : undefined,
|
|
76
|
+
return (_jsx(KptOverlayContainerContext.Provider, { value: open ? dialogEl : null, children: _jsxs("dialog", { ref: composeRefs(dialogRef, setDialogEl, ref), className: cn('kpt-dialog', className), "data-size": size, "aria-labelledby": header ? titleId : undefined, closedby: dismissible ? undefined : 'none', onClose: onClose, onCancel: onCancel, ...rest, children: [header ? (_jsxs("div", { className: "kpt-dialog__header", children: [_jsx("h2", { className: "kpt-dialog__title", id: titleId, children: header }), dismissible ? (_jsx("button", { className: "kpt-dialog__close", type: "button", onClick: () => onOpenChange(false), "aria-label": t('common.close'), children: _jsx(KptIcon, { name: "x" }) })) : null] })) : null, _jsx("div", { className: "kpt-dialog__body", children: body }), footers] }) }));
|
|
56
77
|
}
|
|
57
78
|
/**
|
|
58
79
|
* Stopka okna. Nosi atrybut `kptdialogfooter`, w który celuje wspólny SCSS (nazwy atrybutów
|
package/dist/dialog/llms.txt
CHANGED
|
@@ -51,3 +51,6 @@ page becomes inert, and Escape closes it — all from the browser. With `dismiss
|
|
|
51
51
|
`cancel` event is prevented, so Escape stops working; give the user another way out.
|
|
52
52
|
With `header` the dialog is named by its title (`aria-labelledby`); without one pass `aria-label`.
|
|
53
53
|
`role="alertdialog"` and `aria-describedby` go straight through as `<dialog>` props.
|
|
54
|
+
|
|
55
|
+
## Scrolling and non-dismissible windows
|
|
56
|
+
An open window locks page scrolling underneath (a shared counter with drawers and bottom sheets, so nested windows do not release each other). With `dismissible` off the window gets `closedby="none"`, and if a browser still closes it on a gesture (Chromium lets the second Escape in a row through `cancel`), it is reopened — only code can close it.
|
|
@@ -4,6 +4,11 @@ export interface KptDividerProps extends Omit<ComponentPropsWithRef<'div'>, 'chi
|
|
|
4
4
|
orientation?: KptDividerOrientation;
|
|
5
5
|
/** Wcięcie (odstęp od krawędzi kontenera). */
|
|
6
6
|
inset?: boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Linia czysto ozdobna (np. między kartami, które i tak są osobnymi regionami) — bez
|
|
9
|
+
* `role="separator"`, ukryta przed czytnikiem. Domyślnie separator jest ogłaszany.
|
|
10
|
+
*/
|
|
11
|
+
decorative?: boolean;
|
|
7
12
|
}
|
|
8
13
|
/**
|
|
9
14
|
* Separator (linia pozioma lub pionowa). Kolor z `--kpt-color-border`.
|
|
@@ -12,5 +17,6 @@ export interface KptDividerProps extends Omit<ComponentPropsWithRef<'div'>, 'chi
|
|
|
12
17
|
* <KptDivider />
|
|
13
18
|
* <KptDivider orientation="vertical" />
|
|
14
19
|
* <KptDivider inset />
|
|
20
|
+
* <KptDivider decorative />
|
|
15
21
|
*/
|
|
16
|
-
export declare function KptDivider({ orientation, inset, className, ...rest }: Readonly<KptDividerProps>): import("react").JSX.Element;
|
|
22
|
+
export declare function KptDivider({ orientation, inset, decorative, className, ...rest }: Readonly<KptDividerProps>): import("react").JSX.Element;
|
package/dist/divider/divider.js
CHANGED
|
@@ -7,7 +7,8 @@ import { cn } from "../internal/cn.js";
|
|
|
7
7
|
* <KptDivider />
|
|
8
8
|
* <KptDivider orientation="vertical" />
|
|
9
9
|
* <KptDivider inset />
|
|
10
|
+
* <KptDivider decorative />
|
|
10
11
|
*/
|
|
11
|
-
export function KptDivider({ orientation = 'horizontal', inset = false, className, ...rest }) {
|
|
12
|
-
return (_jsx("div", { className: cn('kpt-divider', inset && 'kpt-divider--inset', className), role:
|
|
12
|
+
export function KptDivider({ orientation = 'horizontal', inset = false, decorative = false, className, ...rest }) {
|
|
13
|
+
return (_jsx("div", { className: cn('kpt-divider', inset && 'kpt-divider--inset', className), role: decorative ? 'none' : 'separator', "aria-orientation": decorative ? undefined : orientation, "aria-hidden": decorative || undefined, "data-orientation": orientation, ...rest }));
|
|
13
14
|
}
|
package/dist/divider/llms.txt
CHANGED
|
@@ -9,6 +9,7 @@ Import: `import { KptDivider } from '@konce-pt/react';`
|
|
|
9
9
|
## Props
|
|
10
10
|
- `orientation`: 'horizontal' | 'vertical' (default 'horizontal')
|
|
11
11
|
- `inset`: boolean — an indent from the container edges
|
|
12
|
+
- `decorative`: boolean — a purely ornamental line: no `role="separator"`, hidden from screen readers
|
|
12
13
|
- `className`, `ref` and every other `<div>` prop go to the divider
|
|
13
14
|
|
|
14
15
|
## Vertical needs a flex parent
|
package/dist/drawer/drawer.d.ts
CHANGED
|
@@ -6,6 +6,11 @@ export interface KptDrawerProps extends Omit<ComponentPropsWithRef<'div'>, 'onCh
|
|
|
6
6
|
onOpenChange: (open: boolean) => void;
|
|
7
7
|
position?: KptDrawerPosition;
|
|
8
8
|
header?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Nazwa panelu bez `header` — okno dialogowe bez nazwy czytnik ogłasza jako samo „okno
|
|
11
|
+
* dialogowe” (WCAG 4.1.2). Z nagłówkiem nazwą jest nagłówek (`aria-labelledby`).
|
|
12
|
+
*/
|
|
13
|
+
ariaLabel?: string;
|
|
9
14
|
/** Pokazuje przycisk × i pozwala zamknąć klawiszem Escape. */
|
|
10
15
|
dismissible?: boolean;
|
|
11
16
|
closeOnBackdrop?: boolean;
|
|
@@ -22,4 +27,4 @@ export interface KptDrawerProps extends Omit<ComponentPropsWithRef<'div'>, 'onCh
|
|
|
22
27
|
* Treść panelu…
|
|
23
28
|
* </KptDrawer>
|
|
24
29
|
*/
|
|
25
|
-
export declare function KptDrawer({ open, onOpenChange, position, header, dismissible, closeOnBackdrop, locale, dictionary, className, children, ...rest }: Readonly<KptDrawerProps>): import("react").JSX.Element;
|
|
30
|
+
export declare function KptDrawer({ open, onOpenChange, position, header, ariaLabel, dismissible, closeOnBackdrop, locale, dictionary, className, children, ...rest }: Readonly<KptDrawerProps>): import("react").JSX.Element;
|
package/dist/drawer/drawer.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
3
|
import { useCallback, useRef } from 'react';
|
|
3
4
|
import { cn } from "../internal/cn.js";
|
|
@@ -16,7 +17,7 @@ import { KptIcon } from "../icon/icon.js";
|
|
|
16
17
|
* Treść panelu…
|
|
17
18
|
* </KptDrawer>
|
|
18
19
|
*/
|
|
19
|
-
export function KptDrawer({ open, onOpenChange, position = 'left', header = '', dismissible = true, closeOnBackdrop = true, locale, dictionary, className, children, ...rest }) {
|
|
20
|
+
export function KptDrawer({ open, onOpenChange, position = 'left', header = '', ariaLabel = '', dismissible = true, closeOnBackdrop = true, locale, dictionary, className, children, ...rest }) {
|
|
20
21
|
const { t } = useKptMessages({ locale, dictionary });
|
|
21
22
|
const close = useCallback(() => onOpenChange(false), [onOpenChange]);
|
|
22
23
|
const panelRef = useRef(null);
|
|
@@ -26,5 +27,5 @@ export function KptDrawer({ open, onOpenChange, position = 'left', header = '',
|
|
|
26
27
|
if (event.key === 'Escape' && dismissible)
|
|
27
28
|
close();
|
|
28
29
|
};
|
|
29
|
-
return (_jsxs("div", { className: cn('kpt-drawer-host', className), ...rest, children: [_jsx("div", { className: cn('kpt-drawer__backdrop', open && 'is-open'), onClick: () => closeOnBackdrop && close() }), _jsxs("aside", { ref: panelRef, className: cn('kpt-drawer__panel', open && 'is-open'), "data-position": position, role: "dialog", "aria-modal": "true", "aria-labelledby": header ? titleId : undefined, tabIndex: -1, onKeyDown: onKeyDown, children: [header || dismissible ? (_jsxs("div", { className: "kpt-drawer__header", children: [_jsx("span", { className: "kpt-drawer__title", id: titleId, children: header }), dismissible ? (_jsx("button", { type: "button", className: "kpt-drawer__close", onClick: close, "aria-label": t('common.close'), children: _jsx(KptIcon, { name: "x" }) })) : null] })) : null, _jsx("div", { className: "kpt-drawer__body", children: children })] })] }));
|
|
30
|
+
return (_jsxs("div", { className: cn('kpt-drawer-host', className), ...rest, children: [_jsx("div", { className: cn('kpt-drawer__backdrop', open && 'is-open'), onClick: () => closeOnBackdrop && close() }), _jsxs("aside", { ref: panelRef, className: cn('kpt-drawer__panel', open && 'is-open'), "data-position": position, role: "dialog", "aria-modal": "true", "aria-labelledby": header ? titleId : undefined, "aria-label": header ? undefined : ariaLabel || undefined, tabIndex: -1, onKeyDown: onKeyDown, children: [header || dismissible ? (_jsxs("div", { className: "kpt-drawer__header", children: [_jsx("span", { className: "kpt-drawer__title", id: titleId, children: header }), dismissible ? (_jsx("button", { type: "button", className: "kpt-drawer__close", onClick: close, "aria-label": t('common.close'), children: _jsx(KptIcon, { name: "x" }) })) : null] })) : null, _jsx("div", { className: "kpt-drawer__body", children: children })] })] }));
|
|
30
31
|
}
|
package/dist/drawer/llms.txt
CHANGED
|
@@ -50,3 +50,6 @@ is named by its `header` through `aria-labelledby`. The shared helper is `intern
|
|
|
50
50
|
## Accessibility
|
|
51
51
|
`role="dialog"`, `aria-modal="true"` and `aria-labelledby` pointing at the title; the close button gets
|
|
52
52
|
its label from `common.close`. Focus handling — see "Focus and keyboard" above.
|
|
53
|
+
|
|
54
|
+
## Name and scrolling
|
|
55
|
+
Without `header` give the panel `ariaLabel` — a `role="dialog"` without a name is announced as a bare "dialog". An open drawer locks page scrolling underneath.
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
3
|
import { cn } from "../internal/cn.js";
|
|
3
4
|
import { useControllableState } from "../internal/use-controllable-state.js";
|
|
5
|
+
import { useKptId } from "../internal/use-id.js";
|
|
4
6
|
import { KptIcon } from "../icon/icon.js";
|
|
5
7
|
/**
|
|
6
8
|
* Grupa pól z legendą (`<fieldset>` + `<legend>`), opcjonalnie zwijana.
|
|
@@ -12,6 +14,7 @@ import { KptIcon } from "../icon/icon.js";
|
|
|
12
14
|
* <KptFieldset legend="Dane osobowe" toggleable>…</KptFieldset>
|
|
13
15
|
*/
|
|
14
16
|
export function KptFieldset({ legend = '', toggleable = false, collapsed, defaultCollapsed = false, onCollapsedChange, animated = true, animationDuration = 500, className, style, children, ...rest }) {
|
|
17
|
+
const bodyId = useKptId(undefined, 'kpt-fieldset-body');
|
|
15
18
|
const [isCollapsed, setCollapsed] = useControllableState({
|
|
16
19
|
value: collapsed,
|
|
17
20
|
defaultValue: defaultCollapsed,
|
|
@@ -20,5 +23,5 @@ export function KptFieldset({ legend = '', toggleable = false, collapsed, defaul
|
|
|
20
23
|
return (_jsx("div", { className: cn('kpt-fieldset-host', className), style: {
|
|
21
24
|
['--kpt-anim-duration']: `${animated ? animationDuration : 0}ms`,
|
|
22
25
|
...style,
|
|
23
|
-
}, ...rest, children: _jsxs("fieldset", { className: "kpt-fieldset", children: [_jsx("legend", { className: "kpt-fieldset__legend", children: toggleable ? (_jsxs("button", { type: "button", className: "kpt-fieldset__toggle", "aria-expanded": !isCollapsed, onClick: () => setCollapsed(!isCollapsed), children: [_jsx(KptIcon, { name: isCollapsed ? 'chevron-down' : 'chevron-up' }), _jsx("span", { children: legend })] })) : (_jsx("span", { children: legend })) }), _jsx("div", { className: cn('kpt-fieldset__collapse', !isCollapsed && 'is-open'), inert: isCollapsed,
|
|
26
|
+
}, ...rest, children: _jsxs("fieldset", { className: "kpt-fieldset", children: [_jsx("legend", { className: "kpt-fieldset__legend", children: toggleable ? (_jsxs("button", { type: "button", className: "kpt-fieldset__toggle", "aria-expanded": !isCollapsed, "aria-controls": bodyId, onClick: () => setCollapsed(!isCollapsed), children: [_jsx(KptIcon, { name: isCollapsed ? 'chevron-down' : 'chevron-up' }), _jsx("span", { children: legend })] })) : (_jsx("span", { children: legend })) }), _jsx("div", { id: bodyId, className: cn('kpt-fieldset__collapse', !isCollapsed && 'is-open'), inert: isCollapsed, children: _jsx("div", { className: "kpt-fieldset__collapse-inner", children: _jsx("div", { className: "kpt-fieldset__body", children: children }) }) })] }) }));
|
|
24
27
|
}
|
|
@@ -8,6 +8,12 @@ export interface KptFileUploadProps extends Omit<ComponentPropsWithRef<'div'>, '
|
|
|
8
8
|
accept?: string;
|
|
9
9
|
multiple?: boolean;
|
|
10
10
|
disabled?: boolean;
|
|
11
|
+
/** Tylko do odczytu — listę plików widać, nie da się jej zmienić. */
|
|
12
|
+
readOnly?: boolean;
|
|
13
|
+
/** Id strefy; bez niego generowane, w `KptFormField` — z wrappera. */
|
|
14
|
+
id?: string;
|
|
15
|
+
/** Nazwa dostępna, gdy strefy nie opisuje etykieta (`KptFormField`). */
|
|
16
|
+
ariaLabel?: string | null;
|
|
11
17
|
invalid?: boolean;
|
|
12
18
|
touched?: boolean;
|
|
13
19
|
onTouch?: () => void;
|
|
@@ -24,4 +30,4 @@ export interface KptFileUploadProps extends Omit<ComponentPropsWithRef<'div'>, '
|
|
|
24
30
|
* @example
|
|
25
31
|
* <KptFileUpload value={files} onValueChange={setFiles} accept="image/*" multiple />
|
|
26
32
|
*/
|
|
27
|
-
export declare function KptFileUpload({ value, defaultValue, onValueChange, accept, multiple, disabled, invalid, touched, onTouch, locale, dictionary, className, ...rest }: Readonly<KptFileUploadProps>): import("react").JSX.Element;
|
|
33
|
+
export declare function KptFileUpload({ value, defaultValue, onValueChange, accept, multiple, disabled, readOnly, id, ariaLabel, invalid, touched, onTouch, locale, dictionary, className, ...rest }: Readonly<KptFileUploadProps>): import("react").JSX.Element;
|