react-audio-wavekit 0.1.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/LICENSE +116 -0
- package/README.md +231 -0
- package/dist/constants.cjs +20 -0
- package/dist/constants.js +20 -0
- package/dist/index.cjs +12 -0
- package/dist/index.d.ts +235 -0
- package/dist/index.js +12 -0
- package/dist/recorder/live-recorder/index.cjs +125 -0
- package/dist/recorder/live-recorder/index.js +125 -0
- package/dist/recorder/live-streaming/recorder/recorder-compound.cjs +244 -0
- package/dist/recorder/live-streaming/recorder/recorder-compound.js +244 -0
- package/dist/recorder/live-streaming/recorder/recorder-context.cjs +20 -0
- package/dist/recorder/live-streaming/recorder/recorder-context.js +20 -0
- package/dist/recorder/live-streaming/stack-recorder/stack-recorder-compound.cjs +126 -0
- package/dist/recorder/live-streaming/stack-recorder/stack-recorder-compound.js +126 -0
- package/dist/recorder/live-streaming/use-recording-amplitudes.cjs +92 -0
- package/dist/recorder/live-streaming/use-recording-amplitudes.js +92 -0
- package/dist/recorder/use-audio-analyser.cjs +59 -0
- package/dist/recorder/use-audio-analyser.js +59 -0
- package/dist/recorder/use-audio-recorder.cjs +139 -0
- package/dist/recorder/use-audio-recorder.js +139 -0
- package/dist/recorder/util-mime-type.cjs +15 -0
- package/dist/recorder/util-mime-type.js +15 -0
- package/dist/waveform/index.cjs +73 -0
- package/dist/waveform/index.js +73 -0
- package/dist/waveform/util-audio-decoder.cjs +45 -0
- package/dist/waveform/util-audio-decoder.js +45 -0
- package/dist/waveform/util-suspense.cjs +24 -0
- package/dist/waveform/util-suspense.js +24 -0
- package/dist/waveform/waveform-renderer.cjs +105 -0
- package/dist/waveform/waveform-renderer.js +105 -0
- package/package.json +74 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
async function decodeAudioBlob(blob, sampleCount) {
|
|
4
|
+
const audioContext = new AudioContext();
|
|
5
|
+
const arrayBuffer = await blob.arrayBuffer();
|
|
6
|
+
if (arrayBuffer.byteLength === 0) {
|
|
7
|
+
await audioContext.close();
|
|
8
|
+
throw new Error("Audio blob is empty");
|
|
9
|
+
}
|
|
10
|
+
let audioBuffer;
|
|
11
|
+
try {
|
|
12
|
+
audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
|
|
13
|
+
} catch {
|
|
14
|
+
await audioContext.close();
|
|
15
|
+
throw new Error(
|
|
16
|
+
`Unable to decode audio data (type: ${blob.type}, size: ${blob.size} bytes). This may be due to an unsupported audio format or corrupted data.`
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
const channelData = audioBuffer.getChannelData(0);
|
|
20
|
+
const blockSize = Math.floor(channelData.length / sampleCount);
|
|
21
|
+
const peaks = [];
|
|
22
|
+
for (let i = 0; i < sampleCount; i++) {
|
|
23
|
+
const start = i * blockSize;
|
|
24
|
+
let sum = 0;
|
|
25
|
+
for (let j = 0; j < blockSize; j++) {
|
|
26
|
+
sum += Math.abs(channelData[start + j] || 0);
|
|
27
|
+
}
|
|
28
|
+
peaks.push(sum / blockSize);
|
|
29
|
+
}
|
|
30
|
+
const maxPeak = Math.max(...peaks);
|
|
31
|
+
const normalizedPeaks = maxPeak > 0 ? peaks.map((p) => p / maxPeak) : peaks;
|
|
32
|
+
await audioContext.close();
|
|
33
|
+
return normalizedPeaks;
|
|
34
|
+
}
|
|
35
|
+
const audioDataCache = /* @__PURE__ */ new WeakMap();
|
|
36
|
+
function getAudioData(blob, sampleCount) {
|
|
37
|
+
let promise = audioDataCache.get(blob);
|
|
38
|
+
if (!promise) {
|
|
39
|
+
promise = decodeAudioBlob(blob, sampleCount);
|
|
40
|
+
audioDataCache.set(blob, promise);
|
|
41
|
+
}
|
|
42
|
+
return promise;
|
|
43
|
+
}
|
|
44
|
+
exports.decodeAudioBlob = decodeAudioBlob;
|
|
45
|
+
exports.getAudioData = getAudioData;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
async function decodeAudioBlob(blob, sampleCount) {
|
|
2
|
+
const audioContext = new AudioContext();
|
|
3
|
+
const arrayBuffer = await blob.arrayBuffer();
|
|
4
|
+
if (arrayBuffer.byteLength === 0) {
|
|
5
|
+
await audioContext.close();
|
|
6
|
+
throw new Error("Audio blob is empty");
|
|
7
|
+
}
|
|
8
|
+
let audioBuffer;
|
|
9
|
+
try {
|
|
10
|
+
audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
|
|
11
|
+
} catch {
|
|
12
|
+
await audioContext.close();
|
|
13
|
+
throw new Error(
|
|
14
|
+
`Unable to decode audio data (type: ${blob.type}, size: ${blob.size} bytes). This may be due to an unsupported audio format or corrupted data.`
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
const channelData = audioBuffer.getChannelData(0);
|
|
18
|
+
const blockSize = Math.floor(channelData.length / sampleCount);
|
|
19
|
+
const peaks = [];
|
|
20
|
+
for (let i = 0; i < sampleCount; i++) {
|
|
21
|
+
const start = i * blockSize;
|
|
22
|
+
let sum = 0;
|
|
23
|
+
for (let j = 0; j < blockSize; j++) {
|
|
24
|
+
sum += Math.abs(channelData[start + j] || 0);
|
|
25
|
+
}
|
|
26
|
+
peaks.push(sum / blockSize);
|
|
27
|
+
}
|
|
28
|
+
const maxPeak = Math.max(...peaks);
|
|
29
|
+
const normalizedPeaks = maxPeak > 0 ? peaks.map((p) => p / maxPeak) : peaks;
|
|
30
|
+
await audioContext.close();
|
|
31
|
+
return normalizedPeaks;
|
|
32
|
+
}
|
|
33
|
+
const audioDataCache = /* @__PURE__ */ new WeakMap();
|
|
34
|
+
function getAudioData(blob, sampleCount) {
|
|
35
|
+
let promise = audioDataCache.get(blob);
|
|
36
|
+
if (!promise) {
|
|
37
|
+
promise = decodeAudioBlob(blob, sampleCount);
|
|
38
|
+
audioDataCache.set(blob, promise);
|
|
39
|
+
}
|
|
40
|
+
return promise;
|
|
41
|
+
}
|
|
42
|
+
export {
|
|
43
|
+
decodeAudioBlob,
|
|
44
|
+
getAudioData
|
|
45
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
function unwrapPromise(promise) {
|
|
4
|
+
const cached = promise;
|
|
5
|
+
if (!cached._status) {
|
|
6
|
+
cached._status = "pending";
|
|
7
|
+
cached._value = void 0;
|
|
8
|
+
cached._error = void 0;
|
|
9
|
+
cached.then(
|
|
10
|
+
(value) => {
|
|
11
|
+
cached._status = "fulfilled";
|
|
12
|
+
cached._value = value;
|
|
13
|
+
},
|
|
14
|
+
(error) => {
|
|
15
|
+
cached._status = "rejected";
|
|
16
|
+
cached._error = error;
|
|
17
|
+
}
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
if (cached._status === "rejected") throw cached._error;
|
|
21
|
+
if (cached._status === "pending") throw promise;
|
|
22
|
+
return cached._value;
|
|
23
|
+
}
|
|
24
|
+
exports.unwrapPromise = unwrapPromise;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
function unwrapPromise(promise) {
|
|
2
|
+
const cached = promise;
|
|
3
|
+
if (!cached._status) {
|
|
4
|
+
cached._status = "pending";
|
|
5
|
+
cached._value = void 0;
|
|
6
|
+
cached._error = void 0;
|
|
7
|
+
cached.then(
|
|
8
|
+
(value) => {
|
|
9
|
+
cached._status = "fulfilled";
|
|
10
|
+
cached._value = value;
|
|
11
|
+
},
|
|
12
|
+
(error) => {
|
|
13
|
+
cached._status = "rejected";
|
|
14
|
+
cached._error = error;
|
|
15
|
+
}
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
if (cached._status === "rejected") throw cached._error;
|
|
19
|
+
if (cached._status === "pending") throw promise;
|
|
20
|
+
return cached._value;
|
|
21
|
+
}
|
|
22
|
+
export {
|
|
23
|
+
unwrapPromise
|
|
24
|
+
};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const jsxRuntime = require("react/jsx-runtime");
|
|
4
|
+
const react = require("react");
|
|
5
|
+
const constants = require("../constants.cjs");
|
|
6
|
+
const WaveformRenderer = react.forwardRef(function WaveformRenderer2({ peaks, appearance, currentTime, duration, onSeek, onClick, style, ...props }, ref) {
|
|
7
|
+
const canvasRef = react.useRef(null);
|
|
8
|
+
const sizeRef = react.useRef({ width: 0, height: 0 });
|
|
9
|
+
const rafRef = react.useRef(0);
|
|
10
|
+
react.useImperativeHandle(ref, () => ({
|
|
11
|
+
canvas: canvasRef.current
|
|
12
|
+
}));
|
|
13
|
+
const drawWaveform = react.useCallback(() => {
|
|
14
|
+
const canvas = canvasRef.current;
|
|
15
|
+
const { width, height } = sizeRef.current;
|
|
16
|
+
if (!canvas || !peaks || width === 0 || height === 0) return;
|
|
17
|
+
const ctx = canvas.getContext("2d");
|
|
18
|
+
if (!ctx) return;
|
|
19
|
+
const dpr = window.devicePixelRatio || 1;
|
|
20
|
+
const targetWidth = width * dpr;
|
|
21
|
+
const targetHeight = height * dpr;
|
|
22
|
+
if (canvas.width !== targetWidth || canvas.height !== targetHeight) {
|
|
23
|
+
canvas.width = targetWidth;
|
|
24
|
+
canvas.height = targetHeight;
|
|
25
|
+
}
|
|
26
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
27
|
+
ctx.clearRect(0, 0, width, height);
|
|
28
|
+
const barColor = appearance?.barColor ?? constants.DEFAULT_WAVEFORM_APPEARANCE.barColor;
|
|
29
|
+
const barWidth = appearance?.barWidth ?? constants.DEFAULT_WAVEFORM_APPEARANCE.barWidth;
|
|
30
|
+
const barGap = appearance?.barGap ?? constants.DEFAULT_WAVEFORM_APPEARANCE.barGap;
|
|
31
|
+
const barRadius = appearance?.barRadius ?? constants.DEFAULT_WAVEFORM_APPEARANCE.barRadius;
|
|
32
|
+
const barHeightScale = appearance?.barHeightScale ?? constants.DEFAULT_WAVEFORM_APPEARANCE.barHeightScale;
|
|
33
|
+
const totalBarWidth = barWidth + barGap;
|
|
34
|
+
const barsCount = Math.floor(width / totalBarWidth);
|
|
35
|
+
const step = peaks.length / barsCount;
|
|
36
|
+
ctx.fillStyle = barColor;
|
|
37
|
+
for (let i = 0; i < barsCount; i++) {
|
|
38
|
+
const peakIndex = Math.min(Math.floor(i * step), peaks.length - 1);
|
|
39
|
+
const peak = peaks[peakIndex];
|
|
40
|
+
const barHeight = Math.max(peak * height * barHeightScale, 2);
|
|
41
|
+
const x = i * totalBarWidth;
|
|
42
|
+
const y = (height - barHeight) / 2;
|
|
43
|
+
if (barRadius > 0) {
|
|
44
|
+
ctx.beginPath();
|
|
45
|
+
ctx.roundRect(x, y, barWidth, barHeight, barRadius);
|
|
46
|
+
ctx.fill();
|
|
47
|
+
} else {
|
|
48
|
+
ctx.fillRect(x, y, barWidth, barHeight);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (currentTime !== void 0 && duration !== void 0 && duration > 0) {
|
|
52
|
+
const playheadX = currentTime / duration * width;
|
|
53
|
+
const playheadColor = appearance?.playheadColor ?? constants.DEFAULT_PLAYHEAD_APPEARANCE.playheadColor;
|
|
54
|
+
const playheadWidth = appearance?.playheadWidth ?? constants.DEFAULT_PLAYHEAD_APPEARANCE.playheadWidth;
|
|
55
|
+
ctx.fillStyle = playheadColor;
|
|
56
|
+
ctx.fillRect(playheadX - playheadWidth / 2, 0, playheadWidth, height);
|
|
57
|
+
}
|
|
58
|
+
}, [peaks, appearance, currentTime, duration]);
|
|
59
|
+
react.useEffect(() => {
|
|
60
|
+
const canvas = canvasRef.current;
|
|
61
|
+
if (!canvas) return;
|
|
62
|
+
const resizeObserver = new ResizeObserver((entries) => {
|
|
63
|
+
const entry = entries[0];
|
|
64
|
+
if (!entry) return;
|
|
65
|
+
const { width, height } = entry.contentRect;
|
|
66
|
+
if (sizeRef.current.width === width && sizeRef.current.height === height) return;
|
|
67
|
+
sizeRef.current = { width, height };
|
|
68
|
+
cancelAnimationFrame(rafRef.current);
|
|
69
|
+
rafRef.current = requestAnimationFrame(drawWaveform);
|
|
70
|
+
});
|
|
71
|
+
resizeObserver.observe(canvas);
|
|
72
|
+
return () => {
|
|
73
|
+
resizeObserver.disconnect();
|
|
74
|
+
cancelAnimationFrame(rafRef.current);
|
|
75
|
+
};
|
|
76
|
+
}, [drawWaveform]);
|
|
77
|
+
react.useEffect(() => {
|
|
78
|
+
drawWaveform();
|
|
79
|
+
}, [drawWaveform]);
|
|
80
|
+
const handleClick = react.useCallback(
|
|
81
|
+
(e) => {
|
|
82
|
+
if (!onSeek || !duration || duration <= 0) return;
|
|
83
|
+
const canvas = canvasRef.current;
|
|
84
|
+
if (!canvas) return;
|
|
85
|
+
const rect = canvas.getBoundingClientRect();
|
|
86
|
+
const x = e.clientX - rect.left;
|
|
87
|
+
const clickRatio = x / rect.width;
|
|
88
|
+
const newTime = Math.max(0, Math.min(clickRatio * duration, duration));
|
|
89
|
+
onSeek(newTime);
|
|
90
|
+
},
|
|
91
|
+
[onSeek, duration]
|
|
92
|
+
);
|
|
93
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
94
|
+
"canvas",
|
|
95
|
+
{
|
|
96
|
+
ref: canvasRef,
|
|
97
|
+
role: "img",
|
|
98
|
+
"aria-label": "Audio waveform",
|
|
99
|
+
onClick: handleClick,
|
|
100
|
+
style: { cursor: onSeek ? "pointer" : void 0, ...style },
|
|
101
|
+
...props
|
|
102
|
+
}
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
exports.WaveformRenderer = WaveformRenderer;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useRef, useImperativeHandle, useCallback, useEffect } from "react";
|
|
3
|
+
import { DEFAULT_WAVEFORM_APPEARANCE, DEFAULT_PLAYHEAD_APPEARANCE } from "../constants.js";
|
|
4
|
+
const WaveformRenderer = forwardRef(function WaveformRenderer2({ peaks, appearance, currentTime, duration, onSeek, onClick, style, ...props }, ref) {
|
|
5
|
+
const canvasRef = useRef(null);
|
|
6
|
+
const sizeRef = useRef({ width: 0, height: 0 });
|
|
7
|
+
const rafRef = useRef(0);
|
|
8
|
+
useImperativeHandle(ref, () => ({
|
|
9
|
+
canvas: canvasRef.current
|
|
10
|
+
}));
|
|
11
|
+
const drawWaveform = useCallback(() => {
|
|
12
|
+
const canvas = canvasRef.current;
|
|
13
|
+
const { width, height } = sizeRef.current;
|
|
14
|
+
if (!canvas || !peaks || width === 0 || height === 0) return;
|
|
15
|
+
const ctx = canvas.getContext("2d");
|
|
16
|
+
if (!ctx) return;
|
|
17
|
+
const dpr = window.devicePixelRatio || 1;
|
|
18
|
+
const targetWidth = width * dpr;
|
|
19
|
+
const targetHeight = height * dpr;
|
|
20
|
+
if (canvas.width !== targetWidth || canvas.height !== targetHeight) {
|
|
21
|
+
canvas.width = targetWidth;
|
|
22
|
+
canvas.height = targetHeight;
|
|
23
|
+
}
|
|
24
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
25
|
+
ctx.clearRect(0, 0, width, height);
|
|
26
|
+
const barColor = appearance?.barColor ?? DEFAULT_WAVEFORM_APPEARANCE.barColor;
|
|
27
|
+
const barWidth = appearance?.barWidth ?? DEFAULT_WAVEFORM_APPEARANCE.barWidth;
|
|
28
|
+
const barGap = appearance?.barGap ?? DEFAULT_WAVEFORM_APPEARANCE.barGap;
|
|
29
|
+
const barRadius = appearance?.barRadius ?? DEFAULT_WAVEFORM_APPEARANCE.barRadius;
|
|
30
|
+
const barHeightScale = appearance?.barHeightScale ?? DEFAULT_WAVEFORM_APPEARANCE.barHeightScale;
|
|
31
|
+
const totalBarWidth = barWidth + barGap;
|
|
32
|
+
const barsCount = Math.floor(width / totalBarWidth);
|
|
33
|
+
const step = peaks.length / barsCount;
|
|
34
|
+
ctx.fillStyle = barColor;
|
|
35
|
+
for (let i = 0; i < barsCount; i++) {
|
|
36
|
+
const peakIndex = Math.min(Math.floor(i * step), peaks.length - 1);
|
|
37
|
+
const peak = peaks[peakIndex];
|
|
38
|
+
const barHeight = Math.max(peak * height * barHeightScale, 2);
|
|
39
|
+
const x = i * totalBarWidth;
|
|
40
|
+
const y = (height - barHeight) / 2;
|
|
41
|
+
if (barRadius > 0) {
|
|
42
|
+
ctx.beginPath();
|
|
43
|
+
ctx.roundRect(x, y, barWidth, barHeight, barRadius);
|
|
44
|
+
ctx.fill();
|
|
45
|
+
} else {
|
|
46
|
+
ctx.fillRect(x, y, barWidth, barHeight);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (currentTime !== void 0 && duration !== void 0 && duration > 0) {
|
|
50
|
+
const playheadX = currentTime / duration * width;
|
|
51
|
+
const playheadColor = appearance?.playheadColor ?? DEFAULT_PLAYHEAD_APPEARANCE.playheadColor;
|
|
52
|
+
const playheadWidth = appearance?.playheadWidth ?? DEFAULT_PLAYHEAD_APPEARANCE.playheadWidth;
|
|
53
|
+
ctx.fillStyle = playheadColor;
|
|
54
|
+
ctx.fillRect(playheadX - playheadWidth / 2, 0, playheadWidth, height);
|
|
55
|
+
}
|
|
56
|
+
}, [peaks, appearance, currentTime, duration]);
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
const canvas = canvasRef.current;
|
|
59
|
+
if (!canvas) return;
|
|
60
|
+
const resizeObserver = new ResizeObserver((entries) => {
|
|
61
|
+
const entry = entries[0];
|
|
62
|
+
if (!entry) return;
|
|
63
|
+
const { width, height } = entry.contentRect;
|
|
64
|
+
if (sizeRef.current.width === width && sizeRef.current.height === height) return;
|
|
65
|
+
sizeRef.current = { width, height };
|
|
66
|
+
cancelAnimationFrame(rafRef.current);
|
|
67
|
+
rafRef.current = requestAnimationFrame(drawWaveform);
|
|
68
|
+
});
|
|
69
|
+
resizeObserver.observe(canvas);
|
|
70
|
+
return () => {
|
|
71
|
+
resizeObserver.disconnect();
|
|
72
|
+
cancelAnimationFrame(rafRef.current);
|
|
73
|
+
};
|
|
74
|
+
}, [drawWaveform]);
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
drawWaveform();
|
|
77
|
+
}, [drawWaveform]);
|
|
78
|
+
const handleClick = useCallback(
|
|
79
|
+
(e) => {
|
|
80
|
+
if (!onSeek || !duration || duration <= 0) return;
|
|
81
|
+
const canvas = canvasRef.current;
|
|
82
|
+
if (!canvas) return;
|
|
83
|
+
const rect = canvas.getBoundingClientRect();
|
|
84
|
+
const x = e.clientX - rect.left;
|
|
85
|
+
const clickRatio = x / rect.width;
|
|
86
|
+
const newTime = Math.max(0, Math.min(clickRatio * duration, duration));
|
|
87
|
+
onSeek(newTime);
|
|
88
|
+
},
|
|
89
|
+
[onSeek, duration]
|
|
90
|
+
);
|
|
91
|
+
return /* @__PURE__ */ jsx(
|
|
92
|
+
"canvas",
|
|
93
|
+
{
|
|
94
|
+
ref: canvasRef,
|
|
95
|
+
role: "img",
|
|
96
|
+
"aria-label": "Audio waveform",
|
|
97
|
+
onClick: handleClick,
|
|
98
|
+
style: { cursor: onSeek ? "pointer" : void 0, ...style },
|
|
99
|
+
...props
|
|
100
|
+
}
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
export {
|
|
104
|
+
WaveformRenderer
|
|
105
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "react-audio-wavekit",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "CC0-1.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.cjs",
|
|
7
|
+
"module": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"sideEffects": [
|
|
10
|
+
"**/*.css"
|
|
11
|
+
],
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"import": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
},
|
|
18
|
+
"require": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"default": "./dist/index.cjs"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist"
|
|
26
|
+
],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "vite build",
|
|
29
|
+
"dev": "vite build --watch",
|
|
30
|
+
"storybook": "storybook dev -p 6006",
|
|
31
|
+
"build-storybook": "storybook build",
|
|
32
|
+
"deploy-storybook": "storybook build && netlify deploy --dir=storybook-static --prod --site 9971038c-e3a3-4338-acb5-d593e65775a8",
|
|
33
|
+
"check": "biome check . && tsgo --noEmit",
|
|
34
|
+
"fix": "biome check --write .",
|
|
35
|
+
"test": "vitest run",
|
|
36
|
+
"test:watch": "vitest",
|
|
37
|
+
"prepublishOnly": "bun run check && bun run build",
|
|
38
|
+
"release": "npm publish --access public"
|
|
39
|
+
},
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=20.19.0 || >=22.12.0"
|
|
42
|
+
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"react": ">=18.0.0",
|
|
45
|
+
"react-dom": ">=18.0.0"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@biomejs/biome": "2.3.8",
|
|
49
|
+
"@storybook/addon-docs": "10.1.4",
|
|
50
|
+
"@storybook/react": "10.1.4",
|
|
51
|
+
"@storybook/react-vite": "10.1.4",
|
|
52
|
+
"@tailwindcss/vite": "4.1.17",
|
|
53
|
+
"@testing-library/react": "^16.3.0",
|
|
54
|
+
"@types/node": "24.10.1",
|
|
55
|
+
"@types/react": "19.2.7",
|
|
56
|
+
"@types/react-dom": "19.2.3",
|
|
57
|
+
"@typescript/native-preview": "7.0.0-dev.20251205.1",
|
|
58
|
+
"@vitejs/plugin-react": "5.1.1",
|
|
59
|
+
"jsdom": "^27.2.0",
|
|
60
|
+
"lucide-react": "0.555.0",
|
|
61
|
+
"react": "19.2.1",
|
|
62
|
+
"react-dom": "19.2.1",
|
|
63
|
+
"storybook": "10.1.4",
|
|
64
|
+
"tailwindcss": "4.1.17",
|
|
65
|
+
"typescript": "5.9.3",
|
|
66
|
+
"vite": "7.2.6",
|
|
67
|
+
"vite-plugin-dts": "4.5.4",
|
|
68
|
+
"vitest": "^4.0.15"
|
|
69
|
+
},
|
|
70
|
+
"dependencies": {
|
|
71
|
+
"overlayscrollbars": "^2.13.0",
|
|
72
|
+
"overlayscrollbars-react": "^0.5.6"
|
|
73
|
+
}
|
|
74
|
+
}
|