@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/knob/knob.js
CHANGED
|
@@ -1,37 +1,42 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
3
|
import { useEffect, useRef, useState } from 'react';
|
|
3
4
|
import { cn } from "../internal/cn.js";
|
|
5
|
+
import { kptSnapToStep } from "../internal/step.js";
|
|
4
6
|
import { kptStepDecimals, kptTween } from "../internal/tween.js";
|
|
5
7
|
import { useControllableState } from "../internal/use-controllable-state.js";
|
|
6
8
|
import { useKptFieldA11y } from "../form-field/form-field-context.js";
|
|
7
9
|
const TAU = Math.PI * 2;
|
|
8
|
-
/** Przerwa u dołu
|
|
10
|
+
/** Przerwa u dołu - kąt "otwarcia" łuku, żeby początek i koniec były rozróżnialne. */
|
|
9
11
|
const GAP = Math.PI / 4;
|
|
10
12
|
const START_ANGLE = Math.PI / 2 + GAP;
|
|
11
13
|
const SWEEP = TAU - GAP * 2;
|
|
12
14
|
/** Czas dojścia łuku i licznika po skoku wartości (klawisz, klik, zmiana z zewnątrz). */
|
|
13
15
|
const TWEEN_MS = 220;
|
|
14
16
|
/**
|
|
15
|
-
* Pokrętło
|
|
17
|
+
* Pokrętło - wybór wartości po łuku, przeciąganiem albo strzałkami.
|
|
16
18
|
*
|
|
17
19
|
* Skok wartości (strzałka, `Home`/`End`, klik w łuk, zmiana z zewnątrz) łuk i licznik pokonują
|
|
18
|
-
* płynnie; przy przeciąganiu idą za kursorem bez opóźnienia. `
|
|
20
|
+
* płynnie; przy przeciąganiu idą za kursorem bez opóźnienia. `PageUp`/`PageDown` skaczą o dziesięć
|
|
21
|
+
* kroków, a wartości leżą na siatce `min + k,step` dla myszy i klawiatury. `prefers-reduced-motion` wyłącza
|
|
19
22
|
* animację, a `aria-valuenow` zawsze niesie wartość docelową, nie pośrednią.
|
|
20
23
|
*
|
|
21
24
|
* @example
|
|
22
25
|
* <KptKnob value={level} onValueChange={setLevel} min={0} max={100} step={5} suffix="%" />
|
|
23
26
|
*/
|
|
24
|
-
export function KptKnob({ value, defaultValue = 0, onValueChange, min = 0, max = 100, step = 1, size = 96, stroke = 10, suffix = '', showValue = true, id, ariaLabel = null, disabled = false, invalid = false, touched, onTouch, className, ...rest }) {
|
|
27
|
+
export function KptKnob({ value, defaultValue = 0, onValueChange, min = 0, max = 100, step = 1, size = 96, stroke = 10, suffix = '', showValue = true, id, ariaLabel = null, disabled = false, readOnly = false, invalid = false, touched, onTouch, className, ...rest }) {
|
|
25
28
|
const field = useKptFieldA11y(id, 'kpt-knob');
|
|
26
29
|
const svgRef = useRef(null);
|
|
27
30
|
const [current, setCurrent] = useControllableState({ value, defaultValue, onChange: onValueChange });
|
|
28
31
|
const showInvalid = invalid && (touched ?? true);
|
|
29
32
|
const range = max - min;
|
|
30
|
-
/** Wartość na rysunku
|
|
33
|
+
/** Wartość na rysunku - w trakcie animacji pośrednia, poza nią równa `current`. */
|
|
31
34
|
const [shown, setShown] = useState(current);
|
|
32
35
|
const shownRef = useRef(current);
|
|
33
|
-
/** Przeciąganie trwa
|
|
36
|
+
/** Przeciąganie trwa - wtedy rysunek idzie za kursorem bez animacji. */
|
|
34
37
|
const dragging = useRef(false);
|
|
38
|
+
/** Pokrętło trzymane wskaźnikiem (od wciśnięcia do puszczenia) - steruje reakcją w CSS. */
|
|
39
|
+
const [grabbing, setGrabbing] = useState(false);
|
|
35
40
|
useEffect(() => {
|
|
36
41
|
const paint = (v) => {
|
|
37
42
|
shownRef.current = v;
|
|
@@ -58,7 +63,6 @@ export function KptKnob({ value, defaultValue = 0, onValueChange, min = 0, max =
|
|
|
58
63
|
return `M ${x0} ${y0}`;
|
|
59
64
|
return `M ${x0} ${y0} A ${r} ${r} 0 ${a1 - a0 > Math.PI ? 1 : 0} 1 ${x1} ${y1}`;
|
|
60
65
|
};
|
|
61
|
-
const clamp = (raw) => Math.min(max, Math.max(min, Math.round(raw / step) * step));
|
|
62
66
|
const setFromPoint = (clientX, clientY) => {
|
|
63
67
|
const svg = svgRef.current;
|
|
64
68
|
if (!svg)
|
|
@@ -68,23 +72,24 @@ export function KptKnob({ value, defaultValue = 0, onValueChange, min = 0, max =
|
|
|
68
72
|
const cy = rect.top + rect.height / 2;
|
|
69
73
|
let angle = Math.atan2(clientY - cy, clientX - cx) - START_ANGLE;
|
|
70
74
|
angle = ((angle % TAU) + TAU) % TAU;
|
|
71
|
-
// Kursor w przerwie u dołu nie ma swojej wartości
|
|
75
|
+
// Kursor w przerwie u dołu nie ma swojej wartości - przyciągamy go do bliższego końca łuku,
|
|
72
76
|
// inaczej przeciągnięcie przez dół przeskakiwałoby z maksimum na minimum.
|
|
73
77
|
if (angle > SWEEP + GAP)
|
|
74
78
|
angle = angle > SWEEP + GAP / 2 ? 0 : SWEEP;
|
|
75
|
-
setCurrent(
|
|
79
|
+
setCurrent(kptSnapToStep(min + Math.min(1, Math.max(0, angle / SWEEP)) * range, min, max, step));
|
|
76
80
|
};
|
|
77
81
|
const onPointerDown = (event) => {
|
|
78
|
-
if (disabled)
|
|
82
|
+
if (disabled || readOnly)
|
|
79
83
|
return;
|
|
80
|
-
// Bez tego przeciąganie zaznacza tekst: licznik w środku i
|
|
84
|
+
// Bez tego przeciąganie zaznacza tekst: licznik w środku i - przez SVG - całe koło, jak po
|
|
81
85
|
// `Ctrl+A`. Zablokowanie domyślnej akcji zabiera też fokus, więc oddajemy go ręcznie.
|
|
82
86
|
event.preventDefault();
|
|
83
87
|
event.currentTarget.focus();
|
|
84
|
-
|
|
88
|
+
setGrabbing(true);
|
|
89
|
+
// Klik w łuk to skok - animowany. Dopiero ruch po nim jest przeciąganiem.
|
|
85
90
|
setFromPoint(event.clientX, event.clientY);
|
|
86
91
|
// Nasłuch wisi na oknie, a nie na pokrętle: przeciąganie ma nie gubić wartości,
|
|
87
|
-
// gdy kursor wyjedzie poza koło
|
|
92
|
+
// gdy kursor wyjedzie poza koło - a wyjeżdża praktycznie zawsze.
|
|
88
93
|
const move = (e) => {
|
|
89
94
|
dragging.current = true;
|
|
90
95
|
setFromPoint(e.clientX, e.clientY);
|
|
@@ -94,6 +99,7 @@ export function KptKnob({ value, defaultValue = 0, onValueChange, min = 0, max =
|
|
|
94
99
|
window.removeEventListener('pointerup', up);
|
|
95
100
|
window.removeEventListener('pointercancel', up);
|
|
96
101
|
dragging.current = false;
|
|
102
|
+
setGrabbing(false);
|
|
97
103
|
onTouch?.();
|
|
98
104
|
};
|
|
99
105
|
window.addEventListener('pointermove', move);
|
|
@@ -101,22 +107,21 @@ export function KptKnob({ value, defaultValue = 0, onValueChange, min = 0, max =
|
|
|
101
107
|
window.addEventListener('pointercancel', up);
|
|
102
108
|
};
|
|
103
109
|
const onKeyDown = (event) => {
|
|
104
|
-
if (disabled)
|
|
110
|
+
if (disabled || readOnly)
|
|
105
111
|
return;
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
else if (event.key === 'ArrowDown' || event.key === 'ArrowLeft')
|
|
110
|
-
next -= step;
|
|
111
|
-
else if (event.key === 'Home')
|
|
112
|
+
const steps = { ArrowUp: 1, ArrowRight: 1, ArrowDown: -1, ArrowLeft: -1, PageUp: 10, PageDown: -10 };
|
|
113
|
+
let next;
|
|
114
|
+
if (event.key === 'Home')
|
|
112
115
|
next = min;
|
|
113
116
|
else if (event.key === 'End')
|
|
114
117
|
next = max;
|
|
118
|
+
else if (steps[event.key] !== undefined)
|
|
119
|
+
next = current + steps[event.key] * step;
|
|
115
120
|
else
|
|
116
121
|
return;
|
|
117
122
|
event.preventDefault();
|
|
118
|
-
setCurrent(
|
|
123
|
+
setCurrent(kptSnapToStep(next, min, max, step));
|
|
119
124
|
onTouch?.();
|
|
120
125
|
};
|
|
121
|
-
return (_jsx("div", { className: cn('kpt-knob-host', className), ...rest, children: _jsx("div", { className: cn('kpt-knob', showInvalid && 'kpt-knob--invalid'), role: "slider", id: field.id, tabIndex: disabled ? -1 : 0, "aria-valuemin": min, "aria-valuemax": max, "aria-valuenow": current, "aria-valuetext": suffix ? `${current}${suffix}` : undefined, "aria-disabled": disabled || undefined, "aria-invalid": showInvalid || undefined, "aria-label": ariaLabel ?? undefined, "aria-labelledby": ariaLabel ? undefined : field.labelledBy, "aria-describedby": field.describedBy, onPointerDown: onPointerDown, onKeyDown: onKeyDown, children: _jsxs("svg", { ref: svgRef, viewBox: `0 0 ${size} ${size}`, style: { width: size, height: size }, children: [_jsx("path", { className: "kpt-knob__range", d: arc(0, 1), fill: "none", strokeWidth: stroke, strokeLinecap: "round" }), _jsx("path", { className: "kpt-knob__value", d: arc(0, ratio), fill: "none", strokeWidth: stroke, strokeLinecap: "round" }), showValue ? (_jsxs("text", { className: "kpt-knob__text", x: size / 2, y: size / 2, textAnchor: "middle", dominantBaseline: "central", children: [shown.toFixed(kptStepDecimals(step)), suffix] })) : null] }) }) }));
|
|
126
|
+
return (_jsx("div", { className: cn('kpt-knob-host', className), ...rest, children: _jsx("div", { className: cn('kpt-knob', showInvalid && 'kpt-knob--invalid', grabbing && 'is-grabbing'), role: "slider", id: field.id, tabIndex: disabled ? -1 : 0, "aria-valuemin": min, "aria-valuemax": max, "aria-valuenow": current, "aria-valuetext": suffix ? `${current}${suffix}` : undefined, "aria-disabled": disabled || undefined, "aria-readonly": readOnly || undefined, "aria-invalid": showInvalid || undefined, "aria-label": ariaLabel ?? undefined, "aria-labelledby": ariaLabel ? undefined : field.labelledBy, "aria-describedby": field.describedBy, onPointerDown: onPointerDown, onKeyDown: onKeyDown, children: _jsxs("svg", { ref: svgRef, viewBox: `0 0 ${size} ${size}`, style: { width: size, height: size }, children: [_jsx("path", { className: "kpt-knob__range", d: arc(0, 1), fill: "none", strokeWidth: stroke, strokeLinecap: "round" }), _jsx("path", { className: "kpt-knob__value", d: arc(0, ratio), fill: "none", strokeWidth: stroke, strokeLinecap: "round" }), showValue ? (_jsxs("text", { className: "kpt-knob__text", x: size / 2, y: size / 2, textAnchor: "middle", dominantBaseline: "central", children: [shown.toFixed(kptStepDecimals(step)), suffix] })) : null] }) }) }));
|
|
122
127
|
}
|
package/dist/knob/llms.txt
CHANGED
|
@@ -1,36 +1,37 @@
|
|
|
1
1
|
# KptKnob (@konce-pt/react)
|
|
2
2
|
|
|
3
|
-
A knob
|
|
3
|
+
A knob - pick a value along an SVG arc, by dragging or with the arrow keys.
|
|
4
4
|
Import: `import { KptKnob } from '@konce-pt/react';`
|
|
5
5
|
|
|
6
6
|
## DOM
|
|
7
7
|
<div class="kpt-knob-host">
|
|
8
8
|
<div class="kpt-knob" role="slider" tabindex="0" aria-valuenow="40">
|
|
9
9
|
<svg viewBox="0 0 96 96">
|
|
10
|
-
<path class="kpt-knob__range" d="M
|
|
11
|
-
<path class="kpt-knob__value" d="M
|
|
10
|
+
<path class="kpt-knob__range" d="M ..." />
|
|
11
|
+
<path class="kpt-knob__value" d="M ..." />
|
|
12
12
|
<text class="kpt-knob__text">40%</text>
|
|
13
13
|
</svg>
|
|
14
14
|
</div>
|
|
15
15
|
</div>
|
|
16
16
|
|
|
17
17
|
## Props
|
|
18
|
-
- `value` / `defaultValue` / `onValueChange`
|
|
18
|
+
- `value` / `defaultValue` / `onValueChange` - the number
|
|
19
19
|
- `min` (0), `max` (100), `step` (1)
|
|
20
|
-
- `size`: px (96)
|
|
20
|
+
- `size`: px (96) - the side of the SVG square; `stroke`: px (10) - arc thickness
|
|
21
21
|
- `suffix`: string appended to the value in the middle; `showValue`: boolean (default true)
|
|
22
|
-
- `disabled`, `invalid`, `touched`, `onTouch()`
|
|
22
|
+
- `disabled`, `readOnly`, `invalid`, `touched`, `onTouch()` - `readOnly` keeps the knob focusable but ignores
|
|
23
|
+
the pointer and the keys (`aria-readonly`)
|
|
23
24
|
- `className`, `ref` and every other `<div>` prop go to the host
|
|
24
25
|
|
|
25
26
|
## The gap at the bottom
|
|
26
27
|
The arc is a full circle minus a 45° gap at the bottom, so its start and end are distinguishable.
|
|
27
|
-
A pointer inside that gap has no value of its own
|
|
28
|
+
A pointer inside that gap has no value of its own - it is snapped to the nearer end, otherwise
|
|
28
29
|
dragging across the bottom would jump between minimum and maximum.
|
|
29
30
|
|
|
30
31
|
## Dragging listens on the window
|
|
31
32
|
`pointermove` / `pointerup` are attached to `window`, not to the knob: the pointer leaves the
|
|
32
33
|
circle during almost every drag, and the value must keep following it. Both listeners are removed
|
|
33
|
-
on `pointerup`, which is also where `onTouch` fires
|
|
34
|
+
on `pointerup`, which is also where `onTouch` fires - one touch per drag, not per pixel.
|
|
34
35
|
|
|
35
36
|
## Examples
|
|
36
37
|
<KptKnob value={level} onValueChange={setLevel} min={0} max={100} step={5} suffix="%" />
|
|
@@ -41,15 +42,20 @@ on `pointerup`, which is also where `onTouch` fires — one touch per drag, not
|
|
|
41
42
|
generated. A `div` with a role is not named by `label[for]`, so inside `KptFormField` it gets
|
|
42
43
|
`aria-labelledby` pointing at the wrapper's label (unless `ariaLabel` is set), and the hint or error
|
|
43
44
|
is added to `aria-describedby`. New: `id`, `ariaLabel`; `aria-valuetext` includes `suffix` ("40%"),
|
|
44
|
-
and a disabled knob leaves the Tab order. Use `ariaLabel`, not `aria-label` on the host
|
|
45
|
+
and a disabled knob leaves the Tab order. Use `ariaLabel`, not `aria-label` on the host - that one
|
|
45
46
|
lands on the wrapper.
|
|
46
47
|
|
|
48
|
+
## Pointer
|
|
49
|
+
While held (from press to release) the knob reacts: the cursor becomes `grabbing`, the dial grows
|
|
50
|
+
slightly, the value arc glows stronger and the number in the middle scales up. On hover the arc glows
|
|
51
|
+
softly. All of it is CSS on the `is-grabbing` class and drops out under "reduce motion".
|
|
52
|
+
|
|
47
53
|
## Tokens
|
|
48
54
|
The background arc `--kpt-color-border`, the value `--kpt-color-primary`, the text
|
|
49
55
|
`--kpt-color-on-surface`.
|
|
50
56
|
|
|
51
57
|
## Motion
|
|
52
|
-
A jump in value
|
|
58
|
+
A jump in value - an arrow key, Home/End, a click on the arc, a change from outside - moves the arc
|
|
53
59
|
and counts the number up or down over ~220 ms (ease-out). Dragging follows the pointer with no
|
|
54
60
|
delay, so it never feels laggy. `prefers-reduced-motion: reduce` turns the animation off.
|
|
55
61
|
`aria-valuenow` always carries the target value, never an in-between frame. Dragging does not
|
|
@@ -57,5 +63,6 @@ select the number or the arc.
|
|
|
57
63
|
|
|
58
64
|
## Accessibility
|
|
59
65
|
`role="slider"` with `aria-valuemin` / `aria-valuemax` / `aria-valuenow`; arrows step by `step`,
|
|
60
|
-
Home and End jump to the bounds.
|
|
66
|
+
PageUp/PageDown by ten steps, Home and End jump to the bounds. Values sit on the grid `min + k,step` for
|
|
67
|
+
both the pointer and the keys - with `min={1} step={2}` that is 1, 3, 5.... `disabled` sets `aria-disabled` and drops the knob out of the
|
|
61
68
|
tab order.
|
|
@@ -9,13 +9,15 @@ export interface KptListboxProps<T> extends Omit<ComponentPropsWithRef<'div'>, '
|
|
|
9
9
|
multiple?: boolean;
|
|
10
10
|
/** Pole filtrowania nad listą. */
|
|
11
11
|
filter?: boolean;
|
|
12
|
-
/** Id listy (nie hosta); bez niego generowane, w `KptFormField`
|
|
12
|
+
/** Id listy (nie hosta); bez niego generowane, w `KptFormField` - z wrappera. */
|
|
13
13
|
id?: string;
|
|
14
14
|
/** Nazwa dostępna listy, gdy nie opisuje jej etykieta (`KptFormField`). */
|
|
15
15
|
ariaLabel?: string | null;
|
|
16
16
|
/** Oznacza listę jako wymaganą atrybutem `aria-required`. */
|
|
17
17
|
required?: boolean;
|
|
18
18
|
disabled?: boolean;
|
|
19
|
+
/** Tylko do odczytu - po liście da się chodzić i czytać wybór, ale nie da się go zmienić. */
|
|
20
|
+
readOnly?: boolean;
|
|
19
21
|
invalid?: boolean;
|
|
20
22
|
touched?: boolean;
|
|
21
23
|
onTouch?: () => void;
|
|
@@ -24,17 +26,17 @@ export interface KptListboxProps<T> extends Omit<ComponentPropsWithRef<'div'>, '
|
|
|
24
26
|
* Lista wyboru widoczna w całości, bez rozwijania. Pojedynczy albo wielokrotny wybór,
|
|
25
27
|
* opcjonalny filtr.
|
|
26
28
|
*
|
|
27
|
-
* W trybie pojedynczym ponowny klik w zaznaczoną opcję odznacza ją
|
|
29
|
+
* W trybie pojedynczym ponowny klik w zaznaczoną opcję odznacza ją - lista bywa filtrem,
|
|
28
30
|
* a filtr trzeba dać się wyczyścić bez sięgania po osobny przycisk.
|
|
29
31
|
*
|
|
30
|
-
* Klawiatura według wzorca WAI-ARIA
|
|
32
|
+
* Klawiatura według wzorca WAI-ARIA "listbox": lista to jeden przystanek Tab, a aktywną opcję
|
|
31
33
|
* ogłasza `aria-activedescendant`. Strzałki, `Home`/`End`, `PageUp`/`PageDown` przesuwają aktywną
|
|
32
34
|
* opcję (wyłączone są pomijane), pisanie liter skacze do kolejnej pasującej (bez względu na
|
|
33
|
-
* diakrytyki), `Spacja`/`Enter` wybiera, a w trybie wielokrotnym `Ctrl
|
|
34
|
-
* widoczne. Pole filtra stoi przed listą;
|
|
35
|
+
* diakrytyki), `Spacja`/`Enter` wybiera, a w trybie wielokrotnym `Ctrl`/`Cmd`+`A` zaznacza wszystkie
|
|
36
|
+
* widoczne. Pole filtra stoi przed listą; `ArrowDown` przenosi z niego fokus do listy.
|
|
35
37
|
*
|
|
36
38
|
* @example
|
|
37
39
|
* <KptListbox options={roles} value={role} onValueChange={setRole} filter />
|
|
38
40
|
* <KptListbox options={tags} value={tags} onValueChange={setTags} multiple />
|
|
39
41
|
*/
|
|
40
|
-
export declare function KptListbox<T>({ options, value, defaultValue, onValueChange, multiple, filter, id, ariaLabel, required, disabled, invalid, touched, onTouch, locale, dictionary, className, ...rest }: Readonly<KptListboxProps<T>>): import("react").JSX.Element;
|
|
42
|
+
export declare function KptListbox<T>({ options, value, defaultValue, onValueChange, multiple, filter, id, ariaLabel, required, disabled, readOnly, invalid, touched, onTouch, locale, dictionary, className, ...rest }: Readonly<KptListboxProps<T>>): import("react").JSX.Element;
|
package/dist/listbox/listbox.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 { useEffect, useMemo, useRef, useState } from 'react';
|
|
3
4
|
import { foldMentionText } from '@konce-pt/mention';
|
|
@@ -11,22 +12,22 @@ import { KptIcon } from "../icon/icon.js";
|
|
|
11
12
|
* Lista wyboru widoczna w całości, bez rozwijania. Pojedynczy albo wielokrotny wybór,
|
|
12
13
|
* opcjonalny filtr.
|
|
13
14
|
*
|
|
14
|
-
* W trybie pojedynczym ponowny klik w zaznaczoną opcję odznacza ją
|
|
15
|
+
* W trybie pojedynczym ponowny klik w zaznaczoną opcję odznacza ją - lista bywa filtrem,
|
|
15
16
|
* a filtr trzeba dać się wyczyścić bez sięgania po osobny przycisk.
|
|
16
17
|
*
|
|
17
|
-
* Klawiatura według wzorca WAI-ARIA
|
|
18
|
+
* Klawiatura według wzorca WAI-ARIA "listbox": lista to jeden przystanek Tab, a aktywną opcję
|
|
18
19
|
* ogłasza `aria-activedescendant`. Strzałki, `Home`/`End`, `PageUp`/`PageDown` przesuwają aktywną
|
|
19
20
|
* opcję (wyłączone są pomijane), pisanie liter skacze do kolejnej pasującej (bez względu na
|
|
20
|
-
* diakrytyki), `Spacja`/`Enter` wybiera, a w trybie wielokrotnym `Ctrl
|
|
21
|
-
* widoczne. Pole filtra stoi przed listą;
|
|
21
|
+
* diakrytyki), `Spacja`/`Enter` wybiera, a w trybie wielokrotnym `Ctrl`/`Cmd`+`A` zaznacza wszystkie
|
|
22
|
+
* widoczne. Pole filtra stoi przed listą; `ArrowDown` przenosi z niego fokus do listy.
|
|
22
23
|
*
|
|
23
24
|
* @example
|
|
24
25
|
* <KptListbox options={roles} value={role} onValueChange={setRole} filter />
|
|
25
26
|
* <KptListbox options={tags} value={tags} onValueChange={setTags} multiple />
|
|
26
27
|
*/
|
|
27
|
-
export function KptListbox({ options = [], value, defaultValue = null, onValueChange, multiple = false, filter = false, id, ariaLabel = null, required = false, disabled = false, invalid = false, touched, onTouch, locale, dictionary, className, ...rest }) {
|
|
28
|
+
export function KptListbox({ options = [], value, defaultValue = null, onValueChange, multiple = false, filter = false, id, ariaLabel = null, required = false, disabled = false, readOnly = false, invalid = false, touched, onTouch, locale, dictionary, className, ...rest }) {
|
|
28
29
|
const { t } = useKptMessages({ locale, dictionary });
|
|
29
|
-
/** `div` z rolą nie dostaje nazwy z `label[for]`
|
|
30
|
+
/** `div` z rolą nie dostaje nazwy z `label[for]` - stąd `aria-labelledby` na etykietę wrappera. */
|
|
30
31
|
const field = useKptFieldA11y(id, 'kpt-listbox');
|
|
31
32
|
const [current, setCurrent] = useControllableState({
|
|
32
33
|
value,
|
|
@@ -39,7 +40,7 @@ export function KptListbox({ options = [], value, defaultValue = null, onValueCh
|
|
|
39
40
|
const listRef = useRef(null);
|
|
40
41
|
const typeahead = useRef(new KptTypeahead()).current;
|
|
41
42
|
const showInvalid = invalid && (touched ?? true);
|
|
42
|
-
/** Filtr bez względu na wielkość liter i diakrytyki (
|
|
43
|
+
/** Filtr bez względu na wielkość liter i diakrytyki ("lodz" znajduje "Łódź"). */
|
|
43
44
|
const visible = useMemo(() => {
|
|
44
45
|
const q = foldMentionText(query.trim());
|
|
45
46
|
return q ? options.filter((option) => foldMentionText(option.label).includes(q)) : options;
|
|
@@ -55,18 +56,33 @@ export function KptListbox({ options = [], value, defaultValue = null, onValueCh
|
|
|
55
56
|
const isSelected = (candidate) => multiple ? Array.isArray(current) && current.includes(candidate) : current === candidate;
|
|
56
57
|
const toggle = (candidate) => {
|
|
57
58
|
if (multiple) {
|
|
58
|
-
const list = Array.isArray(current) ? current : [];
|
|
59
|
+
const list = Array.isArray(current) ? (current) : [];
|
|
59
60
|
setCurrent(list.includes(candidate) ? list.filter((item) => item !== candidate) : [...list, candidate]);
|
|
60
61
|
}
|
|
61
62
|
else {
|
|
62
63
|
setCurrent(current === candidate ? null : candidate);
|
|
63
64
|
}
|
|
64
65
|
};
|
|
65
|
-
|
|
66
|
+
/** Ostatnio kliknięta opcja - początek zakresu dla `Shift+klik` w trybie `multiple`. */
|
|
67
|
+
const anchorIndex = useRef(-1);
|
|
68
|
+
const pick = (index, extend = false) => {
|
|
66
69
|
const option = visible[index];
|
|
67
|
-
if (!option || disabledMap[index])
|
|
70
|
+
if (!option || disabledMap[index] || readOnly)
|
|
68
71
|
return;
|
|
69
72
|
setActiveIndex(index);
|
|
73
|
+
if (extend && multiple && anchorIndex.current >= 0) {
|
|
74
|
+
// Zaznacza (dokłada) dozwolone opcje między kotwicą a klikniętą - `Shift+klik` (APG).
|
|
75
|
+
const [lo, hi] = anchorIndex.current <= index ? [anchorIndex.current, index] : [index, anchorIndex.current];
|
|
76
|
+
const list = Array.isArray(current) ? current : [];
|
|
77
|
+
const add = visible
|
|
78
|
+
.slice(lo, hi + 1)
|
|
79
|
+
.filter((_, offset) => !disabledMap[lo + offset])
|
|
80
|
+
.map((item) => item.value)
|
|
81
|
+
.filter((item) => !list.includes(item));
|
|
82
|
+
setCurrent([...list, ...add]);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
anchorIndex.current = index;
|
|
70
86
|
toggle(option.value);
|
|
71
87
|
};
|
|
72
88
|
/** Wejście fokusem: aktywna zostaje wybrana opcja, a bez niej pierwsza dozwolona. */
|
|
@@ -88,8 +104,12 @@ export function KptListbox({ options = [], value, defaultValue = null, onValueCh
|
|
|
88
104
|
if (OPTION_NAV_KEYS.has(event.key)) {
|
|
89
105
|
event.preventDefault();
|
|
90
106
|
const next = nextOptionIndex(disabledMap, activeIndex, event.key);
|
|
91
|
-
if (next
|
|
92
|
-
|
|
107
|
+
if (next === null)
|
|
108
|
+
return;
|
|
109
|
+
setActiveIndex(next);
|
|
110
|
+
// `Shift+strzałka` w wyborze wielokrotnym zaznacza opcję, na którą przeszedł fokus (APG listbox).
|
|
111
|
+
if (event.shiftKey && multiple && !readOnly && !isSelected(visible[next].value))
|
|
112
|
+
toggle(visible[next].value);
|
|
93
113
|
return;
|
|
94
114
|
}
|
|
95
115
|
if (event.key === ' ' || event.key === 'Enter') {
|
|
@@ -99,11 +119,11 @@ export function KptListbox({ options = [], value, defaultValue = null, onValueCh
|
|
|
99
119
|
pick(activeIndex);
|
|
100
120
|
return;
|
|
101
121
|
}
|
|
102
|
-
if (multiple && (event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'a') {
|
|
122
|
+
if (multiple && !readOnly && (event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'a') {
|
|
103
123
|
event.preventDefault();
|
|
104
124
|
const all = visible.filter((_, index) => !disabledMap[index]).map((option) => option.value);
|
|
105
|
-
const list = Array.isArray(current) ? current : [];
|
|
106
|
-
// Drugi raz `Ctrl+A` przy wszystkich zaznaczonych
|
|
125
|
+
const list = Array.isArray(current) ? (current) : [];
|
|
126
|
+
// Drugi raz `Ctrl+A` przy wszystkich zaznaczonych - odznacza widoczne.
|
|
107
127
|
const allOn = all.every((item) => list.includes(item));
|
|
108
128
|
setCurrent(allOn ? list.filter((item) => !all.includes(item)) : [...list, ...all.filter((item) => !list.includes(item))]);
|
|
109
129
|
return;
|
|
@@ -119,8 +139,8 @@ export function KptListbox({ options = [], value, defaultValue = null, onValueCh
|
|
|
119
139
|
return;
|
|
120
140
|
event.preventDefault();
|
|
121
141
|
listRef.current?.focus();
|
|
122
|
-
} })] })) : null, _jsx("div", { ref: listRef, className: "kpt-listbox__items", role: "listbox", id: field.id, tabIndex: disabled ? -1 : 0, "aria-multiselectable": multiple || undefined, "aria-activedescendant": activeOptionId, "aria-disabled": disabled || undefined, "aria-invalid": showInvalid || undefined, "aria-required": required || undefined, "aria-label": ariaLabel ?? undefined, "aria-labelledby": ariaLabel ? undefined : field.labelledBy, "aria-describedby": field.describedBy, onKeyDown: onKeyDown, onFocus: onFocus, onBlur: () => onTouch?.(), children: visible.
|
|
142
|
+
} })] })) : null, _jsx("div", { ref: listRef, className: "kpt-listbox__items", role: "listbox", id: field.id, tabIndex: disabled ? -1 : 0, "aria-multiselectable": multiple || undefined, "aria-activedescendant": activeOptionId, "aria-disabled": disabled || undefined, "aria-invalid": showInvalid || undefined, "aria-required": required || undefined, "aria-readonly": readOnly || undefined, "aria-label": ariaLabel ?? undefined, "aria-labelledby": ariaLabel ? undefined : field.labelledBy, "aria-describedby": field.describedBy, onKeyDown: onKeyDown, onFocus: onFocus, onBlur: () => onTouch?.(), children: visible.map((option, index) => (_jsxs("div", { role: "option", id: optionId(index), className: cn('kpt-listbox__item', isSelected(option.value) && 'is-selected', index === activeIndex && 'is-active'), "aria-selected": isSelected(option.value), "aria-disabled": disabledMap[index] || undefined, onMouseDown: (event) => {
|
|
123
143
|
event.preventDefault();
|
|
124
144
|
listRef.current?.focus();
|
|
125
|
-
}, onClick: () => pick(index), children: [_jsx("span", { children: option.label }), isSelected(option.value) ? _jsx(KptIcon, { name: "check" }) : null] }, String(option.value)))))
|
|
145
|
+
}, onClick: (event) => pick(index, event.shiftKey), children: [_jsx("span", { children: option.label }), isSelected(option.value) ? _jsx(KptIcon, { name: "check" }) : null] }, typeof option.value === 'object' && option.value !== null ? `#${index}` : String(option.value)))) }), visible.length === 0 ? (_jsx("div", { className: "kpt-listbox__empty", role: "status", children: t('listbox.empty') })) : null] }) }));
|
|
126
146
|
}
|
package/dist/listbox/llms.txt
CHANGED
|
@@ -6,19 +6,19 @@ Import: `import { KptListbox } from '@konce-pt/react';`
|
|
|
6
6
|
## DOM
|
|
7
7
|
<div class="kpt-listbox-host">
|
|
8
8
|
<div class="kpt-listbox">
|
|
9
|
-
<div class="kpt-listbox__filter"
|
|
10
|
-
<div class="kpt-listbox__items" role="listbox" tabindex="0" aria-activedescendant="
|
|
11
|
-
<div class="kpt-listbox__item is-selected is-active" role="option" id="
|
|
9
|
+
<div class="kpt-listbox__filter">...icon + input...</div>
|
|
10
|
+
<div class="kpt-listbox__items" role="listbox" tabindex="0" aria-activedescendant="...-opt-0">
|
|
11
|
+
<div class="kpt-listbox__item is-selected is-active" role="option" id="...-opt-0" aria-selected="true">Admin</div>
|
|
12
12
|
</div>
|
|
13
13
|
</div>
|
|
14
14
|
</div>
|
|
15
15
|
|
|
16
16
|
## Props
|
|
17
17
|
- `options`: `KptOption<T>[]`
|
|
18
|
-
- `value` / `defaultValue` / `onValueChange`
|
|
18
|
+
- `value` / `defaultValue` / `onValueChange` - `T` (single) or `T[]` (multiple)
|
|
19
19
|
- `multiple`, `filter`, `disabled`, `invalid`, `touched`, `onTouch()`
|
|
20
20
|
- `id`, `ariaLabel`, `required`
|
|
21
|
-
- `locale`, `dictionary`
|
|
21
|
+
- `locale`, `dictionary` - i18n overrides for this component only
|
|
22
22
|
|
|
23
23
|
## Clicking the selection clears it
|
|
24
24
|
In single mode clicking the already selected option deselects it. A listbox is often a filter, and a
|
|
@@ -33,17 +33,17 @@ filter has to be clearable without a separate button.
|
|
|
33
33
|
|
|
34
34
|
## Keyboard
|
|
35
35
|
The WAI-ARIA listbox pattern: the list is one Tab stop and the active option is announced through
|
|
36
|
-
`aria-activedescendant`.
|
|
36
|
+
`aria-activedescendant`. `ArrowUp`/`ArrowDown`, `Home`/`End` and `PageUp`/`PageDown` move the active option,
|
|
37
37
|
skipping disabled ones (no wrapping); typing letters jumps to the next match, diacritics ignored;
|
|
38
|
-
`Space`/`Enter` picks; in `multiple` mode `Ctrl
|
|
39
|
-
clears them). The filter field sits before the list (outside the `listbox`) and
|
|
38
|
+
`Space`/`Enter` picks; in `multiple` mode `Ctrl`/`Cmd`+`A` selects every visible option (again -
|
|
39
|
+
clears them). The filter field sits before the list (outside the `listbox`) and `ArrowDown` moves focus
|
|
40
40
|
from it into the list; the filter itself ignores diacritics ("lodz" finds "Łódź").
|
|
41
41
|
|
|
42
42
|
## Label and description
|
|
43
43
|
`id` goes to the list (`role="listbox"`), never the host. A `div` with a role is not named by
|
|
44
44
|
`label[for]`, so inside `KptFormField` the list gets `aria-labelledby` pointing at the wrapper's
|
|
45
45
|
label (unless `ariaLabel` is set) and the hint or error in `aria-describedby`. New: `id`,
|
|
46
|
-
`ariaLabel`, `required` (
|
|
46
|
+
`ariaLabel`, `required` (-> `aria-required`); `aria-invalid` after touch; `touch` fires on blur of the list.
|
|
47
47
|
|
|
48
48
|
## Tokens
|
|
49
49
|
`--kpt-color-surface`, `--kpt-color-border`, `--kpt-color-primary-subtle` (the selected row),
|
|
@@ -53,3 +53,6 @@ label (unless `ariaLabel` is set) and the hint or error in `aria-describedby`. N
|
|
|
53
53
|
The list is `role="listbox"` with `aria-multiselectable`, `aria-activedescendant` and a name from
|
|
54
54
|
`ariaLabel` or `KptFormField`; options are `role="option"` with `aria-selected`/`aria-disabled`.
|
|
55
55
|
The keyboard is described above.
|
|
56
|
+
|
|
57
|
+
## Read-only and required
|
|
58
|
+
`readOnly` lets you move through the list and read the choice but not change it (`aria-readonly`). With `multiple`, `Shift+ArrowUp/ArrowDown` selects the option focus moves to and `Shift+click` adds the range from the last clicked option (APG). The empty message sits next to the list.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# KptLoader (@konce-pt/react)
|
|
2
|
+
|
|
3
|
+
A loader with character: a loop in one of six materials that turns into a result mark instead of
|
|
4
|
+
disappearing. Same classes and the same drawing as `kpt-loader` in Angular - the SVG and its
|
|
5
|
+
choreography come from `createLoader` in `@konce-pt/motion`. Import:
|
|
6
|
+
`import { KptLoader } from '@konce-pt/react';`
|
|
7
|
+
|
|
8
|
+
Variants: `bubbles` (default), `bridge` (the result is a pill with text), `shapes`, `ring` (progress,
|
|
9
|
+
an icon in the centre), `wave` (progress), `bounce`. Use `KptSpinner` for a small inline "wait".
|
|
10
|
+
|
|
11
|
+
## DOM
|
|
12
|
+
<span class="kpt-loader-host">
|
|
13
|
+
<span class="kpt-loader" data-variant="bubbles" data-status="loading" style="--_size: 5rem">
|
|
14
|
+
<span class="kpt-loader__stage" aria-hidden="true"><svg class="kpt-loader__svg">...</svg></span>
|
|
15
|
+
<span class="kpt-loader__content"></span>
|
|
16
|
+
<span class="kpt-loader__label" role="status">Loading</span>
|
|
17
|
+
</span>
|
|
18
|
+
</span>
|
|
19
|
+
|
|
20
|
+
## Props
|
|
21
|
+
- `variant`: KptLoaderVariant (default 'bubbles')
|
|
22
|
+
- `status`: 'loading' | 'success' | 'error' (default 'loading') - a result plays once and the mark
|
|
23
|
+
stays; back to `loading` restarts the loop
|
|
24
|
+
- `value`: number | null (default null), `min` (0), `max` (100) - progress for `ring` and `wave`
|
|
25
|
+
- `size`: string (default '5rem') - the CSS length of 100 drawing units, shared by all variants
|
|
26
|
+
- `colors`: KptLoaderColor[] | null - theme roles ('primary', 'info', 'success', 'warning',
|
|
27
|
+
'danger') or CSS colours, repeated in order; without it each variant has its palette
|
|
28
|
+
- `successColor` (default 'success'), `errorColor` (default 'danger')
|
|
29
|
+
- `speed`: number (default 1) - multiplies the loop tempo
|
|
30
|
+
- `label`, `successLabel`, `errorLabel`: string - default `loader.label`, `loader.success`,
|
|
31
|
+
`loader.error`; the result texts also go into the `bridge` pill and follow the language live
|
|
32
|
+
- `announce`: boolean (default true) - `false` drops the own status region
|
|
33
|
+
- `onSettled`: (result: KptLoaderResult) => void - the result choreography has finished; not called
|
|
34
|
+
when the loader mounts already in a result
|
|
35
|
+
- `children` - content in the centre of `ring` (other variants ignore it)
|
|
36
|
+
- `locale`, `dictionary` - i18n overrides for this component only
|
|
37
|
+
- `className`, `style`, `ref` and other `<span>` props go to `.kpt-loader`; `hostProps` to the wrapper
|
|
38
|
+
|
|
39
|
+
## Examples
|
|
40
|
+
<KptLoader />
|
|
41
|
+
<KptLoader variant="bridge" status={saving ? 'loading' : 'success'} onSettled={() => setShown(false)} />
|
|
42
|
+
<KptLoader variant="wave" value={uploaded} max={total} />
|
|
43
|
+
<KptLoader variant="ring" status={status}><KptIcon name="download" /></KptLoader>
|
|
44
|
+
<KptLoader variant="bounce" colors={['danger', 'oklch(0.6 0.2 300)']} speed={1.5} />
|
|
45
|
+
|
|
46
|
+
## Tokens
|
|
47
|
+
Roles resolve to `--kpt-color-<role>`, marks on filled shapes to `--kpt-color-<role>-contrast`
|
|
48
|
+
(a custom colour gets `--kpt-color-surface`), the `shapes` backdrop to `--kpt-color-<role>-subtle`;
|
|
49
|
+
tracks `--kpt-color-border`, shadows `--kpt-color-overlay-scrim`.
|
|
50
|
+
|
|
51
|
+
## Motion and accessibility
|
|
52
|
+
One `requestAnimationFrame` loop per loader, stopped off-screen and after the result.
|
|
53
|
+
`<KptMotionProvider enabled={false}>` and the system "reduce motion" setting slow the loop down and
|
|
54
|
+
replace the choreography with the mark in a circle. The drawing is `aria-hidden`; a visually hidden
|
|
55
|
+
`role="status"` region gets the label a frame after mount, then the result text. While `ring` or
|
|
56
|
+
`wave` draws progress the stage is a `role="progressbar"` with `aria-valuenow` 0-100.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { type ComponentPropsWithRef, type ReactNode } from 'react';
|
|
2
|
+
import { type KptLoaderColor, type KptLoaderResult, type KptLoaderStatus, type KptLoaderVariant } from '@konce-pt/motion';
|
|
3
|
+
import type { KptHostProps } from '../internal/types.ts';
|
|
4
|
+
import { type KptI18nOverrides } from '../i18n/i18n-context.tsx';
|
|
5
|
+
export type { KptLoaderColor, KptLoaderResult, KptLoaderStatus, KptLoaderVariant } from '@konce-pt/motion';
|
|
6
|
+
export interface KptLoaderProps extends Omit<ComponentPropsWithRef<'span'>, 'children'>, KptI18nOverrides {
|
|
7
|
+
/** Materiał pętli. Domyślnie `bubbles`. */
|
|
8
|
+
variant?: KptLoaderVariant;
|
|
9
|
+
/** `loading` - pętla; `success` / `error` - choreografia wyniku i znak, który zostaje. */
|
|
10
|
+
status?: KptLoaderStatus;
|
|
11
|
+
/** Postęp (`ring`, `wave`); `null` = nieokreślony. Zakres jak w `KptProgress`. */
|
|
12
|
+
value?: number | null;
|
|
13
|
+
min?: number;
|
|
14
|
+
max?: number;
|
|
15
|
+
/** Długość 100 jednostek rysunku (CSS). Wszystkie warianty dzielą skalę. Domyślnie `5rem`. */
|
|
16
|
+
size?: string;
|
|
17
|
+
/** Barwy pętli - role motywu (`primary`, `info`, ...) albo kolory CSS; powtarzane po kolei. */
|
|
18
|
+
colors?: readonly KptLoaderColor[] | null;
|
|
19
|
+
successColor?: KptLoaderColor;
|
|
20
|
+
errorColor?: KptLoaderColor;
|
|
21
|
+
/** Mnożnik tempa pętli. Domyślnie 1. */
|
|
22
|
+
speed?: number;
|
|
23
|
+
/** Etykieta a11y w trakcie; pominięta = `loader.label`. */
|
|
24
|
+
label?: string;
|
|
25
|
+
/** Napis sukcesu (region statusu i kapsuła `bridge`); pominięty = `loader.success`. */
|
|
26
|
+
successLabel?: string;
|
|
27
|
+
/** Napis błędu; pominięty = `loader.error`. */
|
|
28
|
+
errorLabel?: string;
|
|
29
|
+
/** `false` - bez własnego regionu statusu (stan ogłasza komponent wokół, np. `KptUpdate`). */
|
|
30
|
+
announce?: boolean;
|
|
31
|
+
/** Koniec choreografii wyniku - dobry moment, żeby schować loader. */
|
|
32
|
+
onSettled?: (result: KptLoaderResult) => void;
|
|
33
|
+
/** Treść w środku pierścienia (`ring`) - np. ikona. */
|
|
34
|
+
children?: ReactNode;
|
|
35
|
+
/** Propsy wrappera `kpt-loader-host` (odpowiednik hosta Angulara). */
|
|
36
|
+
hostProps?: KptHostProps<'span'>;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Loader z charakterem: pętla w jednym z sześciu materiałów, która przy `status` `success` albo
|
|
40
|
+
* `error` przechodzi w znak wyniku zamiast znikać. Te same klasy co `kpt-loader` w Angularze,
|
|
41
|
+
* a rysunek i choreografię daje `createLoader` z `@konce-pt/motion` - wspólne dla obu portów.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* <KptLoader />
|
|
45
|
+
* <KptLoader variant="bridge" status={saving ? 'loading' : 'success'} />
|
|
46
|
+
* <KptLoader variant="wave" value={uploaded} max={total} />
|
|
47
|
+
* <KptLoader variant="ring" status={status}><KptIcon name="download" /></KptLoader>
|
|
48
|
+
*/
|
|
49
|
+
export declare function KptLoader({ variant, status, value, min, max, size, colors, successColor, errorColor, speed, label, successLabel, errorLabel, announce, onSettled, locale, dictionary, children, hostProps, className, style, ...rest }: Readonly<KptLoaderProps>): import("react").JSX.Element;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useEffectEvent, useRef, useState } from 'react';
|
|
4
|
+
import { createLoader, loaderProgress, } from '@konce-pt/motion';
|
|
5
|
+
import { cn } from "../internal/cn.js";
|
|
6
|
+
import { useKptMessages } from "../i18n/i18n-context.js";
|
|
7
|
+
import { useKptMotion } from "../motion/motion-context.js";
|
|
8
|
+
/**
|
|
9
|
+
* Loader z charakterem: pętla w jednym z sześciu materiałów, która przy `status` `success` albo
|
|
10
|
+
* `error` przechodzi w znak wyniku zamiast znikać. Te same klasy co `kpt-loader` w Angularze,
|
|
11
|
+
* a rysunek i choreografię daje `createLoader` z `@konce-pt/motion` - wspólne dla obu portów.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* <KptLoader />
|
|
15
|
+
* <KptLoader variant="bridge" status={saving ? 'loading' : 'success'} />
|
|
16
|
+
* <KptLoader variant="wave" value={uploaded} max={total} />
|
|
17
|
+
* <KptLoader variant="ring" status={status}><KptIcon name="download" /></KptLoader>
|
|
18
|
+
*/
|
|
19
|
+
export function KptLoader({ variant = 'bubbles', status = 'loading', value = null, min = 0, max = 100, size = '5rem', colors = null, successColor = 'success', errorColor = 'danger', speed = 1, label, successLabel, errorLabel, announce = true, onSettled, locale, dictionary, children, hostProps, className, style, ...rest }) {
|
|
20
|
+
const { t } = useKptMessages({ locale, dictionary });
|
|
21
|
+
const motion = useKptMotion();
|
|
22
|
+
const stageRef = useRef(null);
|
|
23
|
+
const contentRef = useRef(null);
|
|
24
|
+
const controllerRef = useRef(null);
|
|
25
|
+
const [ready, setReady] = useState(false);
|
|
26
|
+
const effectiveLabel = label ?? t('loader.label');
|
|
27
|
+
const successText = successLabel ?? t('loader.success');
|
|
28
|
+
const errorText = errorLabel ?? t('loader.error');
|
|
29
|
+
const progress = loaderProgress(value, min, max);
|
|
30
|
+
// Postęp widać tylko w `ring` i `wave` - tylko tam loader jest paskiem postępu dla czytnika.
|
|
31
|
+
const determinate = status === 'loading' && progress !== null && (variant === 'ring' || variant === 'wave');
|
|
32
|
+
const reduced = motion?.enabled === false;
|
|
33
|
+
const settled = useEffectEvent((result) => onSettled?.(result));
|
|
34
|
+
// Najświeższe opcje dla efektów - bez przepisywania refa w trakcie renderu.
|
|
35
|
+
const currentOptions = useEffectEvent(() => ({
|
|
36
|
+
variant,
|
|
37
|
+
status,
|
|
38
|
+
progress,
|
|
39
|
+
colors,
|
|
40
|
+
successColor,
|
|
41
|
+
errorColor,
|
|
42
|
+
successText,
|
|
43
|
+
errorText,
|
|
44
|
+
speed,
|
|
45
|
+
reduced,
|
|
46
|
+
onSettled: (result) => settled(result),
|
|
47
|
+
}));
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (!stageRef.current)
|
|
50
|
+
return;
|
|
51
|
+
const controller = createLoader(stageRef.current, contentRef.current, currentOptions());
|
|
52
|
+
controllerRef.current = controller;
|
|
53
|
+
// Tekst regionu statusu wpisujemy klatkę po pojawieniu się - pusty region nie jest ogłaszany.
|
|
54
|
+
const frame = requestAnimationFrame(() => setReady(true));
|
|
55
|
+
return () => {
|
|
56
|
+
cancelAnimationFrame(frame);
|
|
57
|
+
controller.destroy();
|
|
58
|
+
controllerRef.current = null;
|
|
59
|
+
};
|
|
60
|
+
}, []);
|
|
61
|
+
const colorsKey = (colors ?? []).join('|');
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
controllerRef.current?.update(currentOptions());
|
|
64
|
+
}, [variant, status, progress, colorsKey, successColor, errorColor, successText, errorText, speed, reduced]);
|
|
65
|
+
const announced = !ready ? '' : status === 'success' ? successText : status === 'error' ? errorText : effectiveLabel;
|
|
66
|
+
const { className: hostClassName, ...hostRest } = hostProps ?? {};
|
|
67
|
+
return (_jsx("span", { className: cn('kpt-loader-host', hostClassName), ...hostRest, children: _jsxs("span", { className: cn('kpt-loader', className), "data-variant": variant, "data-status": status, style: { '--_size': size, ...style }, ...rest, children: [_jsx("span", { ref: stageRef, className: "kpt-loader__stage", role: determinate ? 'progressbar' : undefined, "aria-hidden": determinate ? undefined : true, "aria-label": determinate ? effectiveLabel : undefined, "aria-valuemin": determinate ? 0 : undefined, "aria-valuemax": determinate ? 100 : undefined, "aria-valuenow": determinate ? Math.round((progress ?? 0) * 100) : undefined }), _jsx("span", { ref: contentRef, className: "kpt-loader__content", children: children }), announce ? (_jsx("span", { className: "kpt-loader__label", role: "status", children: announced })) : null] }) }));
|
|
68
|
+
}
|