@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
|
@@ -10,7 +10,7 @@ export interface KptSkeletonProps extends Omit<ComponentPropsWithRef<'span'>, 'c
|
|
|
10
10
|
/**
|
|
11
11
|
* Placeholder ładowania z animacją shimmer.
|
|
12
12
|
*
|
|
13
|
-
* Wariant `text` skraca ostatnią linię do 60%
|
|
13
|
+
* Wariant `text` skraca ostatnią linię do 60% - pełny prostokąt nie wygląda jak akapit, a to
|
|
14
14
|
* właśnie akapit ma udawać. Przy jednej linii nie ma czego udawać, więc ta ma pełną szerokość.
|
|
15
15
|
*
|
|
16
16
|
* @example
|
|
@@ -3,7 +3,7 @@ import { cn } from "../internal/cn.js";
|
|
|
3
3
|
/**
|
|
4
4
|
* Placeholder ładowania z animacją shimmer.
|
|
5
5
|
*
|
|
6
|
-
* Wariant `text` skraca ostatnią linię do 60%
|
|
6
|
+
* Wariant `text` skraca ostatnią linię do 60% - pełny prostokąt nie wygląda jak akapit, a to
|
|
7
7
|
* właśnie akapit ma udawać. Przy jednej linii nie ma czego udawać, więc ta ma pełną szerokość.
|
|
8
8
|
*
|
|
9
9
|
* @example
|
package/dist/slider/llms.txt
CHANGED
|
@@ -6,33 +6,39 @@ Import: `import { KptSlider } from '@konce-pt/react';`
|
|
|
6
6
|
## DOM
|
|
7
7
|
<!-- single -->
|
|
8
8
|
<div class="kpt-slider-host">
|
|
9
|
-
<
|
|
9
|
+
<div class="kpt-slider__track" style="--_p: 0.4">
|
|
10
|
+
<input class="kpt-slider__input" type="range">
|
|
11
|
+
<span class="kpt-slider__bar" aria-hidden="true"><span class="kpt-slider__bar-fill"></span></span>
|
|
12
|
+
<span class="kpt-slider__knob" aria-hidden="true"></span>
|
|
13
|
+
</div>
|
|
10
14
|
<span class="kpt-slider__value">40</span>
|
|
11
15
|
</div>
|
|
12
16
|
|
|
13
17
|
<!-- range -->
|
|
14
18
|
<div class="kpt-slider-host">
|
|
15
|
-
<div class="kpt-
|
|
16
|
-
<div class="kpt-slider__rail"></div>
|
|
17
|
-
<div class="kpt-slider__fill" style="left: 20%; right: 20%"></div>
|
|
19
|
+
<div class="kpt-slider__track kpt-slider__track--range" style="--_lo: 0.2; --_hi: 0.8">
|
|
18
20
|
<input class="kpt-slider__thumb kpt-slider__thumb--low" type="range" aria-label="Minimum">
|
|
19
21
|
<input class="kpt-slider__thumb kpt-slider__thumb--high" type="range" aria-label="Maximum">
|
|
22
|
+
<span class="kpt-slider__bar" aria-hidden="true"><span class="kpt-slider__bar-fill"></span></span>
|
|
23
|
+
<span class="kpt-slider__knob kpt-slider__knob--low" aria-hidden="true"></span>
|
|
24
|
+
<span class="kpt-slider__knob kpt-slider__knob--high" aria-hidden="true"></span>
|
|
20
25
|
</div>
|
|
21
|
-
<span class="kpt-slider__value">20
|
|
26
|
+
<span class="kpt-slider__value">20-80</span>
|
|
22
27
|
</div>
|
|
23
28
|
|
|
24
29
|
## Props
|
|
25
|
-
- `value` / `defaultValue` / `onValueChange`
|
|
26
|
-
- `range`: boolean
|
|
30
|
+
- `value` / `defaultValue` / `onValueChange` - `number`, or `[number, number]` in `range` mode
|
|
31
|
+
- `range`: boolean - two handles
|
|
27
32
|
- `min` (default 0), `max` (default 100), `step` (default 1)
|
|
28
|
-
- `showValue`: boolean
|
|
33
|
+
- `showValue`: boolean - prints the current value next to the slider
|
|
29
34
|
- `disabled`, `onTouch()`
|
|
30
35
|
- `className`, `ref` and every other `<div>` prop go to the host
|
|
31
36
|
|
|
32
37
|
## Native inputs all the way down
|
|
33
38
|
Both handles are native `<input type="range">` stacked on each other, so the keyboard, touch
|
|
34
|
-
gestures and screen readers work with no handling of our own
|
|
35
|
-
|
|
39
|
+
gestures and screen readers work with no handling of our own. The only custom pointer logic is the
|
|
40
|
+
press on the track next to the handles (see "Pointer"). The track, fill and handles you see are
|
|
41
|
+
decoration drawn under the transparent inputs.
|
|
36
42
|
|
|
37
43
|
## The handles cannot cross
|
|
38
44
|
Moving the low handle above the high one clamps it to the high value, and the other way round. The
|
|
@@ -43,7 +49,7 @@ pair therefore always stays ordered, so the fill never inverts.
|
|
|
43
49
|
<KptSlider range value={span} onValueChange={(v) => setSpan(v as [number, number])} showValue />
|
|
44
50
|
|
|
45
51
|
## Screen reader and keyboard
|
|
46
|
-
`suffix`: string (" zł", "%", " km")
|
|
52
|
+
`suffix`: string (" zł", "%", " km") - shown after the value and put into `aria-valuetext` ("40
|
|
47
53
|
zł"), so the unit is read too. `aria-invalid` after touch.
|
|
48
54
|
|
|
49
55
|
## Label and description
|
|
@@ -55,9 +61,25 @@ previously hard-coded and mixed-language); inside `kpt-form-field` each thumb re
|
|
|
55
61
|
Minimum" through `aria-labelledby`, without one `ariaLabel` gives "<ariaLabel>, Minimum". `id` goes
|
|
56
62
|
to the single input or the lower thumb.
|
|
57
63
|
|
|
64
|
+
## Pointer
|
|
65
|
+
The handle reacts to the pointer: it grows on hover, and while held it grows more on a spring with
|
|
66
|
+
a soft halo in the primary colour and a `grabbing` cursor. A click on the track (and a key press) glides
|
|
67
|
+
the handle and the fill to the new value instead of jumping; while dragging they follow the pointer with
|
|
68
|
+
no delay. The native `input[type=range]` stays transparent on top (keyboard, screen reader, dragging) and
|
|
69
|
+
the track, fill and handle are drawn next to it from `--_p` (0..1); `range` draws two handles from
|
|
70
|
+
`--_lo`/`--_hi`. The glide is pure CSS and drops out under "reduce motion".
|
|
71
|
+
|
|
72
|
+
In `range` mode a press on the track outside the handles - before the range, after it or inside it -
|
|
73
|
+
moves the nearest handle there, focuses it (arrow keys keep working) and keeps dragging it until
|
|
74
|
+
release. The handles themselves are dragged natively. When both handles sit in the upper half, the
|
|
75
|
+
lower one is stacked on top, so two handles pushed against `max` can still be pulled apart.
|
|
76
|
+
|
|
58
77
|
## Tokens
|
|
59
|
-
`--kpt-color-primary` (the fill and the handles), `--kpt-color-
|
|
78
|
+
`--kpt-color-primary` (the fill and the handles), `--kpt-color-surface-variant` (the track).
|
|
60
79
|
|
|
61
80
|
## Accessibility
|
|
62
81
|
Native range inputs carry their own role and value semantics. In `range` mode each handle gets an
|
|
63
82
|
`aria-label` telling the two apart, because "slider" twice would say nothing.
|
|
83
|
+
|
|
84
|
+
## Read-only and required
|
|
85
|
+
`readOnly` keeps the thumb focusable but undoes every move (`aria-readonly`). The range thumbs can no longer cross: the clamped value is written back to the native input, so the thumb stops at the other one. `slider` does not accept `aria-required` - the field asterisk and the validator carry it.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Oznacza tor suwaka jako przeciągany (`data-dragging`) od pierwszego ruchu wskaźnika o kilka pikseli
|
|
3
|
+
* do puszczenia. Samo kliknięcie zostawia tor bez znacznika, więc skok wartości jedzie przejściem CSS.
|
|
4
|
+
* Ta sama funkcja jest w porcie Angulara (`slider/slider-drag.ts`).
|
|
5
|
+
*/
|
|
6
|
+
export declare function kptTrackDrag(track: HTMLElement, startX: number): void;
|
|
7
|
+
/** Koniec zakresu: dolny albo górny uchwyt. */
|
|
8
|
+
export type KptRangeEnd = 'low' | 'high';
|
|
9
|
+
export interface KptRangePressOptions {
|
|
10
|
+
min: number;
|
|
11
|
+
max: number;
|
|
12
|
+
step: number;
|
|
13
|
+
low: number;
|
|
14
|
+
high: number;
|
|
15
|
+
/** Ustawia wybrany koniec. Pilnowanie, by uchwyty się nie minęły, zostaje w komponencie. */
|
|
16
|
+
set: (end: KptRangeEnd, value: number) => void;
|
|
17
|
+
/** Puszczenie wskaźnika. */
|
|
18
|
+
release?: () => void;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Wciśnięcie toru zakresu poza uchwytem. Najbliższy uchwyt skacze w to miejsce (wewnątrz zakresu
|
|
22
|
+
* też - ten bliższy), dostaje fokus, żeby dalej działały strzałki, i idzie za wskaźnikiem aż do
|
|
23
|
+
* puszczenia. Natywne suwaki tego nie zrobią: przy dwóch nałożonych inputach wskaźnik łapią tylko
|
|
24
|
+
* kciuki, a kliknięcie w tor trafiłoby w jeden z nich zawsze, niezależnie od odległości.
|
|
25
|
+
*/
|
|
26
|
+
export declare function kptRangePress(track: HTMLElement, event: PointerEvent, opts: KptRangePressOptions): void;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Oznacza tor suwaka jako przeciągany (`data-dragging`) od pierwszego ruchu wskaźnika o kilka pikseli
|
|
3
|
+
* do puszczenia. Samo kliknięcie zostawia tor bez znacznika, więc skok wartości jedzie przejściem CSS.
|
|
4
|
+
* Ta sama funkcja jest w porcie Angulara (`slider/slider-drag.ts`).
|
|
5
|
+
*/
|
|
6
|
+
export function kptTrackDrag(track, startX) {
|
|
7
|
+
const move = (e) => {
|
|
8
|
+
if (Math.abs(e.clientX - startX) >= 3)
|
|
9
|
+
track.setAttribute('data-dragging', '');
|
|
10
|
+
};
|
|
11
|
+
const up = () => {
|
|
12
|
+
window.removeEventListener('pointermove', move);
|
|
13
|
+
window.removeEventListener('pointerup', up);
|
|
14
|
+
window.removeEventListener('pointercancel', up);
|
|
15
|
+
track.removeAttribute('data-dragging');
|
|
16
|
+
};
|
|
17
|
+
window.addEventListener('pointermove', move);
|
|
18
|
+
window.addEventListener('pointerup', up);
|
|
19
|
+
window.addEventListener('pointercancel', up);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Wciśnięcie toru zakresu poza uchwytem. Najbliższy uchwyt skacze w to miejsce (wewnątrz zakresu
|
|
23
|
+
* też - ten bliższy), dostaje fokus, żeby dalej działały strzałki, i idzie za wskaźnikiem aż do
|
|
24
|
+
* puszczenia. Natywne suwaki tego nie zrobią: przy dwóch nałożonych inputach wskaźnik łapią tylko
|
|
25
|
+
* kciuki, a kliknięcie w tor trafiłoby w jeden z nich zawsze, niezależnie od odległości.
|
|
26
|
+
*/
|
|
27
|
+
export function kptRangePress(track, event, opts) {
|
|
28
|
+
if (event.button !== 0)
|
|
29
|
+
return;
|
|
30
|
+
// Bez tego przeglądarka zaznacza tekst w trakcie ciągnięcia i zabiera fokus z uchwytu.
|
|
31
|
+
event.preventDefault();
|
|
32
|
+
const first = rangeValueAt(track, event.clientX, opts);
|
|
33
|
+
const end = first < opts.low ? 'low' : first > opts.high ? 'high' : first - opts.low <= opts.high - first ? 'low' : 'high';
|
|
34
|
+
let last = first;
|
|
35
|
+
opts.set(end, first);
|
|
36
|
+
track.querySelector(`.kpt-slider__thumb--${end}`)?.focus({ preventScroll: true });
|
|
37
|
+
track.setAttribute('data-pressed', end);
|
|
38
|
+
const move = (e) => {
|
|
39
|
+
const next = rangeValueAt(track, e.clientX, opts);
|
|
40
|
+
if (next === last)
|
|
41
|
+
return;
|
|
42
|
+
last = next;
|
|
43
|
+
opts.set(end, next);
|
|
44
|
+
};
|
|
45
|
+
const up = () => {
|
|
46
|
+
window.removeEventListener('pointermove', move);
|
|
47
|
+
window.removeEventListener('pointerup', up);
|
|
48
|
+
window.removeEventListener('pointercancel', up);
|
|
49
|
+
track.removeAttribute('data-pressed');
|
|
50
|
+
opts.release?.();
|
|
51
|
+
};
|
|
52
|
+
window.addEventListener('pointermove', move);
|
|
53
|
+
window.addEventListener('pointerup', up);
|
|
54
|
+
window.addEventListener('pointercancel', up);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Wartość pod wskaźnikiem, przyciągnięta do kroku tak jak natywny suwak. Środek uchwytu jeździ
|
|
58
|
+
* od połowy jego szerokości do szerokości toru minus połowa - stąd odjęcie szerokości uchwytu.
|
|
59
|
+
*/
|
|
60
|
+
function rangeValueAt(track, clientX, { min, max, step }) {
|
|
61
|
+
const rect = track.getBoundingClientRect();
|
|
62
|
+
const thumb = track.querySelector('.kpt-slider__knob')?.offsetWidth ?? 0;
|
|
63
|
+
const usable = rect.width - thumb;
|
|
64
|
+
let frac = usable > 0 ? (clientX - rect.left - thumb / 2) / usable : 0;
|
|
65
|
+
frac = Math.min(1, Math.max(0, frac));
|
|
66
|
+
if (getComputedStyle(track).direction === 'rtl')
|
|
67
|
+
frac = 1 - frac;
|
|
68
|
+
if (max <= min)
|
|
69
|
+
return min;
|
|
70
|
+
if (!(step > 0))
|
|
71
|
+
return min + frac * (max - min);
|
|
72
|
+
let steps = Math.round((frac * (max - min)) / step);
|
|
73
|
+
// Jak natywny suwak: ostatni krok nie wychodzi za `max`, gdy zakres nie dzieli się przez krok.
|
|
74
|
+
if (min + steps * step > max)
|
|
75
|
+
steps -= 1;
|
|
76
|
+
// Ułamkowy krok (0.1) zostawia ogon zmiennoprzecinkowy - przycięcie do liczby miejsc kroku.
|
|
77
|
+
const decimals = String(step).split('.')[1]?.length ?? 0;
|
|
78
|
+
return Number((min + steps * step).toFixed(decimals));
|
|
79
|
+
}
|
package/dist/slider/slider.d.ts
CHANGED
|
@@ -13,25 +13,28 @@ export interface KptSliderProps extends Omit<ComponentPropsWithRef<'div'>, 'onCh
|
|
|
13
13
|
step?: number;
|
|
14
14
|
/** Pokazuje bieżącą wartość obok suwaka. */
|
|
15
15
|
showValue?: boolean;
|
|
16
|
-
/** Jednostka za liczbą (
|
|
16
|
+
/** Jednostka za liczbą (" zł", "%", " km") - w pokazanej wartości i w `aria-valuetext`. */
|
|
17
17
|
suffix?: string;
|
|
18
18
|
invalid?: boolean;
|
|
19
19
|
touched?: boolean;
|
|
20
|
-
/** Id elementu fokusowalnego (nie hosta); bez niego generowane, w `KptFormField`
|
|
20
|
+
/** Id elementu fokusowalnego (nie hosta); bez niego generowane, w `KptFormField` - z wrappera. */
|
|
21
21
|
id?: string;
|
|
22
22
|
/** Nazwa dostępna, gdy kontrolki nie opisuje etykieta (`<label for>` albo `KptFormField`). */
|
|
23
23
|
ariaLabel?: string | null;
|
|
24
24
|
disabled?: boolean;
|
|
25
|
+
/** Tylko do odczytu - uchwyt można skupić, ale nie przesunąć (`aria-readonly`). */
|
|
26
|
+
readOnly?: boolean;
|
|
25
27
|
onTouch?: () => void;
|
|
26
28
|
}
|
|
27
29
|
/**
|
|
28
30
|
* Suwak wartości. W trybie `range` udostępnia dwa uchwyty, a wartością jest para `[min, max]`.
|
|
29
31
|
*
|
|
30
|
-
* Oba uchwyty to natywne `<input type="range">` leżące na sobie
|
|
31
|
-
*
|
|
32
|
+
* Oba uchwyty to natywne `<input type="range">` leżące na sobie - klawiatura, gesty dotykowe
|
|
33
|
+
* i czytniki ekranu działają bez własnej obsługi. Własna jest tylko jedna rzecz: wciśnięcie toru
|
|
34
|
+
* obok kciuków przesuwa najbliższy uchwyt (`kptRangePress`).
|
|
32
35
|
*
|
|
33
36
|
* @example
|
|
34
37
|
* <KptSlider value={volume} onValueChange={setVolume} min={0} max={100} step={5} showValue />
|
|
35
38
|
* <KptSlider range value={range} onValueChange={setRange} showValue />
|
|
36
39
|
*/
|
|
37
|
-
export declare function KptSlider({ value, defaultValue, onValueChange, range, min, max, step, showValue, suffix, invalid, touched, id, ariaLabel, disabled, onTouch, locale, dictionary, className, ...rest }: Readonly<KptSliderProps>): import("react").JSX.Element;
|
|
40
|
+
export declare function KptSlider({ value, defaultValue, onValueChange, range, min, max, step, showValue, suffix, invalid, touched, id, ariaLabel, disabled, readOnly, onTouch, locale, dictionary, className, ...rest }: Readonly<KptSliderProps>): import("react").JSX.Element;
|
package/dist/slider/slider.js
CHANGED
|
@@ -1,22 +1,25 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
3
|
import { cn } from "../internal/cn.js";
|
|
3
4
|
import { useControllableState } from "../internal/use-controllable-state.js";
|
|
4
5
|
import { joinIds, useKptFieldA11y } from "../form-field/form-field-context.js";
|
|
5
6
|
import { useKptMessages } from "../i18n/i18n-context.js";
|
|
7
|
+
import { kptRangePress, kptTrackDrag } from "./slider-drag.js";
|
|
6
8
|
/**
|
|
7
9
|
* Suwak wartości. W trybie `range` udostępnia dwa uchwyty, a wartością jest para `[min, max]`.
|
|
8
10
|
*
|
|
9
|
-
* Oba uchwyty to natywne `<input type="range">` leżące na sobie
|
|
10
|
-
*
|
|
11
|
+
* Oba uchwyty to natywne `<input type="range">` leżące na sobie - klawiatura, gesty dotykowe
|
|
12
|
+
* i czytniki ekranu działają bez własnej obsługi. Własna jest tylko jedna rzecz: wciśnięcie toru
|
|
13
|
+
* obok kciuków przesuwa najbliższy uchwyt (`kptRangePress`).
|
|
11
14
|
*
|
|
12
15
|
* @example
|
|
13
16
|
* <KptSlider value={volume} onValueChange={setVolume} min={0} max={100} step={5} showValue />
|
|
14
17
|
* <KptSlider range value={range} onValueChange={setRange} showValue />
|
|
15
18
|
*/
|
|
16
|
-
export function KptSlider({ value, defaultValue, onValueChange, range = false, min = 0, max = 100, step = 1, showValue = false, suffix = '', invalid = false, touched, id, ariaLabel = null, disabled = false, onTouch, locale, dictionary, className, ...rest }) {
|
|
19
|
+
export function KptSlider({ value, defaultValue, onValueChange, range = false, min = 0, max = 100, step = 1, showValue = false, suffix = '', invalid = false, touched, id, ariaLabel = null, disabled = false, readOnly = false, onTouch, locale, dictionary, className, ...rest }) {
|
|
17
20
|
const { t } = useKptMessages({ locale, dictionary });
|
|
18
21
|
const field = useKptFieldA11y(id, 'kpt-slider');
|
|
19
|
-
/** Uchwyt zakresu bez etykiety pola:
|
|
22
|
+
/** Uchwyt zakresu bez etykiety pola: "Cena, Minimum" z `ariaLabel` albo samo "Minimum". */
|
|
20
23
|
const thumbLabel = (end) => {
|
|
21
24
|
if (field.labelledBy)
|
|
22
25
|
return undefined;
|
|
@@ -36,8 +39,35 @@ export function KptSlider({ value, defaultValue, onValueChange, range = false, m
|
|
|
36
39
|
const span = max - min;
|
|
37
40
|
return span <= 0 ? 0 : ((val - min) / span) * 100;
|
|
38
41
|
};
|
|
39
|
-
// Uchwyty nie mogą się wyminąć
|
|
40
|
-
|
|
41
|
-
const
|
|
42
|
-
|
|
42
|
+
// Uchwyty nie mogą się wyminąć - dolny nie przekracza górnego i odwrotnie.
|
|
43
|
+
// Suwaki są kontrolowane: przy `readOnly` stan się nie zmienia, więc React cofa uchwyt sam.
|
|
44
|
+
const setLow = (next) => {
|
|
45
|
+
if (!readOnly)
|
|
46
|
+
setCurrent([Math.min(next, high), high]);
|
|
47
|
+
};
|
|
48
|
+
const setHigh = (next) => {
|
|
49
|
+
if (!readOnly)
|
|
50
|
+
setCurrent([low, Math.max(next, low)]);
|
|
51
|
+
};
|
|
52
|
+
// Kciuki ciągnie natywny suwak; wciśnięcie obok nich (przed zakresem, za nim albo w środku)
|
|
53
|
+
// przesuwa najbliższy uchwyt i dalej go ciągnie - patrz kptRangePress.
|
|
54
|
+
const onRangePress = (event) => {
|
|
55
|
+
kptTrackDrag(event.currentTarget, event.clientX);
|
|
56
|
+
if (disabled || event.target instanceof HTMLInputElement)
|
|
57
|
+
return;
|
|
58
|
+
kptRangePress(event.currentTarget, event.nativeEvent, {
|
|
59
|
+
min,
|
|
60
|
+
max,
|
|
61
|
+
step,
|
|
62
|
+
low,
|
|
63
|
+
high,
|
|
64
|
+
set: (end, v) => (end === 'low' ? setLow(v) : setHigh(v)),
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
return (_jsx("div", { className: cn('kpt-slider-host', className), ...rest, children: range ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "kpt-slider__track kpt-slider__track--range", style: { ['--_lo']: pct(low) / 100, ['--_hi']: pct(high) / 100 }, "data-low-top": pct(low) > 50 ? '' : undefined, onPointerDown: onRangePress, children: [_jsx("span", { className: "kpt-slider__sr", id: `${field.id}-min-name`, children: t('slider.min') }), _jsx("span", { className: "kpt-slider__sr", id: `${field.id}-max-name`, children: t('slider.max') }), _jsx("input", { className: "kpt-slider__thumb kpt-slider__thumb--low", type: "range", min: min, max: max, step: step, value: low, "aria-valuetext": suffix ? `${low}${suffix}` : undefined, "aria-invalid": invalid && (touched ?? true) ? true : undefined, id: field.id, disabled: disabled, "aria-label": thumbLabel('min'), "aria-labelledby": thumbLabelledBy('min'), "aria-describedby": field.describedBy, "aria-readonly": readOnly || undefined, onChange: (event) => setLow(Number(event.target.value)), onBlur: () => onTouch?.() }), _jsx("input", { className: "kpt-slider__thumb kpt-slider__thumb--high", type: "range", min: min, max: max, step: step, value: high, "aria-valuetext": suffix ? `${high}${suffix}` : undefined, "aria-invalid": invalid && (touched ?? true) ? true : undefined, disabled: disabled, "aria-label": thumbLabel('max'), "aria-labelledby": thumbLabelledBy('max'), "aria-describedby": field.describedBy, "aria-readonly": readOnly || undefined, onChange: (event) => setHigh(Number(event.target.value)), onBlur: () => onTouch?.() }), _jsx("span", { className: "kpt-slider__bar", "aria-hidden": "true", children: _jsx("span", { className: "kpt-slider__bar-fill" }) }), _jsx("span", { className: "kpt-slider__knob kpt-slider__knob--low", "aria-hidden": "true" }), _jsx("span", { className: "kpt-slider__knob kpt-slider__knob--high", "aria-hidden": "true" })] }), showValue ? (_jsxs("span", { className: "kpt-slider__value", children: [low, suffix, "-", high, suffix] })) : null] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: "kpt-slider__track", style: { ['--_p']: pct(single) / 100 },
|
|
68
|
+
// Skok po kliknięciu w tor jedzie (przejście w CSS), przeciąganie już nie - patrz kptTrackDrag.
|
|
69
|
+
onPointerDown: (event) => kptTrackDrag(event.currentTarget, event.clientX), children: [_jsx("input", { className: "kpt-slider__input", type: "range", min: min, max: max, step: step, value: single, "aria-valuetext": suffix ? `${single}${suffix}` : undefined, "aria-invalid": invalid && (touched ?? true) ? true : undefined, id: field.id, disabled: disabled, "aria-label": ariaLabel ?? undefined, "aria-describedby": field.describedBy, "aria-readonly": readOnly || undefined, onChange: (event) => {
|
|
70
|
+
if (!readOnly)
|
|
71
|
+
setCurrent(Number(event.target.value));
|
|
72
|
+
}, onBlur: () => onTouch?.() }), _jsx("span", { className: "kpt-slider__bar", "aria-hidden": "true", children: _jsx("span", { className: "kpt-slider__bar-fill" }) }), _jsx("span", { className: "kpt-slider__knob", "aria-hidden": "true" })] }), showValue ? (_jsxs("span", { className: "kpt-slider__value", children: [single, suffix] })) : null] })) }));
|
|
43
73
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# KptSpeechButton, KptSpeechWave, useKptSpeechRecognition (@konce-pt/react)
|
|
2
|
+
|
|
3
|
+
Dictation with a microphone button. A click turns the icon into "stop", an animated wave slides in
|
|
4
|
+
next to it and the recognised words appear live in the target: a textarea, an input, a
|
|
5
|
+
`contenteditable` element, a plain element or a function. Built on the Web Speech API; the session,
|
|
6
|
+
insertion and wave maths live in `@konce-pt/speech`, shared with the Angular port.
|
|
7
|
+
Import: `import { KptSpeechButton, KptSpeechWave, useKptSpeechRecognition } from '@konce-pt/react';`
|
|
8
|
+
|
|
9
|
+
## The key rule
|
|
10
|
+
Interim text is visible in the target but never reaches your state. A textarea or input shows it in
|
|
11
|
+
the native value without an event; only final text goes in as a regular edit (`setRangeText` +
|
|
12
|
+
`InputEvent`, or `insertText` with native undo when the field has focus), so a controlled field's
|
|
13
|
+
`onChange` sees it like typed text. The core never writes `el.value = x` - React would swallow it.
|
|
14
|
+
|
|
15
|
+
## DOM
|
|
16
|
+
<span class="kpt-speech-button-host" data-status="listening" data-wave-position="start">
|
|
17
|
+
<span class="kpt-speech-wave-host kpt-speech-button__wave" aria-hidden="true" data-mode="live">...</span>
|
|
18
|
+
<span class="kpt-icon-button-host kpt-speech-button__toggle">
|
|
19
|
+
<button class="kpt-icon-button" aria-label="Dictate" aria-pressed="true">...icon...</button>
|
|
20
|
+
</span>
|
|
21
|
+
<span class="kpt-speech-button__status" aria-live="polite">Listening...</span>
|
|
22
|
+
</span>
|
|
23
|
+
|
|
24
|
+
## useKptSpeechRecognition(options)
|
|
25
|
+
Dictation without UI - for your own button, a keyboard shortcut, or to call `flush()` before sending.
|
|
26
|
+
Options:
|
|
27
|
+
- `target`: `HTMLElement | RefObject<HTMLElement | null> | KptSpeechTarget | (snapshot) => void`
|
|
28
|
+
- `lang`: string - BCP 47; without it from `KptI18nProvider` and the browser language
|
|
29
|
+
- `continuous`: boolean = true; `mode`: `'insert' | 'append' | 'replace'` = `'insert'` (`replace`
|
|
30
|
+
clears the target the moment dictation starts, undoable with Ctrl+Z)
|
|
31
|
+
- `interim`: `'inline' | 'ghost' | 'none'`; `capitalize`: boolean = true
|
|
32
|
+
- `wave`: `'auto' | 'live' | 'synthetic' | 'none'` = `'auto'`; `bars`: number = 5
|
|
33
|
+
- `silenceMs`, `maxDuration` (ms, 0 = off), `processLocally` (on-device only, Chrome 135+), `disabled`
|
|
34
|
+
- `onStart`, `onEnd(reason)`, `onResult(snapshot)`, `onError(code)`
|
|
35
|
+
|
|
36
|
+
Returns `KptSpeechRecognition`: `status`, `listening`, `support` (`null` until the first client
|
|
37
|
+
render - SSR safe), `available`, `error`, `lastEnd`, `speaking`, `levels`, `waveMode`, `snapshot`
|
|
38
|
+
(`{ finalText, interimText }`), and `start()`, `stop()` (graceful), `flush()` (interim becomes final
|
|
39
|
+
and dictation ends at once), `abort()`, `toggle()`.
|
|
40
|
+
|
|
41
|
+
## KptSpeechButton - props
|
|
42
|
+
- every option of `useKptSpeechRecognition`, or `speech` - an external controller from the hook (then
|
|
43
|
+
the button's own dictation options are ignored)
|
|
44
|
+
- `unsupported`: `'disable' | 'hide'` = `'disable'` - Firefox, `http:`, or a browser without the
|
|
45
|
+
service: a disabled button with the reason in a tooltip, or nothing
|
|
46
|
+
- `wavePosition`: `'start' | 'end'` = `'start'`
|
|
47
|
+
- `listeningColor`: `'primary' | 'danger' | 'success' | 'warning' | 'info'` = `'primary'` - colour of
|
|
48
|
+
"stop" and the wave while listening (the theme colour by default). Any other colour: set
|
|
49
|
+
`--kpt-speech-color` and `--kpt-speech-on-color` (icon on it) - both inherit, so they also work on
|
|
50
|
+
fields with a built-in microphone or globally in `:root`.
|
|
51
|
+
- `size` = 'md', `variant` = 'text'
|
|
52
|
+
- `aria-label` (default "Dictate" from the dictionary); `locale`, `dictionary`
|
|
53
|
+
- `hostProps` - props for `.kpt-speech-button-host`; `className`, `ref` and other `<button>` props go
|
|
54
|
+
to the inner `<button>`
|
|
55
|
+
|
|
56
|
+
## Examples
|
|
57
|
+
const noteRef = useRef<HTMLTextAreaElement>(null);
|
|
58
|
+
<textarea ref={noteRef} />
|
|
59
|
+
<KptSpeechButton target={noteRef} />
|
|
60
|
+
|
|
61
|
+
<KptSpeechButton target={(snapshot) => setDraft(snapshot.finalText)} lang="en-US" />
|
|
62
|
+
|
|
63
|
+
const speech = useKptSpeechRecognition({ target: fieldRef });
|
|
64
|
+
<KptSpeechButton speech={speech} />
|
|
65
|
+
<KptButton onClick={() => { speech.flush(); send(); }}>Send</KptButton>
|
|
66
|
+
|
|
67
|
+
## KptSpeechWave
|
|
68
|
+
Bars next to the microphone, `aria-hidden`. `levels: readonly number[]` (0..1) draws a live wave;
|
|
69
|
+
empty `levels` gives the synthetic one, higher while `active` (voice heard). `bars`: number = 5.
|
|
70
|
+
Colour: `--kpt-speech-wave-color`, otherwise the listening colour of the button.
|
|
71
|
+
|
|
72
|
+
## Behaviour
|
|
73
|
+
- One dictation per page: starting a microphone stops the previous one.
|
|
74
|
+
- Continuous mode survives Chrome ending recognition after silence or about a minute; on iOS the
|
|
75
|
+
button stops and announces "Tap the microphone to continue".
|
|
76
|
+
- Typing or moving the caret mid-dictation drops the interim text; dictation continues at the caret.
|
|
77
|
+
- A hidden tab stops dictation; unmounting releases the microphone.
|
|
78
|
+
|
|
79
|
+
## Screen reader and keyboard
|
|
80
|
+
- The button name stays the same ("Dictate"); the state is carried by `aria-pressed`.
|
|
81
|
+
- A polite live region announces start, stop and errors - never the transcript.
|
|
82
|
+
- Clicking the button does not move focus out of the target. Escape on the button or in the target
|
|
83
|
+
stops dictation.
|
|
84
|
+
|
|
85
|
+
## Browsers and privacy
|
|
86
|
+
Chrome, Edge and Safari recognise speech; Firefox does not. A secure context is required. Chrome sends
|
|
87
|
+
audio to a Google service unless `processLocally` is set - mention it in the privacy policy.
|
|
88
|
+
|
|
89
|
+
## Built into fields
|
|
90
|
+
`KptInput`, `KptTextarea` and `KptChatComposer` take `speech` (`speechLang`, `speechMode`, `speechColor`) - the same
|
|
91
|
+
layout as in Angular; they use `unsupported="hide"` and reserve room only when `available` is true.
|
|
92
|
+
`KptRichText` has the `dictate` tool, `KptAutocomplete` takes `speech` (default `replace`) and
|
|
93
|
+
`KptDataTable` takes `filterSpeech`, `KptMapSearch` takes `speech` (forwarded by `KptMap` as
|
|
94
|
+
`searchSpeech`). Search fields clear on click and listen for one utterance.
|
|
95
|
+
|
|
96
|
+
The hook option `enabled` (default true) creates no session and no page listeners when false - for
|
|
97
|
+
components where the microphone is optional.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { ComponentPropsWithRef } from 'react';
|
|
2
|
+
import type { KptButtonSize, KptButtonVariant } from '../button/button.tsx';
|
|
3
|
+
import { type KptI18nOverrides } from '../i18n/i18n-context.tsx';
|
|
4
|
+
import type { KptHostProps } from '../internal/types.ts';
|
|
5
|
+
import { type KptSpeechRecognition, type KptSpeechRecognitionOptions } from './use-speech-recognition.ts';
|
|
6
|
+
/** Co zrobić, gdy przeglądarka nie rozpoznaje mowy. */
|
|
7
|
+
export type KptSpeechUnsupported = 'hide' | 'disable';
|
|
8
|
+
/**
|
|
9
|
+
* Kolor przycisku "stop" i fali w trakcie nasłuchu. Domyślnie kolor motywu (`primary`); dowolny inny
|
|
10
|
+
* przez `--kpt-speech-color` i `--kpt-speech-on-color` (dziedziczone, więc działają też w polach).
|
|
11
|
+
*/
|
|
12
|
+
export type KptSpeechColor = 'primary' | 'danger' | 'success' | 'warning' | 'info';
|
|
13
|
+
export interface KptSpeechButtonProps extends KptSpeechRecognitionOptions, Omit<ComponentPropsWithRef<'button'>, 'children' | 'type' | 'onError' | 'disabled'>, KptI18nOverrides {
|
|
14
|
+
/**
|
|
15
|
+
* Zewnętrzny kontroler z `useKptSpeechRecognition` - gdy aplikacja sama chce wołać `flush()`,
|
|
16
|
+
* `stop()` albo czytać `snapshot`. Wtedy opcje dyktowania z propsów przycisku są pomijane.
|
|
17
|
+
*/
|
|
18
|
+
speech?: KptSpeechRecognition;
|
|
19
|
+
/** Brak wsparcia: `disable` (domyślnie, z powodem w podpowiedzi) albo `hide`. */
|
|
20
|
+
unsupported?: KptSpeechUnsupported;
|
|
21
|
+
/** Po której stronie przycisku stoi fala. */
|
|
22
|
+
wavePosition?: 'start' | 'end';
|
|
23
|
+
size?: KptButtonSize;
|
|
24
|
+
variant?: KptButtonVariant;
|
|
25
|
+
/** Kolor "stop" i fali w trakcie nasłuchu - domyślnie kolor motywu. */
|
|
26
|
+
listeningColor?: KptSpeechColor;
|
|
27
|
+
/** Nazwa przycisku; bez niej "Dyktuj" ze słownika. Stała - stan niesie `aria-pressed`. */
|
|
28
|
+
'aria-label'?: string;
|
|
29
|
+
/** Propsy wrappera hosta (`.kpt-speech-button-host`). */
|
|
30
|
+
hostProps?: KptHostProps<'span'>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Mikrofon do dyktowania. Klik zamienia ikonę w "stop", obok wjeżdża animowana fala, a słowa
|
|
34
|
+
* płyną na żywo do celu: textarea, inputu, contenteditable, zwykłego elementu albo funkcji.
|
|
35
|
+
*
|
|
36
|
+
* Tekst tymczasowy widać w celu, ale do stanu kontrolowanego pola trafia wyłącznie tekst
|
|
37
|
+
* zatwierdzony - przez zwykłą edycję, którą `onChange` widzi jak wpisany tekst.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* const noteRef = useRef<HTMLTextAreaElement>(null);
|
|
41
|
+
* <textarea ref={noteRef} />
|
|
42
|
+
* <KptSpeechButton target={noteRef} />
|
|
43
|
+
*/
|
|
44
|
+
export declare function KptSpeechButton({ speech, target, lang, continuous, mode, interim, capitalize, wave, bars, silenceMs, maxDuration, processLocally, disabled, onStart, onEnd, onResult, onError, unsupported, wavePosition, size, variant, listeningColor, 'aria-label': ariaLabel, hostProps, locale, dictionary, className, onClick, onMouseDown, onKeyDown, ...rest }: Readonly<KptSpeechButtonProps>): import("react").JSX.Element;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { speechErrorMessageKey } from '@konce-pt/speech';
|
|
4
|
+
import { KptIcon } from "../icon/icon.js";
|
|
5
|
+
import { KptIconButton } from "../icon-button/icon-button.js";
|
|
6
|
+
import { KptTooltip } from "../tooltip/tooltip.js";
|
|
7
|
+
import { useKptMessages } from "../i18n/i18n-context.js";
|
|
8
|
+
import { cn } from "../internal/cn.js";
|
|
9
|
+
import { KptSpeechWave } from "./speech-wave.js";
|
|
10
|
+
import { useKptSpeechRecognition, } from "./use-speech-recognition.js";
|
|
11
|
+
/**
|
|
12
|
+
* Mikrofon do dyktowania. Klik zamienia ikonę w "stop", obok wjeżdża animowana fala, a słowa
|
|
13
|
+
* płyną na żywo do celu: textarea, inputu, contenteditable, zwykłego elementu albo funkcji.
|
|
14
|
+
*
|
|
15
|
+
* Tekst tymczasowy widać w celu, ale do stanu kontrolowanego pola trafia wyłącznie tekst
|
|
16
|
+
* zatwierdzony - przez zwykłą edycję, którą `onChange` widzi jak wpisany tekst.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* const noteRef = useRef<HTMLTextAreaElement>(null);
|
|
20
|
+
* <textarea ref={noteRef} />
|
|
21
|
+
* <KptSpeechButton target={noteRef} />
|
|
22
|
+
*/
|
|
23
|
+
export function KptSpeechButton({ speech, target, lang, continuous, mode, interim, capitalize, wave, bars = 5, silenceMs, maxDuration, processLocally, disabled = false, onStart, onEnd, onResult, onError, unsupported = 'disable', wavePosition = 'start', size = 'md', variant = 'text', listeningColor = 'primary', 'aria-label': ariaLabel, hostProps, locale, dictionary, className, onClick, onMouseDown, onKeyDown, ...rest }) {
|
|
24
|
+
const { t } = useKptMessages({ locale, dictionary });
|
|
25
|
+
// Hook jest wołany zawsze (zasady hooków); z zewnętrznym kontrolerem własny jest wyłączony
|
|
26
|
+
// (`enabled: false`) - bez sesji i bez nasłuchów strony.
|
|
27
|
+
const own = useKptSpeechRecognition({
|
|
28
|
+
enabled: !speech,
|
|
29
|
+
target,
|
|
30
|
+
lang,
|
|
31
|
+
continuous,
|
|
32
|
+
mode,
|
|
33
|
+
interim,
|
|
34
|
+
capitalize,
|
|
35
|
+
wave,
|
|
36
|
+
bars,
|
|
37
|
+
silenceMs,
|
|
38
|
+
maxDuration,
|
|
39
|
+
processLocally,
|
|
40
|
+
disabled,
|
|
41
|
+
onStart,
|
|
42
|
+
onEnd,
|
|
43
|
+
onResult,
|
|
44
|
+
onError,
|
|
45
|
+
});
|
|
46
|
+
const ctl = speech ?? own;
|
|
47
|
+
const known = ctl.support !== null;
|
|
48
|
+
const hidden = unsupported === 'hide' && (!known || !ctl.available);
|
|
49
|
+
const isDisabled = disabled || !ctl.available;
|
|
50
|
+
// Powód blokady albo ostatni błąd - w podpowiedzi. Cisza nie jest błędem wartym dymka.
|
|
51
|
+
let hint = '';
|
|
52
|
+
if (known && !ctl.available) {
|
|
53
|
+
hint = t(speechErrorMessageKey(ctl.support?.reason ?? 'unsupported'));
|
|
54
|
+
}
|
|
55
|
+
else if (ctl.error && ctl.error !== 'no-speech') {
|
|
56
|
+
hint = t(speechErrorMessageKey(ctl.error));
|
|
57
|
+
}
|
|
58
|
+
// Region statusu ogłasza stan i błędy, nigdy treść - czytnik czytający transkrypt zostałby
|
|
59
|
+
// nagrany przez mikrofon i wpisany z powrotem do pola.
|
|
60
|
+
let announcement = '';
|
|
61
|
+
if (ctl.error)
|
|
62
|
+
announcement = t(speechErrorMessageKey(ctl.error));
|
|
63
|
+
else if (ctl.listening)
|
|
64
|
+
announcement = t('speech.listening');
|
|
65
|
+
else if (ctl.lastEnd && ctl.lastEnd !== 'error') {
|
|
66
|
+
// iOS: restart bez dotknięcia jest zawodny - sesja staje, a użytkownik dostaje wskazówkę.
|
|
67
|
+
const iosPause = ctl.support?.ios && (continuous ?? true) && (ctl.lastEnd === 'ended' || ctl.lastEnd === 'silence');
|
|
68
|
+
announcement = t(iosPause ? 'speech.tapToContinue' : 'speech.stopped');
|
|
69
|
+
}
|
|
70
|
+
const { className: hostClassName, ...hostRest } = hostProps ?? {};
|
|
71
|
+
return (_jsx("span", { className: cn('kpt-speech-button-host', hostClassName), "data-status": ctl.status, "data-wave-position": wavePosition, "data-color": listeningColor, hidden: hidden || undefined, ...hostRest, children: !hidden ? (_jsxs(_Fragment, { children: [ctl.listening && ctl.waveMode !== 'none' ? (_jsx(KptSpeechWave, { className: "kpt-speech-button__wave", levels: ctl.waveMode === 'live' ? ctl.levels : [], active: ctl.speaking, bars: bars })) : null, _jsx(KptTooltip, { text: hint, children: _jsx(KptIconButton, { className: className, hostProps: { className: 'kpt-speech-button__toggle' }, variant: ctl.listening ? 'filled' : variant, size: size, disabled: isDisabled, "aria-label": ariaLabel ?? t('speech.dictate'), "aria-pressed": ctl.listening, onMouseDown: (event) => {
|
|
72
|
+
onMouseDown?.(event);
|
|
73
|
+
// Klik nie zabiera fokusu z celu - kursor zostaje tam, gdzie trafi tekst.
|
|
74
|
+
event.preventDefault();
|
|
75
|
+
}, onClick: (event) => {
|
|
76
|
+
onClick?.(event);
|
|
77
|
+
if (!event.defaultPrevented)
|
|
78
|
+
ctl.toggle();
|
|
79
|
+
}, onKeyDown: (event) => {
|
|
80
|
+
onKeyDown?.(event);
|
|
81
|
+
if (event.key === 'Escape' && ctl.listening) {
|
|
82
|
+
event.preventDefault();
|
|
83
|
+
ctl.stop();
|
|
84
|
+
}
|
|
85
|
+
}, ...rest, children: _jsx(KptIcon, { name: ctl.listening ? 'player-stop' : 'microphone' }) }) }), _jsx("span", { className: "kpt-speech-button__status", "aria-live": "polite", children: announcement })] })) : null }));
|
|
86
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ComponentPropsWithRef } from 'react';
|
|
2
|
+
export interface KptSpeechWaveProps extends Omit<ComponentPropsWithRef<'span'>, 'children'> {
|
|
3
|
+
/** Poziomy 0..1 z miernika; pusta lista przełącza falę w tryb syntetyczny. */
|
|
4
|
+
levels?: readonly number[];
|
|
5
|
+
/** Tryb syntetyczny: silnik słyszy mowę - słupki skaczą wyżej. */
|
|
6
|
+
active?: boolean;
|
|
7
|
+
/** Liczba słupków w trybie syntetycznym (na żywo decyduje długość `levels`). */
|
|
8
|
+
bars?: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Animowana fala dźwięku - słupki obok mikrofonu. Czysta dekoracja (`aria-hidden`): stan
|
|
12
|
+
* nasłuchu niesie przycisk (`aria-pressed`) i region statusu, nie rysunek. Te same klasy co
|
|
13
|
+
* `kpt-speech-wave` w Angularze, więc styl jest wspólny.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* <KptSpeechWave levels={speech.levels} />
|
|
17
|
+
* <KptSpeechWave active={speech.speaking} />
|
|
18
|
+
*/
|
|
19
|
+
export declare function KptSpeechWave({ levels, active, bars, className, ...rest }: Readonly<KptSpeechWaveProps>): import("react").JSX.Element;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../internal/cn.js";
|
|
3
|
+
/**
|
|
4
|
+
* Animowana fala dźwięku - słupki obok mikrofonu. Czysta dekoracja (`aria-hidden`): stan
|
|
5
|
+
* nasłuchu niesie przycisk (`aria-pressed`) i region statusu, nie rysunek. Te same klasy co
|
|
6
|
+
* `kpt-speech-wave` w Angularze, więc styl jest wspólny.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* <KptSpeechWave levels={speech.levels} />
|
|
10
|
+
* <KptSpeechWave active={speech.speaking} />
|
|
11
|
+
*/
|
|
12
|
+
export function KptSpeechWave({ levels = [], active = false, bars = 5, className, ...rest }) {
|
|
13
|
+
const live = levels.length > 0;
|
|
14
|
+
const slots = live ? levels : Array.from({ length: Math.max(1, bars) }, () => null);
|
|
15
|
+
return (_jsx("span", { className: cn('kpt-speech-wave-host', className), "aria-hidden": "true", "data-mode": live ? 'live' : 'synthetic', "data-active": active ? '' : undefined, ...rest, children: slots.map((level, index) => (_jsx("span", { className: "kpt-speech-wave__bar", style: { '--_i': index, ...(level === null ? {} : { '--_level': level }) } }, index))) }));
|
|
16
|
+
}
|