@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
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useInkraLocale } from '../i18n/index.js';
|
|
3
|
+
import { __iconData as pauseIcon } from 'lucide-react/dist/esm/icons/pause.mjs';
|
|
4
|
+
import { __iconData as playIcon } from 'lucide-react/dist/esm/icons/play.mjs';
|
|
5
|
+
import { __iconData as volumeIcon } from 'lucide-react/dist/esm/icons/volume-2.mjs';
|
|
6
|
+
import { __iconData as muteIcon } from 'lucide-react/dist/esm/icons/volume-x.mjs';
|
|
7
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
8
|
+
import { Button } from '../Button.js';
|
|
9
|
+
import { PencilIcon } from '../PencilIcon.js';
|
|
10
|
+
import { PencilText } from '../PencilText.js';
|
|
11
|
+
import { Select } from '../select/index.js';
|
|
12
|
+
import { Slider } from '../slider/index.js';
|
|
13
|
+
function number(attributes, name) {
|
|
14
|
+
const value = Number(attributes[name]);
|
|
15
|
+
return Number.isFinite(value) ? value : 0;
|
|
16
|
+
}
|
|
17
|
+
function iconPaths(nodes) {
|
|
18
|
+
return nodes.flatMap(([tag, attributes]) => {
|
|
19
|
+
if (tag === 'path' && attributes.d)
|
|
20
|
+
return [{ d: attributes.d }];
|
|
21
|
+
if (tag === 'line')
|
|
22
|
+
return [{ d: `M${number(attributes, 'x1')} ${number(attributes, 'y1')}L${number(attributes, 'x2')} ${number(attributes, 'y2')}` }];
|
|
23
|
+
if (tag === 'rect') {
|
|
24
|
+
const x = number(attributes, 'x');
|
|
25
|
+
const y = number(attributes, 'y');
|
|
26
|
+
const width = number(attributes, 'width');
|
|
27
|
+
const height = number(attributes, 'height');
|
|
28
|
+
const radius = Math.min(number(attributes, 'rx'), width / 2, height / 2);
|
|
29
|
+
return [{ d: radius
|
|
30
|
+
? `M${x + radius} ${y}H${x + width - radius}Q${x + width} ${y} ${x + width} ${y + radius}V${y + height - radius}Q${x + width} ${y + height} ${x + width - radius} ${y + height}H${x + radius}Q${x} ${y + height} ${x} ${y + height - radius}V${y + radius}Q${x} ${y} ${x + radius} ${y}Z`
|
|
31
|
+
: `M${x} ${y}H${x + width}V${y + height}H${x}Z` }];
|
|
32
|
+
}
|
|
33
|
+
return [];
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
const playPaths = iconPaths(playIcon.node);
|
|
37
|
+
const pausePaths = iconPaths(pauseIcon.node);
|
|
38
|
+
const volumePaths = iconPaths(volumeIcon.node);
|
|
39
|
+
const mutePaths = iconPaths(muteIcon.node);
|
|
40
|
+
const rates = [0.75, 1, 1.25, 1.5, 2];
|
|
41
|
+
const defaultLabels = (t) => ({
|
|
42
|
+
controls: t('video.controls'),
|
|
43
|
+
play: t('video.play'),
|
|
44
|
+
pause: t('video.pause'),
|
|
45
|
+
seek: t('video.seek'),
|
|
46
|
+
mute: t('video.mute'),
|
|
47
|
+
unmute: t('video.unmute'),
|
|
48
|
+
volume: t('video.volume'),
|
|
49
|
+
rate: t('audio.rate'),
|
|
50
|
+
playbackError: t('video.error'),
|
|
51
|
+
time: (current, duration) => `${formatTime(current)} / ${formatTime(duration)}`,
|
|
52
|
+
});
|
|
53
|
+
function clamp(value, min, max) {
|
|
54
|
+
return Number.isFinite(value) ? Math.min(max, Math.max(min, value)) : min;
|
|
55
|
+
}
|
|
56
|
+
function formatTime(value) {
|
|
57
|
+
if (!Number.isFinite(value) || value < 0)
|
|
58
|
+
return '0:00';
|
|
59
|
+
const seconds = Math.floor(value);
|
|
60
|
+
return `${Math.floor(seconds / 60)}:${String(seconds % 60).padStart(2, '0')}`;
|
|
61
|
+
}
|
|
62
|
+
export function VideoResource({ alt, labels: overrides, onError, onLoad, poster, src }) {
|
|
63
|
+
const { t } = useInkraLocale();
|
|
64
|
+
const videoRef = useRef(null);
|
|
65
|
+
const sourceRef = useRef(src);
|
|
66
|
+
sourceRef.current = src;
|
|
67
|
+
const mountedRef = useRef(false);
|
|
68
|
+
const generationRef = useRef(0);
|
|
69
|
+
const playRequestRef = useRef(0);
|
|
70
|
+
const callbacksRef = useRef({ onError, onLoad });
|
|
71
|
+
callbacksRef.current = { onError, onLoad };
|
|
72
|
+
const [playing, setPlaying] = useState(false);
|
|
73
|
+
const [currentTime, setCurrentTime] = useState(0);
|
|
74
|
+
const [duration, setDuration] = useState(0);
|
|
75
|
+
const [volume, setVolumeState] = useState(1);
|
|
76
|
+
const [muted, setMutedState] = useState(false);
|
|
77
|
+
const [playbackRate, setPlaybackRateState] = useState(1);
|
|
78
|
+
const [playbackError, setPlaybackError] = useState(false);
|
|
79
|
+
const labels = { ...defaultLabels(t), ...overrides };
|
|
80
|
+
const sync = useCallback(() => {
|
|
81
|
+
const video = videoRef.current;
|
|
82
|
+
if (!video)
|
|
83
|
+
return;
|
|
84
|
+
setPlaying(!video.paused && !video.ended);
|
|
85
|
+
setCurrentTime(Number.isFinite(video.currentTime) ? video.currentTime : 0);
|
|
86
|
+
setDuration(Number.isFinite(video.duration) ? video.duration : 0);
|
|
87
|
+
setVolumeState(video.volume);
|
|
88
|
+
setMutedState(video.muted);
|
|
89
|
+
setPlaybackRateState(video.playbackRate);
|
|
90
|
+
}, []);
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
mountedRef.current = true;
|
|
93
|
+
return () => {
|
|
94
|
+
mountedRef.current = false;
|
|
95
|
+
generationRef.current += 1;
|
|
96
|
+
playRequestRef.current += 1;
|
|
97
|
+
videoRef.current?.pause();
|
|
98
|
+
};
|
|
99
|
+
}, []);
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
const video = videoRef.current;
|
|
102
|
+
if (!video)
|
|
103
|
+
return undefined;
|
|
104
|
+
const handlers = {
|
|
105
|
+
loadedmetadata: () => { sync(); callbacksRef.current.onLoad?.(); },
|
|
106
|
+
durationchange: sync,
|
|
107
|
+
timeupdate: sync,
|
|
108
|
+
play: sync,
|
|
109
|
+
pause: sync,
|
|
110
|
+
ended: sync,
|
|
111
|
+
volumechange: sync,
|
|
112
|
+
ratechange: sync,
|
|
113
|
+
emptied: sync,
|
|
114
|
+
error: () => { sync(); callbacksRef.current.onError?.(); },
|
|
115
|
+
};
|
|
116
|
+
Object.entries(handlers).forEach(([name, handler]) => video.addEventListener(name, handler));
|
|
117
|
+
return () => Object.entries(handlers).forEach(([name, handler]) => video.removeEventListener(name, handler));
|
|
118
|
+
}, [sync]);
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
const video = videoRef.current;
|
|
121
|
+
if (!video)
|
|
122
|
+
return undefined;
|
|
123
|
+
generationRef.current += 1;
|
|
124
|
+
playRequestRef.current += 1;
|
|
125
|
+
video.pause();
|
|
126
|
+
try {
|
|
127
|
+
video.currentTime = 0;
|
|
128
|
+
}
|
|
129
|
+
catch { /* A source may not have a timeline yet. */ }
|
|
130
|
+
setPlaying(false);
|
|
131
|
+
setCurrentTime(0);
|
|
132
|
+
setDuration(0);
|
|
133
|
+
setPlaybackError(false);
|
|
134
|
+
video.load();
|
|
135
|
+
return () => { video.pause(); };
|
|
136
|
+
}, [src]);
|
|
137
|
+
const togglePlay = async () => {
|
|
138
|
+
const video = videoRef.current;
|
|
139
|
+
if (!video)
|
|
140
|
+
return;
|
|
141
|
+
if (!video.paused) {
|
|
142
|
+
playRequestRef.current += 1;
|
|
143
|
+
video.pause();
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const generation = generationRef.current;
|
|
147
|
+
const source = sourceRef.current;
|
|
148
|
+
const request = ++playRequestRef.current;
|
|
149
|
+
setPlaybackError(false);
|
|
150
|
+
try {
|
|
151
|
+
await video.play();
|
|
152
|
+
if (!mountedRef.current || generation !== generationRef.current || source !== sourceRef.current || request !== playRequestRef.current)
|
|
153
|
+
return;
|
|
154
|
+
sync();
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
if (!mountedRef.current || generation !== generationRef.current || source !== sourceRef.current || request !== playRequestRef.current)
|
|
158
|
+
return;
|
|
159
|
+
setPlaybackError(true);
|
|
160
|
+
sync();
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
const seek = (next) => {
|
|
164
|
+
const video = videoRef.current;
|
|
165
|
+
if (!video || !Number.isFinite(next))
|
|
166
|
+
return;
|
|
167
|
+
video.currentTime = clamp(next, 0, Number.isFinite(video.duration) ? video.duration : Math.max(0, next));
|
|
168
|
+
sync();
|
|
169
|
+
};
|
|
170
|
+
const setVolume = (next) => {
|
|
171
|
+
const video = videoRef.current;
|
|
172
|
+
if (!video)
|
|
173
|
+
return;
|
|
174
|
+
video.volume = clamp(next, 0, 1);
|
|
175
|
+
if (video.muted && video.volume > 0)
|
|
176
|
+
video.muted = false;
|
|
177
|
+
sync();
|
|
178
|
+
};
|
|
179
|
+
const setMuted = (next) => {
|
|
180
|
+
const video = videoRef.current;
|
|
181
|
+
if (!video)
|
|
182
|
+
return;
|
|
183
|
+
video.muted = next;
|
|
184
|
+
sync();
|
|
185
|
+
};
|
|
186
|
+
const setRate = (next) => {
|
|
187
|
+
const video = videoRef.current;
|
|
188
|
+
if (!video)
|
|
189
|
+
return;
|
|
190
|
+
const rate = clamp(next, 0.25, 4);
|
|
191
|
+
video.defaultPlaybackRate = rate;
|
|
192
|
+
video.playbackRate = rate;
|
|
193
|
+
sync();
|
|
194
|
+
};
|
|
195
|
+
return _jsxs("div", { className: "inkra-image-viewer__video-resource", children: [_jsx("video", { "aria-label": alt, className: "inkra-image-viewer__video-media", controls: false, playsInline: true, poster: poster, preload: "metadata", ref: videoRef, src: src }), _jsxs("div", { "aria-label": labels.controls, className: "inkra-image-viewer__video-controls", role: "group", children: [playbackError ? _jsx(PencilText, { as: "p", className: "inkra-image-viewer__video-error", role: "alert", children: labels.playbackError }) : null, _jsx(Button, { "aria-label": playing ? labels.pause : labels.play, className: "inkra-image-viewer__video-play", onClick: () => void togglePlay(), size: "icon-sm", variant: "outline", children: _jsx(PencilIcon, { decorative: true, paths: playing ? pausePaths : playPaths, size: 22, sourceSize: 24 }) }), _jsx("div", { className: "inkra-image-viewer__video-seek", children: _jsx(Slider, { "aria-label": labels.seek, disabled: !duration, getValueLabel: value => labels.time(value, duration), max: duration || 0, min: 0, onValueChange: values => seek(values[0] ?? 0), size: "sm", step: 0.01, value: [currentTime], variant: "inset" }) }), _jsx(PencilText, { "aria-live": "off", as: "span", className: "inkra-image-viewer__video-time", dir: "auto", children: labels.time(currentTime, duration) }), _jsxs("div", { className: "inkra-image-viewer__video-settings", children: [_jsx(Button, { "aria-label": muted ? labels.unmute : labels.mute, className: "inkra-image-viewer__video-mute", onClick: () => setMuted(!muted), size: "icon-sm", variant: "ghost", children: _jsx(PencilIcon, { decorative: true, paths: muted ? mutePaths : volumePaths, size: 22, sourceSize: 24 }) }), _jsx("div", { className: "inkra-image-viewer__video-volume", children: _jsx(Slider, { "aria-label": labels.volume, getValueLabel: value => `${Math.round(value * 100)}%`, max: 1, min: 0, onValueChange: values => setVolume(values[0] ?? 0), size: "sm", step: 0.01, value: [muted ? 0 : volume], variant: "inset" }) }), _jsxs(Select.Root, { onValueChange: value => setRate(Number(value)), value: String(playbackRate), children: [_jsx(Select.Trigger, { "aria-label": labels.rate, fullWidth: false, size: "sm", children: _jsx(Select.Value, {}) }), _jsx(Select.Portal, { children: _jsx(Select.Content, { position: "popper", sideOffset: 4, children: _jsx(Select.Viewport, { children: rates.map(rate => _jsxs(Select.Item, { value: String(rate), children: [_jsx(Select.ItemText, { children: `${rate}x` }), _jsx(Select.ItemIndicator, {})] }, rate)) }) }) })] })] })] })] });
|
|
196
|
+
}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
2
|
import type { PanZoomWheelMode } from '../pan-zoom/index.js';
|
|
3
3
|
import type { OverlayContentProps, OverlayRootProps, OverlayTriggerProps } from '../overlay/index.js';
|
|
4
|
+
import type { VideoResourceLabels } from './VideoResource.js';
|
|
4
5
|
export interface ImageViewerItem {
|
|
5
6
|
id: string;
|
|
6
7
|
src: string;
|
|
7
8
|
alt: string;
|
|
9
|
+
type?: 'image' | 'video';
|
|
10
|
+
poster?: string;
|
|
8
11
|
thumbnailSrc?: string;
|
|
9
12
|
thumbnailAlt?: string;
|
|
10
13
|
width?: number;
|
|
@@ -68,6 +71,7 @@ export interface ImageViewerContentProps extends Omit<OverlayContentProps, 'chil
|
|
|
68
71
|
minScale?: number;
|
|
69
72
|
maxScale?: number;
|
|
70
73
|
wheel?: PanZoomWheelMode;
|
|
74
|
+
videoLabels?: Partial<VideoResourceLabels>;
|
|
71
75
|
}
|
|
72
76
|
export declare const ImageViewerContent: import("react").ForwardRefExoticComponent<ImageViewerContentProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
73
77
|
export declare const ImageViewer: Readonly<{
|
|
@@ -75,5 +79,15 @@ export declare const ImageViewer: Readonly<{
|
|
|
75
79
|
Trigger: import("react").ForwardRefExoticComponent<import("@radix-ui/react-dialog").DialogTriggerProps & import("react").RefAttributes<HTMLButtonElement>>;
|
|
76
80
|
Content: import("react").ForwardRefExoticComponent<ImageViewerContentProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
77
81
|
}>;
|
|
82
|
+
export declare const MediaViewer: Readonly<{
|
|
83
|
+
Root: typeof ImageViewerRoot;
|
|
84
|
+
Trigger: import("react").ForwardRefExoticComponent<import("@radix-ui/react-dialog").DialogTriggerProps & import("react").RefAttributes<HTMLButtonElement>>;
|
|
85
|
+
Content: import("react").ForwardRefExoticComponent<ImageViewerContentProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
86
|
+
}>;
|
|
87
|
+
export type MediaViewerItem = ImageViewerItem;
|
|
88
|
+
export type MediaViewerLabels = ImageViewerLabels;
|
|
89
|
+
export type MediaViewerRootProps = ImageViewerRootProps;
|
|
90
|
+
export type MediaViewerContentProps = ImageViewerContentProps;
|
|
91
|
+
export type { VideoResourceLabels } from './VideoResource.js';
|
|
78
92
|
export { nextImageViewerIndex, resolveImageViewerSelection } from './model.js';
|
|
79
93
|
export type { ImageViewerIdentifiable, ImageViewerSelection } from './model.js';
|
|
@@ -1,7 +1,9 @@
|
|
|
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 { __iconData as chevronLeftIcon } from 'lucide-react/dist/esm/icons/chevron-left.mjs';
|
|
3
4
|
import { __iconData as chevronRightIcon } from 'lucide-react/dist/esm/icons/chevron-right.mjs';
|
|
4
5
|
import { __iconData as imageOffIcon } from 'lucide-react/dist/esm/icons/image-off.mjs';
|
|
6
|
+
import { __iconData as playIcon } from 'lucide-react/dist/esm/icons/play.mjs';
|
|
5
7
|
import { __iconData as rotateIcon } from 'lucide-react/dist/esm/icons/rotate-cw.mjs';
|
|
6
8
|
import { __iconData as zoomInIcon } from 'lucide-react/dist/esm/icons/zoom-in.mjs';
|
|
7
9
|
import { __iconData as zoomOutIcon } from 'lucide-react/dist/esm/icons/zoom-out.mjs';
|
|
@@ -11,14 +13,17 @@ import { PencilIcon } from '../PencilIcon.js';
|
|
|
11
13
|
import { PencilSurface } from '../PencilSurface.js';
|
|
12
14
|
import { PencilText } from '../PencilText.js';
|
|
13
15
|
import { PanZoom } from '../pan-zoom/index.js';
|
|
14
|
-
import { OverlayBackdrop, OverlayContent, OverlayDescription,
|
|
16
|
+
import { OverlayBackdrop, OverlayCloseButton, OverlayContent, OverlayDescription, OverlayPortal, OverlayRoot, OverlayTitle, OverlayTrigger, } from '../overlay/index.js';
|
|
17
|
+
import { lucideNodesToPencilPaths } from './lucideGeometry.js';
|
|
15
18
|
import { nextImageViewerIndex, resolveImageViewerSelection } from './model.js';
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
const
|
|
19
|
+
import { VideoResource } from './VideoResource.js';
|
|
20
|
+
const chevronLeftPaths = lucideNodesToPencilPaths(chevronLeftIcon.node);
|
|
21
|
+
const chevronRightPaths = lucideNodesToPencilPaths(chevronRightIcon.node);
|
|
22
|
+
const imageOffPaths = lucideNodesToPencilPaths(imageOffIcon.node);
|
|
23
|
+
const playPaths = lucideNodesToPencilPaths(playIcon.node);
|
|
24
|
+
const rotatePaths = lucideNodesToPencilPaths(rotateIcon.node);
|
|
25
|
+
const zoomInPaths = lucideNodesToPencilPaths(zoomInIcon.node);
|
|
26
|
+
const zoomOutPaths = lucideNodesToPencilPaths(zoomOutIcon.node);
|
|
22
27
|
const ImageViewerContext = createContext(null);
|
|
23
28
|
function useImageViewerContext() {
|
|
24
29
|
const context = useContext(ImageViewerContext);
|
|
@@ -73,9 +78,10 @@ export function ImageViewerRoot({ activeId, defaultActiveId, defaultOpen = false
|
|
|
73
78
|
activeItem: selection.index < 0 ? null : items[selection.index],
|
|
74
79
|
activeIndex: selection.index,
|
|
75
80
|
loop,
|
|
81
|
+
open: openControlled ? Boolean(open) : internalOpen,
|
|
76
82
|
selectIndex,
|
|
77
83
|
step,
|
|
78
|
-
}), [items, loop, selectIndex, selection.index, step]);
|
|
84
|
+
}), [internalOpen, items, loop, open, openControlled, selectIndex, selection.index, step]);
|
|
79
85
|
return (_jsx(ImageViewerContext.Provider, { value: context, children: _jsx(OverlayRoot, { ...props, open: openControlled ? open : internalOpen, onOpenChange: (nextOpen) => {
|
|
80
86
|
if (!openControlled)
|
|
81
87
|
setInternalOpen(nextOpen);
|
|
@@ -85,46 +91,70 @@ export function ImageViewerRoot({ activeId, defaultActiveId, defaultOpen = false
|
|
|
85
91
|
export const ImageViewerTrigger = forwardRef(function ImageViewerTrigger(props, ref) {
|
|
86
92
|
return _jsx(OverlayTrigger, { ...props, ref: ref });
|
|
87
93
|
});
|
|
88
|
-
const defaultLabels = {
|
|
89
|
-
title: '
|
|
90
|
-
description: '
|
|
91
|
-
close: '
|
|
92
|
-
previous: '
|
|
93
|
-
next: '
|
|
94
|
-
thumbnails: '
|
|
95
|
-
imagePosition: (current, total) =>
|
|
96
|
-
thumbnail: (item, current, total) =>
|
|
97
|
-
failedTitle: '
|
|
98
|
-
failedDescription: item =>
|
|
99
|
-
retry: '
|
|
100
|
-
zoomIn: '
|
|
101
|
-
zoomOut: '
|
|
102
|
-
resetZoom: '
|
|
103
|
-
rotate: '
|
|
104
|
-
panZoom: item =>
|
|
105
|
-
emptyTitle: '
|
|
106
|
-
emptyDescription: '
|
|
107
|
-
};
|
|
94
|
+
const defaultLabels = (t) => ({
|
|
95
|
+
title: t('viewer.title'),
|
|
96
|
+
description: t('viewer.description'),
|
|
97
|
+
close: t('viewer.close'),
|
|
98
|
+
previous: t('viewer.previous'),
|
|
99
|
+
next: t('viewer.next'),
|
|
100
|
+
thumbnails: t('viewer.thumbnails'),
|
|
101
|
+
imagePosition: (current, total) => t('viewer.position', { current, total }),
|
|
102
|
+
thumbnail: (item, current, total) => t('viewer.thumbnail', { label: item.alt, current, total }),
|
|
103
|
+
failedTitle: t('viewer.failed'),
|
|
104
|
+
failedDescription: item => t('viewer.failedDescription', { label: item.alt }),
|
|
105
|
+
retry: t('common.retry'),
|
|
106
|
+
zoomIn: t('common.zoomIn'),
|
|
107
|
+
zoomOut: t('common.zoomOut'),
|
|
108
|
+
resetZoom: t('viewer.reset'),
|
|
109
|
+
rotate: t('viewer.rotate'),
|
|
110
|
+
panZoom: item => t('viewer.panZoom', { label: item.alt }),
|
|
111
|
+
emptyTitle: t('viewer.empty'),
|
|
112
|
+
emptyDescription: t('viewer.emptyDescription'),
|
|
113
|
+
});
|
|
108
114
|
function directionFor(node) {
|
|
109
115
|
return getComputedStyle(node).direction === 'rtl' ? 'rtl' : 'ltr';
|
|
110
116
|
}
|
|
111
|
-
export const ImageViewerContent = forwardRef(function ImageViewerContent({ className, emptyState, labels: labelOverrides, onOpenAutoFocus, renderImage, rotatable = false, showThumbnails = true, zoomable = true, minScale = 0.5, maxScale = 6, wheel = 'modifier', ...props }, forwardedRef) {
|
|
112
|
-
const {
|
|
113
|
-
const
|
|
117
|
+
export const ImageViewerContent = forwardRef(function ImageViewerContent({ className, emptyState, labels: labelOverrides, onOpenAutoFocus, renderImage, rotatable = false, showThumbnails = true, zoomable = true, minScale = 0.5, maxScale = 6, videoLabels, wheel = 'modifier', ...props }, forwardedRef) {
|
|
118
|
+
const { t } = useInkraLocale();
|
|
119
|
+
const { activeIndex, activeItem, items, loop, open, selectIndex, step } = useImageViewerContext();
|
|
120
|
+
const labels = { ...defaultLabels(t), ...labelOverrides };
|
|
114
121
|
const stageRef = useRef(null);
|
|
122
|
+
const thumbnailsRef = useRef(null);
|
|
115
123
|
const thumbnailRefs = useRef(new Map());
|
|
116
124
|
const panZoomRef = useRef(null);
|
|
117
|
-
const
|
|
125
|
+
const focusFrameRef = useRef(null);
|
|
126
|
+
const fitFrameRef = useRef(null);
|
|
127
|
+
useEffect(() => () => {
|
|
128
|
+
if (focusFrameRef.current !== null)
|
|
129
|
+
cancelAnimationFrame(focusFrameRef.current);
|
|
130
|
+
if (fitFrameRef.current !== null)
|
|
131
|
+
cancelAnimationFrame(fitFrameRef.current);
|
|
132
|
+
}, []);
|
|
133
|
+
const [failedResources, setFailedResources] = useState(() => new Set());
|
|
118
134
|
const [attempts, setAttempts] = useState({});
|
|
119
135
|
const [rotation, setRotation] = useState(0);
|
|
120
136
|
const total = items.length;
|
|
121
137
|
const atStart = activeIndex <= 0;
|
|
122
138
|
const atEnd = activeIndex >= total - 1;
|
|
139
|
+
const activeItemType = activeItem?.type ?? 'image';
|
|
140
|
+
const activeItemKey = activeItem ? JSON.stringify([activeItemType, activeItem.id, activeItem.src]) : undefined;
|
|
141
|
+
const activeResourceRef = useRef(activeItemKey);
|
|
142
|
+
activeResourceRef.current = activeItemKey;
|
|
123
143
|
useEffect(() => {
|
|
124
|
-
|
|
125
|
-
|
|
144
|
+
const thumbnail = activeItem ? thumbnailRefs.current.get(activeItem.id) : undefined;
|
|
145
|
+
const viewport = thumbnailsRef.current;
|
|
146
|
+
if (thumbnail && viewport) {
|
|
147
|
+
const item = thumbnail.getBoundingClientRect();
|
|
148
|
+
const clip = viewport.getBoundingClientRect();
|
|
149
|
+
if (item.left < clip.left)
|
|
150
|
+
viewport.scrollLeft -= clip.left - item.left;
|
|
151
|
+
else if (item.right > clip.right)
|
|
152
|
+
viewport.scrollLeft += item.right - clip.right;
|
|
153
|
+
}
|
|
126
154
|
setRotation(0);
|
|
127
|
-
|
|
155
|
+
setFailedResources(current => activeItemKey && current.has(activeItemKey) ? new Set([activeItemKey]) : new Set());
|
|
156
|
+
setAttempts(current => activeItemKey && current[activeItemKey] !== undefined ? { [activeItemKey]: current[activeItemKey] } : {});
|
|
157
|
+
}, [activeItemKey]);
|
|
128
158
|
const handleStageKeyDown = (event) => {
|
|
129
159
|
if (event.target !== event.currentTarget)
|
|
130
160
|
return;
|
|
@@ -149,72 +179,97 @@ export const ImageViewerContent = forwardRef(function ImageViewerContent({ class
|
|
|
149
179
|
const selectThumbnail = (index, reason = 'thumbnail') => {
|
|
150
180
|
selectIndex(index, reason);
|
|
151
181
|
const item = items[index];
|
|
152
|
-
if (
|
|
153
|
-
|
|
182
|
+
if (focusFrameRef.current !== null)
|
|
183
|
+
cancelAnimationFrame(focusFrameRef.current);
|
|
184
|
+
focusFrameRef.current = item ? requestAnimationFrame(() => {
|
|
185
|
+
focusFrameRef.current = null;
|
|
186
|
+
thumbnailRefs.current.get(item.id)?.focus();
|
|
187
|
+
}) : null;
|
|
154
188
|
};
|
|
155
189
|
const renderDefaultImage = (item, { key, ...imageProps }) => (_jsx("img", { ...imageProps, draggable: false }, key));
|
|
156
|
-
const failed =
|
|
157
|
-
const imageProps = activeItem ? {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
190
|
+
const failed = activeItemKey ? failedResources.has(activeItemKey) : false;
|
|
191
|
+
const imageProps = activeItem && activeItemKey && activeItemType === 'image' ? (() => {
|
|
192
|
+
const resourceKey = activeItemKey;
|
|
193
|
+
return {
|
|
194
|
+
alt: activeItem.alt,
|
|
195
|
+
className: 'inkra-image-viewer__image',
|
|
196
|
+
height: activeItem.height,
|
|
197
|
+
key: `${resourceKey}:${attempts[resourceKey] ?? 0}`,
|
|
198
|
+
onError: () => setFailedResources(current => new Set(current).add(resourceKey)),
|
|
199
|
+
onLoad: () => setFailedResources(current => {
|
|
200
|
+
if (!current.has(resourceKey))
|
|
201
|
+
return current;
|
|
202
|
+
const next = new Set(current);
|
|
203
|
+
next.delete(resourceKey);
|
|
204
|
+
return next;
|
|
205
|
+
}),
|
|
206
|
+
src: activeItem.src,
|
|
207
|
+
width: activeItem.width,
|
|
208
|
+
};
|
|
209
|
+
})() : null;
|
|
173
210
|
return (_jsxs(OverlayPortal, { children: [_jsx(OverlayBackdrop, {}), _jsxs(OverlayContent, { ...props, className: ['inkra-image-viewer', className].filter(Boolean).join(' '), layout: "sections", onOpenAutoFocus: (event) => {
|
|
174
211
|
onOpenAutoFocus?.(event);
|
|
175
212
|
if (event.defaultPrevented)
|
|
176
213
|
return;
|
|
177
214
|
event.preventDefault();
|
|
178
215
|
stageRef.current?.focus();
|
|
179
|
-
}, ref: forwardedRef, surfaceRadius:
|
|
180
|
-
|
|
181
|
-
|
|
216
|
+
}, ref: forwardedRef, surfaceRadius: 0, surfaceVariant: "ghost", children: [_jsx(OverlayTitle, { className: "inkra-image-viewer__accessible-label", children: labels.title }), _jsx(OverlayDescription, { className: "inkra-image-viewer__accessible-label", children: labels.description }), _jsxs("div", { className: "inkra-image-viewer__close-control", children: [_jsx(PencilSurface, { "aria-hidden": "true", className: "inkra-image-viewer__close-paper", radius: 8, seed: "image-viewer-close-paper", variant: "paper" }), _jsx(OverlayCloseButton, { className: "inkra-image-viewer__close", label: labels.close })] }), _jsx("div", { className: "inkra-image-viewer__body", children: activeItem && activeItemKey ? _jsx(_Fragment, { children: _jsxs("div", { "aria-label": activeItem.alt, className: "inkra-image-viewer__stage", onKeyDown: handleStageKeyDown, ref: stageRef, tabIndex: 0, children: [_jsx(PencilSurface, { "aria-hidden": "true", className: "inkra-image-viewer__stage-focus", radius: 6, seed: `image-viewer-stage-${activeItem.id}`, variant: "outline" }), failed ? _jsxs("div", { className: "inkra-image-viewer__state", role: "alert", children: [_jsx(PencilIcon, { decorative: true, paths: imageOffPaths, size: 48, sourceSize: 24 }), _jsx(PencilText, { as: "h3", children: labels.failedTitle }), _jsx(PencilText, { as: "p", children: labels.failedDescription(activeItem) }), _jsx(Button, { variant: "outline", onClick: () => {
|
|
217
|
+
if (!activeItemKey)
|
|
218
|
+
return;
|
|
219
|
+
const failedKey = activeItemKey;
|
|
220
|
+
setFailedResources(current => {
|
|
182
221
|
const next = new Set(current);
|
|
183
|
-
next.delete(
|
|
222
|
+
next.delete(failedKey);
|
|
184
223
|
return next;
|
|
185
224
|
});
|
|
186
|
-
setAttempts(current => ({ ...current, [
|
|
187
|
-
}, children: labels.retry })] }) :
|
|
225
|
+
setAttempts(current => ({ ...current, [failedKey]: (current[failedKey] ?? 0) + 1 }));
|
|
226
|
+
}, children: labels.retry })] }) : activeItemType === 'video' && open ? _jsx(VideoResource, { alt: activeItem.alt, labels: videoLabels, onError: () => setFailedResources(current => new Set(current).add(activeItemKey)), onLoad: () => setFailedResources(current => {
|
|
227
|
+
if (!current.has(activeItemKey))
|
|
228
|
+
return current;
|
|
229
|
+
const next = new Set(current);
|
|
230
|
+
next.delete(activeItemKey);
|
|
231
|
+
return next;
|
|
232
|
+
}), poster: activeItem.poster, src: activeItem.src }, activeItemKey) : imageProps ? _jsx(PanZoom, { "aria-label": labels.panZoom(activeItem), centerOnInit: true, className: "inkra-image-viewer__pan-zoom", fitOnInit: "contain", keyboard: zoomable, maxScale: maxScale, minScale: minScale, pan: zoomable, pinch: zoomable, readOnly: !zoomable, ref: panZoomRef, wheel: zoomable ? wheel : 'disabled', children: _jsx("div", { className: "inkra-image-viewer__image-transform", style: { transform: `rotate(${rotation}deg)` }, children: (renderImage ?? renderDefaultImage)(activeItem, imageProps) }) }, activeItemKey) : null, total > 1 && activeItemType === 'image' ? _jsxs("div", { className: "inkra-image-viewer__navigation", children: [_jsx(Button, { "aria-label": labels.previous, className: "inkra-image-viewer__previous", disabled: !loop && atStart, onClick: () => step(-1), size: "icon", variant: "secondary", children: _jsx(PencilIcon, { decorative: true, paths: chevronLeftPaths, size: 30, sourceSize: 24 }) }), _jsx(Button, { "aria-label": labels.next, className: "inkra-image-viewer__next", disabled: !loop && atEnd, onClick: () => step(1), size: "icon", variant: "secondary", children: _jsx(PencilIcon, { decorative: true, paths: chevronRightPaths, size: 30, sourceSize: 24 }) })] }) : null] }) }) : _jsxs("div", { className: "inkra-image-viewer__state", ref: stageRef, tabIndex: 0, children: [_jsx(PencilSurface, { "aria-hidden": "true", className: "inkra-image-viewer__stage-focus", radius: 6, seed: "image-viewer-empty-stage", variant: "outline" }), emptyState ?? _jsxs(_Fragment, { children: [_jsx(PencilIcon, { decorative: true, paths: imageOffPaths, size: 48, sourceSize: 24 }), _jsx(PencilText, { as: "h3", children: labels.emptyTitle }), _jsx(PencilText, { as: "p", children: labels.emptyDescription })] })] }) }), (activeItem || (showThumbnails && total > 1)) ? _jsxs("div", { className: "inkra-image-viewer__bottom", children: [_jsxs("div", { className: "inkra-image-viewer__bottom-row", children: [total > 0 ? _jsx(PencilText, { as: "span", className: "inkra-image-viewer__counter", dir: "auto", children: labels.imagePosition(activeIndex + 1, total) }) : null, activeItem && activeItemType === 'image' && !failed && (zoomable || rotatable) ? _jsxs("div", { className: "inkra-image-viewer__tools", role: "group", "aria-label": labels.panZoom(activeItem), "data-pan-zoom-no-drag": true, children: [_jsx(PencilSurface, { "aria-hidden": "true", className: "inkra-image-viewer__tools-paper", radius: 8, seed: `image-viewer-tools-${activeItem.id}`, variant: "paper" }), zoomable ? _jsxs(_Fragment, { children: [_jsx(Button, { "aria-label": labels.zoomOut, onClick: () => void panZoomRef.current?.zoomOut(), size: "icon-sm", variant: "outline", children: _jsx(PencilIcon, { decorative: true, paths: zoomOutPaths, size: 24, sourceSize: 24 }) }), _jsx(Button, { "aria-label": labels.resetZoom, onClick: () => void panZoomRef.current?.reset(), size: "sm", variant: "outline", children: "100%" }), _jsx(Button, { "aria-label": labels.zoomIn, onClick: () => void panZoomRef.current?.zoomIn(), size: "icon-sm", variant: "outline", children: _jsx(PencilIcon, { decorative: true, paths: zoomInPaths, size: 24, sourceSize: 24 }) })] }) : null, rotatable ? _jsx(Button, { "aria-label": labels.rotate, onClick: () => {
|
|
188
233
|
setRotation(current => (current + 90) % 360);
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
234
|
+
if (fitFrameRef.current !== null)
|
|
235
|
+
cancelAnimationFrame(fitFrameRef.current);
|
|
236
|
+
const resourceKey = activeItemKey;
|
|
237
|
+
fitFrameRef.current = requestAnimationFrame(() => {
|
|
238
|
+
fitFrameRef.current = null;
|
|
239
|
+
if (activeResourceRef.current === resourceKey)
|
|
240
|
+
void panZoomRef.current?.fit({ mode: 'contain' });
|
|
241
|
+
});
|
|
242
|
+
}, size: "icon-sm", variant: "outline", children: _jsx(PencilIcon, { decorative: true, paths: rotatePaths, size: 24, sourceSize: 24 }) }) : null] }) : null, activeItem && activeItemType === 'video' && total > 1 ? _jsxs("div", { className: "inkra-image-viewer__video-navigation", role: "group", "aria-label": labels.thumbnails, children: [_jsx(Button, { "aria-label": labels.previous, disabled: !loop && atStart, onClick: () => step(-1), size: "icon-sm", variant: "secondary", children: _jsx(PencilIcon, { decorative: true, paths: chevronLeftPaths, size: 24, sourceSize: 24 }) }), _jsx(Button, { "aria-label": labels.next, disabled: !loop && atEnd, onClick: () => step(1), size: "icon-sm", variant: "secondary", children: _jsx(PencilIcon, { decorative: true, paths: chevronRightPaths, size: 24, sourceSize: 24 }) })] }) : null] }), showThumbnails && total > 1 ? _jsx("div", { "aria-label": labels.thumbnails, className: "inkra-image-viewer__thumbnails", ref: thumbnailsRef, role: "listbox", children: items.map((item, index) => {
|
|
243
|
+
const selected = index === activeIndex;
|
|
244
|
+
return _jsxs("button", { "aria-label": labels.thumbnail(item, index + 1, total), "aria-selected": selected, className: "inkra-image-viewer__thumbnail", onClick: () => selectIndex(index, 'thumbnail'), onKeyDown: (event) => {
|
|
245
|
+
const rtl = directionFor(event.currentTarget) === 'rtl';
|
|
246
|
+
let nextIndex = index;
|
|
247
|
+
if (event.key === 'ArrowLeft')
|
|
248
|
+
nextIndex = nextImageViewerIndex(total, index, rtl ? 1 : -1, loop);
|
|
249
|
+
else if (event.key === 'ArrowRight')
|
|
250
|
+
nextIndex = nextImageViewerIndex(total, index, rtl ? -1 : 1, loop);
|
|
251
|
+
else if (event.key === 'Home')
|
|
252
|
+
nextIndex = 0;
|
|
253
|
+
else if (event.key === 'End')
|
|
254
|
+
nextIndex = total - 1;
|
|
255
|
+
else
|
|
256
|
+
return;
|
|
257
|
+
event.preventDefault();
|
|
258
|
+
selectThumbnail(nextIndex, event.key === 'Home' ? 'home' : event.key === 'End' ? 'end' : 'thumbnail');
|
|
259
|
+
}, ref: (node) => {
|
|
260
|
+
if (node)
|
|
261
|
+
thumbnailRefs.current.set(item.id, node);
|
|
262
|
+
else
|
|
263
|
+
thumbnailRefs.current.delete(item.id);
|
|
264
|
+
}, role: "option", tabIndex: selected ? 0 : -1, type: "button", children: [_jsx(PencilSurface, { as: "span", className: "inkra-image-viewer__thumbnail-surface", radius: 6, variant: selected ? 'selection' : 'outline', children: (item.thumbnailSrc ?? item.poster ?? (item.type === 'video' ? undefined : item.src))
|
|
265
|
+
? _jsx("img", { alt: "", className: "inkra-image-viewer__thumbnail-image", draggable: false, src: item.thumbnailSrc ?? item.poster ?? item.src })
|
|
266
|
+
: _jsx(PencilIcon, { decorative: true, paths: item.type === 'video' ? playPaths : imageOffPaths, size: 30, sourceSize: 24 }) }), item.type === 'video' ? _jsx(PencilSurface, { as: "span", "aria-hidden": "true", className: "inkra-image-viewer__thumbnail-video", radius: 6, variant: "paper", children: _jsx(PencilIcon, { decorative: true, paths: playPaths, size: 18, sourceSize: 24 }) }) : null, _jsx(PencilSurface, { as: "span", "aria-hidden": "true", className: "inkra-image-viewer__thumbnail-focus", radius: 6, variant: "outline" })] }, item.id);
|
|
267
|
+
}) }) : null] }) : null] })] }));
|
|
214
268
|
});
|
|
215
|
-
export const ImageViewer = Object.freeze({
|
|
269
|
+
export const ImageViewer = /* @__PURE__ */ Object.freeze({
|
|
216
270
|
Root: ImageViewerRoot,
|
|
217
271
|
Trigger: ImageViewerTrigger,
|
|
218
272
|
Content: ImageViewerContent,
|
|
219
273
|
});
|
|
274
|
+
export const MediaViewer = ImageViewer;
|
|
220
275
|
export { nextImageViewerIndex, resolveImageViewerSelection } from './model.js';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
function number(attributes, name) {
|
|
2
|
+
const value = Number(attributes[name]);
|
|
3
|
+
return Number.isFinite(value) ? value : 0;
|
|
4
|
+
}
|
|
5
|
+
export function lucideNodesToPencilPaths(nodes) {
|
|
6
|
+
return nodes.flatMap(([tag, attributes]) => {
|
|
7
|
+
if (tag === 'path' && attributes.d)
|
|
8
|
+
return [{ d: attributes.d }];
|
|
9
|
+
if (tag === 'line')
|
|
10
|
+
return [{ d: `M${number(attributes, 'x1')} ${number(attributes, 'y1')}L${number(attributes, 'x2')} ${number(attributes, 'y2')}` }];
|
|
11
|
+
if (tag === 'circle') {
|
|
12
|
+
const cx = number(attributes, 'cx');
|
|
13
|
+
const cy = number(attributes, 'cy');
|
|
14
|
+
const radius = number(attributes, 'r');
|
|
15
|
+
return [{ d: `M${cx - radius} ${cy}A${radius} ${radius} 0 1 0 ${cx + radius} ${cy}A${radius} ${radius} 0 1 0 ${cx - radius} ${cy}` }];
|
|
16
|
+
}
|
|
17
|
+
return [];
|
|
18
|
+
});
|
|
19
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { Button, ButtonGroup, ButtonLink } from './Button.js';
|
|
2
|
+
export * from './i18n/index.js';
|
|
3
|
+
export * from './theme/index.js';
|
|
2
4
|
export * from './overlay/index.js';
|
|
3
5
|
export * from './form/index.js';
|
|
4
6
|
export * from './input-number/index.js';
|
|
@@ -38,6 +40,7 @@ export * from './audio-player/index.js';
|
|
|
38
40
|
export * from './carousel/index.js';
|
|
39
41
|
export * from './choice-grid/index.js';
|
|
40
42
|
export * from './color-picker/index.js';
|
|
43
|
+
export * from './color-swatch/index.js';
|
|
41
44
|
export * from './context-menu/index.js';
|
|
42
45
|
export * from './dockable-panel/index.js';
|
|
43
46
|
export * from './editable/index.js';
|
|
@@ -62,6 +65,7 @@ export * from './transfer/index.js';
|
|
|
62
65
|
export * from './tree-select/index.js';
|
|
63
66
|
export * from './typewriter/index.js';
|
|
64
67
|
export * from './virtual-list/index.js';
|
|
68
|
+
export * from './markdown/index.js';
|
|
65
69
|
export type { ButtonGroupOrientation, ButtonAlign, ButtonLoadingPosition, ButtonGroupProps, ButtonLinkProps, ButtonProps, ButtonSize, ButtonVariant, } from './Button.js';
|
|
66
70
|
export { PencilLine } from './PencilLine.js';
|
|
67
71
|
export type { PencilLineOrientation, PencilLineProps } from './PencilLine.js';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { Button, ButtonGroup, ButtonLink } from './Button.js';
|
|
2
|
+
export * from './i18n/index.js';
|
|
3
|
+
export * from './theme/index.js';
|
|
2
4
|
export * from './overlay/index.js';
|
|
3
5
|
export * from './form/index.js';
|
|
4
6
|
export * from './input-number/index.js';
|
|
@@ -38,6 +40,7 @@ export * from './audio-player/index.js';
|
|
|
38
40
|
export * from './carousel/index.js';
|
|
39
41
|
export * from './choice-grid/index.js';
|
|
40
42
|
export * from './color-picker/index.js';
|
|
43
|
+
export * from './color-swatch/index.js';
|
|
41
44
|
export * from './context-menu/index.js';
|
|
42
45
|
export * from './dockable-panel/index.js';
|
|
43
46
|
export * from './editable/index.js';
|
|
@@ -62,6 +65,7 @@ export * from './transfer/index.js';
|
|
|
62
65
|
export * from './tree-select/index.js';
|
|
63
66
|
export * from './typewriter/index.js';
|
|
64
67
|
export * from './virtual-list/index.js';
|
|
68
|
+
export * from './markdown/index.js';
|
|
65
69
|
export { PencilLine } from './PencilLine.js';
|
|
66
70
|
export { PencilSurface } from './PencilSurface.js';
|
|
67
71
|
export { PencilText } from './PencilText.js';
|