@konce-pt/react 0.12.0 → 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 +122 -64
- package/dist/accordion/accordion.d.ts +14 -6
- package/dist/accordion/accordion.js +14 -7
- package/dist/accordion/llms.txt +22 -10
- package/dist/alert/alert.d.ts +2 -2
- package/dist/alert/alert.js +2 -2
- package/dist/alert/llms.txt +10 -10
- package/dist/app-shell/app-shell.d.ts +14 -14
- package/dist/app-shell/app-shell.js +11 -11
- package/dist/app-shell/llms.txt +13 -13
- package/dist/auth/auth-types.d.ts +6 -6
- package/dist/auth/auth.d.ts +2 -2
- package/dist/auth/auth.js +7 -7
- package/dist/auth/llms.txt +9 -9
- package/dist/autocomplete/autocomplete.d.ts +19 -6
- package/dist/autocomplete/autocomplete.js +23 -10
- package/dist/autocomplete/llms.txt +16 -5
- package/dist/avatar/avatar.d.ts +6 -6
- package/dist/avatar/avatar.js +5 -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 +3 -3
- package/dist/avatar-group/llms.txt +8 -8
- package/dist/backdrop/backdrop.d.ts +7 -7
- package/dist/backdrop/backdrop.js +16 -16
- 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 +4 -4
- package/dist/bottom-sheet/bottom-sheet.js +2 -2
- package/dist/bottom-sheet/llms.txt +9 -9
- package/dist/breadcrumb/breadcrumb.d.ts +2 -2
- package/dist/breadcrumb/breadcrumb.js +3 -3
- package/dist/breadcrumb/llms.txt +3 -3
- package/dist/button/button.d.ts +3 -3
- package/dist/button/button.js +8 -8
- package/dist/button/llms.txt +27 -19
- package/dist/button-group/button-group.d.ts +9 -9
- package/dist/button-group/button-group.js +30 -8
- package/dist/button-group/llms.txt +20 -14
- package/dist/card/card.d.ts +8 -8
- package/dist/card/card.js +6 -6
- package/dist/card/llms.txt +20 -20
- package/dist/carousel/carousel.d.ts +6 -6
- package/dist/carousel/carousel.js +4 -4
- package/dist/carousel/llms.txt +10 -10
- package/dist/charts/chart-legend.d.ts +3 -3
- package/dist/charts/chart-legend.js +1 -1
- package/dist/charts/chart-tooltip.d.ts +3 -3
- package/dist/charts/chart-tooltip.js +1 -1
- package/dist/charts/chart.d.ts +1 -1
- package/dist/charts/chart.js +8 -8
- package/dist/charts/llms.txt +6 -6
- package/dist/charts-entry/public-api.js +2 -2
- package/dist/chat/chat.d.ts +17 -10
- package/dist/chat/chat.js +42 -18
- package/dist/chat/llms.txt +32 -17
- package/dist/chat-composer/chat-composer.d.ts +17 -7
- package/dist/chat-composer/chat-composer.js +21 -8
- 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 +9 -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 +17 -7
- 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 +3 -3
- package/dist/chat-typing/llms.txt +6 -6
- package/dist/checkbox/checkbox.d.ts +13 -6
- package/dist/checkbox/checkbox.js +26 -7
- package/dist/checkbox/llms.txt +24 -12
- package/dist/chip/chip.d.ts +4 -4
- package/dist/chip/chip.js +2 -2
- package/dist/chip/llms.txt +11 -11
- package/dist/chips-input/chips-input.d.ts +4 -4
- package/dist/chips-input/chips-input.js +35 -9
- package/dist/chips-input/llms.txt +15 -8
- package/dist/clock/clock.d.ts +4 -4
- package/dist/clock/clock.js +7 -7
- package/dist/clock/llms.txt +8 -8
- package/dist/color-picker/color-picker.d.ts +6 -6
- package/dist/color-picker/color-picker.js +3 -3
- package/dist/color-picker/llms.txt +5 -5
- package/dist/confirm/confirm-container.d.ts +3 -3
- package/dist/confirm/confirm-container.js +5 -5
- package/dist/confirm/confirm-store.d.ts +4 -4
- package/dist/confirm/confirm-store.js +7 -7
- package/dist/confirm/llms.txt +6 -6
- package/dist/context-menu/context-menu.d.ts +1 -1
- package/dist/context-menu/context-menu.js +5 -5
- package/dist/context-menu/llms.txt +7 -7
- package/dist/data-table/data-table-types.d.ts +5 -5
- package/dist/data-table/data-table.d.ts +29 -8
- package/dist/data-table/data-table.js +340 -95
- package/dist/data-table/export-csv.d.ts +3 -3
- package/dist/data-table/export-csv.js +4 -4
- package/dist/data-table/llms.txt +127 -94
- package/dist/data-view/data-view.d.ts +13 -5
- package/dist/data-view/data-view.js +67 -6
- package/dist/data-view/llms.txt +29 -10
- package/dist/date-range/date-range.d.ts +9 -9
- package/dist/date-range/date-range.js +24 -24
- package/dist/date-range/llms.txt +20 -20
- package/dist/datepicker/datepicker.d.ts +10 -10
- package/dist/datepicker/datepicker.js +29 -29
- package/dist/datepicker/llms.txt +24 -24
- package/dist/dialog/dialog.d.ts +4 -4
- package/dist/dialog/dialog.js +4 -4
- package/dist/dialog/llms.txt +11 -11
- package/dist/divider/divider.d.ts +1 -1
- package/dist/divider/llms.txt +4 -4
- package/dist/drawer/drawer.d.ts +5 -5
- package/dist/drawer/drawer.js +2 -2
- package/dist/drawer/llms.txt +11 -11
- 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 +9 -6
- package/dist/fieldset/llms.txt +26 -9
- package/dist/file-upload/file-upload.d.ts +3 -3
- package/dist/file-upload/file-upload.js +7 -7
- package/dist/file-upload/llms.txt +7 -7
- 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 +26 -6
- package/dist/form-field/llms.txt +25 -11
- package/dist/galleria/galleria.d.ts +3 -3
- package/dist/galleria/galleria.js +5 -5
- 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 +3 -3
- package/dist/i18n/i18n-context.js +7 -7
- 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 +6 -6
- 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 +1 -1
- package/dist/image/llms.txt +13 -13
- package/dist/input/input.d.ts +18 -5
- package/dist/input/input.js +21 -6
- package/dist/input/llms.txt +25 -11
- package/dist/input-mask/input-mask.d.ts +1 -1
- package/dist/input-mask/input-mask.js +2 -2
- package/dist/input-mask/llms.txt +5 -5
- package/dist/input-number/input-number.d.ts +9 -9
- package/dist/input-number/input-number.js +11 -11
- package/dist/input-number/llms.txt +14 -14
- package/dist/input-otp/input-otp.d.ts +6 -6
- package/dist/input-otp/input-otp.js +10 -10
- package/dist/input-otp/llms.txt +9 -9
- 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 +2 -2
- package/dist/internal/mask.js +4 -4
- package/dist/internal/option-key.d.ts +2 -2
- package/dist/internal/option-key.js +2 -2
- 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 +8 -8
- package/dist/internal/password-strength.d.ts +2 -2
- package/dist/internal/password-strength.js +6 -6
- package/dist/internal/roving.d.ts +2 -2
- package/dist/internal/roving.js +3 -3
- package/dist/internal/scroll-lock.d.ts +1 -1
- package/dist/internal/scroll-lock.js +2 -2
- package/dist/internal/slot-children.d.ts +3 -3
- package/dist/internal/slot-children.js +4 -4
- package/dist/internal/slot.d.ts +3 -3
- package/dist/internal/slot.js +2 -2
- package/dist/internal/step.d.ts +3 -3
- package/dist/internal/step.js +4 -4
- package/dist/internal/tween.d.ts +5 -5
- package/dist/internal/tween.js +5 -5
- 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 +1 -1
- package/dist/internal/use-controllable-state.js +3 -3
- 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 +1 -1
- package/dist/internal/use-modal-focus.js +3 -3
- package/dist/knob/knob.d.ts +4 -4
- package/dist/knob/knob.js +14 -10
- package/dist/knob/llms.txt +17 -12
- package/dist/listbox/listbox.d.ts +6 -6
- package/dist/listbox/listbox.js +9 -9
- package/dist/listbox/llms.txt +10 -10
- 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 +25 -22
- package/dist/map/map-create-dialog.d.ts +4 -4
- package/dist/map/map-create-dialog.js +10 -10
- package/dist/map/map-hours-field.d.ts +4 -4
- package/dist/map/map-hours-field.js +7 -7
- package/dist/map/map-image-field.d.ts +2 -2
- package/dist/map/map-image-field.js +4 -4
- package/dist/map/map-legend.d.ts +4 -4
- package/dist/map/map-legend.js +2 -2
- package/dist/map/map-poi-card.d.ts +3 -3
- package/dist/map/map-poi-card.js +9 -9
- package/dist/map/map-search.d.ts +17 -6
- package/dist/map/map-search.js +21 -18
- package/dist/map/map.d.ts +21 -14
- package/dist/map/map.js +30 -29
- 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 +9 -9
- package/dist/megamenu/megamenu.d.ts +6 -6
- package/dist/megamenu/megamenu.js +10 -10
- 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.d.ts +2 -2
- package/dist/menu/menu.js +6 -6
- package/dist/menubar/llms.txt +10 -10
- package/dist/menubar/menubar.d.ts +5 -5
- package/dist/menubar/menubar.js +10 -10
- 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 +1 -1
- package/dist/motion/motion-context.d.ts +22 -0
- package/dist/motion/motion-context.js +28 -0
- package/dist/order-list/llms.txt +22 -10
- package/dist/order-list/order-list.d.ts +12 -4
- package/dist/order-list/order-list.js +104 -21
- package/dist/paginator/llms.txt +8 -8
- package/dist/paginator/paginator.js +1 -1
- package/dist/panel/llms.txt +25 -13
- package/dist/panel/panel.d.ts +11 -3
- package/dist/panel/panel.js +7 -4
- package/dist/password/llms.txt +16 -8
- package/dist/password/password.d.ts +2 -2
- package/dist/password/password.js +24 -3
- package/dist/peek/llms.txt +10 -10
- package/dist/peek/peek.d.ts +6 -6
- package/dist/peek/peek.js +7 -7
- package/dist/pick-list/llms.txt +33 -13
- package/dist/pick-list/pick-list.d.ts +12 -4
- package/dist/pick-list/pick-list.js +111 -23
- package/dist/popover/llms.txt +11 -11
- package/dist/popover/popover.d.ts +4 -4
- package/dist/popover/popover.js +5 -5
- package/dist/progress/llms.txt +17 -17
- package/dist/progress/progress.d.ts +7 -7
- package/dist/progress/progress.js +6 -6
- package/dist/public-api.d.ts +15 -3
- package/dist/public-api.js +13 -7
- package/dist/radio-group/llms.txt +13 -6
- package/dist/radio-group/radio-group.d.ts +11 -4
- package/dist/radio-group/radio-group.js +15 -7
- 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 +76 -12
- package/dist/rich-text/lists.d.ts +2 -2
- package/dist/rich-text/lists.js +7 -7
- package/dist/rich-text/llms.txt +26 -17
- package/dist/rich-text/rich-text.d.ts +12 -7
- package/dist/rich-text/rich-text.js +78 -54
- package/dist/rich-text/sanitize.d.ts +3 -3
- package/dist/rich-text/sanitize.js +12 -12
- package/dist/rich-text/tables.d.ts +3 -3
- package/dist/rich-text/tables.js +3 -3
- package/dist/roadmap/llms.txt +15 -15
- package/dist/roadmap/roadmap.d.ts +6 -6
- package/dist/roadmap/roadmap.js +19 -19
- 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 +1 -1
- package/dist/select/llms.txt +19 -12
- package/dist/select/select.d.ts +8 -8
- package/dist/select/select.js +47 -21
- package/dist/shared/types.d.ts +5 -5
- package/dist/shared/types.js +1 -1
- package/dist/sidenav/llms.txt +9 -9
- package/dist/sidenav/sidenav.d.ts +1 -1
- package/dist/sidenav/sidenav.js +1 -1
- 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 +32 -13
- package/dist/slider/slider-drag.d.ts +26 -0
- package/dist/slider/slider-drag.js +79 -0
- package/dist/slider/slider.d.ts +6 -5
- package/dist/slider/slider.js +27 -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 +1 -1
- package/dist/spinner/llms.txt +7 -7
- package/dist/spinner/spinner.d.ts +1 -1
- package/dist/spinner/spinner.js +1 -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 +4 -4
- package/dist/splitter/llms.txt +11 -11
- package/dist/splitter/splitter.d.ts +3 -3
- package/dist/splitter/splitter.js +5 -5
- package/dist/stepper/llms.txt +33 -14
- package/dist/stepper/stepper.d.ts +6 -6
- package/dist/stepper/stepper.js +53 -10
- package/dist/switch/llms.txt +12 -5
- package/dist/switch/switch.d.ts +10 -3
- package/dist/switch/switch.js +12 -5
- package/dist/switch-group/llms.txt +9 -9
- package/dist/switch-group/switch-group.d.ts +4 -4
- package/dist/switch-group/switch-group.js +4 -4
- package/dist/tabs/llms.txt +10 -10
- package/dist/tabs/tabs.d.ts +5 -5
- package/dist/tabs/tabs.js +8 -8
- package/dist/textarea/llms.txt +23 -12
- package/dist/textarea/textarea.d.ts +19 -6
- package/dist/textarea/textarea.js +21 -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 +47 -17
- package/dist/toast/toast-container.d.ts +9 -3
- package/dist/toast/toast-container.js +17 -9
- package/dist/toast/toast-store.d.ts +40 -4
- 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 +10 -10
- package/dist/tooltip/tooltip.d.ts +3 -3
- package/dist/tooltip/tooltip.js +18 -18
- package/dist/tree/llms.txt +25 -12
- package/dist/tree/tree-keys.d.ts +5 -5
- package/dist/tree/tree-keys.js +7 -7
- package/dist/tree/tree.d.ts +14 -7
- package/dist/tree/tree.js +53 -18
- 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 +15 -13
package/dist/knob/knob.d.ts
CHANGED
|
@@ -13,23 +13,23 @@ export interface KptKnobProps extends Omit<ComponentPropsWithRef<'div'>, 'onChan
|
|
|
13
13
|
/** Dopisek za wartością w środku (np. '%'). */
|
|
14
14
|
suffix?: string;
|
|
15
15
|
showValue?: boolean;
|
|
16
|
-
/** Id elementu fokusowalnego (nie hosta); bez niego generowane, w `KptFormField`
|
|
16
|
+
/** Id elementu fokusowalnego (nie hosta); bez niego generowane, w `KptFormField` - z wrappera. */
|
|
17
17
|
id?: string;
|
|
18
18
|
/** Nazwa dostępna, gdy kontrolki nie opisuje etykieta (`<label for>` albo `KptFormField`). */
|
|
19
19
|
ariaLabel?: string | null;
|
|
20
20
|
disabled?: boolean;
|
|
21
|
-
/** Tylko do odczytu
|
|
21
|
+
/** Tylko do odczytu - pokrętło widać i da się na nim stanąć, ale nie zmienić wartości. */
|
|
22
22
|
readOnly?: boolean;
|
|
23
23
|
invalid?: boolean;
|
|
24
24
|
touched?: boolean;
|
|
25
25
|
onTouch?: () => void;
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
28
|
-
* Pokrętło
|
|
28
|
+
* Pokrętło - wybór wartości po łuku, przeciąganiem albo strzałkami.
|
|
29
29
|
*
|
|
30
30
|
* Skok wartości (strzałka, `Home`/`End`, klik w łuk, zmiana z zewnątrz) łuk i licznik pokonują
|
|
31
31
|
* płynnie; przy przeciąganiu idą za kursorem bez opóźnienia. `PageUp`/`PageDown` skaczą o dziesięć
|
|
32
|
-
* kroków, a wartości leżą na siatce `min + k
|
|
32
|
+
* kroków, a wartości leżą na siatce `min + k,step` dla myszy i klawiatury. `prefers-reduced-motion` wyłącza
|
|
33
33
|
* animację, a `aria-valuenow` zawsze niesie wartość docelową, nie pośrednią.
|
|
34
34
|
*
|
|
35
35
|
* @example
|
package/dist/knob/knob.js
CHANGED
|
@@ -7,18 +7,18 @@ import { kptStepDecimals, kptTween } from "../internal/tween.js";
|
|
|
7
7
|
import { useControllableState } from "../internal/use-controllable-state.js";
|
|
8
8
|
import { useKptFieldA11y } from "../form-field/form-field-context.js";
|
|
9
9
|
const TAU = Math.PI * 2;
|
|
10
|
-
/** Przerwa u dołu
|
|
10
|
+
/** Przerwa u dołu - kąt "otwarcia" łuku, żeby początek i koniec były rozróżnialne. */
|
|
11
11
|
const GAP = Math.PI / 4;
|
|
12
12
|
const START_ANGLE = Math.PI / 2 + GAP;
|
|
13
13
|
const SWEEP = TAU - GAP * 2;
|
|
14
14
|
/** Czas dojścia łuku i licznika po skoku wartości (klawisz, klik, zmiana z zewnątrz). */
|
|
15
15
|
const TWEEN_MS = 220;
|
|
16
16
|
/**
|
|
17
|
-
* Pokrętło
|
|
17
|
+
* Pokrętło - wybór wartości po łuku, przeciąganiem albo strzałkami.
|
|
18
18
|
*
|
|
19
19
|
* Skok wartości (strzałka, `Home`/`End`, klik w łuk, zmiana z zewnątrz) łuk i licznik pokonują
|
|
20
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
|
|
21
|
+
* kroków, a wartości leżą na siatce `min + k,step` dla myszy i klawiatury. `prefers-reduced-motion` wyłącza
|
|
22
22
|
* animację, a `aria-valuenow` zawsze niesie wartość docelową, nie pośrednią.
|
|
23
23
|
*
|
|
24
24
|
* @example
|
|
@@ -30,11 +30,13 @@ export function KptKnob({ value, defaultValue = 0, onValueChange, min = 0, max =
|
|
|
30
30
|
const [current, setCurrent] = useControllableState({ value, defaultValue, onChange: onValueChange });
|
|
31
31
|
const showInvalid = invalid && (touched ?? true);
|
|
32
32
|
const range = max - min;
|
|
33
|
-
/** Wartość na rysunku
|
|
33
|
+
/** Wartość na rysunku - w trakcie animacji pośrednia, poza nią równa `current`. */
|
|
34
34
|
const [shown, setShown] = useState(current);
|
|
35
35
|
const shownRef = useRef(current);
|
|
36
|
-
/** Przeciąganie trwa
|
|
36
|
+
/** Przeciąganie trwa - wtedy rysunek idzie za kursorem bez animacji. */
|
|
37
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);
|
|
38
40
|
useEffect(() => {
|
|
39
41
|
const paint = (v) => {
|
|
40
42
|
shownRef.current = v;
|
|
@@ -70,7 +72,7 @@ export function KptKnob({ value, defaultValue = 0, onValueChange, min = 0, max =
|
|
|
70
72
|
const cy = rect.top + rect.height / 2;
|
|
71
73
|
let angle = Math.atan2(clientY - cy, clientX - cx) - START_ANGLE;
|
|
72
74
|
angle = ((angle % TAU) + TAU) % TAU;
|
|
73
|
-
// 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,
|
|
74
76
|
// inaczej przeciągnięcie przez dół przeskakiwałoby z maksimum na minimum.
|
|
75
77
|
if (angle > SWEEP + GAP)
|
|
76
78
|
angle = angle > SWEEP + GAP / 2 ? 0 : SWEEP;
|
|
@@ -79,14 +81,15 @@ export function KptKnob({ value, defaultValue = 0, onValueChange, min = 0, max =
|
|
|
79
81
|
const onPointerDown = (event) => {
|
|
80
82
|
if (disabled || readOnly)
|
|
81
83
|
return;
|
|
82
|
-
// 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
|
|
83
85
|
// `Ctrl+A`. Zablokowanie domyślnej akcji zabiera też fokus, więc oddajemy go ręcznie.
|
|
84
86
|
event.preventDefault();
|
|
85
87
|
event.currentTarget.focus();
|
|
86
|
-
|
|
88
|
+
setGrabbing(true);
|
|
89
|
+
// Klik w łuk to skok - animowany. Dopiero ruch po nim jest przeciąganiem.
|
|
87
90
|
setFromPoint(event.clientX, event.clientY);
|
|
88
91
|
// Nasłuch wisi na oknie, a nie na pokrętle: przeciąganie ma nie gubić wartości,
|
|
89
|
-
// gdy kursor wyjedzie poza koło
|
|
92
|
+
// gdy kursor wyjedzie poza koło - a wyjeżdża praktycznie zawsze.
|
|
90
93
|
const move = (e) => {
|
|
91
94
|
dragging.current = true;
|
|
92
95
|
setFromPoint(e.clientX, e.clientY);
|
|
@@ -96,6 +99,7 @@ export function KptKnob({ value, defaultValue = 0, onValueChange, min = 0, max =
|
|
|
96
99
|
window.removeEventListener('pointerup', up);
|
|
97
100
|
window.removeEventListener('pointercancel', up);
|
|
98
101
|
dragging.current = false;
|
|
102
|
+
setGrabbing(false);
|
|
99
103
|
onTouch?.();
|
|
100
104
|
};
|
|
101
105
|
window.addEventListener('pointermove', move);
|
|
@@ -119,5 +123,5 @@ export function KptKnob({ value, defaultValue = 0, onValueChange, min = 0, max =
|
|
|
119
123
|
setCurrent(kptSnapToStep(next, min, max, step));
|
|
120
124
|
onTouch?.();
|
|
121
125
|
};
|
|
122
|
-
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-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] }) }) }));
|
|
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] }) }) }));
|
|
123
127
|
}
|
package/dist/knob/llms.txt
CHANGED
|
@@ -1,37 +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`, `readOnly`, `invalid`, `touched`, `onTouch()`
|
|
22
|
+
- `disabled`, `readOnly`, `invalid`, `touched`, `onTouch()` - `readOnly` keeps the knob focusable but ignores
|
|
23
23
|
the pointer and the keys (`aria-readonly`)
|
|
24
24
|
- `className`, `ref` and every other `<div>` prop go to the host
|
|
25
25
|
|
|
26
26
|
## The gap at the bottom
|
|
27
27
|
The arc is a full circle minus a 45° gap at the bottom, so its start and end are distinguishable.
|
|
28
|
-
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
|
|
29
29
|
dragging across the bottom would jump between minimum and maximum.
|
|
30
30
|
|
|
31
31
|
## Dragging listens on the window
|
|
32
32
|
`pointermove` / `pointerup` are attached to `window`, not to the knob: the pointer leaves the
|
|
33
33
|
circle during almost every drag, and the value must keep following it. Both listeners are removed
|
|
34
|
-
on `pointerup`, which is also where `onTouch` fires
|
|
34
|
+
on `pointerup`, which is also where `onTouch` fires - one touch per drag, not per pixel.
|
|
35
35
|
|
|
36
36
|
## Examples
|
|
37
37
|
<KptKnob value={level} onValueChange={setLevel} min={0} max={100} step={5} suffix="%" />
|
|
@@ -42,15 +42,20 @@ on `pointerup`, which is also where `onTouch` fires — one touch per drag, not
|
|
|
42
42
|
generated. A `div` with a role is not named by `label[for]`, so inside `KptFormField` it gets
|
|
43
43
|
`aria-labelledby` pointing at the wrapper's label (unless `ariaLabel` is set), and the hint or error
|
|
44
44
|
is added to `aria-describedby`. New: `id`, `ariaLabel`; `aria-valuetext` includes `suffix` ("40%"),
|
|
45
|
-
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
|
|
46
46
|
lands on the wrapper.
|
|
47
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
|
+
|
|
48
53
|
## Tokens
|
|
49
54
|
The background arc `--kpt-color-border`, the value `--kpt-color-primary`, the text
|
|
50
55
|
`--kpt-color-on-surface`.
|
|
51
56
|
|
|
52
57
|
## Motion
|
|
53
|
-
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
|
|
54
59
|
and counts the number up or down over ~220 ms (ease-out). Dragging follows the pointer with no
|
|
55
60
|
delay, so it never feels laggy. `prefers-reduced-motion: reduce` turns the animation off.
|
|
56
61
|
`aria-valuenow` always carries the target value, never an in-between frame. Dragging does not
|
|
@@ -58,6 +63,6 @@ select the number or the arc.
|
|
|
58
63
|
|
|
59
64
|
## Accessibility
|
|
60
65
|
`role="slider"` with `aria-valuemin` / `aria-valuemax` / `aria-valuenow`; arrows step by `step`,
|
|
61
|
-
PageUp/PageDown by ten steps, Home and End jump to the bounds. Values sit on the grid `min + k
|
|
62
|
-
both the pointer and the keys
|
|
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
|
|
63
68
|
tab order.
|
|
@@ -9,14 +9,14 @@ 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
|
|
19
|
+
/** Tylko do odczytu - po liście da się chodzić i czytać wybór, ale nie da się go zmienić. */
|
|
20
20
|
readOnly?: boolean;
|
|
21
21
|
invalid?: boolean;
|
|
22
22
|
touched?: boolean;
|
|
@@ -26,14 +26,14 @@ export interface KptListboxProps<T> extends Omit<ComponentPropsWithRef<'div'>, '
|
|
|
26
26
|
* Lista wyboru widoczna w całości, bez rozwijania. Pojedynczy albo wielokrotny wybór,
|
|
27
27
|
* opcjonalny filtr.
|
|
28
28
|
*
|
|
29
|
-
* W trybie pojedynczym ponowny klik w zaznaczoną opcję odznacza ją
|
|
29
|
+
* W trybie pojedynczym ponowny klik w zaznaczoną opcję odznacza ją - lista bywa filtrem,
|
|
30
30
|
* a filtr trzeba dać się wyczyścić bez sięgania po osobny przycisk.
|
|
31
31
|
*
|
|
32
|
-
* Klawiatura według wzorca WAI-ARIA
|
|
32
|
+
* Klawiatura według wzorca WAI-ARIA "listbox": lista to jeden przystanek Tab, a aktywną opcję
|
|
33
33
|
* ogłasza `aria-activedescendant`. Strzałki, `Home`/`End`, `PageUp`/`PageDown` przesuwają aktywną
|
|
34
34
|
* opcję (wyłączone są pomijane), pisanie liter skacze do kolejnej pasującej (bez względu na
|
|
35
|
-
* diakrytyki), `Spacja`/`Enter` wybiera, a w trybie wielokrotnym `Ctrl
|
|
36
|
-
* 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.
|
|
37
37
|
*
|
|
38
38
|
* @example
|
|
39
39
|
* <KptListbox options={roles} value={role} onValueChange={setRole} filter />
|
package/dist/listbox/listbox.js
CHANGED
|
@@ -12,14 +12,14 @@ import { KptIcon } from "../icon/icon.js";
|
|
|
12
12
|
* Lista wyboru widoczna w całości, bez rozwijania. Pojedynczy albo wielokrotny wybór,
|
|
13
13
|
* opcjonalny filtr.
|
|
14
14
|
*
|
|
15
|
-
* W trybie pojedynczym ponowny klik w zaznaczoną opcję odznacza ją
|
|
15
|
+
* W trybie pojedynczym ponowny klik w zaznaczoną opcję odznacza ją - lista bywa filtrem,
|
|
16
16
|
* a filtr trzeba dać się wyczyścić bez sięgania po osobny przycisk.
|
|
17
17
|
*
|
|
18
|
-
* Klawiatura według wzorca WAI-ARIA
|
|
18
|
+
* Klawiatura według wzorca WAI-ARIA "listbox": lista to jeden przystanek Tab, a aktywną opcję
|
|
19
19
|
* ogłasza `aria-activedescendant`. Strzałki, `Home`/`End`, `PageUp`/`PageDown` przesuwają aktywną
|
|
20
20
|
* opcję (wyłączone są pomijane), pisanie liter skacze do kolejnej pasującej (bez względu na
|
|
21
|
-
* diakrytyki), `Spacja`/`Enter` wybiera, a w trybie wielokrotnym `Ctrl
|
|
22
|
-
* 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.
|
|
23
23
|
*
|
|
24
24
|
* @example
|
|
25
25
|
* <KptListbox options={roles} value={role} onValueChange={setRole} filter />
|
|
@@ -27,7 +27,7 @@ import { KptIcon } from "../icon/icon.js";
|
|
|
27
27
|
*/
|
|
28
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 }) {
|
|
29
29
|
const { t } = useKptMessages({ locale, dictionary });
|
|
30
|
-
/** `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. */
|
|
31
31
|
const field = useKptFieldA11y(id, 'kpt-listbox');
|
|
32
32
|
const [current, setCurrent] = useControllableState({
|
|
33
33
|
value,
|
|
@@ -40,7 +40,7 @@ export function KptListbox({ options = [], value, defaultValue = null, onValueCh
|
|
|
40
40
|
const listRef = useRef(null);
|
|
41
41
|
const typeahead = useRef(new KptTypeahead()).current;
|
|
42
42
|
const showInvalid = invalid && (touched ?? true);
|
|
43
|
-
/** Filtr bez względu na wielkość liter i diakrytyki (
|
|
43
|
+
/** Filtr bez względu na wielkość liter i diakrytyki ("lodz" znajduje "Łódź"). */
|
|
44
44
|
const visible = useMemo(() => {
|
|
45
45
|
const q = foldMentionText(query.trim());
|
|
46
46
|
return q ? options.filter((option) => foldMentionText(option.label).includes(q)) : options;
|
|
@@ -63,7 +63,7 @@ export function KptListbox({ options = [], value, defaultValue = null, onValueCh
|
|
|
63
63
|
setCurrent(current === candidate ? null : candidate);
|
|
64
64
|
}
|
|
65
65
|
};
|
|
66
|
-
/** Ostatnio kliknięta opcja
|
|
66
|
+
/** Ostatnio kliknięta opcja - początek zakresu dla `Shift+klik` w trybie `multiple`. */
|
|
67
67
|
const anchorIndex = useRef(-1);
|
|
68
68
|
const pick = (index, extend = false) => {
|
|
69
69
|
const option = visible[index];
|
|
@@ -71,7 +71,7 @@ export function KptListbox({ options = [], value, defaultValue = null, onValueCh
|
|
|
71
71
|
return;
|
|
72
72
|
setActiveIndex(index);
|
|
73
73
|
if (extend && multiple && anchorIndex.current >= 0) {
|
|
74
|
-
// Zaznacza (dokłada) dozwolone opcje między kotwicą a klikniętą
|
|
74
|
+
// Zaznacza (dokłada) dozwolone opcje między kotwicą a klikniętą - `Shift+klik` (APG).
|
|
75
75
|
const [lo, hi] = anchorIndex.current <= index ? [anchorIndex.current, index] : [index, anchorIndex.current];
|
|
76
76
|
const list = Array.isArray(current) ? current : [];
|
|
77
77
|
const add = visible
|
|
@@ -123,7 +123,7 @@ export function KptListbox({ options = [], value, defaultValue = null, onValueCh
|
|
|
123
123
|
event.preventDefault();
|
|
124
124
|
const all = visible.filter((_, index) => !disabledMap[index]).map((option) => option.value);
|
|
125
125
|
const list = Array.isArray(current) ? (current) : [];
|
|
126
|
-
// Drugi raz `Ctrl+A` przy wszystkich zaznaczonych
|
|
126
|
+
// Drugi raz `Ctrl+A` przy wszystkich zaznaczonych - odznacza widoczne.
|
|
127
127
|
const allOn = all.every((item) => list.includes(item));
|
|
128
128
|
setCurrent(allOn ? list.filter((item) => !all.includes(item)) : [...list, ...all.filter((item) => !list.includes(item))]);
|
|
129
129
|
return;
|
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),
|
|
@@ -55,4 +55,4 @@ The list is `role="listbox"` with `aria-multiselectable`, `aria-activedescendant
|
|
|
55
55
|
The keyboard is described above.
|
|
56
56
|
|
|
57
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
|
|
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
|
+
}
|
package/dist/map/llms.txt
CHANGED
|
@@ -9,34 +9,37 @@ interface.
|
|
|
9
9
|
Import: `import { KptMap, createLeafletEngine } from '@konce-pt/react/map';`
|
|
10
10
|
|
|
11
11
|
## Requirements
|
|
12
|
-
1. The **optional** `leaflet` dependency
|
|
12
|
+
1. The **optional** `leaflet` dependency - the library does not install it: `pnpm add leaflet`
|
|
13
13
|
2. Its stylesheet: `import 'leaflet/dist/leaflet.css';`
|
|
14
14
|
3. An engine factory on the `engine` prop: `engine={() => createLeafletEngine()}`
|
|
15
15
|
|
|
16
16
|
Leaflet is loaded with a dynamic `import()`, so an app that never renders a map does not download
|
|
17
|
-
it. Without the package the component still imports and compiles
|
|
17
|
+
it. Without the package the component still imports and compiles - only rendering the map ends with
|
|
18
18
|
a readable message.
|
|
19
19
|
|
|
20
20
|
## Props
|
|
21
|
-
- `locations`: `readonly KptMapLocation<T>[]`
|
|
22
|
-
- `categories`: `readonly KptMapCategory[]`
|
|
23
|
-
- `engine`: `KptMapEngineFactory`
|
|
21
|
+
- `locations`: `readonly KptMapLocation<T>[]` - **required**
|
|
22
|
+
- `categories`: `readonly KptMapCategory[]` - **required**
|
|
23
|
+
- `engine`: `KptMapEngineFactory` - **required**; a factory, not an instance, because every map
|
|
24
24
|
needs its own object. Read once, at mount.
|
|
25
|
-
- `country`: string = `'PL'`
|
|
26
|
-
- `view`, `bounds`
|
|
27
|
-
- `restrictToCountry`: boolean
|
|
25
|
+
- `country`: string = `'PL'` - the initial view preset; an unknown code falls back to Poland
|
|
26
|
+
- `view`, `bounds` - an explicit view or extent; they override `country`
|
|
27
|
+
- `restrictToCountry`: boolean - blocks panning outside the country's bounds
|
|
28
28
|
- `cluster`: boolean = true, `clusterOptions`, `clusterColorBy`: `'dominant' | 'none'`
|
|
29
29
|
- `legend`, `legendCounts`: boolean = true
|
|
30
|
-
- `hiddenCategories` / `defaultHiddenCategories` / `onHiddenCategoriesChange`
|
|
30
|
+
- `hiddenCategories` / `defaultHiddenCategories` / `onHiddenCategoriesChange` - the ids hidden in
|
|
31
31
|
the legend, controlled or uncontrolled
|
|
32
32
|
- `showUserLocation`, `followUser`: boolean; `geolocation`: `KptMapGeolocationPort | null`
|
|
33
|
-
- `allowCreate`: boolean
|
|
33
|
+
- `allowCreate`: boolean - the "Add location" menu under the right mouse button
|
|
34
34
|
- `search`: boolean, `searchPlaceholder`, `searchOptions`, `searchZoom`, `searchMarker`
|
|
35
|
-
- `
|
|
35
|
+
- `searchSpeech`: boolean = false - a microphone in the search field: a click clears the phrase and one
|
|
36
|
+
spoken utterance becomes the query (same `debounce`/`minLength` as typing); `searchSpeechLang`: string -
|
|
37
|
+
its language (BCP 47); `searchSpeechColor` - listening colour (theme colour by default). Hidden without browser support. `KptMapSearch` on its own takes `speech` / `speechLang` / `speechColor`
|
|
38
|
+
- `geocoder`: `KptMapGeocoderPort | null` - this map's geocoder; it feeds both the search and the
|
|
36
39
|
address suggestions in the create form
|
|
37
40
|
- `popupLayout`: `'plain' | 'card'`; `distanceFrom`: `KptLatLng | null`
|
|
38
41
|
- `height`: string = `'480px'`; `tileUrl`, `attribution` (ignored by the Google engine)
|
|
39
|
-
- `renderPopup`, `renderCreateForm`
|
|
42
|
+
- `renderPopup`, `renderCreateForm` - see below
|
|
40
43
|
- `className`, `ref` and every other `<div>` prop go to the host
|
|
41
44
|
|
|
42
45
|
## Events
|
|
@@ -47,12 +50,12 @@ a readable message.
|
|
|
47
50
|
the error is also logged to the console. `followUser` and the position callbacks are read fresh on every
|
|
48
51
|
fix, so changing them does not restart the geolocation watch.
|
|
49
52
|
|
|
50
|
-
`onLocationCreate` gives `{ at, categoryId, value }`
|
|
53
|
+
`onLocationCreate` gives `{ at, categoryId, value }` - the component appends **nothing** to
|
|
51
54
|
`locations`; the application is the source of truth.
|
|
52
55
|
|
|
53
56
|
## Render props instead of templates
|
|
54
|
-
The Angular port picks a popup template per category (`kptMapPopup category="
|
|
55
|
-
prop and a `switch`: `renderPopup({ location, data, category, distance, close })`
|
|
57
|
+
The Angular port picks a popup template per category (`kptMapPopup category="..."`). React has one
|
|
58
|
+
prop and a `switch`: `renderPopup({ location, data, category, distance, close })` - the category
|
|
56
59
|
travels in the context, and the type of `data` follows the map's generic without an extra input.
|
|
57
60
|
|
|
58
61
|
<KptMap
|
|
@@ -68,7 +71,7 @@ travels in the context, and the type of `data` follows the map's generic without
|
|
|
68
71
|
|
|
69
72
|
## Ports are props, not DI
|
|
70
73
|
Angular injects `KPT_MAP_ENGINE`, `KPT_MAP_GEOCODER` and `KPT_MAP_GEOLOCATION`. React has no
|
|
71
|
-
injector, so all three arrive as props
|
|
74
|
+
injector, so all three arrive as props - and the geocoder was already per-map in Angular for a
|
|
72
75
|
licensing reason (a Google geocoder may only be paired with a Google map), so nothing is lost.
|
|
73
76
|
|
|
74
77
|
The ports themselves are promise-shaped rather than `Observable`-shaped: `reverse()`, `search()`
|
|
@@ -76,7 +79,7 @@ and `resolve()` return promises and take an `AbortSignal`, which is what `fetch`
|
|
|
76
79
|
|
|
77
80
|
import { createNominatimGeocoder, browserGeolocation } from '@konce-pt/react/map';
|
|
78
81
|
|
|
79
|
-
<KptMap geocoder={createNominatimGeocoder()} geolocation={browserGeolocation()}
|
|
82
|
+
<KptMap geocoder={createNominatimGeocoder()} geolocation={browserGeolocation()} ... />
|
|
80
83
|
|
|
81
84
|
`createGeocoderStub()` and `geolocationStub(at)` cover demos and tests.
|
|
82
85
|
|
|
@@ -87,18 +90,18 @@ second mount waits for the first teardown to finish, instead of failing with "Ma
|
|
|
87
90
|
already initialized".
|
|
88
91
|
|
|
89
92
|
## Sub-components
|
|
90
|
-
- `KptMapLegend`
|
|
93
|
+
- `KptMapLegend` - the legend on its own, when you want it somewhere other than under the map
|
|
91
94
|
(`hiddenCategories` instead of Angular's `hidden`: `hidden` on a DOM element is a boolean and
|
|
92
95
|
would clash with the `<div>` prop)
|
|
93
|
-
- `KptMapPoiCard`
|
|
94
|
-
- `KptMapSearch`
|
|
95
|
-
- `KptMapCreateDialog`, `KptMapHoursField`, `KptMapImageField`
|
|
96
|
+
- `KptMapPoiCard` - the ready-made place card for `popupLayout="card"`
|
|
97
|
+
- `KptMapSearch` - the address field alone
|
|
98
|
+
- `KptMapCreateDialog`, `KptMapHoursField`, `KptMapImageField` - the schema-built form and its two
|
|
96
99
|
non-trivial field types
|
|
97
100
|
|
|
98
101
|
## Validation without a form framework
|
|
99
102
|
The create dialog is built from `category.fields`. The Angular port stands on Signal Forms; here the
|
|
100
103
|
model is plain state and the rules are a map of functions beside it. The contract is the same:
|
|
101
|
-
declared once with the schema, and the "do not shout in red" gate lives in presentation
|
|
104
|
+
declared once with the schema, and the "do not shout in red" gate lives in presentation - an error
|
|
102
105
|
shows after the field is touched or after a failed submit.
|
|
103
106
|
|
|
104
107
|
## Tokens
|