@konce-pt/react 0.11.2 → 0.14.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 +128 -64
- package/dist/accordion/accordion.d.ts +14 -6
- package/dist/accordion/accordion.js +25 -17
- package/dist/accordion/llms.txt +22 -10
- package/dist/alert/alert.d.ts +2 -2
- package/dist/alert/alert.js +3 -2
- package/dist/alert/llms.txt +10 -9
- package/dist/app-shell/app-shell.d.ts +14 -14
- package/dist/app-shell/app-shell.js +22 -15
- package/dist/app-shell/llms.txt +16 -15
- package/dist/auth/auth-types.d.ts +6 -6
- package/dist/auth/auth.d.ts +2 -2
- package/dist/auth/auth.js +24 -15
- package/dist/auth/llms.txt +12 -11
- package/dist/autocomplete/autocomplete.d.ts +19 -6
- package/dist/autocomplete/autocomplete.js +32 -11
- package/dist/autocomplete/llms.txt +19 -5
- package/dist/avatar/avatar.d.ts +6 -6
- package/dist/avatar/avatar.js +6 -5
- package/dist/avatar/llms.txt +10 -10
- package/dist/avatar-group/avatar-group.d.ts +4 -4
- package/dist/avatar-group/avatar-group.js +4 -3
- package/dist/avatar-group/llms.txt +8 -8
- package/dist/backdrop/backdrop.d.ts +7 -26
- package/dist/backdrop/backdrop.js +32 -27
- package/dist/backdrop/llms.txt +27 -27
- package/dist/badge/badge.d.ts +3 -3
- package/dist/badge/badge.js +1 -1
- package/dist/badge/llms.txt +11 -11
- package/dist/bottom-sheet/bottom-sheet.d.ts +8 -3
- package/dist/bottom-sheet/bottom-sheet.js +5 -4
- package/dist/bottom-sheet/llms.txt +11 -8
- package/dist/breadcrumb/breadcrumb.d.ts +8 -2
- package/dist/breadcrumb/breadcrumb.js +11 -3
- package/dist/breadcrumb/llms.txt +10 -2
- package/dist/button/button.d.ts +3 -3
- package/dist/button/button.js +10 -9
- package/dist/button/llms.txt +27 -19
- package/dist/button-group/button-group.d.ts +18 -5
- package/dist/button-group/button-group.js +54 -6
- package/dist/button-group/llms.txt +27 -18
- package/dist/card/card.d.ts +8 -8
- package/dist/card/card.js +10 -9
- package/dist/card/llms.txt +20 -20
- package/dist/carousel/carousel.d.ts +6 -6
- package/dist/carousel/carousel.js +8 -5
- package/dist/carousel/llms.txt +10 -10
- package/dist/charts/chart-legend.d.ts +3 -3
- package/dist/charts/chart-legend.js +2 -1
- package/dist/charts/chart-tooltip.d.ts +3 -3
- package/dist/charts/chart-tooltip.js +1 -1
- package/dist/charts/chart.d.ts +4 -3
- package/dist/charts/chart.js +18 -10
- package/dist/charts/llms.txt +10 -7
- package/dist/charts-entry/public-api.js +2 -2
- package/dist/chat/chat.d.ts +17 -10
- package/dist/chat/chat.js +77 -23
- package/dist/chat/llms.txt +39 -15
- package/dist/chat-composer/chat-composer.d.ts +20 -7
- package/dist/chat-composer/chat-composer.js +29 -7
- package/dist/chat-composer/llms.txt +20 -11
- package/dist/chat-dock/chat-dock.d.ts +11 -4
- package/dist/chat-dock/chat-dock.js +10 -6
- package/dist/chat-dock/llms.txt +15 -14
- package/dist/chat-entry/public-api.js +1 -1
- package/dist/chat-message/chat-message.d.ts +12 -5
- package/dist/chat-message/chat-message.js +19 -8
- package/dist/chat-message/llms.txt +16 -15
- package/dist/chat-typing/chat-typing.d.ts +2 -2
- package/dist/chat-typing/chat-typing.js +4 -3
- package/dist/chat-typing/llms.txt +6 -6
- package/dist/checkbox/checkbox.d.ts +13 -6
- package/dist/checkbox/checkbox.js +31 -5
- package/dist/checkbox/llms.txt +26 -11
- package/dist/chip/chip.d.ts +4 -4
- package/dist/chip/chip.js +3 -2
- package/dist/chip/llms.txt +11 -11
- package/dist/chips-input/chips-input.d.ts +6 -4
- package/dist/chips-input/chips-input.js +47 -12
- package/dist/chips-input/llms.txt +18 -8
- package/dist/clock/clock.d.ts +11 -6
- package/dist/clock/clock.js +38 -8
- package/dist/clock/llms.txt +12 -8
- package/dist/color-picker/color-picker.d.ts +9 -5
- package/dist/color-picker/color-picker.js +37 -18
- package/dist/color-picker/llms.txt +8 -5
- package/dist/confirm/confirm-container.d.ts +3 -3
- package/dist/confirm/confirm-container.js +6 -5
- package/dist/confirm/confirm-store.d.ts +4 -4
- package/dist/confirm/confirm-store.js +11 -5
- package/dist/confirm/llms.txt +8 -7
- package/dist/context-menu/context-menu.d.ts +1 -1
- package/dist/context-menu/context-menu.js +37 -14
- package/dist/context-menu/llms.txt +10 -7
- package/dist/data-table/data-table-types.d.ts +5 -5
- package/dist/data-table/data-table.d.ts +37 -5
- package/dist/data-table/data-table.js +426 -119
- package/dist/data-table/export-csv.d.ts +3 -3
- package/dist/data-table/export-csv.js +8 -10
- package/dist/data-table/llms.txt +127 -79
- package/dist/data-view/data-view.d.ts +13 -5
- package/dist/data-view/data-view.js +68 -6
- package/dist/data-view/llms.txt +29 -10
- package/dist/date-range/date-range.d.ts +20 -8
- package/dist/date-range/date-range.js +86 -63
- package/dist/date-range/llms.txt +32 -18
- package/dist/datepicker/datepicker.d.ts +22 -8
- package/dist/datepicker/datepicker.js +103 -71
- package/dist/datepicker/llms.txt +36 -22
- package/dist/dialog/dialog.d.ts +4 -4
- package/dist/dialog/dialog.js +32 -11
- package/dist/dialog/llms.txt +13 -10
- package/dist/divider/divider.d.ts +7 -1
- package/dist/divider/divider.js +3 -2
- package/dist/divider/llms.txt +4 -3
- package/dist/drawer/drawer.d.ts +9 -4
- package/dist/drawer/drawer.js +5 -4
- package/dist/drawer/llms.txt +13 -10
- package/dist/empty/empty.d.ts +2 -2
- package/dist/empty/empty.js +1 -1
- package/dist/empty/llms.txt +9 -9
- package/dist/fab/fab.d.ts +1 -1
- package/dist/fab/fab.js +1 -1
- package/dist/fab/llms.txt +10 -6
- package/dist/fieldset/fieldset.d.ts +18 -4
- package/dist/fieldset/fieldset.js +12 -6
- package/dist/fieldset/llms.txt +26 -9
- package/dist/file-upload/file-upload.d.ts +8 -2
- package/dist/file-upload/file-upload.js +22 -11
- package/dist/file-upload/llms.txt +12 -9
- package/dist/form-field/form-field-context.d.ts +4 -4
- package/dist/form-field/form-field.d.ts +12 -4
- package/dist/form-field/form-field.js +27 -6
- package/dist/form-field/llms.txt +25 -11
- package/dist/galleria/galleria.d.ts +3 -3
- package/dist/galleria/galleria.js +9 -6
- package/dist/galleria/llms.txt +7 -7
- package/dist/grid/col.d.ts +4 -4
- package/dist/grid/col.js +2 -2
- package/dist/grid/flex.d.ts +3 -3
- package/dist/grid/flex.js +1 -1
- package/dist/grid/grid.d.ts +7 -7
- package/dist/grid/grid.js +3 -3
- package/dist/grid/layout.d.ts +2 -2
- package/dist/grid/layout.js +2 -2
- package/dist/grid/llms.txt +15 -15
- package/dist/grid-entry/public-api.js +1 -1
- package/dist/i18n/i18n-context.d.ts +6 -6
- package/dist/i18n/i18n-context.js +27 -5
- package/dist/icon/icon-registry.d.ts +3 -3
- package/dist/icon/icon-registry.js +3 -3
- package/dist/icon/icon.d.ts +4 -4
- package/dist/icon/icon.js +10 -7
- package/dist/icon/llms.txt +13 -13
- package/dist/icon-button/icon-button.d.ts +2 -2
- package/dist/icon-button/icon-button.js +1 -1
- package/dist/icon-button/llms.txt +9 -5
- package/dist/icons-entry/public-api.d.ts +2 -2
- package/dist/icons-entry/public-api.js +3 -3
- package/dist/image/image.js +5 -5
- package/dist/image/llms.txt +13 -13
- package/dist/input/input.d.ts +18 -5
- package/dist/input/input.js +22 -6
- package/dist/input/llms.txt +25 -11
- package/dist/input-mask/input-mask.d.ts +7 -2
- package/dist/input-mask/input-mask.js +17 -43
- package/dist/input-mask/llms.txt +8 -5
- package/dist/input-number/input-number.d.ts +11 -9
- package/dist/input-number/input-number.js +25 -21
- package/dist/input-number/llms.txt +17 -14
- package/dist/input-otp/input-otp.d.ts +18 -5
- package/dist/input-otp/input-otp.js +48 -24
- package/dist/input-otp/llms.txt +18 -7
- package/dist/internal/calendar-focus.d.ts +1 -1
- package/dist/internal/calendar-focus.js +3 -3
- package/dist/internal/cn.d.ts +1 -1
- 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/option-nav.d.ts +4 -4
- package/dist/internal/option-nav.js +5 -5
- package/dist/internal/overlay-container.js +1 -1
- package/dist/internal/overlay.d.ts +3 -3
- package/dist/internal/overlay.js +40 -10
- package/dist/internal/password-strength.d.ts +6 -0
- package/dist/internal/password-strength.js +74 -0
- package/dist/internal/roving.d.ts +2 -2
- package/dist/internal/roving.js +3 -3
- 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/slot.d.ts +3 -3
- package/dist/internal/slot.js +2 -2
- package/dist/internal/step.d.ts +12 -0
- package/dist/internal/step.js +31 -0
- package/dist/internal/tween.d.ts +5 -5
- package/dist/internal/tween.js +7 -8
- package/dist/internal/types.d.ts +2 -2
- package/dist/internal/use-breakpoint.d.ts +2 -2
- package/dist/internal/use-breakpoint.js +4 -4
- package/dist/internal/use-controllable-state.d.ts +5 -0
- package/dist/internal/use-controllable-state.js +25 -4
- package/dist/internal/use-id.d.ts +2 -2
- package/dist/internal/use-id.js +2 -2
- package/dist/internal/use-modal-focus.d.ts +2 -1
- package/dist/internal/use-modal-focus.js +14 -3
- package/dist/knob/knob.d.ts +7 -4
- package/dist/knob/knob.js +27 -22
- package/dist/knob/llms.txt +18 -11
- package/dist/listbox/listbox.d.ts +8 -6
- package/dist/listbox/listbox.js +37 -17
- package/dist/listbox/llms.txt +12 -9
- package/dist/loader/llms.txt +56 -0
- package/dist/loader/loader.d.ts +49 -0
- package/dist/loader/loader.js +68 -0
- package/dist/map/llms.txt +30 -23
- package/dist/map/map-create-dialog.d.ts +4 -4
- package/dist/map/map-create-dialog.js +36 -28
- package/dist/map/map-hours-field.d.ts +4 -4
- package/dist/map/map-hours-field.js +8 -7
- package/dist/map/map-image-field.d.ts +2 -2
- package/dist/map/map-image-field.js +6 -5
- package/dist/map/map-legend.d.ts +4 -4
- package/dist/map/map-legend.js +3 -2
- package/dist/map/map-poi-card.d.ts +3 -3
- package/dist/map/map-poi-card.js +10 -9
- package/dist/map/map-search.d.ts +17 -6
- package/dist/map/map-search.js +70 -43
- package/dist/map/map.d.ts +25 -13
- package/dist/map/map.js +86 -57
- package/dist/map/ports.d.ts +9 -9
- package/dist/map/ports.js +6 -6
- package/dist/map-entry/public-api.js +2 -2
- package/dist/megamenu/llms.txt +18 -7
- package/dist/megamenu/megamenu.d.ts +12 -6
- package/dist/megamenu/megamenu.js +23 -14
- package/dist/mention/llms.txt +15 -15
- package/dist/mention/mention.d.ts +5 -5
- package/dist/mention/mention.js +2 -2
- package/dist/mention/mentions.d.ts +1 -1
- package/dist/mention/mentions.js +1 -1
- package/dist/menu/llms.txt +12 -12
- package/dist/menu/menu-item.d.ts +4 -4
- package/dist/menu/menu-keys.d.ts +2 -2
- package/dist/menu/menu-keys.js +5 -5
- package/dist/menu/menu-rows.js +1 -0
- package/dist/menu/menu.d.ts +2 -2
- package/dist/menu/menu.js +15 -8
- package/dist/menubar/llms.txt +19 -8
- package/dist/menubar/menubar.d.ts +11 -5
- package/dist/menubar/menubar.js +23 -11
- package/dist/meter-group/llms.txt +12 -12
- package/dist/meter-group/meter-group.d.ts +2 -2
- package/dist/meter-group/meter-group.js +2 -1
- package/dist/motion/motion-context.d.ts +22 -0
- package/dist/motion/motion-context.js +28 -0
- package/dist/order-list/llms.txt +24 -9
- package/dist/order-list/order-list.d.ts +12 -4
- package/dist/order-list/order-list.js +123 -25
- package/dist/paginator/llms.txt +8 -8
- package/dist/paginator/paginator.js +7 -4
- package/dist/panel/llms.txt +25 -13
- package/dist/panel/panel.d.ts +11 -3
- package/dist/panel/panel.js +12 -7
- package/dist/password/llms.txt +19 -7
- package/dist/password/password.d.ts +2 -2
- package/dist/password/password.js +27 -16
- package/dist/peek/llms.txt +11 -11
- package/dist/peek/peek.d.ts +6 -6
- package/dist/peek/peek.js +41 -19
- package/dist/pick-list/llms.txt +35 -12
- package/dist/pick-list/pick-list.d.ts +12 -4
- package/dist/pick-list/pick-list.js +128 -24
- package/dist/popover/llms.txt +13 -10
- package/dist/popover/popover.d.ts +6 -4
- package/dist/popover/popover.js +14 -7
- package/dist/progress/llms.txt +17 -17
- package/dist/progress/progress.d.ts +7 -7
- package/dist/progress/progress.js +7 -6
- package/dist/public-api.d.ts +16 -4
- package/dist/public-api.js +13 -7
- package/dist/radio-group/llms.txt +16 -6
- package/dist/radio-group/radio-group.d.ts +12 -3
- package/dist/radio-group/radio-group.js +23 -4
- package/dist/rating/llms.txt +20 -13
- package/dist/rating/rating-pointer.d.ts +16 -0
- package/dist/rating/rating-pointer.js +26 -0
- package/dist/rating/rating.d.ts +6 -6
- package/dist/rating/rating.js +77 -12
- package/dist/rich-text/lists.d.ts +2 -2
- package/dist/rich-text/lists.js +7 -7
- package/dist/rich-text/llms.txt +35 -12
- package/dist/rich-text/rich-text.d.ts +13 -14
- package/dist/rich-text/rich-text.js +311 -142
- package/dist/rich-text/sanitize.d.ts +11 -2
- package/dist/rich-text/sanitize.js +80 -32
- package/dist/rich-text/tables.d.ts +3 -3
- package/dist/rich-text/tables.js +3 -3
- package/dist/roadmap/llms.txt +23 -17
- package/dist/roadmap/roadmap.d.ts +6 -6
- package/dist/roadmap/roadmap.js +102 -25
- package/dist/roadmap-entry/public-api.js +2 -2
- package/dist/scroll-top/llms.txt +6 -6
- package/dist/scroll-top/scroll-top.d.ts +1 -1
- package/dist/scroll-top/scroll-top.js +2 -1
- package/dist/select/llms.txt +22 -12
- package/dist/select/select.d.ts +10 -8
- package/dist/select/select.js +87 -47
- package/dist/shared/types.d.ts +22 -1
- package/dist/shared/types.js +9 -1
- package/dist/sidenav/llms.txt +9 -9
- package/dist/sidenav/sidenav.d.ts +2 -3
- package/dist/sidenav/sidenav.js +5 -4
- package/dist/skeleton/llms.txt +3 -3
- package/dist/skeleton/skeleton.d.ts +1 -1
- package/dist/skeleton/skeleton.js +1 -1
- package/dist/slider/llms.txt +34 -12
- package/dist/slider/slider-drag.d.ts +26 -0
- package/dist/slider/slider-drag.js +79 -0
- package/dist/slider/slider.d.ts +8 -5
- package/dist/slider/slider.js +38 -8
- package/dist/speech/llms.txt +97 -0
- package/dist/speech/speech-button.d.ts +44 -0
- package/dist/speech/speech-button.js +86 -0
- package/dist/speech/speech-wave.d.ts +19 -0
- package/dist/speech/speech-wave.js +16 -0
- package/dist/speech/use-speech-recognition.d.ts +81 -0
- package/dist/speech/use-speech-recognition.js +179 -0
- package/dist/speed-dial/llms.txt +14 -8
- package/dist/speed-dial/speed-dial.d.ts +1 -1
- package/dist/speed-dial/speed-dial.js +3 -2
- package/dist/spinner/llms.txt +7 -7
- package/dist/spinner/spinner.d.ts +1 -1
- package/dist/spinner/spinner.js +2 -1
- package/dist/split-button/llms.txt +21 -15
- package/dist/split-button/split-button.d.ts +2 -2
- package/dist/split-button/split-button.js +13 -6
- package/dist/splitter/llms.txt +11 -11
- package/dist/splitter/splitter.d.ts +3 -3
- package/dist/splitter/splitter.js +25 -10
- package/dist/stepper/llms.txt +35 -13
- package/dist/stepper/stepper.d.ts +6 -6
- package/dist/stepper/stepper.js +60 -13
- package/dist/switch/llms.txt +15 -5
- package/dist/switch/switch.d.ts +10 -3
- package/dist/switch/switch.js +18 -4
- package/dist/switch-group/llms.txt +11 -8
- package/dist/switch-group/switch-group.d.ts +6 -4
- package/dist/switch-group/switch-group.js +9 -7
- package/dist/tabs/llms.txt +12 -9
- package/dist/tabs/tabs.d.ts +5 -5
- package/dist/tabs/tabs.js +36 -20
- package/dist/textarea/llms.txt +23 -12
- package/dist/textarea/textarea.d.ts +19 -6
- package/dist/textarea/textarea.js +22 -7
- package/dist/timeline/llms.txt +4 -4
- package/dist/timeline/timeline.d.ts +1 -1
- package/dist/timeline/timeline.js +1 -1
- package/dist/toast/llms.txt +50 -17
- package/dist/toast/toast-container.d.ts +9 -3
- package/dist/toast/toast-container.js +18 -9
- package/dist/toast/toast-store.d.ts +42 -2
- package/dist/toast/toast-store.js +36 -1
- package/dist/toolbar/llms.txt +7 -7
- package/dist/toolbar/toolbar.d.ts +3 -3
- package/dist/toolbar/toolbar.js +2 -2
- package/dist/tooltip/llms.txt +14 -12
- package/dist/tooltip/tooltip.d.ts +5 -4
- package/dist/tooltip/tooltip.js +100 -41
- package/dist/tree/llms.txt +25 -11
- package/dist/tree/tree-keys.d.ts +5 -5
- package/dist/tree/tree-keys.js +7 -7
- package/dist/tree/tree.d.ts +17 -5
- package/dist/tree/tree.js +62 -20
- package/dist/update/llms.txt +54 -0
- package/dist/update/update.d.ts +49 -0
- package/dist/update/update.js +82 -0
- package/package.json +18 -15
package/dist/clock/clock.js
CHANGED
|
@@ -1,26 +1,56 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
3
|
import { useRef, useState } from 'react';
|
|
3
|
-
import { from12h, handAngle, handIsInner, hourFromAngle, hourMarks, minuteFromAngle, minuteMarks, pad, parseTime, } from '@konce-pt/datetime';
|
|
4
|
+
import { dayPeriodLabels, from12h, handAngle, handIsInner, hourFromAngle, hourMarks, minuteFromAngle, minuteMarks, pad, parseTime, stepClockTime, } from '@konce-pt/datetime';
|
|
4
5
|
import { cn } from "../internal/cn.js";
|
|
6
|
+
import { useKptMessages } from "../i18n/i18n-context.js";
|
|
7
|
+
/** Klawisze segmentu czasu -> liczba kroków. */
|
|
8
|
+
const SEGMENT_STEPS = { ArrowUp: 1, ArrowDown: -1, PageUp: 5, PageDown: -5 };
|
|
5
9
|
/**
|
|
6
|
-
* Tarcza zegara
|
|
7
|
-
* wewnętrzny pierścień 13
|
|
8
|
-
* i `KptDateRange`.
|
|
10
|
+
* Tarcza zegara - dwa etapy: godziny -> minuty, klikiem albo przeciąganiem. W trybie 24h dochodzi
|
|
11
|
+
* wewnętrzny pierścień 13-00; w 12h przełącznik AM/PM z etykietami pory dnia z `Intl`. Używana
|
|
12
|
+
* przez `KptDatepicker` i `KptDateRange`.
|
|
13
|
+
*
|
|
14
|
+
* Klawiatura: segmenty godzin i minut to `role="spinbutton"` - `ArrowUp`/`ArrowDown` o jeden krok (minuty
|
|
15
|
+
* o `minuteStep`), `PageUp`/`PageDown` o pięć, `Home`/`End` do granic. Tak da się ustawić każdą
|
|
16
|
+
* minutę, także spoza znaczników tarczy co pięć.
|
|
9
17
|
*
|
|
10
18
|
* @example
|
|
11
19
|
* <KptClock value={time} onValueChange={setTime} hourFormat={12} minuteStep={5} />
|
|
12
20
|
*/
|
|
13
|
-
export function KptClock({ value = '', onValueChange, hourFormat = 24, minuteStep = 1, className, ...rest }) {
|
|
21
|
+
export function KptClock({ value = '', onValueChange, hourFormat = 24, minuteStep = 1, locale, dictionary, className, ...rest }) {
|
|
22
|
+
const { t, locale: activeLocale } = useKptMessages({ locale, dictionary });
|
|
14
23
|
const faceRef = useRef(null);
|
|
15
24
|
const [phase, setPhase] = useState('hours');
|
|
16
|
-
// Wartość jest jedynym źródłem prawdy
|
|
25
|
+
// Wartość jest jedynym źródłem prawdy - stan tarczy wyliczamy z niej przy każdym renderze,
|
|
17
26
|
// więc podmiana `value` z zewnątrz nie wymaga synchronizacji.
|
|
18
27
|
const parsed = parseTime(value);
|
|
19
28
|
const hour = parsed?.h ?? 0;
|
|
20
29
|
const minute = parsed?.m ?? 0;
|
|
21
30
|
const ampm = hour < 12 ? 'AM' : 'PM';
|
|
22
31
|
const hour12 = hour % 12 === 0 ? 12 : hour % 12;
|
|
32
|
+
const periods = dayPeriodLabels(activeLocale);
|
|
33
|
+
const hourLabel = hourFormat === 12 ? String(hour12) : pad(hour);
|
|
34
|
+
// W 12h czytnik słyszy godzinę z porą dnia ("9 PM"), nie samą liczbę 21.
|
|
35
|
+
const hourText = hourFormat === 12 ? `${hour12} ${ampm === 'AM' ? periods.am : periods.pm}` : hourLabel;
|
|
23
36
|
const emit = (h, m) => onValueChange?.(`${pad(h)}:${pad(m)}`);
|
|
37
|
+
const onSegmentKey = (part, event) => {
|
|
38
|
+
let next;
|
|
39
|
+
if (event.key === 'Home') {
|
|
40
|
+
next = part === 'hours' ? { h: 0, m: minute } : { h: hour, m: 0 };
|
|
41
|
+
}
|
|
42
|
+
else if (event.key === 'End') {
|
|
43
|
+
next = part === 'hours' ? { h: 23, m: minute } : { h: hour, m: 60 - Math.max(1, minuteStep) };
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
const delta = SEGMENT_STEPS[event.key];
|
|
47
|
+
if (delta === undefined)
|
|
48
|
+
return;
|
|
49
|
+
next = stepClockTime(hour, minute, part, delta, minuteStep);
|
|
50
|
+
}
|
|
51
|
+
event.preventDefault();
|
|
52
|
+
emit(next.h, next.m);
|
|
53
|
+
};
|
|
24
54
|
const onDialPointer = (event) => {
|
|
25
55
|
const face = faceRef.current;
|
|
26
56
|
if (!face)
|
|
@@ -28,7 +58,7 @@ export function KptClock({ value = '', onValueChange, hourFormat = 24, minuteSte
|
|
|
28
58
|
if (event.type === 'pointerdown')
|
|
29
59
|
face.setPointerCapture(event.pointerId);
|
|
30
60
|
// Przy ruchu bez wciśniętego przycisku tarcza ma tylko śledzić kursor wzrokiem, nie zmieniać
|
|
31
|
-
// wartości
|
|
61
|
+
// wartości - inaczej samo przesunięcie myszy nad zegarem przestawiałoby godzinę.
|
|
32
62
|
else if (!(event.buttons & 1))
|
|
33
63
|
return;
|
|
34
64
|
const rect = face.getBoundingClientRect();
|
|
@@ -45,7 +75,7 @@ export function KptClock({ value = '', onValueChange, hourFormat = 24, minuteSte
|
|
|
45
75
|
}
|
|
46
76
|
};
|
|
47
77
|
const marks = phase === 'hours' ? hourMarks(hour, hourFormat) : minuteMarks(minute);
|
|
48
|
-
return (_jsx("div", { className: cn('kpt-clock-host', className), ...rest, children: _jsxs("div", { className: "kpt-clock", children: [_jsxs("div", { className: "kpt-clock__time", children: [_jsx("
|
|
78
|
+
return (_jsx("div", { className: cn('kpt-clock-host', className), ...rest, children: _jsxs("div", { className: "kpt-clock", children: [_jsxs("div", { className: "kpt-clock__time", children: [_jsx("span", { className: cn('kpt-clock__seg', phase === 'hours' && 'is-active'), role: "spinbutton", tabIndex: 0, "aria-label": t('clock.hours'), "aria-valuemin": 0, "aria-valuemax": 23, "aria-valuenow": hour, "aria-valuetext": hourText, onClick: () => setPhase('hours'), onFocus: () => setPhase('hours'), onKeyDown: (event) => onSegmentKey('hours', event), children: hourLabel }), _jsx("span", { className: "kpt-clock__colon", "aria-hidden": "true", children: ":" }), _jsx("span", { className: cn('kpt-clock__seg', phase === 'minutes' && 'is-active'), role: "spinbutton", tabIndex: 0, "aria-label": t('clock.minutes'), "aria-valuemin": 0, "aria-valuemax": 59, "aria-valuenow": minute, "aria-valuetext": pad(minute), onClick: () => setPhase('minutes'), onFocus: () => setPhase('minutes'), onKeyDown: (event) => onSegmentKey('minutes', event), children: pad(minute) }), hourFormat === 12 ? (_jsxs("div", { className: "kpt-clock__ampm", role: "group", children: [_jsx("button", { type: "button", className: cn(ampm === 'AM' && 'is-active'), "aria-pressed": ampm === 'AM', onClick: () => emit(from12h(hour12, 'AM'), minute), children: periods.am }), _jsx("button", { type: "button", className: cn(ampm === 'PM' && 'is-active'), "aria-pressed": ampm === 'PM', onClick: () => emit(from12h(hour12, 'PM'), minute), children: periods.pm })] })) : null] }), _jsxs("div", { ref: faceRef, className: "kpt-clock__face", role: "group", "aria-label": phase === 'hours' ? t('clock.hours') : t('clock.minutes'), onPointerDown: onDialPointer, onPointerMove: onDialPointer, onPointerUp: () => phase === 'hours' && setPhase('minutes'), children: [_jsx("span", { className: "kpt-clock__center" }), _jsx("span", { className: cn('kpt-clock__hand', handIsInner(hour, hourFormat, phase) && 'kpt-clock__hand--inner'), style: { transform: `rotate(${handAngle(hour, minute, phase)}deg)` } }), marks.map((mark) => (_jsx("button", { type: "button", className: cn('kpt-clock__num', mark.inner && 'kpt-clock__num--inner', mark.selected && 'kpt-clock__num--selected'), "aria-pressed": mark.selected, style: { left: `${mark.x}%`, top: `${mark.y}%` }, onClick: () => {
|
|
49
79
|
if (phase === 'hours') {
|
|
50
80
|
emit(mark.value, minute);
|
|
51
81
|
setPhase('minutes');
|
package/dist/clock/llms.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# KptClock (@konce-pt/react)
|
|
2
2
|
|
|
3
|
-
A clock dial
|
|
3
|
+
A clock dial - two stages: hours -> minutes, by click or by drag. Used by `KptDatepicker` and
|
|
4
4
|
`KptDateRange`, and available on its own.
|
|
5
5
|
Import: `import { KptClock } from '@konce-pt/react';`
|
|
6
6
|
|
|
@@ -9,19 +9,19 @@ Import: `import { KptClock } from '@konce-pt/react';`
|
|
|
9
9
|
<div class="kpt-clock">
|
|
10
10
|
<div class="kpt-clock__time">
|
|
11
11
|
<button class="kpt-clock__seg is-active">15</button>:<button class="kpt-clock__seg">30</button>
|
|
12
|
-
<div class="kpt-clock__ampm"
|
|
12
|
+
<div class="kpt-clock__ampm">...12h only...</div>
|
|
13
13
|
</div>
|
|
14
14
|
<div class="kpt-clock__face">
|
|
15
15
|
<span class="kpt-clock__hand"></span>
|
|
16
|
-
<button class="kpt-clock__num kpt-clock__num--inner">15</button
|
|
16
|
+
<button class="kpt-clock__num kpt-clock__num--inner">15</button>...
|
|
17
17
|
</div>
|
|
18
18
|
</div>
|
|
19
19
|
</div>
|
|
20
20
|
|
|
21
21
|
## Props
|
|
22
|
-
- `value` / `onValueChange`
|
|
22
|
+
- `value` / `onValueChange` - 'HH:mm', always 24h regardless of `hourFormat`
|
|
23
23
|
- `hourFormat`: 12 | 24 (default 24)
|
|
24
|
-
- `minuteStep`: number (default 1)
|
|
24
|
+
- `minuteStep`: number (default 1) - minute snapping while dragging
|
|
25
25
|
- `className`, `ref` and every other `<div>` prop go to the host
|
|
26
26
|
|
|
27
27
|
## The value is the only state
|
|
@@ -29,7 +29,7 @@ Hours and minutes are derived from `value` on every render, so replacing it from
|
|
|
29
29
|
synchronisation. The component keeps one piece of state of its own: which stage the dial is on.
|
|
30
30
|
|
|
31
31
|
## Two rings in 24h
|
|
32
|
-
12 and 1
|
|
32
|
+
12 and 1-11 sit on the outer ring, 00 and 13-23 on the inner one, because 24 numbers on one circle
|
|
33
33
|
are unreadable. While dragging, the click radius decides the ring; in 12h the AM/PM toggle decides
|
|
34
34
|
the half-day instead.
|
|
35
35
|
|
|
@@ -46,5 +46,9 @@ The dial `--kpt-color-surface-raised`, the hand and the selection `--kpt-color-p
|
|
|
46
46
|
the numbers `--kpt-color-on-surface`.
|
|
47
47
|
|
|
48
48
|
## Accessibility
|
|
49
|
-
Every mark is a real `<button
|
|
50
|
-
segments are
|
|
49
|
+
Every mark is a real `<button>` with `aria-pressed`, inside a group named "Hours"/"Minutes". The hour and
|
|
50
|
+
minute segments are `role="spinbutton"`: Tab to them, `ArrowUp`/`ArrowDown` step by one (minutes by `minuteStep`),
|
|
51
|
+
`PageUp`/`PageDown` by five, `Home`/`End` to the bounds - so every minute is reachable, not only the
|
|
52
|
+
five-minute marks. Focusing a segment switches the stage. AM/PM labels come from `Intl` for the active
|
|
53
|
+
language and the hour's `aria-valuetext` includes the period ("9 PM"). `locale`/`dictionary` override the
|
|
54
|
+
language for this component.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ComponentPropsWithRef } from 'react';
|
|
2
2
|
import { type KptI18nOverrides } from '../i18n/i18n-context.tsx';
|
|
3
|
-
/** Gotowy kolor z nazwą
|
|
3
|
+
/** Gotowy kolor z nazwą - nazwa trafia do `aria-label` próbki. */
|
|
4
4
|
export interface KptColorPreset {
|
|
5
5
|
value: string;
|
|
6
6
|
label: string;
|
|
@@ -12,14 +12,18 @@ export interface KptColorPickerProps extends Omit<ComponentPropsWithRef<'div'>,
|
|
|
12
12
|
onValueChange?: (value: string) => void;
|
|
13
13
|
/**
|
|
14
14
|
* Paleta szybkiego wyboru: kody albo `{ value, label }`. Kod sam w sobie niewiele mówi
|
|
15
|
-
* czytnikowi (
|
|
15
|
+
* czytnikowi ("#3b82f6"), więc własną paletę warto nazwać; domyślne kolory mają nazwy z i18n.
|
|
16
16
|
*/
|
|
17
17
|
presets?: readonly (string | KptColorPreset)[];
|
|
18
|
-
/** Id elementu fokusowalnego (nie hosta); bez niego generowane, w `KptFormField`
|
|
18
|
+
/** Id elementu fokusowalnego (nie hosta); bez niego generowane, w `KptFormField` - z wrappera. */
|
|
19
19
|
id?: string;
|
|
20
20
|
/** Nazwa dostępna, gdy kontrolki nie opisuje etykieta (`<label for>` albo `KptFormField`). */
|
|
21
21
|
ariaLabel?: string | null;
|
|
22
22
|
disabled?: boolean;
|
|
23
|
+
/** Tylko do odczytu - kolor widać, nie da się go zmienić. */
|
|
24
|
+
readOnly?: boolean;
|
|
25
|
+
/** Pole wymagane - `aria-required` na polu kodu. */
|
|
26
|
+
required?: boolean;
|
|
23
27
|
invalid?: boolean;
|
|
24
28
|
touched?: boolean;
|
|
25
29
|
onTouch?: () => void;
|
|
@@ -27,10 +31,10 @@ export interface KptColorPickerProps extends Omit<ComponentPropsWithRef<'div'>,
|
|
|
27
31
|
/**
|
|
28
32
|
* Wybór koloru: próbka, pole hex i presety. Wartością jest hex.
|
|
29
33
|
*
|
|
30
|
-
* Próbka to natywny `<input type="color">` schowany pod kolorowym kwadratem
|
|
34
|
+
* Próbka to natywny `<input type="color">` schowany pod kolorowym kwadratem - paletę systemową
|
|
31
35
|
* otwiera przeglądarka, więc działa też na telefonie i nie duplikujemy jej własnym kodem.
|
|
32
36
|
*
|
|
33
37
|
* @example
|
|
34
38
|
* <KptColorPicker value={color} onValueChange={setColor} />
|
|
35
39
|
*/
|
|
36
|
-
export declare function KptColorPicker({ value, defaultValue, onValueChange, presets, id, ariaLabel, disabled, invalid, touched, onTouch, locale, dictionary, className, ...rest }: Readonly<KptColorPickerProps>): import("react").JSX.Element;
|
|
40
|
+
export declare function KptColorPicker({ value, defaultValue, onValueChange, presets, id, ariaLabel, disabled, readOnly, required, invalid, touched, onTouch, locale, dictionary, className, ...rest }: Readonly<KptColorPickerProps>): 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 { useState } from 'react';
|
|
3
4
|
import { cn } from "../internal/cn.js";
|
|
@@ -16,22 +17,35 @@ const DEFAULT_NAMES = {
|
|
|
16
17
|
'#111827': 'black',
|
|
17
18
|
'#ffffff': 'white',
|
|
18
19
|
};
|
|
19
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Kod z pola tekstowego jako `#rrggbb` albo `null`. Przyjmuje zapis bez `#` i skrót `#rgb`
|
|
22
|
+
* (`#fff` -> `#ffffff`). Bliźniak w porcie Angulara (`color-picker.component.ts`) - zmiana w obu.
|
|
23
|
+
*/
|
|
24
|
+
function parseHex(text) {
|
|
25
|
+
const raw = text.trim().replace(/^#/, '').toLowerCase();
|
|
26
|
+
if (/^[0-9a-f]{3}$/.test(raw))
|
|
27
|
+
return `#${[...raw].map((c) => c + c).join('')}`;
|
|
28
|
+
return /^[0-9a-f]{6}$/.test(raw) ? `#${raw}` : null;
|
|
29
|
+
}
|
|
20
30
|
/**
|
|
21
31
|
* Wybór koloru: próbka, pole hex i presety. Wartością jest hex.
|
|
22
32
|
*
|
|
23
|
-
* Próbka to natywny `<input type="color">` schowany pod kolorowym kwadratem
|
|
33
|
+
* Próbka to natywny `<input type="color">` schowany pod kolorowym kwadratem - paletę systemową
|
|
24
34
|
* otwiera przeglądarka, więc działa też na telefonie i nie duplikujemy jej własnym kodem.
|
|
25
35
|
*
|
|
26
36
|
* @example
|
|
27
37
|
* <KptColorPicker value={color} onValueChange={setColor} />
|
|
28
38
|
*/
|
|
29
|
-
export function KptColorPicker({ value, defaultValue = '#3b82f6', onValueChange, presets = DEFAULT_PRESETS, id, ariaLabel = null, disabled = false, invalid = false, touched, onTouch, locale, dictionary, className, ...rest }) {
|
|
39
|
+
export function KptColorPicker({ value, defaultValue = '#3b82f6', onValueChange, presets = DEFAULT_PRESETS, id, ariaLabel = null, disabled = false, readOnly = false, required = false, invalid = false, touched, onTouch, locale, dictionary, className, ...rest }) {
|
|
30
40
|
const { t } = useKptMessages({ locale, dictionary });
|
|
31
41
|
const field = useKptFieldA11y(id, 'kpt-color-picker');
|
|
32
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* Tekst w polu hex dokładnie tak, jak go wpisano, do wyjścia z pola. Wartość zmienia się, gdy tekst
|
|
44
|
+
* staje się poprawnym kodem, ale pole nie jest przepisywane w trakcie pisania (doklejenie `#`
|
|
45
|
+
* przenosiło kursor na koniec przy edycji w środku).
|
|
46
|
+
*/
|
|
33
47
|
const [hexDraft, setHexDraft] = useState(null);
|
|
34
|
-
const hexInvalid = hexDraft !== null &&
|
|
48
|
+
const hexInvalid = hexDraft !== null && hexDraft.trim() !== '' && parseHex(hexDraft) === null;
|
|
35
49
|
const presetList = presets.map((preset) => {
|
|
36
50
|
const item = typeof preset === 'string' ? { value: preset, label: '' } : preset;
|
|
37
51
|
const named = DEFAULT_NAMES[item.value.toLowerCase()];
|
|
@@ -39,24 +53,29 @@ export function KptColorPicker({ value, defaultValue = '#3b82f6', onValueChange,
|
|
|
39
53
|
});
|
|
40
54
|
const isActive = (color) => color.toLowerCase() === current.toLowerCase();
|
|
41
55
|
const [current, setCurrent] = useControllableState({ value, defaultValue, onChange: onValueChange });
|
|
42
|
-
return (_jsx("div", { className: cn('kpt-color-picker-host', className), ...rest, children: _jsxs("div", { className: "kpt-color-picker", children: [_jsx("label", { className: "kpt-color-picker__swatch", style: { backgroundColor: current }, children: _jsx("input", { type: "color", value: current, disabled: disabled, "aria-label": t('colorPicker.picker'),
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
56
|
+
return (_jsx("div", { className: cn('kpt-color-picker-host', className), ...rest, children: _jsxs("div", { className: "kpt-color-picker", children: [_jsx("label", { className: "kpt-color-picker__swatch", style: { backgroundColor: current }, children: _jsx("input", { type: "color", value: current, disabled: disabled, "aria-label": t('colorPicker.picker'), "aria-readonly": readOnly || undefined, onClick: (event) => {
|
|
57
|
+
if (readOnly)
|
|
58
|
+
event.preventDefault();
|
|
59
|
+
}, onChange: (event) => {
|
|
60
|
+
if (readOnly)
|
|
61
|
+
return;
|
|
47
62
|
setHexDraft(null);
|
|
48
|
-
setCurrent(
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
63
|
+
setCurrent(event.target.value);
|
|
64
|
+
}, onBlur: () => onTouch?.() }) }), _jsx("input", { type: "text", className: "kpt-color-picker__hex", id: field.id, "aria-label": field.labelledBy ? undefined : (ariaLabel ?? t('colorPicker.hex')), "aria-describedby": field.describedBy, value: hexDraft ?? current, disabled: disabled, readOnly: readOnly, "aria-required": required || undefined, maxLength: 7, "aria-invalid": (invalid && (touched ?? true)) || hexInvalid || undefined, onChange: (event) => {
|
|
65
|
+
const text = event.target.value;
|
|
66
|
+
setHexDraft(text);
|
|
67
|
+
const hex = parseHex(text);
|
|
68
|
+
if (hex)
|
|
69
|
+
setCurrent(hex);
|
|
53
70
|
}, onBlur: () => {
|
|
54
|
-
//
|
|
55
|
-
if (hexDraft === '')
|
|
71
|
+
// Poprawny albo pusty kod chowa szkic; niepełny zostaje widoczny z `aria-invalid`.
|
|
72
|
+
if (hexDraft !== null && (hexDraft.trim() === '' || parseHex(hexDraft)))
|
|
56
73
|
setHexDraft(null);
|
|
57
74
|
onTouch?.();
|
|
58
|
-
} }), _jsx("div", { className: "kpt-color-picker__presets", children: presetList.map((preset) => (_jsx("button", { type: "button", className: cn('kpt-color-picker__preset', isActive(preset.value) && 'is-active'), style: { backgroundColor: preset.value }, disabled: disabled, "aria-pressed": isActive(preset.value), onClick: () => {
|
|
75
|
+
} }), _jsx("div", { className: "kpt-color-picker__presets", children: presetList.map((preset) => (_jsx("button", { type: "button", className: cn('kpt-color-picker__preset', isActive(preset.value) && 'is-active'), style: { backgroundColor: preset.value }, disabled: disabled || readOnly, "aria-pressed": isActive(preset.value), onClick: () => {
|
|
59
76
|
setHexDraft(null);
|
|
60
77
|
setCurrent(preset.value);
|
|
78
|
+
// Wybór presetem to też dotknięcie pola - inaczej błąd walidacji nigdy by się nie pokazał.
|
|
79
|
+
onTouch?.();
|
|
61
80
|
}, "aria-label": preset.label }, preset.value))) })] }) }));
|
|
62
81
|
}
|
|
@@ -11,20 +11,20 @@ Import: `import { KptColorPicker } from '@konce-pt/react';`
|
|
|
11
11
|
</label>
|
|
12
12
|
<input type="text" class="kpt-color-picker__hex" maxlength="7" value="#3b82f6">
|
|
13
13
|
<div class="kpt-color-picker__presets">
|
|
14
|
-
<button class="kpt-color-picker__preset is-active" aria-label="#3b82f6"></button
|
|
14
|
+
<button class="kpt-color-picker__preset is-active" aria-label="#3b82f6"></button>...
|
|
15
15
|
</div>
|
|
16
16
|
</div>
|
|
17
17
|
</div>
|
|
18
18
|
|
|
19
19
|
## Props
|
|
20
|
-
- `value` / `defaultValue` / `onValueChange`
|
|
21
|
-
- `presets`: string[]
|
|
20
|
+
- `value` / `defaultValue` / `onValueChange` - the hex colour (default `'#3b82f6'`)
|
|
21
|
+
- `presets`: string[] - the quick-pick palette (8 colours by default; `[]` hides the row)
|
|
22
22
|
- `disabled`, `invalid`, `touched`, `onTouch()`
|
|
23
23
|
- `className`, `ref` and every other `<div>` prop go to the host
|
|
24
24
|
|
|
25
25
|
## The swatch is a native colour input
|
|
26
26
|
`<input type="color">` sits transparent under the coloured square, so the system palette is the
|
|
27
|
-
browser's
|
|
27
|
+
browser's - it works on a phone and there is no picker of ours to maintain. The hex field edits the
|
|
28
28
|
same value as text, and the presets write it directly.
|
|
29
29
|
|
|
30
30
|
## Examples
|
|
@@ -34,7 +34,7 @@ same value as text, and the presets write it directly.
|
|
|
34
34
|
## Screen reader and keyboard
|
|
35
35
|
`presets` accepts codes or `{ value, label }` (the `KptColorPreset` type): the label names the
|
|
36
36
|
swatch, because a hex code says little to a screen reader; the default palette is named from i18n
|
|
37
|
-
(`colorPicker.red
|
|
37
|
+
(`colorPicker.red`...). The active swatch has `aria-pressed`. The hex field changes the value only on
|
|
38
38
|
a full `#rrggbb` code (a missing `#` is added); an incomplete code stays visible with
|
|
39
39
|
`aria-invalid`.
|
|
40
40
|
|
|
@@ -52,3 +52,6 @@ preset `--kpt-color-focus-ring`.
|
|
|
52
52
|
## Accessibility
|
|
53
53
|
Every preset is a `<button>` labelled with its hex value; the hex field carries `aria-invalid` once
|
|
54
54
|
the field is touched.
|
|
55
|
+
|
|
56
|
+
## Read-only and required
|
|
57
|
+
`readOnly` blocks the picker, the code field and the presets; `required` sets `aria-required` on the code field. Picking a preset or a colour counts as a touch. The code field takes `fff`, `#fff` and `#ffffff` and is not rewritten while typing, so the caret does not jump.
|
|
@@ -3,15 +3,15 @@ import { type KptI18nOverrides } from '../i18n/i18n-context.tsx';
|
|
|
3
3
|
export interface KptConfirmContainerProps extends ComponentPropsWithRef<'div'>, KptI18nOverrides {
|
|
4
4
|
}
|
|
5
5
|
/**
|
|
6
|
-
* Kontener potwierdzeń
|
|
6
|
+
* Kontener potwierdzeń - renderuje żądanie z `kptConfirm()` na `KptDialog`. Umieść raz
|
|
7
7
|
* w aplikacji, obok `<KptToastContainer />`.
|
|
8
8
|
*
|
|
9
9
|
* Okno to `role="alertdialog"` z treścią pytania w `aria-describedby`, więc czytnik odczytuje
|
|
10
|
-
* je od razu po otwarciu. Fokus startuje na przycisku odmowy
|
|
10
|
+
* je od razu po otwarciu. Fokus startuje na przycisku odmowy - bezpieczniejszej odpowiedzi,
|
|
11
11
|
* której przypadkowy `Enter` niczego nie usunie.
|
|
12
12
|
*
|
|
13
13
|
* @example
|
|
14
14
|
* <KptConfirmContainer />
|
|
15
|
-
* // gdziekolwiek: if (await kptConfirm({ message: 'Usunąć?' })) {
|
|
15
|
+
* // gdziekolwiek: if (await kptConfirm({ message: 'Usunąć?' })) { ... }
|
|
16
16
|
*/
|
|
17
17
|
export declare function KptConfirmContainer({ locale, dictionary, className, ...rest }: Readonly<KptConfirmContainerProps>): 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, useRef } from 'react';
|
|
3
4
|
import { cn } from "../internal/cn.js";
|
|
@@ -8,30 +9,30 @@ import { KptDialog } from "../dialog/dialog.js";
|
|
|
8
9
|
import { KptIcon } from "../icon/icon.js";
|
|
9
10
|
import { acceptKptConfirm, rejectKptConfirm, useKptConfirmRequest } from "./confirm-store.js";
|
|
10
11
|
/**
|
|
11
|
-
* Kontener potwierdzeń
|
|
12
|
+
* Kontener potwierdzeń - renderuje żądanie z `kptConfirm()` na `KptDialog`. Umieść raz
|
|
12
13
|
* w aplikacji, obok `<KptToastContainer />`.
|
|
13
14
|
*
|
|
14
15
|
* Okno to `role="alertdialog"` z treścią pytania w `aria-describedby`, więc czytnik odczytuje
|
|
15
|
-
* je od razu po otwarciu. Fokus startuje na przycisku odmowy
|
|
16
|
+
* je od razu po otwarciu. Fokus startuje na przycisku odmowy - bezpieczniejszej odpowiedzi,
|
|
16
17
|
* której przypadkowy `Enter` niczego nie usunie.
|
|
17
18
|
*
|
|
18
19
|
* @example
|
|
19
20
|
* <KptConfirmContainer />
|
|
20
|
-
* // gdziekolwiek: if (await kptConfirm({ message: 'Usunąć?' })) {
|
|
21
|
+
* // gdziekolwiek: if (await kptConfirm({ message: 'Usunąć?' })) { ... }
|
|
21
22
|
*/
|
|
22
23
|
export function KptConfirmContainer({ locale, dictionary, className, ...rest }) {
|
|
23
24
|
const { t } = useKptMessages({ locale, dictionary });
|
|
24
25
|
const request = useKptConfirmRequest();
|
|
25
26
|
const messageId = useKptId(undefined, 'kpt-confirm-message');
|
|
26
27
|
const rejectRef = useRef(null);
|
|
27
|
-
// `showModal()` skupia pierwszy element okna, czyli
|
|
28
|
+
// `showModal()` skupia pierwszy element okna, czyli "x" w nagłówku. Efekt dziecka (`KptDialog`)
|
|
28
29
|
// biegnie przed efektem rodzica, więc tu okno jest już otwarte i fokus można przenieść.
|
|
29
30
|
useEffect(() => {
|
|
30
31
|
if (request)
|
|
31
32
|
rejectRef.current?.focus();
|
|
32
33
|
}, [request]);
|
|
33
34
|
return (_jsx("div", { className: cn('kpt-confirm-container-host', className), ...rest, children: _jsxs(KptDialog, { open: request !== null,
|
|
34
|
-
// Escape i
|
|
35
|
+
// Escape i x zamykają okno bez odpowiedzi - traktujemy to jak odmowę, żeby czekający
|
|
35
36
|
// `await` zawsze coś dostał.
|
|
36
37
|
onOpenChange: (open) => {
|
|
37
38
|
if (!open)
|
|
@@ -15,15 +15,15 @@ export interface KptConfirmRequest extends KptConfirmOptions {
|
|
|
15
15
|
* Pyta użytkownika o potwierdzenie. Wymaga `<KptConfirmContainer />` w aplikacji.
|
|
16
16
|
*
|
|
17
17
|
* Jedno żądanie naraz: kolejne wywołanie przy otwartym oknie odrzuca poprzednie, żeby jego
|
|
18
|
-
* Promise nigdy nie został nierozstrzygnięty
|
|
18
|
+
* Promise nigdy nie został nierozstrzygnięty - czekający `await` zostałby inaczej na zawsze.
|
|
19
19
|
*
|
|
20
20
|
* @example
|
|
21
|
-
* if (await kptConfirm({ message: 'Usunąć rekord?', variant: 'danger' })) {
|
|
21
|
+
* if (await kptConfirm({ message: 'Usunąć rekord?', variant: 'danger' })) { ... }
|
|
22
22
|
*/
|
|
23
23
|
export declare function kptConfirm(options: KptConfirmOptions): Promise<boolean>;
|
|
24
|
-
/** Zamyka bieżące potwierdzenie odpowiedzią
|
|
24
|
+
/** Zamyka bieżące potwierdzenie odpowiedzią "tak". Używane przez kontener. */
|
|
25
25
|
export declare function acceptKptConfirm(): void;
|
|
26
|
-
/** Zamyka bieżące potwierdzenie odpowiedzią
|
|
26
|
+
/** Zamyka bieżące potwierdzenie odpowiedzią "nie" - także przy Escape i kliknięciu w tło. */
|
|
27
27
|
export declare function rejectKptConfirm(): void;
|
|
28
28
|
/** Bieżące żądanie potwierdzenia albo `null`. */
|
|
29
29
|
export declare function useKptConfirmRequest(): KptConfirmRequest | null;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useSyncExternalStore } from 'react';
|
|
2
|
+
import { isDevBuild } from "../internal/dev.js";
|
|
2
3
|
/*
|
|
3
|
-
* Magazyn potwierdzeń jako moduł-singleton
|
|
4
|
+
* Magazyn potwierdzeń jako moduł-singleton - odpowiednik serwisu `providedIn: 'root'` z Angulara,
|
|
4
5
|
* ten sam wzorzec co `kptToast`. Dzięki temu `await kptConfirm(...)` woła się także spoza drzewa
|
|
5
6
|
* Reacta (warstwa danych, guard nawigacji), bez owijania aplikacji providerem.
|
|
6
7
|
*/
|
|
@@ -15,12 +16,17 @@ function emit() {
|
|
|
15
16
|
* Pyta użytkownika o potwierdzenie. Wymaga `<KptConfirmContainer />` w aplikacji.
|
|
16
17
|
*
|
|
17
18
|
* Jedno żądanie naraz: kolejne wywołanie przy otwartym oknie odrzuca poprzednie, żeby jego
|
|
18
|
-
* Promise nigdy nie został nierozstrzygnięty
|
|
19
|
+
* Promise nigdy nie został nierozstrzygnięty - czekający `await` zostałby inaczej na zawsze.
|
|
19
20
|
*
|
|
20
21
|
* @example
|
|
21
|
-
* if (await kptConfirm({ message: 'Usunąć rekord?', variant: 'danger' })) {
|
|
22
|
+
* if (await kptConfirm({ message: 'Usunąć rekord?', variant: 'danger' })) { ... }
|
|
22
23
|
*/
|
|
23
24
|
export function kptConfirm(options) {
|
|
25
|
+
// Kontener subskrybuje magazyn - bez subskrybentów nikt nie pokaże okna ani nie rozstrzygnie obietnicy.
|
|
26
|
+
if (isDevBuild() && listeners.size === 0) {
|
|
27
|
+
console.warn('kptConfirm: brak <KptConfirmContainer /> w aplikacji - okno się nie pokaże, ' +
|
|
28
|
+
'a obietnica nie rozstrzygnie się, dopóki kontener nie zostanie dodany.');
|
|
29
|
+
}
|
|
24
30
|
return new Promise((resolve) => {
|
|
25
31
|
current?.resolve(false);
|
|
26
32
|
current = { id: ++seq, ...options, resolve };
|
|
@@ -32,11 +38,11 @@ function settle(accepted) {
|
|
|
32
38
|
current = null;
|
|
33
39
|
emit();
|
|
34
40
|
}
|
|
35
|
-
/** Zamyka bieżące potwierdzenie odpowiedzią
|
|
41
|
+
/** Zamyka bieżące potwierdzenie odpowiedzią "tak". Używane przez kontener. */
|
|
36
42
|
export function acceptKptConfirm() {
|
|
37
43
|
settle(true);
|
|
38
44
|
}
|
|
39
|
-
/** Zamyka bieżące potwierdzenie odpowiedzią
|
|
45
|
+
/** Zamyka bieżące potwierdzenie odpowiedzią "nie" - także przy Escape i kliknięciu w tło. */
|
|
40
46
|
export function rejectKptConfirm() {
|
|
41
47
|
settle(false);
|
|
42
48
|
}
|
package/dist/confirm/llms.txt
CHANGED
|
@@ -6,23 +6,24 @@ Import: `import { kptConfirm, KptConfirmContainer } from '@konce-pt/react';`
|
|
|
6
6
|
|
|
7
7
|
## Usage
|
|
8
8
|
1. Render `<KptConfirmContainer />` once in the app, next to `<KptToastContainer />`.
|
|
9
|
-
2. `await kptConfirm(options)` from anywhere
|
|
9
|
+
2. `await kptConfirm(options)` from anywhere - it resolves to `true` or `false`.
|
|
10
10
|
|
|
11
11
|
## KptConfirmOptions
|
|
12
12
|
- `message`: string (required); `header?`: string
|
|
13
13
|
- `acceptLabel?`, `rejectLabel?`: string
|
|
14
|
-
- `variant?`: `'filled'` | `'danger'`
|
|
14
|
+
- `variant?`: `'filled'` | `'danger'` - the accept button and the icon colour
|
|
15
15
|
- `icon?`: an icon name from the registry
|
|
16
16
|
|
|
17
17
|
## A store, not a hook
|
|
18
|
-
The state is a module singleton, so `kptConfirm()` works outside the React tree too
|
|
19
|
-
a navigation guard
|
|
18
|
+
The state is a module singleton, so `kptConfirm()` works outside the React tree too - a data layer,
|
|
19
|
+
a navigation guard - without wrapping the app in a provider. `useKptConfirmRequest()` exposes the
|
|
20
20
|
current request through `useSyncExternalStore` for anyone rendering their own container.
|
|
21
21
|
|
|
22
22
|
## The promise always settles
|
|
23
|
-
Escape and the
|
|
23
|
+
Escape and the x close the dialog through `onOpenChange`, which resolves `false` - an awaiting
|
|
24
24
|
caller is never left hanging. A second `kptConfirm()` while one is open rejects the previous
|
|
25
|
-
request for the same reason: one request at a time, and nothing is dropped silently.
|
|
25
|
+
request for the same reason: one request at a time, and nothing is dropped silently. Without a
|
|
26
|
+
mounted `<KptConfirmContainer />` nobody can show the dialog, so a dev build warns in the console.
|
|
26
27
|
|
|
27
28
|
## Examples
|
|
28
29
|
<KptConfirmContainer />
|
|
@@ -43,4 +44,4 @@ Inherited from `KptDialog`; the icon follows the variant (`--kpt-color-danger` /
|
|
|
43
44
|
Built on `KptDialog`, so the focus trap, Escape and the backdrop are the native `<dialog>`'s.
|
|
44
45
|
The window is `role="alertdialog"` named by its header, with the message tied to it through
|
|
45
46
|
`aria-describedby`, so a screen reader reads the question as soon as it opens. Focus starts on the
|
|
46
|
-
reject button
|
|
47
|
+
reject button - the safe answer, so a stray `Enter` deletes nothing. Escape and x reject.
|
|
@@ -14,7 +14,7 @@ export interface KptContextMenuProps<T> extends Omit<ComponentPropsWithRef<'div'
|
|
|
14
14
|
* gdzie był. `Shift+F10` i klawisz Menu na skupionym elemencie obszaru też otwierają menu
|
|
15
15
|
* (panel staje wtedy przy tym elemencie). Panel jest dociskany do krawędzi okna.
|
|
16
16
|
*
|
|
17
|
-
* W odróżnieniu od `KptMenu` panel nie idzie przez `KptOverlay`
|
|
17
|
+
* W odróżnieniu od `KptMenu` panel nie idzie przez `KptOverlay` - nie ma tu wyzwalacza,
|
|
18
18
|
* względem którego dałoby się go przypiąć; pozycję wyznaczają współrzędne kliknięcia,
|
|
19
19
|
* a wygląd i warstwę niesie wspólny SCSS (`.kpt-context-menu__panel`).
|
|
20
20
|
*
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useEffectEvent, useLayoutEffect, useRef, useState } from 'react';
|
|
3
4
|
import { cn } from "../internal/cn.js";
|
|
4
5
|
import { MenuRows } from "../menu/menu-rows.js";
|
|
5
6
|
import { focusMenuEdge, handleMenuKeydown } from "../menu/menu-keys.js";
|
|
@@ -13,7 +14,7 @@ const EDGE = 8;
|
|
|
13
14
|
* gdzie był. `Shift+F10` i klawisz Menu na skupionym elemencie obszaru też otwierają menu
|
|
14
15
|
* (panel staje wtedy przy tym elemencie). Panel jest dociskany do krawędzi okna.
|
|
15
16
|
*
|
|
16
|
-
* W odróżnieniu od `KptMenu` panel nie idzie przez `KptOverlay`
|
|
17
|
+
* W odróżnieniu od `KptMenu` panel nie idzie przez `KptOverlay` - nie ma tu wyzwalacza,
|
|
17
18
|
* względem którego dałoby się go przypiąć; pozycję wyznaczają współrzędne kliknięcia,
|
|
18
19
|
* a wygląd i warstwę niesie wspólny SCSS (`.kpt-context-menu__panel`).
|
|
19
20
|
*
|
|
@@ -26,7 +27,10 @@ export function KptContextMenu({ items = [], children, onSelect, className, ...r
|
|
|
26
27
|
const [open, setOpen] = useState(false);
|
|
27
28
|
const [point, setPoint] = useState({ x: 0, y: 0 });
|
|
28
29
|
const panelRef = useRef(null);
|
|
29
|
-
|
|
30
|
+
const layerRef = useRef(null);
|
|
31
|
+
/** Licznik otwarć - każde PPM (także przy otwartym menu) dociska panel i ustawia fokus od nowa. */
|
|
32
|
+
const [openSeq, setOpenSeq] = useState(0);
|
|
33
|
+
/** Element, który miał fokus przed otwarciem - tam fokus wraca po zamknięciu. */
|
|
30
34
|
const returnTo = useRef(null);
|
|
31
35
|
const onContextMenu = (event) => {
|
|
32
36
|
event.preventDefault();
|
|
@@ -41,21 +45,27 @@ export function KptContextMenu({ items = [], children, onSelect, className, ...r
|
|
|
41
45
|
setPoint({ x: event.clientX, y: event.clientY });
|
|
42
46
|
}
|
|
43
47
|
setOpen(true);
|
|
48
|
+
setOpenSeq((n) => n + 1);
|
|
44
49
|
};
|
|
45
|
-
// Po otwarciu: dociśnięcie do okna i fokus na pierwszej pozycji.
|
|
46
|
-
|
|
50
|
+
// Po każdym otwarciu: warstwa w top layer, dociśnięcie do okna i fokus na pierwszej pozycji.
|
|
51
|
+
// Przed malowaniem (`useLayoutEffect`), żeby panel nie mignął w niedociśniętym miejscu. Docisk
|
|
52
|
+
// idzie wprost w styl elementu - stan `point` zostaje, więc React go nie nadpisze.
|
|
53
|
+
useLayoutEffect(() => {
|
|
47
54
|
const panel = panelRef.current;
|
|
48
|
-
if (
|
|
55
|
+
if (openSeq === 0 || !panel)
|
|
49
56
|
return;
|
|
57
|
+
const layer = layerRef.current;
|
|
58
|
+
if (layer && typeof layer.showPopover === 'function' && !layer.matches(':popover-open'))
|
|
59
|
+
layer.showPopover();
|
|
50
60
|
const r = panel.getBoundingClientRect();
|
|
51
61
|
const maxX = window.innerWidth - r.width - EDGE;
|
|
52
62
|
const maxY = window.innerHeight - r.height - EDGE;
|
|
53
|
-
if (
|
|
54
|
-
|
|
55
|
-
|
|
63
|
+
if (r.left > maxX)
|
|
64
|
+
panel.style.left = `${Math.max(EDGE, maxX)}px`;
|
|
65
|
+
if (r.top > maxY)
|
|
66
|
+
panel.style.top = `${Math.max(EDGE, maxY)}px`;
|
|
56
67
|
focusMenuEdge(panel, 'first');
|
|
57
|
-
|
|
58
|
-
}, [open]);
|
|
68
|
+
}, [openSeq]);
|
|
59
69
|
const close = () => {
|
|
60
70
|
setOpen(false);
|
|
61
71
|
const target = returnTo.current;
|
|
@@ -63,6 +73,19 @@ export function KptContextMenu({ items = [], children, onSelect, className, ...r
|
|
|
63
73
|
if (target?.isConnected)
|
|
64
74
|
target.focus();
|
|
65
75
|
};
|
|
76
|
+
// Przewinięcie albo zmiana rozmiaru okna odrywa panel od miejsca kliknięcia - zamykamy go.
|
|
77
|
+
const closeOnMove = useEffectEvent(() => close());
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (!open)
|
|
80
|
+
return;
|
|
81
|
+
const onMove = () => closeOnMove();
|
|
82
|
+
window.addEventListener('scroll', onMove, true);
|
|
83
|
+
window.addEventListener('resize', onMove);
|
|
84
|
+
return () => {
|
|
85
|
+
window.removeEventListener('scroll', onMove, true);
|
|
86
|
+
window.removeEventListener('resize', onMove);
|
|
87
|
+
};
|
|
88
|
+
}, [open]);
|
|
66
89
|
const onPanelKeyDown = (event) => {
|
|
67
90
|
if (panelRef.current && handleMenuKeydown(panelRef.current, event.nativeEvent) === 'close')
|
|
68
91
|
close();
|
|
@@ -73,8 +96,8 @@ export function KptContextMenu({ items = [], children, onSelect, className, ...r
|
|
|
73
96
|
onSelect?.(item);
|
|
74
97
|
close();
|
|
75
98
|
};
|
|
76
|
-
return (_jsxs("div", { className: cn('kpt-context-menu-host', className), ...rest, children: [_jsx("div", { className: "kpt-context-menu__area", onContextMenu: onContextMenu, children: children }), open ? (_jsxs(
|
|
99
|
+
return (_jsxs("div", { className: cn('kpt-context-menu-host', className), ...rest, children: [_jsx("div", { className: "kpt-context-menu__area", onContextMenu: onContextMenu, children: children }), open ? (_jsxs("div", { ref: layerRef, className: "kpt-context-menu__layer", popover: "manual", children: [_jsx("div", { className: "kpt-context-menu__backdrop", onClick: close, onContextMenu: (event) => {
|
|
77
100
|
event.preventDefault();
|
|
78
101
|
close();
|
|
79
|
-
} }), _jsx("div", { ref: panelRef, className: "kpt-context-menu__panel", role: "menu", style: { left: point.x, top: point.y }, onKeyDown: onPanelKeyDown, children: _jsx(MenuRows, { items: items, block: "kpt-context-menu", onPick: pick }) })] })) : null] }));
|
|
102
|
+
} }), _jsx("div", { ref: panelRef, className: "kpt-context-menu__panel", role: "menu", tabIndex: -1, style: { left: point.x, top: point.y }, onKeyDown: onPanelKeyDown, children: _jsx(MenuRows, { items: items, block: "kpt-context-menu", onPick: pick }) })] })) : null] }));
|
|
80
103
|
}
|