@konce-pt/react 0.11.1 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -2
- package/dist/accordion/accordion.js +11 -10
- package/dist/alert/alert.js +1 -0
- package/dist/alert/llms.txt +1 -0
- package/dist/app-shell/app-shell.js +12 -5
- package/dist/app-shell/llms.txt +5 -4
- package/dist/auth/auth.js +17 -8
- package/dist/auth/llms.txt +3 -2
- package/dist/autocomplete/autocomplete.d.ts +1 -1
- package/dist/autocomplete/autocomplete.js +12 -4
- package/dist/autocomplete/llms.txt +3 -0
- package/dist/avatar/avatar.js +1 -0
- package/dist/avatar-group/avatar-group.js +1 -0
- package/dist/backdrop/backdrop.d.ts +0 -19
- package/dist/backdrop/backdrop.js +18 -13
- package/dist/bottom-sheet/bottom-sheet.d.ts +6 -1
- package/dist/bottom-sheet/bottom-sheet.js +3 -2
- package/dist/bottom-sheet/llms.txt +3 -0
- package/dist/breadcrumb/breadcrumb.d.ts +7 -1
- package/dist/breadcrumb/breadcrumb.js +10 -2
- package/dist/breadcrumb/llms.txt +8 -0
- package/dist/button/button.js +2 -1
- package/dist/button-group/button-group.d.ts +15 -2
- package/dist/button-group/button-group.js +31 -4
- package/dist/button-group/llms.txt +9 -6
- package/dist/card/card.js +4 -3
- package/dist/carousel/carousel.js +4 -1
- package/dist/charts/chart-legend.js +1 -0
- package/dist/charts/chart.d.ts +3 -2
- package/dist/charts/chart.js +12 -4
- package/dist/charts/llms.txt +6 -3
- package/dist/chat/chat.js +39 -9
- package/dist/chat/llms.txt +9 -0
- package/dist/chat-composer/chat-composer.d.ts +5 -2
- package/dist/chat-composer/chat-composer.js +10 -1
- package/dist/chat-dock/chat-dock.js +1 -0
- package/dist/chat-message/chat-message.js +2 -1
- package/dist/chat-typing/chat-typing.js +1 -0
- package/dist/checkbox/checkbox.d.ts +1 -1
- package/dist/checkbox/checkbox.js +9 -2
- package/dist/checkbox/llms.txt +3 -0
- package/dist/chip/chip.js +1 -0
- package/dist/chips-input/chips-input.d.ts +3 -1
- package/dist/chips-input/chips-input.js +14 -5
- package/dist/chips-input/llms.txt +3 -0
- package/dist/clock/clock.d.ts +9 -4
- package/dist/clock/clock.js +35 -5
- package/dist/clock/llms.txt +6 -2
- package/dist/color-picker/color-picker.d.ts +5 -1
- package/dist/color-picker/color-picker.js +36 -17
- package/dist/color-picker/llms.txt +3 -0
- package/dist/confirm/confirm-container.js +1 -0
- package/dist/confirm/confirm-store.js +6 -0
- package/dist/confirm/llms.txt +2 -1
- package/dist/context-menu/context-menu.js +35 -12
- package/dist/context-menu/llms.txt +3 -0
- package/dist/data-table/data-table.d.ts +12 -1
- package/dist/data-table/data-table.js +113 -51
- package/dist/data-table/export-csv.d.ts +2 -2
- package/dist/data-table/export-csv.js +7 -9
- package/dist/data-table/llms.txt +19 -4
- package/dist/data-view/data-view.js +1 -0
- package/dist/date-range/date-range.d.ts +16 -4
- package/dist/date-range/date-range.js +69 -46
- package/dist/date-range/llms.txt +17 -3
- package/dist/datepicker/datepicker.d.ts +17 -3
- package/dist/datepicker/datepicker.js +86 -54
- package/dist/datepicker/llms.txt +17 -3
- package/dist/dialog/dialog.js +29 -8
- package/dist/dialog/llms.txt +3 -0
- package/dist/divider/divider.d.ts +7 -1
- package/dist/divider/divider.js +3 -2
- package/dist/divider/llms.txt +1 -0
- package/dist/drawer/drawer.d.ts +6 -1
- package/dist/drawer/drawer.js +3 -2
- package/dist/drawer/llms.txt +3 -0
- package/dist/fieldset/fieldset.js +4 -1
- package/dist/file-upload/file-upload.d.ts +7 -1
- package/dist/file-upload/file-upload.js +18 -7
- package/dist/file-upload/llms.txt +6 -3
- package/dist/form-field/form-field.js +1 -0
- package/dist/galleria/galleria.js +4 -1
- package/dist/i18n/i18n-context.d.ts +3 -3
- package/dist/i18n/i18n-context.js +25 -3
- package/dist/icon/icon.js +4 -1
- package/dist/image/image.js +4 -4
- package/dist/input/input.js +1 -0
- package/dist/input-mask/input-mask.d.ts +7 -2
- package/dist/input-mask/input-mask.js +15 -41
- package/dist/input-mask/llms.txt +3 -0
- package/dist/input-number/input-number.d.ts +3 -1
- package/dist/input-number/input-number.js +15 -11
- package/dist/input-number/llms.txt +5 -2
- package/dist/input-otp/input-otp.d.ts +16 -3
- package/dist/input-otp/input-otp.js +43 -19
- package/dist/input-otp/llms.txt +11 -0
- package/dist/internal/mask.d.ts +14 -0
- package/dist/internal/mask.js +62 -0
- package/dist/internal/number-group.d.ts +22 -0
- package/dist/internal/number-group.js +53 -0
- package/dist/internal/option-key.d.ts +6 -0
- package/dist/internal/option-key.js +8 -0
- package/dist/internal/overlay.js +37 -7
- package/dist/internal/password-strength.d.ts +6 -0
- package/dist/internal/password-strength.js +74 -0
- package/dist/internal/scroll-lock.d.ts +6 -0
- package/dist/internal/scroll-lock.js +35 -0
- package/dist/internal/slot-children.d.ts +13 -0
- package/dist/internal/slot-children.js +31 -0
- package/dist/internal/step.d.ts +12 -0
- package/dist/internal/step.js +31 -0
- package/dist/internal/tween.js +2 -3
- package/dist/internal/use-controllable-state.d.ts +5 -0
- package/dist/internal/use-controllable-state.js +25 -4
- package/dist/internal/use-modal-focus.d.ts +1 -0
- package/dist/internal/use-modal-focus.js +12 -1
- package/dist/knob/knob.d.ts +5 -2
- package/dist/knob/knob.js +15 -14
- package/dist/knob/llms.txt +4 -2
- package/dist/listbox/listbox.d.ts +3 -1
- package/dist/listbox/listbox.js +30 -10
- package/dist/listbox/llms.txt +3 -0
- package/dist/map/llms.txt +5 -1
- package/dist/map/map-create-dialog.js +30 -22
- package/dist/map/map-hours-field.js +1 -0
- package/dist/map/map-image-field.js +2 -1
- package/dist/map/map-legend.js +1 -0
- package/dist/map/map-poi-card.js +2 -1
- package/dist/map/map-search.js +54 -30
- package/dist/map/map.d.ts +6 -1
- package/dist/map/map.js +62 -34
- package/dist/megamenu/llms.txt +11 -0
- package/dist/megamenu/megamenu.d.ts +7 -1
- package/dist/megamenu/megamenu.js +15 -6
- package/dist/menu/menu-rows.js +1 -0
- package/dist/menu/menu.js +10 -3
- package/dist/menubar/llms.txt +11 -0
- package/dist/menubar/menubar.d.ts +7 -1
- package/dist/menubar/menubar.js +15 -3
- package/dist/meter-group/meter-group.js +1 -0
- package/dist/order-list/llms.txt +3 -0
- package/dist/order-list/order-list.js +21 -6
- package/dist/paginator/paginator.js +7 -4
- package/dist/panel/panel.js +5 -3
- package/dist/password/llms.txt +6 -2
- package/dist/password/password.js +4 -14
- package/dist/peek/llms.txt +1 -1
- package/dist/peek/peek.js +37 -15
- package/dist/pick-list/llms.txt +3 -0
- package/dist/pick-list/pick-list.js +19 -3
- package/dist/popover/llms.txt +3 -0
- package/dist/popover/popover.d.ts +3 -1
- package/dist/popover/popover.js +10 -3
- package/dist/progress/progress.js +1 -0
- package/dist/public-api.d.ts +1 -1
- package/dist/radio-group/llms.txt +3 -0
- package/dist/radio-group/radio-group.d.ts +3 -1
- package/dist/radio-group/radio-group.js +13 -2
- package/dist/rating/rating.js +1 -0
- package/dist/rich-text/llms.txt +14 -0
- package/dist/rich-text/rich-text.d.ts +3 -9
- package/dist/rich-text/rich-text.js +241 -96
- package/dist/rich-text/sanitize.d.ts +9 -0
- package/dist/rich-text/sanitize.js +77 -29
- package/dist/roadmap/llms.txt +9 -3
- package/dist/roadmap/roadmap.js +88 -11
- package/dist/scroll-top/scroll-top.js +1 -0
- package/dist/select/llms.txt +3 -0
- package/dist/select/select.d.ts +3 -1
- package/dist/select/select.js +46 -32
- package/dist/shared/types.d.ts +21 -0
- package/dist/shared/types.js +9 -1
- package/dist/sidenav/sidenav.d.ts +1 -2
- package/dist/sidenav/sidenav.js +4 -3
- package/dist/slider/llms.txt +3 -0
- package/dist/slider/slider.d.ts +3 -1
- package/dist/slider/slider.js +15 -4
- package/dist/speed-dial/speed-dial.js +2 -1
- package/dist/spinner/spinner.js +1 -0
- package/dist/split-button/split-button.js +10 -3
- package/dist/splitter/splitter.js +23 -8
- package/dist/stepper/llms.txt +3 -0
- package/dist/stepper/stepper.js +9 -5
- package/dist/switch/llms.txt +3 -0
- package/dist/switch/switch.d.ts +1 -1
- package/dist/switch/switch.js +9 -2
- package/dist/switch-group/llms.txt +3 -0
- package/dist/switch-group/switch-group.d.ts +3 -1
- package/dist/switch-group/switch-group.js +5 -3
- package/dist/tabs/llms.txt +3 -0
- package/dist/tabs/tabs.js +30 -14
- package/dist/textarea/textarea.js +1 -0
- package/dist/toast/llms.txt +3 -0
- package/dist/toast/toast-container.js +1 -0
- package/dist/toast/toast-store.d.ts +5 -1
- package/dist/toast/toast-store.js +1 -1
- package/dist/tooltip/llms.txt +5 -3
- package/dist/tooltip/tooltip.d.ts +3 -2
- package/dist/tooltip/tooltip.js +148 -55
- package/dist/tree/llms.txt +1 -0
- package/dist/tree/tree.d.ts +6 -1
- package/dist/tree/tree.js +14 -7
- package/package.json +15 -14
|
@@ -1,18 +1,39 @@
|
|
|
1
|
-
import { useCallback, useState } from 'react';
|
|
1
|
+
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
|
2
|
+
import { isDevBuild } from "./dev.js";
|
|
2
3
|
/**
|
|
3
4
|
* Stan działający w obu trybach naraz: kontrolowanym (`value` podane z zewnątrz)
|
|
4
5
|
* i niekontrolowanym (`defaultValue` + stan wewnętrzny). Kontrakt jest ten sam
|
|
5
6
|
* co w natywnych polach formularza, więc komponenty współpracują z natywnym
|
|
6
7
|
* `<form>`, React 19 Actions i React Hook Form bez adapterów.
|
|
8
|
+
*
|
|
9
|
+
* `setValue` ma stałą tożsamość: rodzic przekazujący `onChange` jako funkcję inline nie restartuje
|
|
10
|
+
* efektów, które mają `setValue` w zależnościach. Przejście między trybami (`value` raz podane,
|
|
11
|
+
* raz `undefined`) działa jak w Reakcie — po cichu przełącza źródło — ale w buildzie deweloperskim
|
|
12
|
+
* ostrzega, tak jak React robi to dla `<input>`.
|
|
7
13
|
*/
|
|
8
14
|
export function useControllableState(options) {
|
|
9
15
|
const { value, defaultValue, onChange } = options;
|
|
10
16
|
const isControlled = value !== undefined;
|
|
11
17
|
const [uncontrolled, setUncontrolled] = useState(defaultValue);
|
|
18
|
+
// Najświeższy tryb i callback dla stabilnego `setValue` — odświeżane po zatwierdzeniu renderu.
|
|
19
|
+
const latest = useRef({ isControlled, onChange });
|
|
20
|
+
useLayoutEffect(() => {
|
|
21
|
+
latest.current = { isControlled, onChange };
|
|
22
|
+
});
|
|
12
23
|
const setValue = useCallback((next) => {
|
|
13
|
-
if (!isControlled)
|
|
24
|
+
if (!latest.current.isControlled)
|
|
14
25
|
setUncontrolled(next);
|
|
15
|
-
onChange?.(next);
|
|
16
|
-
}, [
|
|
26
|
+
latest.current.onChange?.(next);
|
|
27
|
+
}, []);
|
|
28
|
+
const [initiallyControlled] = useState(isControlled);
|
|
29
|
+
const warned = useRef(false);
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (warned.current || isControlled === initiallyControlled || !isDevBuild())
|
|
32
|
+
return;
|
|
33
|
+
warned.current = true;
|
|
34
|
+
console.warn(isControlled
|
|
35
|
+
? '[koncept-ui] Komponent przeszedł z trybu niekontrolowanego w kontrolowany (`value` było undefined). Wybierz jeden tryb na całe życie komponentu.'
|
|
36
|
+
: '[koncept-ui] Komponent przeszedł z trybu kontrolowanego w niekontrolowany (`value` stało się undefined). Do „braku wartości” użyj null, nie undefined.');
|
|
37
|
+
}, [isControlled, initiallyControlled]);
|
|
17
38
|
return [isControlled ? value : uncontrolled, setValue];
|
|
18
39
|
}
|
|
@@ -4,6 +4,7 @@ import { type RefObject } from 'react';
|
|
|
4
4
|
* z fokusem, przenosi fokus do panelu (pierwszy element z kolejki Tab albo sam panel) i zawija
|
|
5
5
|
* `Tab` / `Shift+Tab` w jego obrębie; przy zamknięciu oddaje fokus zapamiętanemu elementowi.
|
|
6
6
|
* Bez tego `aria-modal="true"` obiecywał czytnikowi okno, z którego Tab po cichu wychodził.
|
|
7
|
+
* Otwarty panel blokuje też przewijanie strony pod spodem (wspólny licznik z `KptDialog`).
|
|
7
8
|
*
|
|
8
9
|
* Port Angulara robi to samo na pułapce CDK (`shared/modal-focus.ts`) — zmiana zachowania w obu.
|
|
9
10
|
*/
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import { useEffect } from 'react';
|
|
2
|
+
import { kptLockScroll } from "./scroll-lock.js";
|
|
3
|
+
/**
|
|
4
|
+
* Stos otwartych paneli modalnych. Pułapki słuchają `keydown` na dokumencie, więc przy dwóch
|
|
5
|
+
* otwartych naraz (szuflada → dialog) walczyłyby o ten sam Tab — klawisz obsługuje tylko najwyższa.
|
|
6
|
+
*/
|
|
7
|
+
const stack = [];
|
|
2
8
|
/** Elementy z kolejki Tab — na nich zawija się pułapka. */
|
|
3
9
|
const TABBABLE = 'a[href], button:not(:disabled), input:not(:disabled), select:not(:disabled), textarea:not(:disabled), [tabindex]:not([tabindex="-1"])';
|
|
4
10
|
function tabbables(panel) {
|
|
@@ -9,6 +15,7 @@ function tabbables(panel) {
|
|
|
9
15
|
* z fokusem, przenosi fokus do panelu (pierwszy element z kolejki Tab albo sam panel) i zawija
|
|
10
16
|
* `Tab` / `Shift+Tab` w jego obrębie; przy zamknięciu oddaje fokus zapamiętanemu elementowi.
|
|
11
17
|
* Bez tego `aria-modal="true"` obiecywał czytnikowi okno, z którego Tab po cichu wychodził.
|
|
18
|
+
* Otwarty panel blokuje też przewijanie strony pod spodem (wspólny licznik z `KptDialog`).
|
|
12
19
|
*
|
|
13
20
|
* Port Angulara robi to samo na pułapce CDK (`shared/modal-focus.ts`) — zmiana zachowania w obu.
|
|
14
21
|
*/
|
|
@@ -19,6 +26,8 @@ export function useModalFocus(open, panelRef) {
|
|
|
19
26
|
return;
|
|
20
27
|
const active = document.activeElement;
|
|
21
28
|
const returnTo = active instanceof HTMLElement && !panel.contains(active) ? active : null;
|
|
29
|
+
stack.push(panel);
|
|
30
|
+
const releaseScroll = kptLockScroll();
|
|
22
31
|
// Klatkę później: panel dostaje `is-open` (i `visibility: visible`) w tym samym commicie,
|
|
23
32
|
// a przeglądarka nie skupi elementu, który w chwili wywołania jest jeszcze niewidoczny.
|
|
24
33
|
const frame = requestAnimationFrame(() => {
|
|
@@ -26,7 +35,7 @@ export function useModalFocus(open, panelRef) {
|
|
|
26
35
|
(first ?? panel).focus();
|
|
27
36
|
});
|
|
28
37
|
const onKeyDown = (event) => {
|
|
29
|
-
if (event.key !== 'Tab')
|
|
38
|
+
if (event.key !== 'Tab' || stack[stack.length - 1] !== panel)
|
|
30
39
|
return;
|
|
31
40
|
const items = tabbables(panel);
|
|
32
41
|
if (!items.length) {
|
|
@@ -50,6 +59,8 @@ export function useModalFocus(open, panelRef) {
|
|
|
50
59
|
return () => {
|
|
51
60
|
cancelAnimationFrame(frame);
|
|
52
61
|
document.removeEventListener('keydown', onKeyDown);
|
|
62
|
+
stack.splice(stack.indexOf(panel), 1);
|
|
63
|
+
releaseScroll();
|
|
53
64
|
if (returnTo?.isConnected)
|
|
54
65
|
returnTo.focus();
|
|
55
66
|
};
|
package/dist/knob/knob.d.ts
CHANGED
|
@@ -18,6 +18,8 @@ export interface KptKnobProps extends Omit<ComponentPropsWithRef<'div'>, 'onChan
|
|
|
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 — pokrętło widać i da się na nim stanąć, ale nie zmienić wartości. */
|
|
22
|
+
readOnly?: boolean;
|
|
21
23
|
invalid?: boolean;
|
|
22
24
|
touched?: boolean;
|
|
23
25
|
onTouch?: () => void;
|
|
@@ -26,10 +28,11 @@ export interface KptKnobProps extends Omit<ComponentPropsWithRef<'div'>, 'onChan
|
|
|
26
28
|
* Pokrętło — wybór wartości po łuku, przeciąganiem albo strzałkami.
|
|
27
29
|
*
|
|
28
30
|
* Skok wartości (strzałka, `Home`/`End`, klik w łuk, zmiana z zewnątrz) łuk i licznik pokonują
|
|
29
|
-
* płynnie; przy przeciąganiu idą za kursorem bez opóźnienia. `
|
|
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·step` dla myszy i klawiatury. `prefers-reduced-motion` wyłącza
|
|
30
33
|
* animację, a `aria-valuenow` zawsze niesie wartość docelową, nie pośrednią.
|
|
31
34
|
*
|
|
32
35
|
* @example
|
|
33
36
|
* <KptKnob value={level} onValueChange={setLevel} min={0} max={100} step={5} suffix="%" />
|
|
34
37
|
*/
|
|
35
|
-
export declare function KptKnob({ value, defaultValue, onValueChange, min, max, step, size, stroke, suffix, showValue, id, ariaLabel, disabled, invalid, touched, onTouch, className, ...rest }: Readonly<KptKnobProps>): import("react").JSX.Element;
|
|
38
|
+
export declare function KptKnob({ value, defaultValue, onValueChange, min, max, step, size, stroke, suffix, showValue, id, ariaLabel, disabled, readOnly, invalid, touched, onTouch, className, ...rest }: Readonly<KptKnobProps>): import("react").JSX.Element;
|
package/dist/knob/knob.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
3
|
import { useEffect, useRef, useState } from 'react';
|
|
3
4
|
import { cn } from "../internal/cn.js";
|
|
5
|
+
import { kptSnapToStep } from "../internal/step.js";
|
|
4
6
|
import { kptStepDecimals, kptTween } from "../internal/tween.js";
|
|
5
7
|
import { useControllableState } from "../internal/use-controllable-state.js";
|
|
6
8
|
import { useKptFieldA11y } from "../form-field/form-field-context.js";
|
|
@@ -15,13 +17,14 @@ const TWEEN_MS = 220;
|
|
|
15
17
|
* Pokrętło — wybór wartości po łuku, przeciąganiem albo strzałkami.
|
|
16
18
|
*
|
|
17
19
|
* Skok wartości (strzałka, `Home`/`End`, klik w łuk, zmiana z zewnątrz) łuk i licznik pokonują
|
|
18
|
-
* płynnie; przy przeciąganiu idą za kursorem bez opóźnienia. `
|
|
20
|
+
* płynnie; przy przeciąganiu idą za kursorem bez opóźnienia. `PageUp`/`PageDown` skaczą o dziesięć
|
|
21
|
+
* kroków, a wartości leżą na siatce `min + k·step` dla myszy i klawiatury. `prefers-reduced-motion` wyłącza
|
|
19
22
|
* animację, a `aria-valuenow` zawsze niesie wartość docelową, nie pośrednią.
|
|
20
23
|
*
|
|
21
24
|
* @example
|
|
22
25
|
* <KptKnob value={level} onValueChange={setLevel} min={0} max={100} step={5} suffix="%" />
|
|
23
26
|
*/
|
|
24
|
-
export function KptKnob({ value, defaultValue = 0, onValueChange, min = 0, max = 100, step = 1, size = 96, stroke = 10, suffix = '', showValue = true, id, ariaLabel = null, disabled = false, invalid = false, touched, onTouch, className, ...rest }) {
|
|
27
|
+
export function KptKnob({ value, defaultValue = 0, onValueChange, min = 0, max = 100, step = 1, size = 96, stroke = 10, suffix = '', showValue = true, id, ariaLabel = null, disabled = false, readOnly = false, invalid = false, touched, onTouch, className, ...rest }) {
|
|
25
28
|
const field = useKptFieldA11y(id, 'kpt-knob');
|
|
26
29
|
const svgRef = useRef(null);
|
|
27
30
|
const [current, setCurrent] = useControllableState({ value, defaultValue, onChange: onValueChange });
|
|
@@ -58,7 +61,6 @@ export function KptKnob({ value, defaultValue = 0, onValueChange, min = 0, max =
|
|
|
58
61
|
return `M ${x0} ${y0}`;
|
|
59
62
|
return `M ${x0} ${y0} A ${r} ${r} 0 ${a1 - a0 > Math.PI ? 1 : 0} 1 ${x1} ${y1}`;
|
|
60
63
|
};
|
|
61
|
-
const clamp = (raw) => Math.min(max, Math.max(min, Math.round(raw / step) * step));
|
|
62
64
|
const setFromPoint = (clientX, clientY) => {
|
|
63
65
|
const svg = svgRef.current;
|
|
64
66
|
if (!svg)
|
|
@@ -72,10 +74,10 @@ export function KptKnob({ value, defaultValue = 0, onValueChange, min = 0, max =
|
|
|
72
74
|
// inaczej przeciągnięcie przez dół przeskakiwałoby z maksimum na minimum.
|
|
73
75
|
if (angle > SWEEP + GAP)
|
|
74
76
|
angle = angle > SWEEP + GAP / 2 ? 0 : SWEEP;
|
|
75
|
-
setCurrent(
|
|
77
|
+
setCurrent(kptSnapToStep(min + Math.min(1, Math.max(0, angle / SWEEP)) * range, min, max, step));
|
|
76
78
|
};
|
|
77
79
|
const onPointerDown = (event) => {
|
|
78
|
-
if (disabled)
|
|
80
|
+
if (disabled || readOnly)
|
|
79
81
|
return;
|
|
80
82
|
// Bez tego przeciąganie zaznacza tekst: licznik w środku i — przez SVG — całe koło, jak po
|
|
81
83
|
// `Ctrl+A`. Zablokowanie domyślnej akcji zabiera też fokus, więc oddajemy go ręcznie.
|
|
@@ -101,22 +103,21 @@ export function KptKnob({ value, defaultValue = 0, onValueChange, min = 0, max =
|
|
|
101
103
|
window.addEventListener('pointercancel', up);
|
|
102
104
|
};
|
|
103
105
|
const onKeyDown = (event) => {
|
|
104
|
-
if (disabled)
|
|
106
|
+
if (disabled || readOnly)
|
|
105
107
|
return;
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
else if (event.key === 'ArrowDown' || event.key === 'ArrowLeft')
|
|
110
|
-
next -= step;
|
|
111
|
-
else if (event.key === 'Home')
|
|
108
|
+
const steps = { ArrowUp: 1, ArrowRight: 1, ArrowDown: -1, ArrowLeft: -1, PageUp: 10, PageDown: -10 };
|
|
109
|
+
let next;
|
|
110
|
+
if (event.key === 'Home')
|
|
112
111
|
next = min;
|
|
113
112
|
else if (event.key === 'End')
|
|
114
113
|
next = max;
|
|
114
|
+
else if (steps[event.key] !== undefined)
|
|
115
|
+
next = current + steps[event.key] * step;
|
|
115
116
|
else
|
|
116
117
|
return;
|
|
117
118
|
event.preventDefault();
|
|
118
|
-
setCurrent(
|
|
119
|
+
setCurrent(kptSnapToStep(next, min, max, step));
|
|
119
120
|
onTouch?.();
|
|
120
121
|
};
|
|
121
|
-
return (_jsx("div", { className: cn('kpt-knob-host', className), ...rest, children: _jsx("div", { className: cn('kpt-knob', showInvalid && 'kpt-knob--invalid'), role: "slider", id: field.id, tabIndex: disabled ? -1 : 0, "aria-valuemin": min, "aria-valuemax": max, "aria-valuenow": current, "aria-valuetext": suffix ? `${current}${suffix}` : undefined, "aria-disabled": disabled || undefined, "aria-invalid": showInvalid || undefined, "aria-label": ariaLabel ?? undefined, "aria-labelledby": ariaLabel ? undefined : field.labelledBy, "aria-describedby": field.describedBy, onPointerDown: onPointerDown, onKeyDown: onKeyDown, children: _jsxs("svg", { ref: svgRef, viewBox: `0 0 ${size} ${size}`, style: { width: size, height: size }, children: [_jsx("path", { className: "kpt-knob__range", d: arc(0, 1), fill: "none", strokeWidth: stroke, strokeLinecap: "round" }), _jsx("path", { className: "kpt-knob__value", d: arc(0, ratio), fill: "none", strokeWidth: stroke, strokeLinecap: "round" }), showValue ? (_jsxs("text", { className: "kpt-knob__text", x: size / 2, y: size / 2, textAnchor: "middle", dominantBaseline: "central", children: [shown.toFixed(kptStepDecimals(step)), suffix] })) : null] }) }) }));
|
|
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] }) }) }));
|
|
122
123
|
}
|
package/dist/knob/llms.txt
CHANGED
|
@@ -19,7 +19,8 @@ Import: `import { KptKnob } from '@konce-pt/react';`
|
|
|
19
19
|
- `min` (0), `max` (100), `step` (1)
|
|
20
20
|
- `size`: px (96) — the side of the SVG square; `stroke`: px (10) — arc thickness
|
|
21
21
|
- `suffix`: string appended to the value in the middle; `showValue`: boolean (default true)
|
|
22
|
-
- `disabled`, `invalid`, `touched`, `onTouch()`
|
|
22
|
+
- `disabled`, `readOnly`, `invalid`, `touched`, `onTouch()` — `readOnly` keeps the knob focusable but ignores
|
|
23
|
+
the pointer and the keys (`aria-readonly`)
|
|
23
24
|
- `className`, `ref` and every other `<div>` prop go to the host
|
|
24
25
|
|
|
25
26
|
## The gap at the bottom
|
|
@@ -57,5 +58,6 @@ select the number or the arc.
|
|
|
57
58
|
|
|
58
59
|
## Accessibility
|
|
59
60
|
`role="slider"` with `aria-valuemin` / `aria-valuemax` / `aria-valuenow`; arrows step by `step`,
|
|
60
|
-
Home and End jump to the bounds.
|
|
61
|
+
PageUp/PageDown by ten steps, Home and End jump to the bounds. Values sit on the grid `min + k·step` for
|
|
62
|
+
both the pointer and the keys — with `min={1} step={2}` that is 1, 3, 5…. `disabled` sets `aria-disabled` and drops the knob out of the
|
|
61
63
|
tab order.
|
|
@@ -16,6 +16,8 @@ export interface KptListboxProps<T> extends Omit<ComponentPropsWithRef<'div'>, '
|
|
|
16
16
|
/** Oznacza listę jako wymaganą atrybutem `aria-required`. */
|
|
17
17
|
required?: boolean;
|
|
18
18
|
disabled?: boolean;
|
|
19
|
+
/** Tylko do odczytu — po liście da się chodzić i czytać wybór, ale nie da się go zmienić. */
|
|
20
|
+
readOnly?: boolean;
|
|
19
21
|
invalid?: boolean;
|
|
20
22
|
touched?: boolean;
|
|
21
23
|
onTouch?: () => void;
|
|
@@ -37,4 +39,4 @@ export interface KptListboxProps<T> extends Omit<ComponentPropsWithRef<'div'>, '
|
|
|
37
39
|
* <KptListbox options={roles} value={role} onValueChange={setRole} filter />
|
|
38
40
|
* <KptListbox options={tags} value={tags} onValueChange={setTags} multiple />
|
|
39
41
|
*/
|
|
40
|
-
export declare function KptListbox<T>({ options, value, defaultValue, onValueChange, multiple, filter, id, ariaLabel, required, disabled, invalid, touched, onTouch, locale, dictionary, className, ...rest }: Readonly<KptListboxProps<T>>): import("react").JSX.Element;
|
|
42
|
+
export declare function KptListbox<T>({ options, value, defaultValue, onValueChange, multiple, filter, id, ariaLabel, required, disabled, readOnly, invalid, touched, onTouch, locale, dictionary, className, ...rest }: Readonly<KptListboxProps<T>>): import("react").JSX.Element;
|
package/dist/listbox/listbox.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
3
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
3
4
|
import { foldMentionText } from '@konce-pt/mention';
|
|
@@ -24,7 +25,7 @@ import { KptIcon } from "../icon/icon.js";
|
|
|
24
25
|
* <KptListbox options={roles} value={role} onValueChange={setRole} filter />
|
|
25
26
|
* <KptListbox options={tags} value={tags} onValueChange={setTags} multiple />
|
|
26
27
|
*/
|
|
27
|
-
export function KptListbox({ options = [], value, defaultValue = null, onValueChange, multiple = false, filter = false, id, ariaLabel = null, required = false, disabled = false, invalid = false, touched, onTouch, locale, dictionary, className, ...rest }) {
|
|
28
|
+
export function KptListbox({ options = [], value, defaultValue = null, onValueChange, multiple = false, filter = false, id, ariaLabel = null, required = false, disabled = false, readOnly = false, invalid = false, touched, onTouch, locale, dictionary, className, ...rest }) {
|
|
28
29
|
const { t } = useKptMessages({ locale, dictionary });
|
|
29
30
|
/** `div` z rolą nie dostaje nazwy z `label[for]` — stąd `aria-labelledby` na etykietę wrappera. */
|
|
30
31
|
const field = useKptFieldA11y(id, 'kpt-listbox');
|
|
@@ -55,18 +56,33 @@ export function KptListbox({ options = [], value, defaultValue = null, onValueCh
|
|
|
55
56
|
const isSelected = (candidate) => multiple ? Array.isArray(current) && current.includes(candidate) : current === candidate;
|
|
56
57
|
const toggle = (candidate) => {
|
|
57
58
|
if (multiple) {
|
|
58
|
-
const list = Array.isArray(current) ? current : [];
|
|
59
|
+
const list = Array.isArray(current) ? (current) : [];
|
|
59
60
|
setCurrent(list.includes(candidate) ? list.filter((item) => item !== candidate) : [...list, candidate]);
|
|
60
61
|
}
|
|
61
62
|
else {
|
|
62
63
|
setCurrent(current === candidate ? null : candidate);
|
|
63
64
|
}
|
|
64
65
|
};
|
|
65
|
-
|
|
66
|
+
/** Ostatnio kliknięta opcja — początek zakresu dla `Shift+klik` w trybie `multiple`. */
|
|
67
|
+
const anchorIndex = useRef(-1);
|
|
68
|
+
const pick = (index, extend = false) => {
|
|
66
69
|
const option = visible[index];
|
|
67
|
-
if (!option || disabledMap[index])
|
|
70
|
+
if (!option || disabledMap[index] || readOnly)
|
|
68
71
|
return;
|
|
69
72
|
setActiveIndex(index);
|
|
73
|
+
if (extend && multiple && anchorIndex.current >= 0) {
|
|
74
|
+
// Zaznacza (dokłada) dozwolone opcje między kotwicą a klikniętą — `Shift+klik` (APG).
|
|
75
|
+
const [lo, hi] = anchorIndex.current <= index ? [anchorIndex.current, index] : [index, anchorIndex.current];
|
|
76
|
+
const list = Array.isArray(current) ? current : [];
|
|
77
|
+
const add = visible
|
|
78
|
+
.slice(lo, hi + 1)
|
|
79
|
+
.filter((_, offset) => !disabledMap[lo + offset])
|
|
80
|
+
.map((item) => item.value)
|
|
81
|
+
.filter((item) => !list.includes(item));
|
|
82
|
+
setCurrent([...list, ...add]);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
anchorIndex.current = index;
|
|
70
86
|
toggle(option.value);
|
|
71
87
|
};
|
|
72
88
|
/** Wejście fokusem: aktywna zostaje wybrana opcja, a bez niej pierwsza dozwolona. */
|
|
@@ -88,8 +104,12 @@ export function KptListbox({ options = [], value, defaultValue = null, onValueCh
|
|
|
88
104
|
if (OPTION_NAV_KEYS.has(event.key)) {
|
|
89
105
|
event.preventDefault();
|
|
90
106
|
const next = nextOptionIndex(disabledMap, activeIndex, event.key);
|
|
91
|
-
if (next
|
|
92
|
-
|
|
107
|
+
if (next === null)
|
|
108
|
+
return;
|
|
109
|
+
setActiveIndex(next);
|
|
110
|
+
// `Shift+strzałka` w wyborze wielokrotnym zaznacza opcję, na którą przeszedł fokus (APG listbox).
|
|
111
|
+
if (event.shiftKey && multiple && !readOnly && !isSelected(visible[next].value))
|
|
112
|
+
toggle(visible[next].value);
|
|
93
113
|
return;
|
|
94
114
|
}
|
|
95
115
|
if (event.key === ' ' || event.key === 'Enter') {
|
|
@@ -99,10 +119,10 @@ export function KptListbox({ options = [], value, defaultValue = null, onValueCh
|
|
|
99
119
|
pick(activeIndex);
|
|
100
120
|
return;
|
|
101
121
|
}
|
|
102
|
-
if (multiple && (event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'a') {
|
|
122
|
+
if (multiple && !readOnly && (event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'a') {
|
|
103
123
|
event.preventDefault();
|
|
104
124
|
const all = visible.filter((_, index) => !disabledMap[index]).map((option) => option.value);
|
|
105
|
-
const list = Array.isArray(current) ? current : [];
|
|
125
|
+
const list = Array.isArray(current) ? (current) : [];
|
|
106
126
|
// Drugi raz `Ctrl+A` przy wszystkich zaznaczonych — odznacza widoczne.
|
|
107
127
|
const allOn = all.every((item) => list.includes(item));
|
|
108
128
|
setCurrent(allOn ? list.filter((item) => !all.includes(item)) : [...list, ...all.filter((item) => !list.includes(item))]);
|
|
@@ -119,8 +139,8 @@ export function KptListbox({ options = [], value, defaultValue = null, onValueCh
|
|
|
119
139
|
return;
|
|
120
140
|
event.preventDefault();
|
|
121
141
|
listRef.current?.focus();
|
|
122
|
-
} })] })) : null, _jsx("div", { ref: listRef, className: "kpt-listbox__items", role: "listbox", id: field.id, tabIndex: disabled ? -1 : 0, "aria-multiselectable": multiple || undefined, "aria-activedescendant": activeOptionId, "aria-disabled": disabled || undefined, "aria-invalid": showInvalid || undefined, "aria-required": required || undefined, "aria-label": ariaLabel ?? undefined, "aria-labelledby": ariaLabel ? undefined : field.labelledBy, "aria-describedby": field.describedBy, onKeyDown: onKeyDown, onFocus: onFocus, onBlur: () => onTouch?.(), children: visible.
|
|
142
|
+
} })] })) : null, _jsx("div", { ref: listRef, className: "kpt-listbox__items", role: "listbox", id: field.id, tabIndex: disabled ? -1 : 0, "aria-multiselectable": multiple || undefined, "aria-activedescendant": activeOptionId, "aria-disabled": disabled || undefined, "aria-invalid": showInvalid || undefined, "aria-required": required || undefined, "aria-readonly": readOnly || undefined, "aria-label": ariaLabel ?? undefined, "aria-labelledby": ariaLabel ? undefined : field.labelledBy, "aria-describedby": field.describedBy, onKeyDown: onKeyDown, onFocus: onFocus, onBlur: () => onTouch?.(), children: visible.map((option, index) => (_jsxs("div", { role: "option", id: optionId(index), className: cn('kpt-listbox__item', isSelected(option.value) && 'is-selected', index === activeIndex && 'is-active'), "aria-selected": isSelected(option.value), "aria-disabled": disabledMap[index] || undefined, onMouseDown: (event) => {
|
|
123
143
|
event.preventDefault();
|
|
124
144
|
listRef.current?.focus();
|
|
125
|
-
}, onClick: () => pick(index), children: [_jsx("span", { children: option.label }), isSelected(option.value) ? _jsx(KptIcon, { name: "check" }) : null] }, String(option.value)))))
|
|
145
|
+
}, onClick: (event) => pick(index, event.shiftKey), children: [_jsx("span", { children: option.label }), isSelected(option.value) ? _jsx(KptIcon, { name: "check" }) : null] }, typeof option.value === 'object' && option.value !== null ? `#${index}` : String(option.value)))) }), visible.length === 0 ? (_jsx("div", { className: "kpt-listbox__empty", role: "status", children: t('listbox.empty') })) : null] }) }));
|
|
126
146
|
}
|
package/dist/listbox/llms.txt
CHANGED
|
@@ -53,3 +53,6 @@ label (unless `ariaLabel` is set) and the hint or error in `aria-describedby`. N
|
|
|
53
53
|
The list is `role="listbox"` with `aria-multiselectable`, `aria-activedescendant` and a name from
|
|
54
54
|
`ariaLabel` or `KptFormField`; options are `role="option"` with `aria-selected`/`aria-disabled`.
|
|
55
55
|
The keyboard is described above.
|
|
56
|
+
|
|
57
|
+
## Read-only and required
|
|
58
|
+
`readOnly` lets you move through the list and read the choice but not change it (`aria-readonly`). With `multiple`, `Shift+↑/↓` 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.
|
package/dist/map/llms.txt
CHANGED
|
@@ -41,7 +41,11 @@ a readable message.
|
|
|
41
41
|
|
|
42
42
|
## Events
|
|
43
43
|
`onLocationClick`, `onClusterClick`, `onLocationCreate`, `onSearchSelect`, `onViewChange`,
|
|
44
|
-
`onUserPositionChange`, `onUserLocationError`.
|
|
44
|
+
`onUserPositionChange`, `onUserLocationError`, `onEngineError`.
|
|
45
|
+
|
|
46
|
+
`onEngineError(error)` fires when the engine fails to start (no `leaflet` package, a rejected Google key);
|
|
47
|
+
the error is also logged to the console. `followUser` and the position callbacks are read fresh on every
|
|
48
|
+
fix, so changing them does not restart the geolocation watch.
|
|
45
49
|
|
|
46
50
|
`onLocationCreate` gives `{ at, categoryId, value }` — the component appends **nothing** to
|
|
47
51
|
`locations`; the application is the source of truth.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
3
|
import { emptyWeek, parseTime, weekdayName } from '@konce-pt/map';
|
|
3
4
|
import { useEffect, useMemo, useState } from 'react';
|
|
@@ -40,6 +41,10 @@ function initialModel(fields) {
|
|
|
40
41
|
out[def.key] = initialValue(def);
|
|
41
42
|
return out;
|
|
42
43
|
}
|
|
44
|
+
/** Wartość pola tekstowego z modelu `unknown` — liczba zostaje liczbą zapisaną tekstem, reszta pustym napisem. */
|
|
45
|
+
function asText(value) {
|
|
46
|
+
return typeof value === 'string' || typeof value === 'number' ? String(value) : '';
|
|
47
|
+
}
|
|
43
48
|
/**
|
|
44
49
|
* Treść dialogu „dodaj lokalizację" budowana ze schemy kategorii (`category.fields`).
|
|
45
50
|
*
|
|
@@ -55,42 +60,48 @@ export function KptMapCreateDialog({ category, at, geocoder = null, onSave, onCa
|
|
|
55
60
|
const [touched, setTouched] = useState({});
|
|
56
61
|
/** Po nieudanej próbie zapisu pokazujemy błędy wszystkich pól, nie tylko dotkniętych. */
|
|
57
62
|
const [submitted, setSubmitted] = useState(false);
|
|
58
|
-
/** Trwa zapytanie do geokodera — pokazujemy notkę zamiast ciszy. */
|
|
59
|
-
const [geocoding, setGeocoding] = useState(false);
|
|
60
63
|
/** Ostatnio ustalony adres; `address` bywa czytelniejszy niż suma pól. */
|
|
61
64
|
const [address, setAddress] = useState(null);
|
|
62
|
-
// Inna kategoria to inny formularz — model wraca do wartości początkowych.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
// Inna kategoria to inny formularz — model wraca do wartości początkowych. Liczone w renderze
|
|
66
|
+
// (wzorzec poprzedniej wartości): efekt pokazałby jedną klatkę pól starej kategorii.
|
|
67
|
+
const [modelFields, setModelFields] = useState(fields);
|
|
68
|
+
if (modelFields !== fields) {
|
|
69
|
+
setModelFields(fields);
|
|
70
|
+
setModel(initialModel(fields));
|
|
66
71
|
setTouched({});
|
|
67
72
|
setSubmitted(false);
|
|
68
|
-
}
|
|
73
|
+
}
|
|
69
74
|
/** Czy kategoria ma pola do podpowiedzenia i jest czym je podpowiedzieć. */
|
|
70
75
|
const autofillFields = useMemo(() => fields.filter((def) => def.autofill && TEXTUAL.has(def.type)), [fields]);
|
|
71
76
|
const hasAutofill = !!geocoder && autofillFields.length > 0;
|
|
77
|
+
/** Tożsamość bieżącego zapytania do geokodera — nowa przy każdej zmianie jego wejść. */
|
|
78
|
+
const geocodeRequest = useMemo(() => (geocoder && autofillFields.length > 0 ? { geocoder, at, locale, autofillFields } : null), [geocoder, at, locale, autofillFields]);
|
|
79
|
+
/** Zapytanie, na które przyszła już odpowiedź (albo błąd). */
|
|
80
|
+
const [settledRequest, setSettledRequest] = useState(null);
|
|
81
|
+
/** Trwa zapytanie do geokodera — pokazujemy notkę zamiast ciszy. */
|
|
82
|
+
const geocoding = geocodeRequest !== null && settledRequest !== geocodeRequest;
|
|
72
83
|
/**
|
|
73
84
|
* Podpowiada adres z geokodowania wstecznego współrzędnych kliknięcia. Wypełnia wyłącznie
|
|
74
85
|
* pola z `autofill` i wyłącznie te, które są jeszcze puste — podpowiedź nigdy nie kasuje
|
|
75
86
|
* tego, co użytkownik zdążył wpisać.
|
|
76
87
|
*/
|
|
77
88
|
useEffect(() => {
|
|
78
|
-
if (!
|
|
89
|
+
if (!geocodeRequest)
|
|
79
90
|
return;
|
|
91
|
+
const request = geocodeRequest;
|
|
80
92
|
const controller = new AbortController();
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
.reverse(at, { language: locale, signal: controller.signal })
|
|
93
|
+
request.geocoder
|
|
94
|
+
.reverse(request.at, { language: request.locale, signal: controller.signal })
|
|
84
95
|
.then((found) => {
|
|
85
96
|
if (controller.signal.aborted)
|
|
86
97
|
return;
|
|
87
|
-
|
|
98
|
+
setSettledRequest(request);
|
|
88
99
|
setAddress(found);
|
|
89
100
|
if (!found)
|
|
90
101
|
return;
|
|
91
102
|
setModel((current) => {
|
|
92
103
|
const next = { ...current };
|
|
93
|
-
for (const def of autofillFields) {
|
|
104
|
+
for (const def of request.autofillFields) {
|
|
94
105
|
const value = found[def.autofill];
|
|
95
106
|
// Puste pole = to, czego użytkownik nie tknął.
|
|
96
107
|
if (typeof value === 'string' && value && !next[def.key])
|
|
@@ -101,13 +112,10 @@ export function KptMapCreateDialog({ category, at, geocoder = null, onSave, onCa
|
|
|
101
112
|
})
|
|
102
113
|
.catch(() => {
|
|
103
114
|
if (!controller.signal.aborted)
|
|
104
|
-
|
|
115
|
+
setSettledRequest(request);
|
|
105
116
|
});
|
|
106
|
-
return () =>
|
|
107
|
-
|
|
108
|
-
setGeocoding(false);
|
|
109
|
-
};
|
|
110
|
-
}, [geocoder, at, locale, autofillFields]);
|
|
117
|
+
return () => controller.abort();
|
|
118
|
+
}, [geocodeRequest]);
|
|
111
119
|
/**
|
|
112
120
|
* Komunikat błędu pola albo `null`. Dzień otwarty musi mieć obie godziny i różne od siebie;
|
|
113
121
|
* dzień oznaczony jako zamknięty przechodzi bez zastrzeżeń — to prawidłowa odpowiedź,
|
|
@@ -174,7 +182,7 @@ export function KptMapCreateDialog({ category, at, geocoder = null, onSave, onCa
|
|
|
174
182
|
const common = { onTouch: touch(def.key), invalid: !!visibleError(def), touched: !!touched[def.key] };
|
|
175
183
|
switch (def.type) {
|
|
176
184
|
case 'textarea':
|
|
177
|
-
return (_jsx(KptTextarea, { value:
|
|
185
|
+
return (_jsx(KptTextarea, { value: asText(value), onValueChange: (next) => set(def.key, next), placeholder: def.placeholder ?? '', ...common }));
|
|
178
186
|
case 'number':
|
|
179
187
|
return (_jsx(KptInputNumber, { value: value, onValueChange: (next) => set(def.key, next), min: typeof def.min === 'number' ? def.min : undefined, max: typeof def.max === 'number' ? def.max : undefined, placeholder: def.placeholder ?? '', ...common }));
|
|
180
188
|
case 'date':
|
|
@@ -186,11 +194,11 @@ export function KptMapCreateDialog({ category, at, geocoder = null, onSave, onCa
|
|
|
186
194
|
case 'checkbox':
|
|
187
195
|
return (_jsx(KptCheckbox, { checked: !!value, onCheckedChange: (next) => set(def.key, next), label: def.placeholder ?? '', onTouch: touch(def.key) }));
|
|
188
196
|
case 'image':
|
|
189
|
-
return (_jsx(KptMapImageField, { value:
|
|
197
|
+
return (_jsx(KptMapImageField, { value: asText(value), onValueChange: (next) => set(def.key, next), invalid: !!visibleError(def), touched: !!touched[def.key], onTouch: touch(def.key) }));
|
|
190
198
|
case 'hours':
|
|
191
199
|
return (_jsx(KptMapHoursField, { value: value ?? emptyWeek(), onValueChange: (next) => set(def.key, next), onTouch: touch(def.key) }));
|
|
192
200
|
default:
|
|
193
|
-
return (_jsx(KptInput, { id: controlId(def), type: def.type === 'tel' || def.type === 'email' || def.type === 'url' ? def.type : 'text', value:
|
|
201
|
+
return (_jsx(KptInput, { id: controlId(def), type: def.type === 'tel' || def.type === 'email' || def.type === 'url' ? def.type : 'text', value: asText(value), onValueChange: (next) => set(def.key, next), placeholder: def.placeholder ?? (def.type === 'url' ? 'https://' : ''), ...common }));
|
|
194
202
|
}
|
|
195
203
|
}
|
|
196
204
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
3
|
import { useState } from 'react';
|
|
3
4
|
import { cn } from "../internal/cn.js";
|
|
@@ -42,7 +43,7 @@ export function KptMapImageField({ value, onValueChange, disabled = false, readO
|
|
|
42
43
|
return;
|
|
43
44
|
}
|
|
44
45
|
const reader = new FileReader();
|
|
45
|
-
reader.onload = () => onValueChange(
|
|
46
|
+
reader.onload = () => onValueChange((typeof reader.result === 'string' ? reader.result : ''));
|
|
46
47
|
reader.onerror = () => setSizeError(t('map.field.imageTooLarge', { max: maxLabel }));
|
|
47
48
|
reader.readAsDataURL(file);
|
|
48
49
|
};
|
package/dist/map/map-legend.js
CHANGED
package/dist/map/map-poi-card.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
3
|
import { foregroundVarOf, formatDistance, formatLatLng, groupWeek, isAlwaysOpen, isOpenAt, nextChange, normalizeUrl, prettyUrl, } from '@konce-pt/map';
|
|
3
4
|
import { useId, useRef, useState } from 'react';
|
|
@@ -148,5 +149,5 @@ export function KptMapPoiCard({ poi, distance = null, category = null, primaryLa
|
|
|
148
149
|
// Odświeżamy zegar przy rozwinięciu — karta bywa otwarta dłużej niż minutę.
|
|
149
150
|
setNow(Date.now());
|
|
150
151
|
setExpanded(!expanded);
|
|
151
|
-
}, children: [_jsx(KptIcon, { className: "kpt-map-poi__row-icon", name: "clock" }), _jsx("span", { className: "kpt-map-poi__status", "data-open": open || alwaysOpen, children: statusLabel }), statusHint ? _jsxs("span", { className: "kpt-map-poi__status-hint", children: ["\u00B7 ", statusHint] }) : null, _jsx(KptIcon, { className: "kpt-map-poi__chevron", name: "chevron-down" })] }), expanded ? (_jsx("dl", { className: "kpt-map-poi__week", id: hoursId, children: hoursGroups.map((group) => (_jsxs("div", { style: { display: 'contents' }, children: [_jsx("dt", { className: "kpt-map-poi__week-day", children: group.label }), _jsx("dd", { className: "kpt-map-poi__week-hours", children: group.closed ? (_jsx("span", { className: "kpt-map-poi__week-closed", children: t('map.poi.closedDay') })) : (group.ranges.map((range) => (_jsx("span", { className: "kpt-map-poi__week-range", children: range }, range)))) })] }, group.label))) })) : null] })) : null, rows.length || poi.facts?.length ? (_jsxs("ul", { className: "kpt-map-poi__rows", children: [rows.map((row) => (_jsxs("li", { className: "kpt-map-poi__row", children: [_jsx(KptIcon, { className: "kpt-map-poi__row-icon", name: row.icon }), row.href ? (_jsx("a", { className: "kpt-map-poi__row-value kpt-map-poi__row-link", href: row.href, target: row.external ? '_blank' : undefined, rel: row.external ? 'noopener' : undefined, children: row.text })) : (_jsx("span", { className: "kpt-map-poi__row-value", children: row.text })), _jsxs("span", { className: "kpt-map-poi__row-actions", children: [row.href ? (_jsx("a", { className: "kpt-map-poi__row-action", href: row.href, target: row.external ? '_blank' : undefined, rel: row.external ? 'noopener' : undefined, "aria-label": row.actionLabel, title: row.actionLabel, children: _jsx(KptIcon, { name: row.actionIcon }) })) : null, canCopy ? (_jsx("button", { className: "kpt-map-poi__row-action", type: "button", "aria-label": t('map.poi.copy'), title: t('map.poi.copy'), onClick: () => void copy(row), children: _jsx(KptIcon, { name: justCopied === row.kind ? 'check' : 'copy' }) })) : null] })] }, row.kind))), (poi.facts ?? []).map((fact) => (_jsxs("li", { className: "kpt-map-poi__row kpt-map-poi__row--fact", children: [fact.icon ? (_jsx(KptIcon, { className: "kpt-map-poi__row-icon", name: fact.icon })) : (_jsx("span", { className: "kpt-map-poi__row-icon", "aria-hidden": "true" })), _jsxs("span", { className: "kpt-map-poi__row-value", children: [_jsx("span", { className: "kpt-map-poi__fact-label", children: fact.label }), " ", fact.value] })] }, fact.label)))] })) : null, _jsx("p", { className: "kpt-map-poi__live", "aria-live": "polite", children: justCopied ? t('map.poi.copied') : '' })] })] }));
|
|
152
|
+
}, children: [_jsx(KptIcon, { className: "kpt-map-poi__row-icon", name: "clock" }), _jsx("span", { className: "kpt-map-poi__status", "data-open": open || alwaysOpen, children: statusLabel }), statusHint ? _jsxs("span", { className: "kpt-map-poi__status-hint", children: ["\u00B7 ", statusHint] }) : null, _jsx(KptIcon, { className: "kpt-map-poi__chevron", name: "chevron-down" })] }), expanded ? (_jsx("dl", { className: "kpt-map-poi__week", id: hoursId, children: hoursGroups.map((group) => (_jsxs("div", { style: { display: 'contents' }, children: [_jsx("dt", { className: "kpt-map-poi__week-day", children: group.label }), _jsx("dd", { className: "kpt-map-poi__week-hours", children: group.closed ? (_jsx("span", { className: "kpt-map-poi__week-closed", children: t('map.poi.closedDay') })) : (group.ranges.map((range) => (_jsx("span", { className: "kpt-map-poi__week-range", children: range }, range)))) })] }, group.label))) })) : null] })) : null, rows.length || poi.facts?.length ? (_jsxs("ul", { className: "kpt-map-poi__rows", children: [rows.map((row) => (_jsxs("li", { className: "kpt-map-poi__row", children: [_jsx(KptIcon, { className: "kpt-map-poi__row-icon", name: row.icon }), row.href ? (_jsx("a", { className: "kpt-map-poi__row-value kpt-map-poi__row-link", href: row.href, target: row.external ? '_blank' : undefined, rel: row.external ? 'noopener noreferrer' : undefined, children: row.text })) : (_jsx("span", { className: "kpt-map-poi__row-value", children: row.text })), _jsxs("span", { className: "kpt-map-poi__row-actions", children: [row.href ? (_jsx("a", { className: "kpt-map-poi__row-action", href: row.href, target: row.external ? '_blank' : undefined, rel: row.external ? 'noopener noreferrer' : undefined, "aria-label": row.actionLabel, title: row.actionLabel, children: _jsx(KptIcon, { name: row.actionIcon }) })) : null, canCopy ? (_jsx("button", { className: "kpt-map-poi__row-action", type: "button", "aria-label": t('map.poi.copy'), title: t('map.poi.copy'), onClick: () => void copy(row), children: _jsx(KptIcon, { name: justCopied === row.kind ? 'check' : 'copy' }) })) : null] })] }, row.kind))), (poi.facts ?? []).map((fact) => (_jsxs("li", { className: "kpt-map-poi__row kpt-map-poi__row--fact", children: [fact.icon ? (_jsx(KptIcon, { className: "kpt-map-poi__row-icon", name: fact.icon })) : (_jsx("span", { className: "kpt-map-poi__row-icon", "aria-hidden": "true" })), _jsxs("span", { className: "kpt-map-poi__row-value", children: [_jsx("span", { className: "kpt-map-poi__fact-label", children: fact.label }), " ", fact.value] })] }, fact.label)))] })) : null, _jsx("p", { className: "kpt-map-poi__live", "aria-live": "polite", children: justCopied ? t('map.poi.copied') : '' })] })] }));
|
|
152
153
|
}
|