@inkra/react 0.3.0 → 0.5.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 +113 -3
- package/dist/Button.js +3 -2
- package/dist/PencilIcon.d.ts +10 -0
- package/dist/PencilIcon.js +83 -39
- package/dist/PencilLine.js +9 -1
- package/dist/PencilText.js +15 -9
- package/dist/accordion/Accordion.js +65 -5
- package/dist/alert-dialog/index.js +14 -4
- package/dist/audio-player/index.d.ts +2 -0
- package/dist/audio-player/index.js +131 -34
- package/dist/breadcrumb/index.js +4 -1
- package/dist/calendar/index.d.ts +4 -1
- package/dist/calendar/index.js +37 -12
- package/dist/calendar/locales.d.ts +7 -0
- package/dist/calendar/locales.js +2 -0
- package/dist/carousel/index.js +20 -10
- package/dist/choice-grid/index.d.ts +6 -1
- package/dist/choice-grid/index.js +63 -33
- package/dist/color-picker/GradientEditor.d.ts +40 -0
- package/dist/color-picker/GradientEditor.js +64 -0
- package/dist/color-picker/gradient.d.ts +25 -0
- package/dist/color-picker/gradient.js +60 -0
- package/dist/color-picker/index.d.ts +27 -7
- package/dist/color-picker/index.js +144 -40
- package/dist/color-swatch/index.d.ts +10 -0
- package/dist/color-swatch/index.js +9 -0
- package/dist/command/index.js +27 -4
- package/dist/content/index.d.ts +5 -0
- package/dist/content/index.js +15 -3
- package/dist/context-menu/index.js +1 -1
- package/dist/data-table/index.d.ts +7 -1
- package/dist/data-table/index.js +67 -16
- package/dist/data-table/model.d.ts +2 -0
- package/dist/data-table/model.js +7 -0
- package/dist/date-picker/index.d.ts +2 -0
- package/dist/date-picker/index.js +20 -21
- package/dist/day-agenda/index.d.ts +6 -1
- package/dist/day-agenda/index.js +49 -21
- package/dist/day-agenda/layout.js +65 -9
- package/dist/display/ProgressRing.js +7 -3
- package/dist/display/index.d.ts +4 -1
- package/dist/display/index.js +3 -2
- package/dist/dockable-panel/index.js +15 -13
- package/dist/drawer/index.js +11 -5
- package/dist/editable/index.d.ts +24 -2
- package/dist/editable/index.js +148 -40
- package/dist/empty/illustrations.d.ts +8 -0
- package/dist/empty/illustrations.js +78 -0
- package/dist/empty/index.d.ts +6 -1
- package/dist/empty/index.js +11 -3
- package/dist/flip/index.js +28 -8
- package/dist/form/Field.js +11 -10
- package/dist/form/Input.js +4 -1
- package/dist/form/Textarea.d.ts +3 -1
- package/dist/form/Textarea.js +9 -5
- package/dist/heatmap/index.js +4 -1
- package/dist/hotkey/index.d.ts +1 -0
- package/dist/hotkey/index.js +9 -4
- package/dist/hover-card/index.js +1 -1
- package/dist/i18n/catalog.d.ts +5 -0
- package/dist/i18n/catalog.js +289 -0
- package/dist/i18n/index.d.ts +23 -0
- package/dist/i18n/index.js +50 -0
- package/dist/image-cropper/index.js +21 -17
- package/dist/image-viewer/VideoResource.d.ts +21 -0
- package/dist/image-viewer/VideoResource.js +196 -0
- package/dist/image-viewer/index.d.ts +14 -0
- package/dist/image-viewer/index.js +141 -86
- package/dist/image-viewer/lucideGeometry.d.ts +3 -0
- package/dist/image-viewer/lucideGeometry.js +19 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/input-number/index.js +13 -5
- package/dist/internal/cache.d.ts +4 -0
- package/dist/internal/cache.js +18 -1
- package/dist/internal/iconGeometry.d.ts +2 -0
- package/dist/internal/iconGeometry.js +32 -0
- package/dist/internal/renderTokens.d.ts +4 -0
- package/dist/internal/renderTokens.js +4 -0
- package/dist/internal/stroke.d.ts +7 -0
- package/dist/internal/stroke.js +63 -7
- package/dist/internal/surfaceBudget.d.ts +20 -0
- package/dist/internal/surfaceBudget.js +69 -0
- package/dist/internal/surfaceRenderer.js +87 -28
- package/dist/internal/surfaceVisibility.d.ts +3 -0
- package/dist/internal/surfaceVisibility.js +60 -0
- package/dist/internal/textRenderer.d.ts +3 -0
- package/dist/internal/textRenderer.js +13 -5
- package/dist/internal/usePencilSurface.js +17 -1
- package/dist/internal/wash.js +4 -3
- package/dist/journey-map/index.d.ts +1 -1
- package/dist/journey-map/index.js +6 -3
- package/dist/journey-map/terrain.js +10 -5
- package/dist/keypad/index.js +4 -1
- package/dist/layout/index.js +1 -1
- package/dist/log/index.js +42 -33
- package/dist/markdown/CodeBlock.d.ts +19 -0
- package/dist/markdown/CodeBlock.js +86 -0
- package/dist/markdown/Deletion.d.ts +2 -0
- package/dist/markdown/Deletion.js +69 -0
- package/dist/markdown/icons.d.ts +14 -0
- package/dist/markdown/icons.js +16 -0
- package/dist/markdown/index.d.ts +17 -0
- package/dist/markdown/index.js +177 -0
- package/dist/node-graph/index.d.ts +3 -1
- package/dist/node-graph/index.js +63 -28
- package/dist/number-ticker/index.js +4 -1
- package/dist/number-ticker/precision.js +9 -2
- package/dist/overflow-list/index.js +9 -6
- package/dist/overlay/Overlay.js +8 -3
- package/dist/overlay/Popover.js +3 -3
- package/dist/overlay/themeBridge.js +14 -2
- package/dist/pagination/index.d.ts +10 -1
- package/dist/pagination/index.js +13 -9
- package/dist/pan-zoom/index.d.ts +2 -0
- package/dist/pan-zoom/index.js +12 -8
- package/dist/pencil.d.ts +1 -0
- package/dist/pencil.js +4 -4
- package/dist/radar/index.d.ts +2 -0
- package/dist/radar/index.js +44 -21
- package/dist/range-slider/index.js +4 -3
- package/dist/rating/index.d.ts +4 -0
- package/dist/rating/index.js +131 -24
- package/dist/resizable/index.d.ts +1 -1
- package/dist/resizable/index.js +5 -2
- package/dist/scroll-area/index.d.ts +1 -1
- package/dist/scroll-area/index.js +5 -2
- package/dist/select/MultipleSelect.js +5 -4
- package/dist/select/SearchableSelect.js +64 -7
- package/dist/select/Select.d.ts +1 -0
- package/dist/select/Select.js +6 -2
- package/dist/select/StepperSelect.d.ts +29 -0
- package/dist/select/StepperSelect.js +122 -0
- package/dist/select/index.d.ts +2 -0
- package/dist/select/index.js +1 -0
- package/dist/select/stepperModel.d.ts +7 -0
- package/dist/select/stepperModel.js +29 -0
- package/dist/selection/Checkbox.js +4 -3
- package/dist/selection/RadioGroup.js +29 -13
- package/dist/selection/Switch.js +4 -3
- package/dist/selection/useNativeChecked.d.ts +2 -1
- package/dist/selection/useNativeChecked.js +26 -8
- package/dist/slider/index.js +50 -6
- package/dist/sortable/index.js +17 -11
- package/dist/sparkline/index.js +21 -6
- package/dist/sparkline/model.js +11 -4
- package/dist/steps/index.js +4 -1
- package/dist/styles/accordion.css +228 -0
- package/dist/styles/alert-dialog.css +139 -0
- package/dist/styles/audio-player.css +22 -0
- package/dist/styles/base.css +638 -0
- package/dist/styles/breadcrumb.css +7 -0
- package/dist/styles/calendar.css +62 -0
- package/dist/styles/carousel.css +173 -0
- package/dist/styles/choice-grid.css +226 -0
- package/dist/styles/color-picker.css +281 -0
- package/dist/styles/color-swatch.css +47 -0
- package/dist/styles/command.css +52 -0
- package/dist/styles/content.css +79 -0
- package/dist/styles/context-menu.css +35 -0
- package/dist/styles/data-table.css +31 -0
- package/dist/styles/date-picker.css +107 -0
- package/dist/styles/day-agenda.css +30 -0
- package/dist/styles/display.css +304 -0
- package/dist/styles/dockable-panel.css +110 -0
- package/dist/styles/drawer.css +133 -0
- package/dist/styles/dropdown-menu.css +68 -0
- package/dist/styles/editable.css +145 -0
- package/dist/styles/empty.css +108 -0
- package/dist/styles/flip.css +101 -0
- package/dist/styles/form.css +634 -0
- package/dist/styles/heatmap.css +256 -0
- package/dist/styles/hotkey.css +189 -0
- package/dist/styles/hover-card.css +27 -0
- package/dist/styles/image-cropper.css +205 -0
- package/dist/styles/image-viewer.css +422 -0
- package/dist/styles/input-number.css +21 -0
- package/dist/styles/journey-map.css +19 -0
- package/dist/styles/keypad.css +92 -0
- package/dist/styles/layout.css +72 -0
- package/dist/styles/leaflet.css +661 -0
- package/dist/styles/log.css +203 -0
- package/dist/styles/markdown.css +379 -0
- package/dist/styles/node-graph.css +142 -0
- package/dist/styles/number-ticker.css +83 -0
- package/dist/styles/overflow-list.css +51 -0
- package/dist/styles/overlay.css +284 -0
- package/dist/styles/pagination.css +21 -0
- package/dist/styles/pan-zoom.css +136 -0
- package/dist/styles/radar.css +1 -0
- package/dist/styles/rating.css +121 -0
- package/dist/styles/resizable.css +108 -0
- package/dist/styles/scroll-area.css +122 -0
- package/dist/styles/select.css +554 -0
- package/dist/styles/selection.css +264 -0
- package/dist/styles/slider.css +145 -0
- package/dist/styles/slot.css +109 -0
- package/dist/styles/sortable.css +159 -0
- package/dist/styles/sparkline.css +24 -0
- package/dist/styles/steps.css +27 -0
- package/dist/styles/tabs.css +79 -0
- package/dist/styles/tag-input.css +213 -0
- package/dist/styles/time-picker.css +66 -0
- package/dist/styles/timeline.css +24 -0
- package/dist/styles/toast.css +160 -0
- package/dist/styles/toggle-group.css +107 -0
- package/dist/styles/tooltip.css +78 -0
- package/dist/styles/transfer.css +148 -0
- package/dist/styles/tree-select.css +131 -0
- package/dist/styles/tree.css +59 -0
- package/dist/styles/typewriter.css +62 -0
- package/dist/styles/upload.css +60 -0
- package/dist/styles/virtual-list.css +112 -0
- package/dist/styles.css +2091 -421
- package/dist/tabs/index.d.ts +9 -0
- package/dist/tabs/index.js +69 -16
- package/dist/tabs/useOverflow.d.ts +20 -0
- package/dist/tabs/useOverflow.js +145 -0
- package/dist/tag-input/index.d.ts +7 -2
- package/dist/tag-input/index.js +81 -42
- package/dist/theme/index.d.ts +11 -0
- package/dist/theme/index.js +23 -0
- package/dist/time-picker/index.js +45 -27
- package/dist/timeline/index.js +4 -1
- package/dist/toast/index.d.ts +19 -11
- package/dist/toast/index.js +51 -8
- package/dist/toast/manager.d.ts +42 -0
- package/dist/toast/manager.js +85 -0
- package/dist/toast/useToastNavigation.d.ts +3 -0
- package/dist/toast/useToastNavigation.js +75 -0
- package/dist/tokens.css +4 -0
- package/dist/tooltip/Tooltip.js +3 -3
- package/dist/transfer/index.d.ts +5 -0
- package/dist/transfer/index.js +120 -53
- package/dist/transfer/model.d.ts +4 -0
- package/dist/transfer/model.js +37 -11
- package/dist/tree/index.d.ts +5 -1
- package/dist/tree/index.js +11 -6
- package/dist/tree-select/index.js +70 -31
- package/dist/tree-select/model.d.ts +11 -5
- package/dist/tree-select/model.js +111 -83
- package/dist/typewriter/index.js +10 -10
- package/dist/upload/index.js +50 -33
- package/dist/virtual-list/index.js +11 -6
- package/package.json +423 -3
|
@@ -43,7 +43,7 @@ export const AlertDialogContent = forwardRef(function AlertDialogContent({ child
|
|
|
43
43
|
const contentRef = useRef(null);
|
|
44
44
|
const id = useId();
|
|
45
45
|
props = usePortalThemeProps(props);
|
|
46
|
-
return (
|
|
46
|
+
return (_jsxs(DialogPrimitive.Content, { ...props, role: "alertdialog", "aria-busy": context?.pending || undefined, className: classes('inkra-alert-dialog__content', className), "data-pending": context?.pending || undefined, ref: node => {
|
|
47
47
|
contentRef.current = node;
|
|
48
48
|
assignRef(forwardedRef, node);
|
|
49
49
|
}, onEscapeKeyDown: event => {
|
|
@@ -61,8 +61,18 @@ export const AlertDialogContent = forwardRef(function AlertDialogContent({ child
|
|
|
61
61
|
if (event.defaultPrevented)
|
|
62
62
|
return;
|
|
63
63
|
event.preventDefault();
|
|
64
|
-
queueMicrotask(() =>
|
|
65
|
-
|
|
64
|
+
queueMicrotask(() => {
|
|
65
|
+
const cancel = context?.cancelRef.current;
|
|
66
|
+
const content = contentRef.current;
|
|
67
|
+
const ownerDocument = content?.ownerDocument;
|
|
68
|
+
if (cancel && !cancel.disabled && cancel.isConnected)
|
|
69
|
+
cancel.focus({ preventScroll: true });
|
|
70
|
+
if (ownerDocument?.activeElement === cancel)
|
|
71
|
+
return;
|
|
72
|
+
if (content?.isConnected)
|
|
73
|
+
content.focus({ preventScroll: true });
|
|
74
|
+
});
|
|
75
|
+
}, children: [_jsx(PencilSurface, { "aria-hidden": "true", as: "span", className: "inkra-alert-dialog__focus-ring", radius: 8, seed: `${seed ?? `inkra-alert-dialog-${id}`}-focus`, variant: "outline" }), _jsx(PencilSurface, { className: "inkra-alert-dialog__surface", radius: 8, seed: seed ?? `inkra-alert-dialog-${id}`, variant: "paper", children: _jsx("div", { className: "inkra-alert-dialog__layout", children: children }) })] }));
|
|
66
76
|
});
|
|
67
77
|
export const AlertDialogTitle = forwardRef(function AlertDialogTitle({ children, className, seed, ...props }, ref) {
|
|
68
78
|
const id = useId();
|
|
@@ -95,7 +105,7 @@ export const AlertDialogAction = forwardRef(function AlertDialogAction({ closeOn
|
|
|
95
105
|
const button = (_jsx(Button, { ...props, "aria-busy": pending || undefined, disabled: disabled || pending, loading: loading || pending, ref: ref, type: "button", variant: variant }));
|
|
96
106
|
return closeOnAction ? _jsx(DialogPrimitive.Close, { asChild: true, children: button }) : button;
|
|
97
107
|
});
|
|
98
|
-
export const AlertDialog = Object.freeze({
|
|
108
|
+
export const AlertDialog = /* @__PURE__ */ Object.freeze({
|
|
99
109
|
Root: AlertDialogRoot,
|
|
100
110
|
Trigger: AlertDialogTrigger,
|
|
101
111
|
Portal: AlertDialogPortal,
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useInkraLocale } from '../i18n/index.js';
|
|
2
3
|
import { forwardRef, useCallback, useEffect, useId, useImperativeHandle, useRef, useState } from 'react';
|
|
3
4
|
import { Button } from '../Button.js';
|
|
4
5
|
import { PencilIcon } from '../PencilIcon.js';
|
|
5
6
|
import { PencilSurface } from '../PencilSurface.js';
|
|
6
7
|
import { PencilText } from '../PencilText.js';
|
|
7
|
-
import {
|
|
8
|
+
import { SelectRoot, SelectTrigger, SelectValue, SelectPortal, SelectContent, SelectViewport, SelectItem, SelectItemText, SelectItemIndicator } from '../select/Select.js';
|
|
8
9
|
import { Slider } from '../slider/index.js';
|
|
9
10
|
const pausePaths = [
|
|
10
11
|
{ d: 'M 15 3 H 18 A 1 1 0 0 1 19 4 V 20 A 1 1 0 0 1 18 21 H 15 A 1 1 0 0 1 14 20 V 4 A 1 1 0 0 1 15 3 Z' },
|
|
@@ -21,27 +22,40 @@ const mutePaths = [
|
|
|
21
22
|
{ d: 'm16.5 14.5 5-5' },
|
|
22
23
|
{ d: 'm16.5 9.5 5 5' },
|
|
23
24
|
];
|
|
24
|
-
const DEFAULT_LABELS = {
|
|
25
|
-
play: '
|
|
26
|
-
};
|
|
25
|
+
const DEFAULT_LABELS = (t) => ({
|
|
26
|
+
controls: t('audio.controls'), audio: t('audio.label'), play: t('audio.play'), pause: t('audio.pause'), mute: t('audio.mute'), unmute: t('audio.unmute'), seek: t('audio.seek'), volume: t('audio.volume'), rate: t('audio.rate'), loading: t('audio.loading'), blocked: t('audio.blocked'), error: t('audio.error'),
|
|
27
|
+
});
|
|
27
28
|
const rates = [0.75, 1, 1.25, 1.5, 2];
|
|
28
29
|
function clamp(value, min, max) { return Number.isFinite(value) ? Math.min(max, Math.max(min, value)) : min; }
|
|
29
30
|
function formatTime(value) { if (!Number.isFinite(value) || value < 0)
|
|
30
31
|
return '0:00'; const seconds = Math.floor(value); return `${Math.floor(seconds / 60)}:${String(seconds % 60).padStart(2, '0')}`; }
|
|
31
32
|
export const AudioPlayer = forwardRef(function AudioPlayer({ src, preload = 'metadata', autoPlay = false, crossOrigin, className, defaultMuted = false, defaultPlaybackRate = 1, defaultVolume = 1, volume, muted, playbackRate, onVolumeChange, onMutedChange, onPlaybackRateChange, onTimeChange, onStateChange, onError, labels, renderControls, audioProps, ...props }, forwardedRef) {
|
|
33
|
+
const { t } = useInkraLocale();
|
|
32
34
|
const id = useId();
|
|
33
35
|
const audioRef = useRef(null);
|
|
34
36
|
const rootRef = useRef(null);
|
|
37
|
+
const sourceVersionRef = useRef(0);
|
|
38
|
+
const requestedSourceRef = useRef(src);
|
|
39
|
+
requestedSourceRef.current = src;
|
|
35
40
|
const [state, setState] = useState({ status: src ? 'loading' : 'idle', currentTime: 0, duration: 0, buffered: 0, volume: clamp(volume ?? defaultVolume, 0, 1), muted: muted ?? defaultMuted, playbackRate: clamp(playbackRate ?? defaultPlaybackRate, 0.25, 4), canPlay: false, error: null });
|
|
36
41
|
const stateRef = useRef(state);
|
|
37
42
|
stateRef.current = state;
|
|
38
|
-
const
|
|
43
|
+
const controlledRef = useRef({ volume, muted, playbackRate });
|
|
44
|
+
controlledRef.current = { volume, muted, playbackRate };
|
|
45
|
+
const callbacksRef = useRef({ onVolumeChange, onMutedChange, onPlaybackRateChange, onTimeChange, onStateChange, onError });
|
|
46
|
+
callbacksRef.current = { onVolumeChange, onMutedChange, onPlaybackRateChange, onTimeChange, onStateChange, onError };
|
|
47
|
+
const mergedLabels = { ...DEFAULT_LABELS(t), ...labels };
|
|
39
48
|
const update = useCallback((patch) => {
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
49
|
+
const current = stateRef.current;
|
|
50
|
+
const changed = Object.keys(patch).some(key => !Object.is(current[key], patch[key]));
|
|
51
|
+
const next = changed ? { ...current, ...patch } : current;
|
|
52
|
+
if (changed) {
|
|
53
|
+
stateRef.current = next;
|
|
54
|
+
setState(next);
|
|
55
|
+
}
|
|
56
|
+
// Native events remain observable without rendering identical media snapshots.
|
|
57
|
+
callbacksRef.current.onStateChange?.(next);
|
|
58
|
+
}, []);
|
|
45
59
|
const sync = useCallback(() => {
|
|
46
60
|
const audio = audioRef.current;
|
|
47
61
|
if (!audio)
|
|
@@ -49,14 +63,42 @@ export const AudioPlayer = forwardRef(function AudioPlayer({ src, preload = 'met
|
|
|
49
63
|
const buffered = audio.duration > 0 && audio.buffered.length ? audio.buffered.end(audio.buffered.length - 1) / audio.duration : 0;
|
|
50
64
|
update({ currentTime: Number.isFinite(audio.currentTime) ? audio.currentTime : 0, duration: Number.isFinite(audio.duration) ? audio.duration : 0, buffered: clamp(buffered, 0, 1), volume: audio.volume, muted: audio.muted, playbackRate: audio.playbackRate, canPlay: audio.readyState >= 3 });
|
|
51
65
|
}, [update]);
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
66
|
+
const applyControlledSettings = useCallback(() => {
|
|
67
|
+
const audio = audioRef.current;
|
|
68
|
+
if (!audio)
|
|
69
|
+
return;
|
|
70
|
+
const controlled = controlledRef.current;
|
|
71
|
+
if (controlled.volume !== undefined) {
|
|
72
|
+
const value = clamp(controlled.volume, 0, 1);
|
|
73
|
+
if (audio.volume !== value)
|
|
74
|
+
audio.volume = value;
|
|
75
|
+
}
|
|
76
|
+
if (controlled.muted !== undefined && audio.muted !== controlled.muted)
|
|
77
|
+
audio.muted = controlled.muted;
|
|
78
|
+
if (controlled.playbackRate !== undefined) {
|
|
79
|
+
const value = clamp(controlled.playbackRate, 0.25, 4);
|
|
80
|
+
if (audio.defaultPlaybackRate !== value)
|
|
81
|
+
audio.defaultPlaybackRate = value;
|
|
82
|
+
if (audio.playbackRate !== value)
|
|
83
|
+
audio.playbackRate = value;
|
|
84
|
+
}
|
|
85
|
+
}, []);
|
|
86
|
+
const play = useCallback(async () => {
|
|
87
|
+
const audio = audioRef.current;
|
|
88
|
+
if (!audio)
|
|
89
|
+
return;
|
|
90
|
+
const sourceVersion = sourceVersionRef.current;
|
|
91
|
+
try {
|
|
92
|
+
await audio.play();
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
// Loading another source or unmounting can reject an earlier play request.
|
|
96
|
+
if (audioRef.current !== audio || sourceVersionRef.current !== sourceVersion)
|
|
97
|
+
return;
|
|
98
|
+
update({ status: 'blocked' });
|
|
99
|
+
callbacksRef.current.onError?.(error instanceof DOMException ? error : null);
|
|
100
|
+
}
|
|
101
|
+
}, [update]);
|
|
60
102
|
const pause = useCallback(() => { audioRef.current?.pause(); }, []);
|
|
61
103
|
const togglePlay = useCallback(async () => { const audio = audioRef.current; if (!audio)
|
|
62
104
|
return; if (audio.paused)
|
|
@@ -64,38 +106,93 @@ export const AudioPlayer = forwardRef(function AudioPlayer({ src, preload = 'met
|
|
|
64
106
|
else
|
|
65
107
|
pause(); }, [pause, play]);
|
|
66
108
|
const seek = useCallback((time) => { const audio = audioRef.current; if (!audio || !Number.isFinite(time))
|
|
67
|
-
return; audio.currentTime = clamp(time, 0, Number.isFinite(audio.duration) ? audio.duration : Math.max(0, time)); sync(); onTimeChange?.(audio.currentTime); }, [
|
|
68
|
-
const setVolume = useCallback((next) => {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
109
|
+
return; audio.currentTime = clamp(time, 0, Number.isFinite(audio.duration) ? audio.duration : Math.max(0, time)); sync(); callbacksRef.current.onTimeChange?.(audio.currentTime); }, [sync]);
|
|
110
|
+
const setVolume = useCallback((next) => {
|
|
111
|
+
const audio = audioRef.current;
|
|
112
|
+
if (!audio)
|
|
113
|
+
return;
|
|
114
|
+
const value = clamp(next, 0, 1);
|
|
115
|
+
const current = controlledRef.current.volume ?? stateRef.current.volume;
|
|
116
|
+
if (value === current)
|
|
117
|
+
return;
|
|
118
|
+
if (controlledRef.current.volume === undefined) {
|
|
119
|
+
audio.volume = value;
|
|
120
|
+
sync();
|
|
121
|
+
}
|
|
122
|
+
callbacksRef.current.onVolumeChange?.(value);
|
|
123
|
+
}, [sync]);
|
|
124
|
+
const setMuted = useCallback((next) => {
|
|
125
|
+
const audio = audioRef.current;
|
|
126
|
+
if (!audio)
|
|
127
|
+
return;
|
|
128
|
+
const current = controlledRef.current.muted ?? stateRef.current.muted;
|
|
129
|
+
if (next === current)
|
|
130
|
+
return;
|
|
131
|
+
if (controlledRef.current.muted === undefined) {
|
|
132
|
+
audio.muted = next;
|
|
133
|
+
sync();
|
|
134
|
+
}
|
|
135
|
+
callbacksRef.current.onMutedChange?.(next);
|
|
136
|
+
}, [sync]);
|
|
137
|
+
const setPlaybackRate = useCallback((next) => {
|
|
138
|
+
const audio = audioRef.current;
|
|
139
|
+
if (!audio)
|
|
140
|
+
return;
|
|
141
|
+
const value = clamp(next, 0.25, 4);
|
|
142
|
+
const current = controlledRef.current.playbackRate ?? stateRef.current.playbackRate;
|
|
143
|
+
if (value === current)
|
|
144
|
+
return;
|
|
145
|
+
if (controlledRef.current.playbackRate === undefined) {
|
|
146
|
+
audio.defaultPlaybackRate = value;
|
|
147
|
+
audio.playbackRate = value;
|
|
148
|
+
sync();
|
|
149
|
+
}
|
|
150
|
+
callbacksRef.current.onPlaybackRateChange?.(value);
|
|
151
|
+
}, [sync]);
|
|
74
152
|
const focus = useCallback(() => { rootRef.current?.querySelector('[data-audio-player-control]')?.focus(); }, []);
|
|
75
153
|
const controls = { audio: audioRef.current, state, play, pause, togglePlay, seek, setVolume, setMuted, setPlaybackRate, focus };
|
|
76
154
|
useImperativeHandle(forwardedRef, () => controls, [state, play, pause, togglePlay, seek, setVolume, setMuted, setPlaybackRate, focus]);
|
|
77
|
-
useEffect(() => { const audio = audioRef.current; if (!audio)
|
|
78
|
-
return; audio.volume = clamp(volume ?? defaultVolume, 0, 1); audio.muted = muted ?? defaultMuted; audio.playbackRate = clamp(playbackRate ?? defaultPlaybackRate, 0.25, 4); sync(); }, [defaultMuted, defaultPlaybackRate, defaultVolume, muted, playbackRate, src, volume, sync]);
|
|
79
155
|
useEffect(() => {
|
|
156
|
+
sourceVersionRef.current += 1;
|
|
80
157
|
const audio = audioRef.current;
|
|
81
|
-
if (!audio
|
|
158
|
+
if (!audio)
|
|
82
159
|
return;
|
|
83
160
|
audio.pause();
|
|
84
|
-
|
|
85
|
-
|
|
161
|
+
try {
|
|
162
|
+
audio.currentTime = 0;
|
|
163
|
+
}
|
|
164
|
+
catch { /* Empty media has no seekable timeline. */ }
|
|
165
|
+
if (!src)
|
|
166
|
+
audio.removeAttribute('src');
|
|
167
|
+
update({ status: src ? 'loading' : 'idle', currentTime: 0, duration: 0, buffered: 0, canPlay: false, error: null });
|
|
168
|
+
const current = stateRef.current;
|
|
169
|
+
audio.volume = clamp(controlledRef.current.volume ?? current.volume, 0, 1);
|
|
170
|
+
audio.muted = controlledRef.current.muted ?? current.muted;
|
|
171
|
+
const rate = clamp(controlledRef.current.playbackRate ?? current.playbackRate, 0.25, 4);
|
|
172
|
+
audio.defaultPlaybackRate = rate;
|
|
173
|
+
audio.playbackRate = rate;
|
|
86
174
|
audio.load();
|
|
87
175
|
}, [src, update]);
|
|
176
|
+
useEffect(() => { applyControlledSettings(); sync(); }, [applyControlledSettings, muted, playbackRate, sync, volume]);
|
|
88
177
|
useEffect(() => {
|
|
89
178
|
const audio = audioRef.current;
|
|
90
179
|
if (!audio)
|
|
91
180
|
return;
|
|
181
|
+
const hasSource = () => Boolean(requestedSourceRef.current || audio.getAttribute('src'));
|
|
92
182
|
const handlers = {
|
|
93
|
-
loadstart: () => update({ status: 'loading', error: null, canPlay: false }), loadedmetadata: () => { sync(); update({ status: 'ready' }); }, canplay: () => { sync(); update({ canPlay: true, status: audio.paused ? 'paused' : 'playing' }); }, play: () => update({ status: 'playing' }), pause: () => update({ status: audio.ended ? 'ended' : 'paused' }), playing: () => update({ status: 'playing', canPlay: true }), waiting: () => update({ status: 'buffering' }), progress: sync, timeupdate: () => { sync(); onTimeChange?.(audio.currentTime); }, ratechange:
|
|
183
|
+
loadstart: () => update({ status: hasSource() ? 'loading' : 'idle', error: null, canPlay: false }), loadedmetadata: () => { applyControlledSettings(); sync(); update({ status: 'ready' }); }, canplay: () => { sync(); update({ canPlay: true, status: audio.paused ? 'paused' : 'playing' }); }, play: () => update({ status: 'playing' }), pause: () => update({ status: hasSource() ? (audio.ended ? 'ended' : 'paused') : 'idle' }), playing: () => update({ status: 'playing', canPlay: true }), waiting: () => update({ status: 'buffering' }), progress: sync, timeupdate: () => { sync(); callbacksRef.current.onTimeChange?.(audio.currentTime); }, ratechange: () => { const previous = stateRef.current.playbackRate; applyControlledSettings(); sync(); if (controlledRef.current.playbackRate === undefined && previous !== audio.playbackRate)
|
|
184
|
+
callbacksRef.current.onPlaybackRateChange?.(audio.playbackRate); }, volumechange: () => { const previous = stateRef.current; applyControlledSettings(); sync(); if (controlledRef.current.volume === undefined && previous.volume !== audio.volume)
|
|
185
|
+
callbacksRef.current.onVolumeChange?.(audio.volume); if (controlledRef.current.muted === undefined && previous.muted !== audio.muted)
|
|
186
|
+
callbacksRef.current.onMutedChange?.(audio.muted); }, ended: () => update({ status: 'ended', currentTime: audio.duration || audio.currentTime }), error: () => { if (!hasSource()) {
|
|
187
|
+
update({ status: 'idle', error: null, canPlay: false });
|
|
188
|
+
return;
|
|
189
|
+
} const error = audio.error; update({ status: 'error', error }); callbacksRef.current.onError?.(error); }, abort: () => update({ status: 'idle' }), emptied: () => { if (!hasSource())
|
|
190
|
+
update({ status: 'idle', currentTime: 0, duration: 0, buffered: 0, canPlay: false, error: null }); },
|
|
94
191
|
};
|
|
95
192
|
Object.entries(handlers).forEach(([name, handler]) => audio.addEventListener(name, handler));
|
|
96
193
|
return () => Object.entries(handlers).forEach(([name, handler]) => audio.removeEventListener(name, handler));
|
|
97
|
-
}, [
|
|
194
|
+
}, [applyControlledSettings, sync, update]);
|
|
98
195
|
const playLabel = state.status === 'playing' ? mergedLabels.pause : mergedLabels.play;
|
|
99
|
-
const defaultControls = _jsxs("div", { className: "inkra-audio-player__controls", role: "group", "aria-label":
|
|
100
|
-
return _jsxs(PencilSurface, { ref: rootRef, ...props, className: ['inkra-audio-player', className].filter(Boolean).join(' '), variant: "paper", radius: 8, "data-status": state.status, "data-audio-player-id": id, children: [_jsx("audio", { ...audioProps, ref: audioRef, "aria-label": audioProps?.['aria-label'] ??
|
|
196
|
+
const defaultControls = _jsxs("div", { className: "inkra-audio-player__controls", role: "group", "aria-label": mergedLabels.controls, children: [_jsx(Button, { "data-audio-player-control": true, "data-tooltip": playLabel, type: "button", size: "icon-sm", variant: "outline", "aria-label": playLabel, onClick: () => void togglePlay(), children: _jsx(PencilIcon, { paths: state.status === 'playing' ? pausePaths : playPaths, size: 20, sourceSize: 24 }) }), _jsx("div", { className: "inkra-audio-player__seek", children: _jsx(Slider, { "data-audio-player-control": true, min: 0, max: state.duration || 0, step: 0.01, value: [state.currentTime], onValueChange: values => seek(values[0] ?? 0), disabled: !state.duration, "aria-label": mergedLabels.seek, getValueLabel: value => labels?.time?.(value, state.duration) ?? `${formatTime(value)} / ${formatTime(state.duration)}`, size: "sm", variant: "inset" }) }), _jsx(PencilText, { as: "span", className: "inkra-audio-player__time", "aria-live": "off", children: labels?.time?.(state.currentTime, state.duration) ?? `${formatTime(state.currentTime)} / ${formatTime(state.duration)}` }), _jsxs("div", { className: "inkra-audio-player__settings", children: [_jsx(Button, { "data-audio-player-control": true, "data-tooltip": state.muted ? mergedLabels.unmute : mergedLabels.mute, type: "button", size: "icon-sm", variant: "ghost", "aria-label": state.muted ? mergedLabels.unmute : mergedLabels.mute, onClick: () => setMuted(!state.muted), children: _jsx(PencilIcon, { paths: state.muted ? mutePaths : volumePaths, size: 20, sourceSize: 24 }) }), _jsx("div", { className: "inkra-audio-player__volume", children: _jsx(Slider, { "data-audio-player-control": true, min: 0, max: 1, step: 0.01, value: [state.muted ? 0 : state.volume], onValueChange: values => { setMuted(false); setVolume(values[0] ?? 0); }, "aria-label": mergedLabels.volume, getValueLabel: value => `${Math.round(value * 100)}%`, size: "sm", variant: "inset" }) }), _jsxs(SelectRoot, { value: String(state.playbackRate), onValueChange: value => setPlaybackRate(Number(value)), children: [_jsx(SelectTrigger, { "data-audio-player-control": true, "aria-label": mergedLabels.rate, fullWidth: false, size: "sm", children: _jsx(SelectValue, {}) }), _jsx(SelectPortal, { children: _jsx(SelectContent, { position: "popper", sideOffset: 4, children: _jsx(SelectViewport, { children: rates.map(rate => _jsxs(SelectItem, { value: String(rate), children: [_jsx(SelectItemText, { children: `${rate}x` }), _jsx(SelectItemIndicator, {})] }, rate)) }) }) })] })] })] });
|
|
197
|
+
return _jsxs(PencilSurface, { ref: rootRef, ...props, className: ['inkra-audio-player', className].filter(Boolean).join(' '), variant: "paper", radius: 8, "data-status": state.status, "data-audio-player-id": id, children: [_jsx("audio", { ...audioProps, ref: audioRef, "aria-label": audioProps?.['aria-label'] ?? mergedLabels.audio, autoPlay: autoPlay, crossOrigin: crossOrigin, muted: state.muted, preload: preload, src: src }), state.status === 'blocked' && _jsx(PencilText, { as: "p", className: "inkra-audio-player__message", role: "status", children: mergedLabels.blocked }), state.status === 'error' && _jsx(PencilText, { as: "p", className: "inkra-audio-player__message", role: "alert", children: mergedLabels.error }), renderControls ? renderControls(controls) : defaultControls] });
|
|
101
198
|
});
|
package/dist/breadcrumb/index.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useInkraLocale } from '../i18n/index.js';
|
|
2
3
|
import { forwardRef } from 'react';
|
|
3
4
|
import { ButtonLink } from '../Button.js';
|
|
4
5
|
import { PencilText } from '../PencilText.js';
|
|
5
6
|
const classes = (...values) => values.filter(Boolean).join(' ');
|
|
6
7
|
const copy = (children) => typeof children === 'string' ? _jsx(PencilText, { children: children }) : children;
|
|
7
|
-
const Root = forwardRef(function BreadcrumbRoot(
|
|
8
|
+
const Root = forwardRef(function BreadcrumbRoot(inkraProps, ref) {
|
|
9
|
+
const { t } = useInkraLocale();
|
|
10
|
+
const { className, 'aria-label': label = t('breadcrumb.label'), ...props } = inkraProps;
|
|
8
11
|
return _jsx("nav", { ...props, ref: ref, "aria-label": label, className: classes('inkra-breadcrumb', className) });
|
|
9
12
|
});
|
|
10
13
|
const List = forwardRef(function BreadcrumbList({ className, ...props }, ref) {
|
package/dist/calendar/index.d.ts
CHANGED
|
@@ -17,5 +17,8 @@ export type CalendarProps = DayPickerProps & {
|
|
|
17
17
|
from: Date;
|
|
18
18
|
to: Date;
|
|
19
19
|
};
|
|
20
|
+
/** Opt-in itinerary summary; every event can be revealed for its day. */
|
|
21
|
+
maxEventsPerDay?: number;
|
|
22
|
+
showDayEventsLabel?: (date: Date, total: number, expanded: boolean) => string;
|
|
20
23
|
};
|
|
21
|
-
export declare function Calendar({ events, eventIndicator, appearance, onEventSelect, renderDayAccessory, displayRange, className, components, showOutsideDays, ...props }: CalendarProps): import("react").JSX.Element;
|
|
24
|
+
export declare function Calendar({ events, eventIndicator, appearance, onEventSelect, renderDayAccessory, displayRange, maxEventsPerDay, showDayEventsLabel, className, components, showOutsideDays, ...props }: CalendarProps): import("react").JSX.Element;
|
package/dist/calendar/index.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { useInkraLocale, resolveInkraLocale } from '../i18n/index.js';
|
|
3
|
+
import { calendarLocales } from './locales.js';
|
|
4
|
+
import { createContext, useContext, useMemo, useState } from 'react';
|
|
3
5
|
import { CaptionLabel, Day, DayButton, DayPicker, Weekday } from 'react-day-picker';
|
|
4
6
|
import { Button } from '../Button.js';
|
|
5
7
|
import { PencilIcon } from '../PencilIcon.js';
|
|
6
8
|
import { PencilSurface } from '../PencilSurface.js';
|
|
7
9
|
import { PencilText } from '../PencilText.js';
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
|
|
10
|
+
const emptyEvents = [];
|
|
11
|
+
const EventContext = createContext({ eventsByDay: new Map(), indicator: 'count', appearance: 'itinerary' });
|
|
12
|
+
// Match local calendar dates, not UTC dates, and retain input order within each day.
|
|
13
|
+
const dayKey = (date) => `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`;
|
|
11
14
|
function CalendarCaption({ children, ...props }) {
|
|
12
15
|
return _jsx(CaptionLabel, { ...props, children: typeof children === 'string' ? _jsx(PencilText, { children: children }) : children });
|
|
13
16
|
}
|
|
@@ -24,18 +27,40 @@ function CalendarChevron({ className, orientation = 'left', size = 28 }) {
|
|
|
24
27
|
}
|
|
25
28
|
// The trip calendar uses a full-cell date target with sibling event buttons.
|
|
26
29
|
function CalendarCell({ children, day, modifiers, ...props }) {
|
|
27
|
-
const {
|
|
30
|
+
const { t } = useInkraLocale();
|
|
31
|
+
const { eventsByDay, appearance, onEventSelect, renderDayAccessory, maxEventsPerDay, showDayEventsLabel } = useContext(EventContext);
|
|
32
|
+
const [expandedDay, setExpandedDay] = useState();
|
|
28
33
|
if (appearance === 'picker')
|
|
29
34
|
return _jsx(Day, { ...props, day: day, modifiers: modifiers, children: children });
|
|
30
|
-
const entries =
|
|
31
|
-
|
|
35
|
+
const entries = eventsByDay.get(dayKey(day.date)) ?? emptyEvents;
|
|
36
|
+
const limit = maxEventsPerDay !== undefined && Number.isFinite(maxEventsPerDay) ? Math.max(0, Math.floor(maxEventsPerDay)) : entries.length;
|
|
37
|
+
const expanded = expandedDay === dayKey(day.date);
|
|
38
|
+
const shown = expanded || limit >= entries.length ? entries : entries.slice(0, limit);
|
|
39
|
+
return _jsx(Day, { ...props, day: day, modifiers: modifiers, children: _jsxs("div", { className: "inkra-calendar__day-card", children: [_jsx(PencilSurface, { variant: modifiers.selected ? 'action' : 'quiet', radius: 8, className: "inkra-calendar__day-paper", "aria-hidden": "true", seed: `day-${day.date.toISOString()}` }), children, renderDayAccessory && _jsx("div", { className: "inkra-calendar__accessory", children: renderDayAccessory(day.date) }), _jsxs("div", { className: "inkra-calendar__events", children: [shown.map((event, index) => _jsx(PencilSurface, { as: "button", variant: "quiet", radius: 3, className: "inkra-calendar__event-card", disabled: modifiers.disabled || !onEventSelect, style: event.color ? { '--inkra-accent-soft': event.color } : undefined, "aria-label": `${event.label}${event.description ? `, ${event.description}` : ''}`, title: event.description ?? event.label, onClick: click => { click.stopPropagation(); onEventSelect?.(event); }, children: _jsx(PencilText, { ellipsis: true, children: event.label }) }, event.id ?? index)), entries.length > limit && _jsx(Button, { className: "inkra-calendar__events-toggle", size: "sm", variant: "ghost", "aria-expanded": expanded, onClick: click => { click.stopPropagation(); setExpandedDay(expanded ? undefined : dayKey(day.date)); }, children: showDayEventsLabel?.(day.date, entries.length, expanded) ?? (expanded ? t('agenda.fewer') : t('agenda.all', { count: entries.length })) })] })] }) });
|
|
32
40
|
}
|
|
33
41
|
function CalendarDay({ children, modifiers, day, ...props }) {
|
|
34
|
-
const {
|
|
35
|
-
const
|
|
36
|
-
|
|
42
|
+
const { t } = useInkraLocale();
|
|
43
|
+
const { eventsByDay, indicator, appearance, maxEventsPerDay, showDayEventsLabel } = useContext(EventContext);
|
|
44
|
+
const events = eventsByDay.get(dayKey(day.date)) ?? emptyEvents;
|
|
45
|
+
return _jsxs(DayButton, { ...props, day: day, modifiers: modifiers, "aria-label": [props['aria-label'], ...(maxEventsPerDay !== undefined && Number.isFinite(maxEventsPerDay) && events.length > Math.max(0, Math.floor(maxEventsPerDay))
|
|
46
|
+
? [showDayEventsLabel?.(day.date, events.length, false) ?? t('agenda.count', { count: events.length })] : events.map(event => event.label))].filter(Boolean).join(', '), children: [modifiers.selected && appearance === 'picker' && _jsx(PencilSurface, { variant: "selection", className: "inkra-calendar__selection", "aria-hidden": "true" }), _jsx(PencilText, { children: String(children) }), appearance === 'picker' && events.length > 0 && indicator !== 'none' && _jsx("span", { className: "inkra-calendar__event", "data-indicator": indicator, "aria-hidden": "true", children: indicator === 'count' ? events.length : '\u2022' })] });
|
|
37
47
|
}
|
|
38
|
-
export function Calendar({ events =
|
|
48
|
+
export function Calendar({ events = emptyEvents, eventIndicator = 'count', appearance = 'itinerary', onEventSelect, renderDayAccessory, displayRange, maxEventsPerDay, showDayEventsLabel, className, components, showOutsideDays = true, ...props }) {
|
|
49
|
+
const { locale, t } = useInkraLocale(props.locale?.code);
|
|
50
|
+
const eventSummary = useMemo(() => showDayEventsLabel ?? ((_date, total, expanded) => t(expanded ? 'agenda.fewer' : 'agenda.all', { count: total })), [showDayEventsLabel, t]);
|
|
51
|
+
const eventsByDay = useMemo(() => {
|
|
52
|
+
const index = new Map();
|
|
53
|
+
for (const event of events) {
|
|
54
|
+
const key = dayKey(event.date);
|
|
55
|
+
const entries = index.get(key);
|
|
56
|
+
if (entries)
|
|
57
|
+
entries.push(event);
|
|
58
|
+
else
|
|
59
|
+
index.set(key, [event]);
|
|
60
|
+
}
|
|
61
|
+
return index;
|
|
62
|
+
}, [events]);
|
|
63
|
+
const context = useMemo(() => ({ eventsByDay, indicator: eventIndicator, appearance, onEventSelect, renderDayAccessory, maxEventsPerDay, showDayEventsLabel: eventSummary }), [eventsByDay, eventIndicator, appearance, onEventSelect, renderDayAccessory, maxEventsPerDay, eventSummary]);
|
|
39
64
|
const hidden = displayRange ? [...(Array.isArray(props.hidden) ? props.hidden : props.hidden ? [props.hidden] : []), { before: displayRange.from }, { after: displayRange.to }] : props.hidden;
|
|
40
|
-
return _jsx(EventContext.Provider, { value:
|
|
65
|
+
return _jsx(EventContext.Provider, { value: context, children: _jsx("div", { className: "inkra-calendar-scroll", "data-appearance": appearance, "data-event-summary": maxEventsPerDay !== undefined && Number.isFinite(maxEventsPerDay) || undefined, children: _jsx(DayPicker, { ...props, locale: props.locale ?? calendarLocales[resolveInkraLocale(locale)], labels: { labelPrevious: () => t('datePicker.previous'), labelNext: () => t('datePicker.next'), ...props.labels }, hidden: hidden, "data-appearance": appearance, "data-week-numbers": props.showWeekNumber || undefined, showOutsideDays: showOutsideDays, className: ['inkra-calendar', className].filter(Boolean).join(' '), components: { CaptionLabel: CalendarCaption, Weekday: CalendarWeekday, Chevron: CalendarChevron, PreviousMonthButton: CalendarMonthButton, NextMonthButton: CalendarMonthButton, Day: CalendarCell, DayButton: CalendarDay, ...components } }) }) });
|
|
41
66
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const calendarLocales: {
|
|
2
|
+
en: import("react-day-picker/locale").DayPickerLocale;
|
|
3
|
+
'zh-CN': import("react-day-picker/locale").DayPickerLocale;
|
|
4
|
+
ja: import("react-day-picker/locale").DayPickerLocale;
|
|
5
|
+
ko: import("react-day-picker/locale").DayPickerLocale;
|
|
6
|
+
fr: import("react-day-picker/locale").DayPickerLocale;
|
|
7
|
+
};
|
package/dist/carousel/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useInkraLocale } from '../i18n/index.js';
|
|
2
3
|
import Autoplay from 'embla-carousel-autoplay';
|
|
3
4
|
import useEmblaCarousel from 'embla-carousel-react';
|
|
4
5
|
import { Children, forwardRef, useCallback, useEffect, useId, useImperativeHandle, useMemo, useRef, useState, } from 'react';
|
|
@@ -40,7 +41,9 @@ function useReducedMotion() {
|
|
|
40
41
|
}, []);
|
|
41
42
|
return reduced;
|
|
42
43
|
}
|
|
43
|
-
export const Carousel = forwardRef(function Carousel(
|
|
44
|
+
export const Carousel = forwardRef(function Carousel(inkraProps, forwardedRef) {
|
|
45
|
+
const { t } = useInkraLocale();
|
|
46
|
+
const { 'aria-label': ariaLabel = t('carousel.label'), align = 'start', autoplay = false, children, className, controls = true, currentIndex, defaultIndex = 0, dir, disabled = false, dragFree = false, emptyState, gap = 16, labels, loop = false, onBlurCapture, onCurrentIndexChange, onFocusCapture, onKeyDown, onPointerEnter, onPointerLeave, pagination = true, role = 'region', style, tabIndex, visibleItems = { base: 1, sm: 2, md: 3 }, ...props } = inkraProps;
|
|
44
47
|
const generatedId = useId();
|
|
45
48
|
const rootRef = useRef(null);
|
|
46
49
|
const slideItems = Children.toArray(children);
|
|
@@ -84,18 +87,25 @@ export const Carousel = forwardRef(function Carousel({ 'aria-label': ariaLabel =
|
|
|
84
87
|
stopOnMouseEnter: false,
|
|
85
88
|
}), [autoplayActive, autoplayOptions.delay, stopOnInteraction]);
|
|
86
89
|
const plugins = useMemo(() => autoplayActive ? [autoplayPlugin] : [], [autoplayActive, autoplayPlugin]);
|
|
87
|
-
|
|
88
|
-
|
|
90
|
+
// Ordinary selection changes use scrollTo, not an engine rebuild. When real
|
|
91
|
+
// options/plugins change, seed their new engine from the latest selection.
|
|
92
|
+
const activeIndexRef = useRef(activeIndex);
|
|
93
|
+
activeIndexRef.current = activeIndex;
|
|
94
|
+
const engineActive = !disabled && count > 0;
|
|
95
|
+
const engineLoop = loop && count > 1;
|
|
96
|
+
const engineOptions = useMemo(() => ({
|
|
97
|
+
active: engineActive,
|
|
89
98
|
align,
|
|
90
99
|
containScroll: 'trimSnaps',
|
|
91
100
|
direction: resolvedDir,
|
|
92
101
|
dragFree,
|
|
93
|
-
loop:
|
|
102
|
+
loop: engineLoop,
|
|
94
103
|
slidesToScroll: 1,
|
|
95
|
-
startIndex:
|
|
104
|
+
startIndex: activeIndexRef.current,
|
|
96
105
|
watchResize: true,
|
|
97
106
|
watchSlides: true,
|
|
98
|
-
}, plugins);
|
|
107
|
+
}), [align, dragFree, engineActive, engineLoop, plugins, resolvedDir]);
|
|
108
|
+
const [viewportRef, emblaApi] = useEmblaCarousel(engineOptions, plugins);
|
|
99
109
|
const refresh = useCallback(() => {
|
|
100
110
|
if (!emblaApi)
|
|
101
111
|
return;
|
|
@@ -281,10 +291,10 @@ export const Carousel = forwardRef(function Carousel({ 'aria-label': ariaLabel =
|
|
|
281
291
|
'--inkra-carousel-visible-md': visible.md,
|
|
282
292
|
'--inkra-carousel-visible-lg': visible.lg,
|
|
283
293
|
};
|
|
284
|
-
const slideLabel = labels?.slide ?? ((index, total) =>
|
|
285
|
-
const pageLabel = labels?.page ?? ((index, total) =>
|
|
286
|
-
const statusLabel = labels?.status ?? ((index, total) =>
|
|
294
|
+
const slideLabel = labels?.slide ?? ((index, total) => t('carousel.slide', { index, total }));
|
|
295
|
+
const pageLabel = labels?.page ?? ((index, total) => t('carousel.page', { index, total }));
|
|
296
|
+
const statusLabel = labels?.status ?? ((index, total) => t('carousel.slide', { index, total }));
|
|
287
297
|
const showNavigation = controls && count > 1;
|
|
288
298
|
const showPagination = pagination && scrollSnaps.length > 1;
|
|
289
|
-
return _jsxs("div", { ...props, "aria-label": ariaLabel, "aria-roledescription": "carousel", className: ['inkra-carousel', className].filter(Boolean).join(' '), "data-autoplay": autoplayEnabled || undefined, "data-disabled": disabled || undefined, "data-playing": playing || undefined, dir: resolvedDir, onBlurCapture: handleBlur, onFocusCapture: handleFocus, onKeyDown: handleKeyDown, onPointerEnter: handlePointerEnter, onPointerLeave: handlePointerLeave, ref: rootRef, role: role, style: rootStyle, tabIndex: !disabled && count > 1 ? (tabIndex ?? 0) : tabIndex, children: [count === 0 ? _jsx("div", { className: "inkra-carousel__empty", children: emptyState ?? labels?.empty ?? '
|
|
299
|
+
return _jsxs("div", { ...props, "aria-label": ariaLabel, "aria-roledescription": "carousel", className: ['inkra-carousel', className].filter(Boolean).join(' '), "data-autoplay": autoplayEnabled || undefined, "data-disabled": disabled || undefined, "data-playing": playing || undefined, dir: resolvedDir, onBlurCapture: handleBlur, onFocusCapture: handleFocus, onKeyDown: handleKeyDown, onPointerEnter: handlePointerEnter, onPointerLeave: handlePointerLeave, ref: rootRef, role: role, style: rootStyle, tabIndex: !disabled && count > 1 ? (tabIndex ?? 0) : tabIndex, children: [_jsx(PencilSurface, { "aria-hidden": "true", as: "span", className: "inkra-carousel__focus-ring", radius: 8, seed: `${generatedId}-focus`, variant: "outline" }), count === 0 ? _jsx("div", { className: "inkra-carousel__empty", children: emptyState ?? labels?.empty ?? t('common.noItems') }) : _jsxs(_Fragment, { children: [_jsx("div", { className: "inkra-carousel__viewport", ref: viewportRef, children: _jsx("div", { className: "inkra-carousel__container", children: slideItems.map((item, index) => _jsx("div", { "aria-label": slideLabel(index + 1, count), "aria-roledescription": "slide", className: "inkra-carousel__slide", "data-current": index === activeIndex || undefined, role: "group", children: item }, item.key ?? index)) }) }), (showNavigation || showPagination || autoplayEnabled) && _jsxs("div", { className: "inkra-carousel__toolbar", children: [showNavigation && _jsxs("div", { className: "inkra-carousel__navigation", children: [_jsx(Button, { "aria-label": labels?.previous ?? t('carousel.previous'), disabled: disabled || !canScrollPrevious, onClick: previous, size: "icon", variant: "outline", children: _jsx(PencilIcon, { decorative: true, paths: resolvedDir === 'rtl' ? nextPaths : previousPaths, seed: `${generatedId}-previous`, size: 20, sourceSize: 24 }) }), _jsx(Button, { "aria-label": labels?.next ?? t('carousel.next'), disabled: disabled || !canScrollNext, onClick: next, size: "icon", variant: "outline", children: _jsx(PencilIcon, { decorative: true, paths: resolvedDir === 'rtl' ? previousPaths : nextPaths, seed: `${generatedId}-next`, size: 20, sourceSize: 24 }) })] }), showPagination && _jsx("div", { "aria-label": labels?.pages ?? t('carousel.pages'), className: "inkra-carousel__pagination", role: "group", children: scrollSnaps.map((_, index) => _jsxs("button", { "aria-current": index === selectedIndex ? 'true' : undefined, "aria-label": pageLabel(index + 1, scrollSnaps.length), className: "inkra-carousel__page", disabled: disabled, onClick: () => scrollTo(index), type: "button", children: [_jsx(PencilSurface, { as: "span", "aria-hidden": "true", className: "inkra-carousel__page-mark", radius: 6, seed: `${generatedId}-page-${index}`, variant: index === selectedIndex ? 'selection' : 'outline' }), _jsx(PencilSurface, { as: "span", "aria-hidden": "true", className: "inkra-carousel__page-focus", radius: 6, seed: `${generatedId}-page-${index}-focus`, variant: "outline" })] }, index)) }), autoplayEnabled && _jsx(Button, { "aria-label": playing ? (labels?.pause ?? t('carousel.pause')) : (labels?.play ?? t('carousel.play')), disabled: !autoplayAllowed, onClick: toggleAutoplay, onPointerDown: () => { autoplayControlPointerRef.current = true; }, size: "icon", variant: "ghost", children: _jsx(PencilIcon, { decorative: true, paths: playing ? pausePaths : playPaths, seed: `${generatedId}-autoplay`, size: 20, sourceSize: 24 }) })] })] }), count > 0 && _jsx("span", { "aria-atomic": "true", "aria-live": playing ? 'off' : 'polite', className: "inkra-carousel__status", children: statusLabel(activeIndex + 1, count) })] });
|
|
290
300
|
});
|
|
@@ -11,6 +11,11 @@ interface ChoiceGridCommonProps extends Omit<FieldsetHTMLAttributes<HTMLFieldSet
|
|
|
11
11
|
error?: ReactNode;
|
|
12
12
|
columns?: ChoiceGridColumns;
|
|
13
13
|
minItemWidth?: string;
|
|
14
|
+
/** Opt-in paging for controlled, direct non-required ChoiceGridOption children. */
|
|
15
|
+
pageSize?: number;
|
|
16
|
+
previousPageLabel?: string;
|
|
17
|
+
nextPageLabel?: string;
|
|
18
|
+
pageLabel?: (page: number, pages: number) => ReactNode;
|
|
14
19
|
}
|
|
15
20
|
export interface SingleChoiceGridProps extends ChoiceGridCommonProps {
|
|
16
21
|
type?: 'single';
|
|
@@ -33,6 +38,6 @@ export interface ChoiceGridOptionProps extends Omit<InputHTMLAttributes<HTMLInpu
|
|
|
33
38
|
image?: ChoiceGridImage;
|
|
34
39
|
seed?: string | number;
|
|
35
40
|
}
|
|
36
|
-
export declare const ChoiceGrid: import("react").
|
|
41
|
+
export declare const ChoiceGrid: import("react").NamedExoticComponent<ChoiceGridProps & import("react").RefAttributes<HTMLFieldSetElement>>;
|
|
37
42
|
export declare const ChoiceGridOption: import("react").ForwardRefExoticComponent<ChoiceGridOptionProps & import("react").RefAttributes<HTMLInputElement>>;
|
|
38
43
|
export {};
|