@waveform-playlist/ui-components 7.1.1 → 7.1.3

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/dist/index.mjs CHANGED
@@ -8,10 +8,7 @@ var PositionDisplay = styled.span`
8
8
  color: ${(props) => props.theme?.textColor || "#333"};
9
9
  user-select: none;
10
10
  `;
11
- var AudioPosition = ({
12
- formattedTime,
13
- className
14
- }) => {
11
+ var AudioPosition = ({ formattedTime, className }) => {
15
12
  return /* @__PURE__ */ jsx(PositionDisplay, { className, "aria-label": "Audio position", children: formattedTime });
16
13
  };
17
14
 
@@ -31,7 +28,9 @@ var BaseButton = styled2.button`
31
28
  border-radius: ${(props) => props.theme.borderRadius};
32
29
  cursor: pointer;
33
30
  outline: none;
34
- transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out,
31
+ transition:
32
+ background-color 0.15s ease-in-out,
33
+ border-color 0.15s ease-in-out,
35
34
  box-shadow 0.15s ease-in-out;
36
35
 
37
36
  &:hover:not(:disabled) {
@@ -128,7 +127,9 @@ var BaseInput = styled5.input`
128
127
  border: 1px solid ${(props) => props.theme.inputBorder};
129
128
  border-radius: ${(props) => props.theme.borderRadius};
130
129
  outline: none;
131
- transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
130
+ transition:
131
+ border-color 0.15s ease-in-out,
132
+ box-shadow 0.15s ease-in-out;
132
133
 
133
134
  &::placeholder {
134
135
  color: ${(props) => props.theme.inputPlaceholder};
@@ -196,7 +197,9 @@ var BaseSelect = styled7.select`
196
197
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23666' d='M6 8L1 3h10z'/%3E%3C/svg%3E");
197
198
  background-repeat: no-repeat;
198
199
  background-position: right 0.75rem center;
199
- transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
200
+ transition:
201
+ border-color 0.15s ease-in-out,
202
+ box-shadow 0.15s ease-in-out;
200
203
 
201
204
  &:focus {
202
205
  border-color: ${(props) => props.theme.inputFocusBorder};
@@ -242,7 +245,9 @@ var BaseSlider = styled8.input.attrs({ type: "range" })`
242
245
  border-radius: 50%;
243
246
  cursor: pointer;
244
247
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
245
- transition: transform 0.15s ease, box-shadow 0.15s ease;
248
+ transition:
249
+ transform 0.15s ease,
250
+ box-shadow 0.15s ease;
246
251
  }
247
252
 
248
253
  &::-webkit-slider-thumb:hover {
@@ -259,7 +264,9 @@ var BaseSlider = styled8.input.attrs({ type: "range" })`
259
264
  border-radius: 50%;
260
265
  cursor: pointer;
261
266
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
262
- transition: transform 0.15s ease, box-shadow 0.15s ease;
267
+ transition:
268
+ transform 0.15s ease,
269
+ box-shadow 0.15s ease;
263
270
  }
264
271
 
265
272
  &::-moz-range-thumb:hover {
@@ -327,7 +334,7 @@ var AutomaticScrollCheckbox = ({
327
334
  };
328
335
 
329
336
  // src/components/Channel.tsx
330
- import { useLayoutEffect, useEffect as useEffect2, useCallback as useCallback2, useRef as useRef2 } from "react";
337
+ import { useLayoutEffect } from "react";
331
338
  import styled9 from "styled-components";
332
339
 
333
340
  // src/wfpl-theme.ts
@@ -493,6 +500,7 @@ import {
493
500
  useContext,
494
501
  useEffect,
495
502
  useCallback,
503
+ useMemo,
496
504
  useRef,
497
505
  useSyncExternalStore
498
506
  } from "react";
@@ -529,10 +537,7 @@ var ViewportStoreContext = createContext(null);
529
537
  var EMPTY_SUBSCRIBE = () => () => {
530
538
  };
531
539
  var NULL_SNAPSHOT = () => null;
532
- var ScrollViewportProvider = ({
533
- containerRef,
534
- children
535
- }) => {
540
+ var ScrollViewportProvider = ({ containerRef, children }) => {
536
541
  const storeRef = useRef(null);
537
542
  if (storeRef.current === null) {
538
543
  storeRef.current = new ViewportStore();
@@ -587,12 +592,64 @@ function useScrollViewportSelector(selector) {
587
592
  () => selector(null)
588
593
  );
589
594
  }
595
+ function useVisibleChunkIndices(totalWidth, chunkWidth, originX = 0) {
596
+ const visibleChunkKey = useScrollViewportSelector((viewport) => {
597
+ const totalChunks = Math.ceil(totalWidth / chunkWidth);
598
+ const indices = [];
599
+ for (let i = 0; i < totalChunks; i++) {
600
+ const chunkLeft = i * chunkWidth;
601
+ const thisChunkWidth = Math.min(totalWidth - chunkLeft, chunkWidth);
602
+ if (viewport) {
603
+ const chunkLeftGlobal = originX + chunkLeft;
604
+ const chunkEndGlobal = chunkLeftGlobal + thisChunkWidth;
605
+ if (chunkEndGlobal <= viewport.visibleStart || chunkLeftGlobal >= viewport.visibleEnd) {
606
+ continue;
607
+ }
608
+ }
609
+ indices.push(i);
610
+ }
611
+ return indices.join(",");
612
+ });
613
+ return useMemo(
614
+ () => visibleChunkKey ? visibleChunkKey.split(",").map(Number) : [],
615
+ [visibleChunkKey]
616
+ );
617
+ }
590
618
 
591
- // src/constants.ts
592
- var MAX_CANVAS_WIDTH = 1e3;
619
+ // src/contexts/ClipViewportOrigin.tsx
620
+ import { createContext as createContext2, useContext as useContext2 } from "react";
621
+ import { jsx as jsx4 } from "react/jsx-runtime";
622
+ var ClipViewportOriginContext = createContext2(0);
623
+ var ClipViewportOriginProvider = ({
624
+ originX,
625
+ children
626
+ }) => /* @__PURE__ */ jsx4(ClipViewportOriginContext.Provider, { value: originX, children });
627
+ var useClipViewportOrigin = () => useContext2(ClipViewportOriginContext);
628
+
629
+ // src/hooks/useChunkedCanvasRefs.ts
630
+ import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2 } from "react";
631
+ function useChunkedCanvasRefs() {
632
+ const canvasMapRef = useRef2(/* @__PURE__ */ new Map());
633
+ const canvasRef = useCallback2((canvas) => {
634
+ if (canvas !== null) {
635
+ const idx = parseInt(canvas.dataset.index, 10);
636
+ canvasMapRef.current.set(idx, canvas);
637
+ }
638
+ }, []);
639
+ useEffect2(() => {
640
+ const map = canvasMapRef.current;
641
+ for (const [idx, canvas] of map.entries()) {
642
+ if (!canvas.isConnected) {
643
+ map.delete(idx);
644
+ }
645
+ }
646
+ });
647
+ return { canvasRef, canvasMapRef };
648
+ }
593
649
 
594
650
  // src/components/Channel.tsx
595
- import { jsx as jsx4 } from "react/jsx-runtime";
651
+ import { MAX_CANVAS_WIDTH } from "@waveform-playlist/core";
652
+ import { jsx as jsx5 } from "react/jsx-runtime";
596
653
  function createCanvasFillStyle(ctx, color, width, height) {
597
654
  if (!isWaveformGradient(color)) {
598
655
  return color;
@@ -652,48 +709,12 @@ var Channel = (props) => {
652
709
  transparentBackground = false,
653
710
  drawMode = "inverted"
654
711
  } = props;
655
- const canvasesRef = useRef2([]);
656
- const visibleChunkKey = useScrollViewportSelector((viewport) => {
657
- const totalChunks = Math.ceil(length / MAX_CANVAS_WIDTH);
658
- const indices = [];
659
- for (let i = 0; i < totalChunks; i++) {
660
- const chunkLeft = i * MAX_CANVAS_WIDTH;
661
- const chunkWidth = Math.min(length - chunkLeft, MAX_CANVAS_WIDTH);
662
- if (viewport) {
663
- const chunkEnd = chunkLeft + chunkWidth;
664
- if (chunkEnd <= viewport.visibleStart || chunkLeft >= viewport.visibleEnd) {
665
- continue;
666
- }
667
- }
668
- indices.push(i);
669
- }
670
- return indices.join(",");
671
- });
672
- const visibleChunkIndices = visibleChunkKey ? visibleChunkKey.split(",").map(Number) : [];
673
- const canvasRef = useCallback2(
674
- (canvas) => {
675
- if (canvas !== null) {
676
- const index2 = parseInt(canvas.dataset.index, 10);
677
- canvasesRef.current[index2] = canvas;
678
- }
679
- },
680
- []
681
- );
682
- useEffect2(() => {
683
- const canvases = canvasesRef.current;
684
- for (let i = canvases.length - 1; i >= 0; i--) {
685
- if (canvases[i] && !canvases[i].isConnected) {
686
- delete canvases[i];
687
- }
688
- }
689
- });
712
+ const { canvasRef, canvasMapRef } = useChunkedCanvasRefs();
713
+ const clipOriginX = useClipViewportOrigin();
714
+ const visibleChunkIndices = useVisibleChunkIndices(length, MAX_CANVAS_WIDTH, clipOriginX);
690
715
  useLayoutEffect(() => {
691
- const canvases = canvasesRef.current;
692
716
  const step = barWidth + barGap;
693
- for (let i = 0; i < canvases.length; i++) {
694
- const canvas = canvases[i];
695
- if (!canvas) continue;
696
- const canvasIdx = parseInt(canvas.dataset.index, 10);
717
+ for (const [canvasIdx, canvas] of canvasMapRef.current.entries()) {
697
718
  const globalPixelOffset = canvasIdx * MAX_CANVAS_WIDTH;
698
719
  const ctx = canvas.getContext("2d");
699
720
  const h2 = Math.floor(waveHeight / 2);
@@ -710,12 +731,7 @@ var Channel = (props) => {
710
731
  } else {
711
732
  fillColor = waveOutlineColor;
712
733
  }
713
- ctx.fillStyle = createCanvasFillStyle(
714
- ctx,
715
- fillColor,
716
- canvasWidth,
717
- waveHeight
718
- );
734
+ ctx.fillStyle = createCanvasFillStyle(ctx, fillColor, canvasWidth, waveHeight);
719
735
  const canvasStartGlobal = globalPixelOffset;
720
736
  const canvasEndGlobal = globalPixelOffset + canvasWidth;
721
737
  const firstBarGlobal = Math.floor((canvasStartGlobal - barWidth + step) / step) * step;
@@ -739,6 +755,7 @@ var Channel = (props) => {
739
755
  }
740
756
  }
741
757
  }, [
758
+ canvasMapRef,
742
759
  data,
743
760
  bits,
744
761
  waveHeight,
@@ -749,12 +766,12 @@ var Channel = (props) => {
749
766
  barWidth,
750
767
  barGap,
751
768
  drawMode,
752
- visibleChunkKey
769
+ visibleChunkIndices
753
770
  ]);
754
771
  const waveforms = visibleChunkIndices.map((i) => {
755
772
  const chunkLeft = i * MAX_CANVAS_WIDTH;
756
773
  const currentWidth = Math.min(length - chunkLeft, MAX_CANVAS_WIDTH);
757
- return /* @__PURE__ */ jsx4(
774
+ return /* @__PURE__ */ jsx5(
758
775
  Waveform,
759
776
  {
760
777
  $cssWidth: currentWidth,
@@ -770,7 +787,7 @@ var Channel = (props) => {
770
787
  });
771
788
  const bgColor = waveFillColor;
772
789
  const backgroundCss = transparentBackground ? "transparent" : waveformColorToCss(bgColor);
773
- return /* @__PURE__ */ jsx4(
790
+ return /* @__PURE__ */ jsx5(
774
791
  Wrapper,
775
792
  {
776
793
  $index: index,
@@ -784,8 +801,8 @@ var Channel = (props) => {
784
801
  };
785
802
 
786
803
  // src/components/ErrorBoundary.tsx
787
- import React3 from "react";
788
- import { jsx as jsx5 } from "react/jsx-runtime";
804
+ import React4 from "react";
805
+ import { jsx as jsx6 } from "react/jsx-runtime";
789
806
  var errorContainerStyle = {
790
807
  padding: "16px",
791
808
  background: "#1a1a2e",
@@ -799,7 +816,7 @@ var errorContainerStyle = {
799
816
  alignItems: "center",
800
817
  justifyContent: "center"
801
818
  };
802
- var PlaylistErrorBoundary = class extends React3.Component {
819
+ var PlaylistErrorBoundary = class extends React4.Component {
803
820
  constructor(props) {
804
821
  super(props);
805
822
  this.state = { hasError: false, error: null };
@@ -815,7 +832,7 @@ var PlaylistErrorBoundary = class extends React3.Component {
815
832
  if (this.props.fallback) {
816
833
  return this.props.fallback;
817
834
  }
818
- return /* @__PURE__ */ jsx5("div", { style: errorContainerStyle, children: "Waveform playlist encountered an error. Check console for details." });
835
+ return /* @__PURE__ */ jsx6("div", { style: errorContainerStyle, children: "Waveform playlist encountered an error. Check console for details." });
819
836
  }
820
837
  return this.props.children;
821
838
  }
@@ -828,7 +845,7 @@ import { CSS } from "@dnd-kit/utilities";
828
845
 
829
846
  // src/components/ClipHeader.tsx
830
847
  import styled10 from "styled-components";
831
- import { jsx as jsx6 } from "react/jsx-runtime";
848
+ import { jsx as jsx7 } from "react/jsx-runtime";
832
849
  var CLIP_HEADER_HEIGHT = 22;
833
850
  var HeaderContainer = styled10.div`
834
851
  position: relative;
@@ -868,15 +885,7 @@ var ClipHeaderPresentational = ({
868
885
  trackName,
869
886
  isSelected = false
870
887
  }) => {
871
- return /* @__PURE__ */ jsx6(
872
- HeaderContainer,
873
- {
874
- $isDragging: false,
875
- $interactive: false,
876
- $isSelected: isSelected,
877
- children: /* @__PURE__ */ jsx6(TrackName, { children: trackName })
878
- }
879
- );
888
+ return /* @__PURE__ */ jsx7(HeaderContainer, { $isDragging: false, $interactive: false, $isSelected: isSelected, children: /* @__PURE__ */ jsx7(TrackName, { children: trackName }) });
880
889
  };
881
890
  var ClipHeader = ({
882
891
  clipId,
@@ -888,16 +897,10 @@ var ClipHeader = ({
888
897
  dragHandleProps
889
898
  }) => {
890
899
  if (disableDrag || !dragHandleProps) {
891
- return /* @__PURE__ */ jsx6(
892
- ClipHeaderPresentational,
893
- {
894
- trackName,
895
- isSelected
896
- }
897
- );
900
+ return /* @__PURE__ */ jsx7(ClipHeaderPresentational, { trackName, isSelected });
898
901
  }
899
902
  const { attributes, listeners, setActivatorNodeRef } = dragHandleProps;
900
- return /* @__PURE__ */ jsx6(
903
+ return /* @__PURE__ */ jsx7(
901
904
  HeaderContainer,
902
905
  {
903
906
  ref: setActivatorNodeRef,
@@ -906,15 +909,15 @@ var ClipHeader = ({
906
909
  $isSelected: isSelected,
907
910
  ...listeners,
908
911
  ...attributes,
909
- children: /* @__PURE__ */ jsx6(TrackName, { children: trackName })
912
+ children: /* @__PURE__ */ jsx7(TrackName, { children: trackName })
910
913
  }
911
914
  );
912
915
  };
913
916
 
914
917
  // src/components/ClipBoundary.tsx
915
- import React4 from "react";
918
+ import React5 from "react";
916
919
  import styled11 from "styled-components";
917
- import { jsx as jsx7 } from "react/jsx-runtime";
920
+ import { jsx as jsx8 } from "react/jsx-runtime";
918
921
  var CLIP_BOUNDARY_WIDTH = 8;
919
922
  var CLIP_BOUNDARY_WIDTH_TOUCH = 24;
920
923
  var BoundaryContainer = styled11.div`
@@ -954,12 +957,12 @@ var ClipBoundary = ({
954
957
  dragHandleProps,
955
958
  touchOptimized = false
956
959
  }) => {
957
- const [isHovered, setIsHovered] = React4.useState(false);
960
+ const [isHovered, setIsHovered] = React5.useState(false);
958
961
  if (!dragHandleProps) {
959
962
  return null;
960
963
  }
961
964
  const { attributes, listeners, setActivatorNodeRef, isDragging } = dragHandleProps;
962
- return /* @__PURE__ */ jsx7(
965
+ return /* @__PURE__ */ jsx8(
963
966
  BoundaryContainer,
964
967
  {
965
968
  ref: setActivatorNodeRef,
@@ -979,7 +982,7 @@ var ClipBoundary = ({
979
982
 
980
983
  // src/components/FadeOverlay.tsx
981
984
  import styled12, { useTheme } from "styled-components";
982
- import { jsx as jsx8 } from "react/jsx-runtime";
985
+ import { jsx as jsx9 } from "react/jsx-runtime";
983
986
  var FadeContainer = styled12.div.attrs((props) => ({
984
987
  style: {
985
988
  left: `${props.$left}px`,
@@ -1036,17 +1039,11 @@ var FadeOverlay = ({
1036
1039
  const theme = useTheme();
1037
1040
  if (width < 1) return null;
1038
1041
  const fillColor = color || theme?.fadeOverlayColor || "rgba(0, 0, 0, 0.4)";
1039
- return /* @__PURE__ */ jsx8(FadeContainer, { $left: left, $width: width, $type: type, children: /* @__PURE__ */ jsx8(FadeSvg, { $type: type, viewBox: `0 0 ${width} 100`, preserveAspectRatio: "none", children: /* @__PURE__ */ jsx8(
1040
- "path",
1041
- {
1042
- d: generateFadePath(width, 100, curveType),
1043
- fill: fillColor
1044
- }
1045
- ) }) });
1042
+ return /* @__PURE__ */ jsx9(FadeContainer, { $left: left, $width: width, $type: type, children: /* @__PURE__ */ jsx9(FadeSvg, { $type: type, viewBox: `0 0 ${width} 100`, preserveAspectRatio: "none", children: /* @__PURE__ */ jsx9("path", { d: generateFadePath(width, 100, curveType), fill: fillColor }) }) });
1046
1043
  };
1047
1044
 
1048
1045
  // src/components/Clip.tsx
1049
- import { Fragment, jsx as jsx9, jsxs as jsxs2 } from "react/jsx-runtime";
1046
+ import { Fragment, jsx as jsx10, jsxs as jsxs2 } from "react/jsx-runtime";
1050
1047
  var ClipContainer = styled13.div.attrs((props) => ({
1051
1048
  style: props.$isOverlay ? {} : {
1052
1049
  left: `${props.$left}px`,
@@ -1144,7 +1141,7 @@ var Clip = ({
1144
1141
  "data-track-id": trackId,
1145
1142
  onMouseDown,
1146
1143
  children: [
1147
- showHeader && /* @__PURE__ */ jsx9(
1144
+ showHeader && /* @__PURE__ */ jsx10(
1148
1145
  ClipHeader,
1149
1146
  {
1150
1147
  clipId,
@@ -1156,9 +1153,9 @@ var Clip = ({
1156
1153
  dragHandleProps: enableDrag ? { attributes, listeners, setActivatorNodeRef } : void 0
1157
1154
  }
1158
1155
  ),
1159
- /* @__PURE__ */ jsxs2(ChannelsWrapper, { $isOverlay: isOverlay, children: [
1156
+ /* @__PURE__ */ jsx10(ClipViewportOriginProvider, { originX: left, children: /* @__PURE__ */ jsxs2(ChannelsWrapper, { $isOverlay: isOverlay, children: [
1160
1157
  children,
1161
- showFades && fadeIn && fadeIn.duration > 0 && /* @__PURE__ */ jsx9(
1158
+ showFades && fadeIn && fadeIn.duration > 0 && /* @__PURE__ */ jsx10(
1162
1159
  FadeOverlay,
1163
1160
  {
1164
1161
  left: 0,
@@ -1167,7 +1164,7 @@ var Clip = ({
1167
1164
  curveType: fadeIn.type
1168
1165
  }
1169
1166
  ),
1170
- showFades && fadeOut && fadeOut.duration > 0 && /* @__PURE__ */ jsx9(
1167
+ showFades && fadeOut && fadeOut.duration > 0 && /* @__PURE__ */ jsx10(
1171
1168
  FadeOverlay,
1172
1169
  {
1173
1170
  left: width - Math.floor(fadeOut.duration * sampleRate / samplesPerPixel),
@@ -1176,9 +1173,9 @@ var Clip = ({
1176
1173
  curveType: fadeOut.type
1177
1174
  }
1178
1175
  )
1179
- ] }),
1176
+ ] }) }),
1180
1177
  showHeader && !disableHeaderDrag && !isOverlay && /* @__PURE__ */ jsxs2(Fragment, { children: [
1181
- /* @__PURE__ */ jsx9(
1178
+ /* @__PURE__ */ jsx10(
1182
1179
  ClipBoundary,
1183
1180
  {
1184
1181
  clipId,
@@ -1194,7 +1191,7 @@ var Clip = ({
1194
1191
  }
1195
1192
  }
1196
1193
  ),
1197
- /* @__PURE__ */ jsx9(
1194
+ /* @__PURE__ */ jsx10(
1198
1195
  ClipBoundary,
1199
1196
  {
1200
1197
  clipId,
@@ -1218,7 +1215,7 @@ var Clip = ({
1218
1215
 
1219
1216
  // src/components/MasterVolumeControl.tsx
1220
1217
  import styled14 from "styled-components";
1221
- import { jsx as jsx10, jsxs as jsxs3 } from "react/jsx-runtime";
1218
+ import { jsx as jsx11, jsxs as jsxs3 } from "react/jsx-runtime";
1222
1219
  var VolumeContainer = styled14.div`
1223
1220
  display: inline-flex;
1224
1221
  align-items: center;
@@ -1241,8 +1238,8 @@ var MasterVolumeControl = ({
1241
1238
  onChange(parseFloat(e.target.value) / 100);
1242
1239
  };
1243
1240
  return /* @__PURE__ */ jsxs3(VolumeContainer, { className, children: [
1244
- /* @__PURE__ */ jsx10(VolumeLabel, { htmlFor: "master-gain", children: "Master Volume" }),
1245
- /* @__PURE__ */ jsx10(
1241
+ /* @__PURE__ */ jsx11(VolumeLabel, { htmlFor: "master-gain", children: "Master Volume" }),
1242
+ /* @__PURE__ */ jsx11(
1246
1243
  VolumeSlider,
1247
1244
  {
1248
1245
  min: "0",
@@ -1259,7 +1256,7 @@ var MasterVolumeControl = ({
1259
1256
  // src/components/Playhead.tsx
1260
1257
  import { useRef as useRef3, useEffect as useEffect3 } from "react";
1261
1258
  import styled15 from "styled-components";
1262
- import { jsx as jsx11, jsxs as jsxs4 } from "react/jsx-runtime";
1259
+ import { jsx as jsx12, jsxs as jsxs4 } from "react/jsx-runtime";
1263
1260
  var PlayheadLine = styled15.div.attrs((props) => ({
1264
1261
  style: {
1265
1262
  transform: `translate3d(${props.$position}px, 0, 0)`
@@ -1276,7 +1273,7 @@ var PlayheadLine = styled15.div.attrs((props) => ({
1276
1273
  will-change: transform;
1277
1274
  `;
1278
1275
  var Playhead = ({ position, color = "#ff0000" }) => {
1279
- return /* @__PURE__ */ jsx11(PlayheadLine, { $position: position, $color: color });
1276
+ return /* @__PURE__ */ jsx12(PlayheadLine, { $position: position, $color: color });
1280
1277
  };
1281
1278
  var PlayheadWithMarkerContainer = styled15.div`
1282
1279
  position: absolute;
@@ -1346,7 +1343,16 @@ var PlayheadWithMarker = ({
1346
1343
  animationFrameRef.current = null;
1347
1344
  }
1348
1345
  };
1349
- }, [isPlaying, sampleRate, samplesPerPixel, controlsOffset, currentTimeRef, playbackStartTimeRef, audioStartPositionRef, getAudioContextTime]);
1346
+ }, [
1347
+ isPlaying,
1348
+ sampleRate,
1349
+ samplesPerPixel,
1350
+ controlsOffset,
1351
+ currentTimeRef,
1352
+ playbackStartTimeRef,
1353
+ audioStartPositionRef,
1354
+ getAudioContextTime
1355
+ ]);
1350
1356
  useEffect3(() => {
1351
1357
  if (!isPlaying && containerRef.current) {
1352
1358
  const time = currentTimeRef.current ?? 0;
@@ -1355,15 +1361,15 @@ var PlayheadWithMarker = ({
1355
1361
  }
1356
1362
  });
1357
1363
  return /* @__PURE__ */ jsxs4(PlayheadWithMarkerContainer, { ref: containerRef, $color: color, children: [
1358
- /* @__PURE__ */ jsx11(MarkerTriangle, { $color: color }),
1359
- /* @__PURE__ */ jsx11(MarkerLine, { $color: color })
1364
+ /* @__PURE__ */ jsx12(MarkerTriangle, { $color: color }),
1365
+ /* @__PURE__ */ jsx12(MarkerLine, { $color: color })
1360
1366
  ] });
1361
1367
  };
1362
1368
 
1363
1369
  // src/components/Playlist.tsx
1364
1370
  import styled16, { withTheme } from "styled-components";
1365
1371
  import { useRef as useRef4, useCallback as useCallback3 } from "react";
1366
- import { jsx as jsx12, jsxs as jsxs5 } from "react/jsx-runtime";
1372
+ import { jsx as jsx13, jsxs as jsxs5 } from "react/jsx-runtime";
1367
1373
  var Wrapper2 = styled16.div`
1368
1374
  overflow-y: hidden;
1369
1375
  overflow-x: auto;
@@ -1418,40 +1424,36 @@ var Playlist = ({
1418
1424
  "data-playlist-state": playlistState
1419
1425
  }) => {
1420
1426
  const wrapperRef = useRef4(null);
1421
- const handleRef = useCallback3((el) => {
1422
- wrapperRef.current = el;
1423
- scrollContainerRef?.(el);
1424
- }, [scrollContainerRef]);
1425
- return /* @__PURE__ */ jsx12(Wrapper2, { "data-scroll-container": "true", "data-playlist-state": playlistState, ref: handleRef, children: /* @__PURE__ */ jsx12(ScrollViewportProvider, { containerRef: wrapperRef, children: /* @__PURE__ */ jsxs5(
1426
- ScrollContainer,
1427
- {
1428
- $backgroundColor: backgroundColor,
1429
- $width: scrollContainerWidth,
1430
- children: [
1431
- timescale && /* @__PURE__ */ jsx12(TimescaleWrapper, { $width: timescaleWidth, $backgroundColor: timescaleBackgroundColor, children: timescale }),
1432
- /* @__PURE__ */ jsxs5(TracksContainer, { $width: tracksWidth, $backgroundColor: backgroundColor, children: [
1433
- children,
1434
- (onTracksClick || onTracksMouseDown) && /* @__PURE__ */ jsx12(
1435
- ClickOverlay,
1436
- {
1437
- $controlsWidth: controlsWidth,
1438
- $isSelecting: isSelecting,
1439
- onClick: onTracksClick,
1440
- onMouseDown: onTracksMouseDown,
1441
- onMouseMove: onTracksMouseMove,
1442
- onMouseUp: onTracksMouseUp
1443
- }
1444
- )
1445
- ] })
1446
- ]
1447
- }
1448
- ) }) });
1427
+ const handleRef = useCallback3(
1428
+ (el) => {
1429
+ wrapperRef.current = el;
1430
+ scrollContainerRef?.(el);
1431
+ },
1432
+ [scrollContainerRef]
1433
+ );
1434
+ return /* @__PURE__ */ jsx13(Wrapper2, { "data-scroll-container": "true", "data-playlist-state": playlistState, ref: handleRef, children: /* @__PURE__ */ jsx13(ScrollViewportProvider, { containerRef: wrapperRef, children: /* @__PURE__ */ jsxs5(ScrollContainer, { $backgroundColor: backgroundColor, $width: scrollContainerWidth, children: [
1435
+ timescale && /* @__PURE__ */ jsx13(TimescaleWrapper, { $width: timescaleWidth, $backgroundColor: timescaleBackgroundColor, children: timescale }),
1436
+ /* @__PURE__ */ jsxs5(TracksContainer, { $width: tracksWidth, $backgroundColor: backgroundColor, children: [
1437
+ children,
1438
+ (onTracksClick || onTracksMouseDown) && /* @__PURE__ */ jsx13(
1439
+ ClickOverlay,
1440
+ {
1441
+ $controlsWidth: controlsWidth,
1442
+ $isSelecting: isSelecting,
1443
+ onClick: onTracksClick,
1444
+ onMouseDown: onTracksMouseDown,
1445
+ onMouseMove: onTracksMouseMove,
1446
+ onMouseUp: onTracksMouseUp
1447
+ }
1448
+ )
1449
+ ] })
1450
+ ] }) }) });
1449
1451
  };
1450
1452
  var StyledPlaylist = withTheme(Playlist);
1451
1453
 
1452
1454
  // src/components/Selection.tsx
1453
1455
  import styled17 from "styled-components";
1454
- import { jsx as jsx13 } from "react/jsx-runtime";
1456
+ import { jsx as jsx14 } from "react/jsx-runtime";
1455
1457
  var SelectionOverlay = styled17.div.attrs((props) => ({
1456
1458
  style: {
1457
1459
  left: `${props.$left}px`,
@@ -1475,13 +1477,13 @@ var Selection = ({
1475
1477
  if (width <= 0) {
1476
1478
  return null;
1477
1479
  }
1478
- return /* @__PURE__ */ jsx13(SelectionOverlay, { $left: startPosition, $width: width, $color: color, "data-selection": true });
1480
+ return /* @__PURE__ */ jsx14(SelectionOverlay, { $left: startPosition, $width: width, $color: color, "data-selection": true });
1479
1481
  };
1480
1482
 
1481
1483
  // src/components/LoopRegion.tsx
1482
1484
  import { useCallback as useCallback4, useRef as useRef5, useState } from "react";
1483
1485
  import styled18 from "styled-components";
1484
- import { Fragment as Fragment2, jsx as jsx14, jsxs as jsxs6 } from "react/jsx-runtime";
1486
+ import { Fragment as Fragment2, jsx as jsx15, jsxs as jsxs6 } from "react/jsx-runtime";
1485
1487
  var LoopRegionOverlayDiv = styled18.div.attrs((props) => ({
1486
1488
  style: {
1487
1489
  left: `${props.$left}px`,
@@ -1531,7 +1533,7 @@ var LoopRegion = ({
1531
1533
  return null;
1532
1534
  }
1533
1535
  return /* @__PURE__ */ jsxs6(Fragment2, { children: [
1534
- /* @__PURE__ */ jsx14(
1536
+ /* @__PURE__ */ jsx15(
1535
1537
  LoopRegionOverlayDiv,
1536
1538
  {
1537
1539
  $left: startPosition,
@@ -1540,7 +1542,7 @@ var LoopRegion = ({
1540
1542
  "data-loop-region": true
1541
1543
  }
1542
1544
  ),
1543
- /* @__PURE__ */ jsx14(
1545
+ /* @__PURE__ */ jsx15(
1544
1546
  LoopMarker,
1545
1547
  {
1546
1548
  $left: startPosition,
@@ -1549,7 +1551,7 @@ var LoopRegion = ({
1549
1551
  "data-loop-marker": "start"
1550
1552
  }
1551
1553
  ),
1552
- /* @__PURE__ */ jsx14(
1554
+ /* @__PURE__ */ jsx15(
1553
1555
  LoopMarker,
1554
1556
  {
1555
1557
  $left: endPosition - 2,
@@ -1635,66 +1637,72 @@ var LoopRegionMarkers = ({
1635
1637
  const dragStartPosition = useRef5(0);
1636
1638
  const dragStartEnd = useRef5(0);
1637
1639
  const width = Math.max(0, endPosition - startPosition);
1638
- const handleMarkerMouseDown = useCallback4((e, marker) => {
1639
- e.preventDefault();
1640
- e.stopPropagation();
1641
- setDraggingMarker(marker);
1642
- dragStartX.current = e.clientX;
1643
- dragStartPosition.current = marker === "start" ? startPosition : endPosition;
1644
- const handleMouseMove = (moveEvent) => {
1645
- const delta = moveEvent.clientX - dragStartX.current;
1646
- const newPosition = dragStartPosition.current + delta;
1647
- if (marker === "start") {
1648
- const clampedPosition = Math.max(minPosition, Math.min(endPosition - 10, newPosition));
1649
- onLoopStartChange?.(clampedPosition);
1650
- } else {
1651
- const clampedPosition = Math.max(startPosition + 10, Math.min(maxPosition, newPosition));
1652
- onLoopEndChange?.(clampedPosition);
1653
- }
1654
- };
1655
- const handleMouseUp = () => {
1656
- setDraggingMarker(null);
1657
- document.removeEventListener("mousemove", handleMouseMove);
1658
- document.removeEventListener("mouseup", handleMouseUp);
1659
- };
1660
- document.addEventListener("mousemove", handleMouseMove);
1661
- document.addEventListener("mouseup", handleMouseUp);
1662
- }, [startPosition, endPosition, minPosition, maxPosition, onLoopStartChange, onLoopEndChange]);
1663
- const handleRegionMouseDown = useCallback4((e) => {
1664
- e.preventDefault();
1665
- e.stopPropagation();
1666
- setDraggingMarker("region");
1667
- dragStartX.current = e.clientX;
1668
- dragStartPosition.current = startPosition;
1669
- dragStartEnd.current = endPosition;
1670
- const regionWidth = endPosition - startPosition;
1671
- const handleMouseMove = (moveEvent) => {
1672
- const delta = moveEvent.clientX - dragStartX.current;
1673
- let newStart = dragStartPosition.current + delta;
1674
- let newEnd = dragStartEnd.current + delta;
1675
- if (newStart < minPosition) {
1676
- newStart = minPosition;
1677
- newEnd = minPosition + regionWidth;
1678
- }
1679
- if (newEnd > maxPosition) {
1680
- newEnd = maxPosition;
1681
- newStart = maxPosition - regionWidth;
1682
- }
1683
- onLoopRegionMove?.(newStart, newEnd);
1684
- };
1685
- const handleMouseUp = () => {
1686
- setDraggingMarker(null);
1687
- document.removeEventListener("mousemove", handleMouseMove);
1688
- document.removeEventListener("mouseup", handleMouseUp);
1689
- };
1690
- document.addEventListener("mousemove", handleMouseMove);
1691
- document.addEventListener("mouseup", handleMouseUp);
1692
- }, [startPosition, endPosition, minPosition, maxPosition, onLoopRegionMove]);
1640
+ const handleMarkerMouseDown = useCallback4(
1641
+ (e, marker) => {
1642
+ e.preventDefault();
1643
+ e.stopPropagation();
1644
+ setDraggingMarker(marker);
1645
+ dragStartX.current = e.clientX;
1646
+ dragStartPosition.current = marker === "start" ? startPosition : endPosition;
1647
+ const handleMouseMove = (moveEvent) => {
1648
+ const delta = moveEvent.clientX - dragStartX.current;
1649
+ const newPosition = dragStartPosition.current + delta;
1650
+ if (marker === "start") {
1651
+ const clampedPosition = Math.max(minPosition, Math.min(endPosition - 10, newPosition));
1652
+ onLoopStartChange?.(clampedPosition);
1653
+ } else {
1654
+ const clampedPosition = Math.max(startPosition + 10, Math.min(maxPosition, newPosition));
1655
+ onLoopEndChange?.(clampedPosition);
1656
+ }
1657
+ };
1658
+ const handleMouseUp = () => {
1659
+ setDraggingMarker(null);
1660
+ document.removeEventListener("mousemove", handleMouseMove);
1661
+ document.removeEventListener("mouseup", handleMouseUp);
1662
+ };
1663
+ document.addEventListener("mousemove", handleMouseMove);
1664
+ document.addEventListener("mouseup", handleMouseUp);
1665
+ },
1666
+ [startPosition, endPosition, minPosition, maxPosition, onLoopStartChange, onLoopEndChange]
1667
+ );
1668
+ const handleRegionMouseDown = useCallback4(
1669
+ (e) => {
1670
+ e.preventDefault();
1671
+ e.stopPropagation();
1672
+ setDraggingMarker("region");
1673
+ dragStartX.current = e.clientX;
1674
+ dragStartPosition.current = startPosition;
1675
+ dragStartEnd.current = endPosition;
1676
+ const regionWidth = endPosition - startPosition;
1677
+ const handleMouseMove = (moveEvent) => {
1678
+ const delta = moveEvent.clientX - dragStartX.current;
1679
+ let newStart = dragStartPosition.current + delta;
1680
+ let newEnd = dragStartEnd.current + delta;
1681
+ if (newStart < minPosition) {
1682
+ newStart = minPosition;
1683
+ newEnd = minPosition + regionWidth;
1684
+ }
1685
+ if (newEnd > maxPosition) {
1686
+ newEnd = maxPosition;
1687
+ newStart = maxPosition - regionWidth;
1688
+ }
1689
+ onLoopRegionMove?.(newStart, newEnd);
1690
+ };
1691
+ const handleMouseUp = () => {
1692
+ setDraggingMarker(null);
1693
+ document.removeEventListener("mousemove", handleMouseMove);
1694
+ document.removeEventListener("mouseup", handleMouseUp);
1695
+ };
1696
+ document.addEventListener("mousemove", handleMouseMove);
1697
+ document.addEventListener("mouseup", handleMouseUp);
1698
+ },
1699
+ [startPosition, endPosition, minPosition, maxPosition, onLoopRegionMove]
1700
+ );
1693
1701
  if (width <= 0) {
1694
1702
  return null;
1695
1703
  }
1696
1704
  return /* @__PURE__ */ jsxs6(Fragment2, { children: [
1697
- /* @__PURE__ */ jsx14(
1705
+ /* @__PURE__ */ jsx15(
1698
1706
  TimescaleLoopShade,
1699
1707
  {
1700
1708
  $left: startPosition,
@@ -1705,7 +1713,7 @@ var LoopRegionMarkers = ({
1705
1713
  "data-loop-region-timescale": true
1706
1714
  }
1707
1715
  ),
1708
- /* @__PURE__ */ jsx14(
1716
+ /* @__PURE__ */ jsx15(
1709
1717
  DraggableMarkerHandle,
1710
1718
  {
1711
1719
  $left: startPosition,
@@ -1716,7 +1724,7 @@ var LoopRegionMarkers = ({
1716
1724
  "data-loop-marker-handle": "start"
1717
1725
  }
1718
1726
  ),
1719
- /* @__PURE__ */ jsx14(
1727
+ /* @__PURE__ */ jsx15(
1720
1728
  DraggableMarkerHandle,
1721
1729
  {
1722
1730
  $left: endPosition,
@@ -1755,42 +1763,45 @@ var TimescaleLoopRegion = ({
1755
1763
  const createStartX = useRef5(0);
1756
1764
  const containerRef = useRef5(null);
1757
1765
  const hasLoopRegion = endPosition > startPosition;
1758
- const handleBackgroundMouseDown = useCallback4((e) => {
1759
- const target = e.target;
1760
- if (target.closest("[data-loop-marker-handle]") || target.closest("[data-loop-region-timescale]")) {
1761
- return;
1762
- }
1763
- e.preventDefault();
1764
- setIsCreating(true);
1765
- const rect = containerRef.current?.getBoundingClientRect();
1766
- if (!rect) return;
1767
- const clickX = e.clientX - rect.left;
1768
- const clampedX = Math.max(minPosition, Math.min(maxPosition, clickX));
1769
- createStartX.current = clampedX;
1770
- onLoopRegionChange?.(clampedX, clampedX);
1771
- const handleMouseMove = (moveEvent) => {
1772
- const currentX = moveEvent.clientX - rect.left;
1773
- const clampedCurrentX = Math.max(minPosition, Math.min(maxPosition, currentX));
1774
- const newStart = Math.min(createStartX.current, clampedCurrentX);
1775
- const newEnd = Math.max(createStartX.current, clampedCurrentX);
1776
- onLoopRegionChange?.(newStart, newEnd);
1777
- };
1778
- const handleMouseUp = () => {
1779
- setIsCreating(false);
1780
- document.removeEventListener("mousemove", handleMouseMove);
1781
- document.removeEventListener("mouseup", handleMouseUp);
1782
- };
1783
- document.addEventListener("mousemove", handleMouseMove);
1784
- document.addEventListener("mouseup", handleMouseUp);
1785
- }, [minPosition, maxPosition, onLoopRegionChange]);
1786
- return /* @__PURE__ */ jsx14(
1766
+ const handleBackgroundMouseDown = useCallback4(
1767
+ (e) => {
1768
+ const target = e.target;
1769
+ if (target.closest("[data-loop-marker-handle]") || target.closest("[data-loop-region-timescale]")) {
1770
+ return;
1771
+ }
1772
+ e.preventDefault();
1773
+ setIsCreating(true);
1774
+ const rect = containerRef.current?.getBoundingClientRect();
1775
+ if (!rect) return;
1776
+ const clickX = e.clientX - rect.left;
1777
+ const clampedX = Math.max(minPosition, Math.min(maxPosition, clickX));
1778
+ createStartX.current = clampedX;
1779
+ onLoopRegionChange?.(clampedX, clampedX);
1780
+ const handleMouseMove = (moveEvent) => {
1781
+ const currentX = moveEvent.clientX - rect.left;
1782
+ const clampedCurrentX = Math.max(minPosition, Math.min(maxPosition, currentX));
1783
+ const newStart = Math.min(createStartX.current, clampedCurrentX);
1784
+ const newEnd = Math.max(createStartX.current, clampedCurrentX);
1785
+ onLoopRegionChange?.(newStart, newEnd);
1786
+ };
1787
+ const handleMouseUp = () => {
1788
+ setIsCreating(false);
1789
+ document.removeEventListener("mousemove", handleMouseMove);
1790
+ document.removeEventListener("mouseup", handleMouseUp);
1791
+ };
1792
+ document.addEventListener("mousemove", handleMouseMove);
1793
+ document.addEventListener("mouseup", handleMouseUp);
1794
+ },
1795
+ [minPosition, maxPosition, onLoopRegionChange]
1796
+ );
1797
+ return /* @__PURE__ */ jsx15(
1787
1798
  TimescaleLoopCreator,
1788
1799
  {
1789
1800
  ref: containerRef,
1790
1801
  $leftOffset: controlsOffset,
1791
1802
  onMouseDown: handleBackgroundMouseDown,
1792
1803
  "data-timescale-loop-creator": true,
1793
- children: hasLoopRegion && /* @__PURE__ */ jsx14(
1804
+ children: hasLoopRegion && /* @__PURE__ */ jsx15(
1794
1805
  LoopRegionMarkers,
1795
1806
  {
1796
1807
  startPosition,
@@ -1862,7 +1873,7 @@ function parseTime(timeStr, format) {
1862
1873
  }
1863
1874
 
1864
1875
  // src/components/TimeInput.tsx
1865
- import { Fragment as Fragment3, jsx as jsx15, jsxs as jsxs7 } from "react/jsx-runtime";
1876
+ import { Fragment as Fragment3, jsx as jsx16, jsxs as jsxs7 } from "react/jsx-runtime";
1866
1877
  var TimeInput = ({
1867
1878
  id,
1868
1879
  label,
@@ -1894,8 +1905,8 @@ var TimeInput = ({
1894
1905
  }
1895
1906
  };
1896
1907
  return /* @__PURE__ */ jsxs7(Fragment3, { children: [
1897
- /* @__PURE__ */ jsx15(ScreenReaderOnly, { as: "label", htmlFor: id, children: label }),
1898
- /* @__PURE__ */ jsx15(
1908
+ /* @__PURE__ */ jsx16(ScreenReaderOnly, { as: "label", htmlFor: id, children: label }),
1909
+ /* @__PURE__ */ jsx16(
1899
1910
  BaseInput,
1900
1911
  {
1901
1912
  type: "text",
@@ -1912,7 +1923,7 @@ var TimeInput = ({
1912
1923
  };
1913
1924
 
1914
1925
  // src/components/SelectionTimeInputs.tsx
1915
- import { jsx as jsx16, jsxs as jsxs8 } from "react/jsx-runtime";
1926
+ import { jsx as jsx17, jsxs as jsxs8 } from "react/jsx-runtime";
1916
1927
  var SelectionTimeInputs = ({
1917
1928
  selectionStart,
1918
1929
  selectionEnd,
@@ -1946,7 +1957,7 @@ var SelectionTimeInputs = ({
1946
1957
  }
1947
1958
  };
1948
1959
  return /* @__PURE__ */ jsxs8("div", { className, children: [
1949
- /* @__PURE__ */ jsx16(
1960
+ /* @__PURE__ */ jsx17(
1950
1961
  TimeInput,
1951
1962
  {
1952
1963
  id: "audio_start",
@@ -1957,7 +1968,7 @@ var SelectionTimeInputs = ({
1957
1968
  onChange: handleStartChange
1958
1969
  }
1959
1970
  ),
1960
- /* @__PURE__ */ jsx16(
1971
+ /* @__PURE__ */ jsx17(
1961
1972
  TimeInput,
1962
1973
  {
1963
1974
  id: "audio_end",
@@ -1972,12 +1983,12 @@ var SelectionTimeInputs = ({
1972
1983
  };
1973
1984
 
1974
1985
  // src/contexts/DevicePixelRatio.tsx
1975
- import { useState as useState4, createContext as createContext2, useContext as useContext2 } from "react";
1976
- import { jsx as jsx17 } from "react/jsx-runtime";
1986
+ import { useState as useState4, createContext as createContext3, useContext as useContext3 } from "react";
1987
+ import { jsx as jsx18 } from "react/jsx-runtime";
1977
1988
  function getScale() {
1978
1989
  return window.devicePixelRatio;
1979
1990
  }
1980
- var DevicePixelRatioContext = createContext2(getScale());
1991
+ var DevicePixelRatioContext = createContext3(getScale());
1981
1992
  var DevicePixelRatioProvider = ({ children }) => {
1982
1993
  const [scale, setScale] = useState4(getScale());
1983
1994
  matchMedia(`(resolution: ${getScale()}dppx)`).addEventListener(
@@ -1987,13 +1998,13 @@ var DevicePixelRatioProvider = ({ children }) => {
1987
1998
  },
1988
1999
  { once: true }
1989
2000
  );
1990
- return /* @__PURE__ */ jsx17(DevicePixelRatioContext.Provider, { value: Math.ceil(scale), children });
2001
+ return /* @__PURE__ */ jsx18(DevicePixelRatioContext.Provider, { value: Math.ceil(scale), children });
1991
2002
  };
1992
- var useDevicePixelRatio = () => useContext2(DevicePixelRatioContext);
2003
+ var useDevicePixelRatio = () => useContext3(DevicePixelRatioContext);
1993
2004
 
1994
2005
  // src/contexts/PlaylistInfo.tsx
1995
- import { createContext as createContext3, useContext as useContext3 } from "react";
1996
- var PlaylistInfoContext = createContext3({
2006
+ import { createContext as createContext4, useContext as useContext4 } from "react";
2007
+ var PlaylistInfoContext = createContext4({
1997
2008
  sampleRate: 48e3,
1998
2009
  samplesPerPixel: 1e3,
1999
2010
  zoomLevels: [1e3, 1500, 2e3, 2500],
@@ -2007,26 +2018,26 @@ var PlaylistInfoContext = createContext3({
2007
2018
  barWidth: 1,
2008
2019
  barGap: 0
2009
2020
  });
2010
- var usePlaylistInfo = () => useContext3(PlaylistInfoContext);
2021
+ var usePlaylistInfo = () => useContext4(PlaylistInfoContext);
2011
2022
 
2012
2023
  // src/contexts/Theme.tsx
2013
- import { useContext as useContext4 } from "react";
2024
+ import { useContext as useContext5 } from "react";
2014
2025
  import { ThemeContext } from "styled-components";
2015
- var useTheme2 = () => useContext4(ThemeContext);
2026
+ var useTheme2 = () => useContext5(ThemeContext);
2016
2027
 
2017
2028
  // src/contexts/TrackControls.tsx
2018
- import { createContext as createContext4, useContext as useContext5, Fragment as Fragment4 } from "react";
2019
- import { jsx as jsx18 } from "react/jsx-runtime";
2020
- var TrackControlsContext = createContext4(/* @__PURE__ */ jsx18(Fragment4, {}));
2021
- var useTrackControls = () => useContext5(TrackControlsContext);
2029
+ import { createContext as createContext5, useContext as useContext6, Fragment as Fragment4 } from "react";
2030
+ import { jsx as jsx19 } from "react/jsx-runtime";
2031
+ var TrackControlsContext = createContext5(/* @__PURE__ */ jsx19(Fragment4, {}));
2032
+ var useTrackControls = () => useContext6(TrackControlsContext);
2022
2033
 
2023
2034
  // src/contexts/Playout.tsx
2024
2035
  import {
2025
2036
  useState as useState5,
2026
- createContext as createContext5,
2027
- useContext as useContext6
2037
+ createContext as createContext6,
2038
+ useContext as useContext7
2028
2039
  } from "react";
2029
- import { jsx as jsx19 } from "react/jsx-runtime";
2040
+ import { jsx as jsx20 } from "react/jsx-runtime";
2030
2041
  var defaultProgress = 0;
2031
2042
  var defaultIsPlaying = false;
2032
2043
  var defaultSelectionStart = 0;
@@ -2037,8 +2048,8 @@ var defaultPlayout = {
2037
2048
  selectionStart: defaultSelectionStart,
2038
2049
  selectionEnd: defaultSelectionEnd
2039
2050
  };
2040
- var PlayoutStatusContext = createContext5(defaultPlayout);
2041
- var PlayoutStatusUpdateContext = createContext5({
2051
+ var PlayoutStatusContext = createContext6(defaultPlayout);
2052
+ var PlayoutStatusUpdateContext = createContext6({
2042
2053
  setIsPlaying: () => {
2043
2054
  },
2044
2055
  setProgress: () => {
@@ -2055,15 +2066,16 @@ var PlayoutProvider = ({ children }) => {
2055
2066
  setSelectionStart(start);
2056
2067
  setSelectionEnd(end);
2057
2068
  };
2058
- return /* @__PURE__ */ jsx19(PlayoutStatusUpdateContext.Provider, { value: { setIsPlaying, setProgress, setSelection }, children: /* @__PURE__ */ jsx19(PlayoutStatusContext.Provider, { value: { isPlaying, progress, selectionStart, selectionEnd }, children }) });
2069
+ return /* @__PURE__ */ jsx20(PlayoutStatusUpdateContext.Provider, { value: { setIsPlaying, setProgress, setSelection }, children: /* @__PURE__ */ jsx20(PlayoutStatusContext.Provider, { value: { isPlaying, progress, selectionStart, selectionEnd }, children }) });
2059
2070
  };
2060
- var usePlayoutStatus = () => useContext6(PlayoutStatusContext);
2061
- var usePlayoutStatusUpdate = () => useContext6(PlayoutStatusUpdateContext);
2071
+ var usePlayoutStatus = () => useContext7(PlayoutStatusContext);
2072
+ var usePlayoutStatusUpdate = () => useContext7(PlayoutStatusUpdateContext);
2062
2073
 
2063
2074
  // src/components/SpectrogramChannel.tsx
2064
- import { useLayoutEffect as useLayoutEffect2, useCallback as useCallback5, useRef as useRef6, useEffect as useEffect6 } from "react";
2075
+ import { useLayoutEffect as useLayoutEffect2, useRef as useRef6, useEffect as useEffect6 } from "react";
2065
2076
  import styled19 from "styled-components";
2066
- import { jsx as jsx20 } from "react/jsx-runtime";
2077
+ import { MAX_CANVAS_WIDTH as MAX_CANVAS_WIDTH2 } from "@waveform-playlist/core";
2078
+ import { jsx as jsx21 } from "react/jsx-runtime";
2067
2079
  var LINEAR_FREQUENCY_SCALE = (f, minF, maxF) => (f - minF) / (maxF - minF);
2068
2080
  var Wrapper3 = styled19.div.attrs((props) => ({
2069
2081
  style: {
@@ -2116,38 +2128,14 @@ var SpectrogramChannel = ({
2116
2128
  onCanvasesReady
2117
2129
  }) => {
2118
2130
  const channelIndex = channelIndexProp ?? index;
2119
- const canvasesRef = useRef6([]);
2131
+ const { canvasRef, canvasMapRef } = useChunkedCanvasRefs();
2120
2132
  const registeredIdsRef = useRef6([]);
2121
2133
  const transferredCanvasesRef = useRef6(/* @__PURE__ */ new WeakSet());
2122
2134
  const workerApiRef = useRef6(workerApi);
2123
2135
  const onCanvasesReadyRef = useRef6(onCanvasesReady);
2124
2136
  const isWorkerMode = !!(workerApi && clipId);
2125
- const visibleChunkKey = useScrollViewportSelector((viewport) => {
2126
- const totalChunks = Math.ceil(length / MAX_CANVAS_WIDTH);
2127
- const indices = [];
2128
- for (let i = 0; i < totalChunks; i++) {
2129
- const chunkLeft = i * MAX_CANVAS_WIDTH;
2130
- const chunkWidth = Math.min(length - chunkLeft, MAX_CANVAS_WIDTH);
2131
- if (viewport) {
2132
- const chunkEnd = chunkLeft + chunkWidth;
2133
- if (chunkEnd <= viewport.visibleStart || chunkLeft >= viewport.visibleEnd) {
2134
- continue;
2135
- }
2136
- }
2137
- indices.push(i);
2138
- }
2139
- return indices.join(",");
2140
- });
2141
- const visibleChunkIndices = visibleChunkKey ? visibleChunkKey.split(",").map(Number) : [];
2142
- const canvasRef = useCallback5(
2143
- (canvas) => {
2144
- if (canvas !== null) {
2145
- const idx = parseInt(canvas.dataset.index, 10);
2146
- canvasesRef.current[idx] = canvas;
2147
- }
2148
- },
2149
- []
2150
- );
2137
+ const clipOriginX = useClipViewportOrigin();
2138
+ const visibleChunkIndices = useVisibleChunkIndices(length, MAX_CANVAS_WIDTH2, clipOriginX);
2151
2139
  const lut = colorLUT ?? DEFAULT_COLOR_LUT;
2152
2140
  const maxF = maxFrequency ?? (data ? data.sampleRate / 2 : 22050);
2153
2141
  const scaleFn = frequencyScaleFn ?? LINEAR_FREQUENCY_SCALE;
@@ -2162,14 +2150,30 @@ var SpectrogramChannel = ({
2162
2150
  if (!isWorkerMode) return;
2163
2151
  const currentWorkerApi = workerApiRef.current;
2164
2152
  if (!currentWorkerApi || !clipId) return;
2165
- const canvases2 = canvasesRef.current;
2153
+ const previousCount = registeredIdsRef.current.length;
2154
+ const remaining = [];
2155
+ for (const id of registeredIdsRef.current) {
2156
+ const match = id.match(/chunk(\d+)$/);
2157
+ if (!match) {
2158
+ remaining.push(id);
2159
+ continue;
2160
+ }
2161
+ const chunkIdx = parseInt(match[1], 10);
2162
+ const canvas = canvasMapRef.current.get(chunkIdx);
2163
+ if (canvas && canvas.isConnected) {
2164
+ remaining.push(id);
2165
+ } else {
2166
+ try {
2167
+ currentWorkerApi.unregisterCanvas(id);
2168
+ } catch (err) {
2169
+ console.warn(`[spectrogram] unregisterCanvas failed for ${id}:`, err);
2170
+ }
2171
+ }
2172
+ }
2173
+ registeredIdsRef.current = remaining;
2166
2174
  const newIds = [];
2167
- const newWidths = [];
2168
- for (let i = 0; i < canvases2.length; i++) {
2169
- const canvas = canvases2[i];
2170
- if (!canvas) continue;
2175
+ for (const [canvasIdx, canvas] of canvasMapRef.current.entries()) {
2171
2176
  if (transferredCanvasesRef.current.has(canvas)) continue;
2172
- const canvasIdx = parseInt(canvas.dataset.index, 10);
2173
2177
  const canvasId = `${clipId}-ch${channelIndex}-chunk${canvasIdx}`;
2174
2178
  let offscreen;
2175
2179
  try {
@@ -2182,7 +2186,6 @@ var SpectrogramChannel = ({
2182
2186
  try {
2183
2187
  currentWorkerApi.registerCanvas(canvasId, offscreen);
2184
2188
  newIds.push(canvasId);
2185
- newWidths.push(Math.min(length - canvasIdx * MAX_CANVAS_WIDTH, MAX_CANVAS_WIDTH));
2186
2189
  } catch (err) {
2187
2190
  console.warn(`[spectrogram] registerCanvas failed for ${canvasId}:`, err);
2188
2191
  continue;
@@ -2190,34 +2193,22 @@ var SpectrogramChannel = ({
2190
2193
  }
2191
2194
  if (newIds.length > 0) {
2192
2195
  registeredIdsRef.current = [...registeredIdsRef.current, ...newIds];
2193
- onCanvasesReadyRef.current?.(newIds, newWidths);
2194
2196
  }
2195
- }, [isWorkerMode, clipId, channelIndex, length, visibleChunkKey]);
2196
- useEffect6(() => {
2197
- if (!isWorkerMode) return;
2198
- const currentWorkerApi = workerApiRef.current;
2199
- if (!currentWorkerApi) return;
2200
- const remaining = [];
2201
- for (const id of registeredIdsRef.current) {
2202
- const match = id.match(/chunk(\d+)$/);
2203
- if (!match) {
2204
- remaining.push(id);
2205
- continue;
2206
- }
2207
- const chunkIdx = parseInt(match[1], 10);
2208
- const canvas = canvasesRef.current[chunkIdx];
2209
- if (canvas && canvas.isConnected) {
2210
- remaining.push(id);
2211
- } else {
2212
- try {
2213
- currentWorkerApi.unregisterCanvas(id);
2214
- } catch (err) {
2215
- console.warn(`[spectrogram] unregisterCanvas failed for ${id}:`, err);
2197
+ const canvasSetChanged = newIds.length > 0 || remaining.length < previousCount;
2198
+ if (canvasSetChanged) {
2199
+ const allIds = registeredIdsRef.current;
2200
+ const allWidths = allIds.map((id) => {
2201
+ const match = id.match(/chunk(\d+)$/);
2202
+ if (!match) {
2203
+ console.warn(`[spectrogram] Unexpected canvas ID format: ${id}`);
2204
+ return MAX_CANVAS_WIDTH2;
2216
2205
  }
2217
- }
2206
+ const chunkIdx = parseInt(match[1], 10);
2207
+ return Math.min(length - chunkIdx * MAX_CANVAS_WIDTH2, MAX_CANVAS_WIDTH2);
2208
+ });
2209
+ onCanvasesReadyRef.current?.(allIds, allWidths);
2218
2210
  }
2219
- registeredIdsRef.current = remaining;
2220
- });
2211
+ }, [canvasMapRef, isWorkerMode, clipId, channelIndex, length, visibleChunkIndices]);
2221
2212
  useEffect6(() => {
2222
2213
  return () => {
2223
2214
  const api = workerApiRef.current;
@@ -2234,15 +2225,18 @@ var SpectrogramChannel = ({
2234
2225
  }, []);
2235
2226
  useLayoutEffect2(() => {
2236
2227
  if (isWorkerMode || !data) return;
2237
- const canvases2 = canvasesRef.current;
2238
- const { frequencyBinCount, frameCount, hopSize, sampleRate, gainDb, rangeDb: rawRangeDb } = data;
2228
+ const {
2229
+ frequencyBinCount,
2230
+ frameCount,
2231
+ hopSize,
2232
+ sampleRate,
2233
+ gainDb,
2234
+ rangeDb: rawRangeDb
2235
+ } = data;
2239
2236
  const rangeDb = rawRangeDb === 0 ? 1 : rawRangeDb;
2240
2237
  const binToFreq = (bin) => bin / frequencyBinCount * (sampleRate / 2);
2241
- for (let i = 0; i < canvases2.length; i++) {
2242
- const canvas = canvases2[i];
2243
- if (!canvas) continue;
2244
- const canvasIdx = parseInt(canvas.dataset.index, 10);
2245
- const globalPixelOffset = canvasIdx * MAX_CANVAS_WIDTH;
2238
+ for (const [canvasIdx, canvas] of canvasMapRef.current.entries()) {
2239
+ const globalPixelOffset = canvasIdx * MAX_CANVAS_WIDTH2;
2246
2240
  const ctx = canvas.getContext("2d");
2247
2241
  if (!ctx) continue;
2248
2242
  const canvasWidth = canvas.width / devicePixelRatio;
@@ -2302,11 +2296,25 @@ var SpectrogramChannel = ({
2302
2296
  ctx.drawImage(tmpCanvas, 0, 0, canvas.width, canvas.height);
2303
2297
  }
2304
2298
  }
2305
- }, [isWorkerMode, data, length, waveHeight, devicePixelRatio, samplesPerPixel, lut, minFrequency, maxF, scaleFn, hasCustomFrequencyScale, visibleChunkKey]);
2299
+ }, [
2300
+ canvasMapRef,
2301
+ isWorkerMode,
2302
+ data,
2303
+ length,
2304
+ waveHeight,
2305
+ devicePixelRatio,
2306
+ samplesPerPixel,
2307
+ lut,
2308
+ minFrequency,
2309
+ maxF,
2310
+ scaleFn,
2311
+ hasCustomFrequencyScale,
2312
+ visibleChunkIndices
2313
+ ]);
2306
2314
  const canvases = visibleChunkIndices.map((i) => {
2307
- const chunkLeft = i * MAX_CANVAS_WIDTH;
2308
- const currentWidth = Math.min(length - chunkLeft, MAX_CANVAS_WIDTH);
2309
- return /* @__PURE__ */ jsx20(
2315
+ const chunkLeft = i * MAX_CANVAS_WIDTH2;
2316
+ const currentWidth = Math.min(length - chunkLeft, MAX_CANVAS_WIDTH2);
2317
+ return /* @__PURE__ */ jsx21(
2310
2318
  SpectrogramCanvas,
2311
2319
  {
2312
2320
  $cssWidth: currentWidth,
@@ -2320,11 +2328,11 @@ var SpectrogramChannel = ({
2320
2328
  `${length}-${i}`
2321
2329
  );
2322
2330
  });
2323
- return /* @__PURE__ */ jsx20(Wrapper3, { $index: index, $cssWidth: length, $waveHeight: waveHeight, children: canvases });
2331
+ return /* @__PURE__ */ jsx21(Wrapper3, { $index: index, $cssWidth: length, $waveHeight: waveHeight, children: canvases });
2324
2332
  };
2325
2333
 
2326
2334
  // src/components/SmartChannel.tsx
2327
- import { Fragment as Fragment5, jsx as jsx21, jsxs as jsxs9 } from "react/jsx-runtime";
2335
+ import { Fragment as Fragment5, jsx as jsx22, jsxs as jsxs9 } from "react/jsx-runtime";
2328
2336
  var SmartChannel = ({
2329
2337
  isSelected,
2330
2338
  transparentBackground,
@@ -2349,7 +2357,7 @@ var SmartChannel = ({
2349
2357
  const drawMode = theme?.waveformDrawMode || "inverted";
2350
2358
  const hasSpectrogram = spectrogramData || spectrogramWorkerApi;
2351
2359
  if (renderMode === "spectrogram" && hasSpectrogram) {
2352
- return /* @__PURE__ */ jsx21(
2360
+ return /* @__PURE__ */ jsx22(
2353
2361
  SpectrogramChannel,
2354
2362
  {
2355
2363
  index: props.index,
@@ -2371,7 +2379,7 @@ var SmartChannel = ({
2371
2379
  if (renderMode === "both" && hasSpectrogram) {
2372
2380
  const halfHeight = Math.floor(waveHeight / 2);
2373
2381
  return /* @__PURE__ */ jsxs9(Fragment5, { children: [
2374
- /* @__PURE__ */ jsx21(
2382
+ /* @__PURE__ */ jsx22(
2375
2383
  SpectrogramChannel,
2376
2384
  {
2377
2385
  index: props.index * 2,
@@ -2390,24 +2398,35 @@ var SmartChannel = ({
2390
2398
  onCanvasesReady: spectrogramOnCanvasesReady
2391
2399
  }
2392
2400
  ),
2393
- /* @__PURE__ */ jsx21("div", { style: { position: "absolute", top: (props.index * 2 + 1) * halfHeight, width: props.length, height: halfHeight }, children: /* @__PURE__ */ jsx21(
2394
- Channel,
2401
+ /* @__PURE__ */ jsx22(
2402
+ "div",
2395
2403
  {
2396
- ...props,
2397
- index: 0,
2398
- waveOutlineColor,
2399
- waveFillColor,
2400
- waveHeight: halfHeight,
2401
- devicePixelRatio,
2402
- barWidth,
2403
- barGap,
2404
- transparentBackground,
2405
- drawMode
2404
+ style: {
2405
+ position: "absolute",
2406
+ top: (props.index * 2 + 1) * halfHeight,
2407
+ width: props.length,
2408
+ height: halfHeight
2409
+ },
2410
+ children: /* @__PURE__ */ jsx22(
2411
+ Channel,
2412
+ {
2413
+ ...props,
2414
+ index: 0,
2415
+ waveOutlineColor,
2416
+ waveFillColor,
2417
+ waveHeight: halfHeight,
2418
+ devicePixelRatio,
2419
+ barWidth,
2420
+ barGap,
2421
+ transparentBackground,
2422
+ drawMode
2423
+ }
2424
+ )
2406
2425
  }
2407
- ) })
2426
+ )
2408
2427
  ] });
2409
2428
  }
2410
- return /* @__PURE__ */ jsx21(
2429
+ return /* @__PURE__ */ jsx22(
2411
2430
  Channel,
2412
2431
  {
2413
2432
  ...props,
@@ -2426,7 +2445,7 @@ var SmartChannel = ({
2426
2445
  // src/components/SpectrogramLabels.tsx
2427
2446
  import { useRef as useRef7, useLayoutEffect as useLayoutEffect3 } from "react";
2428
2447
  import styled20 from "styled-components";
2429
- import { jsx as jsx22 } from "react/jsx-runtime";
2448
+ import { jsx as jsx23 } from "react/jsx-runtime";
2430
2449
  var LABELS_WIDTH = 72;
2431
2450
  var LabelsStickyWrapper = styled20.div`
2432
2451
  position: sticky;
@@ -2507,8 +2526,19 @@ var SpectrogramLabels = ({
2507
2526
  ctx.fillText(text, padding, y);
2508
2527
  }
2509
2528
  }
2510
- }, [waveHeight, numChannels, frequencyScaleFn, minFrequency, maxFrequency, labelsColor, labelsBackground, devicePixelRatio, spectrogramHeight, clipHeaderOffset]);
2511
- return /* @__PURE__ */ jsx22(LabelsStickyWrapper, { $height: totalHeight + clipHeaderOffset, children: /* @__PURE__ */ jsx22(
2529
+ }, [
2530
+ waveHeight,
2531
+ numChannels,
2532
+ frequencyScaleFn,
2533
+ minFrequency,
2534
+ maxFrequency,
2535
+ labelsColor,
2536
+ labelsBackground,
2537
+ devicePixelRatio,
2538
+ spectrogramHeight,
2539
+ clipHeaderOffset
2540
+ ]);
2541
+ return /* @__PURE__ */ jsx23(LabelsStickyWrapper, { $height: totalHeight + clipHeaderOffset, children: /* @__PURE__ */ jsx23(
2512
2542
  "canvas",
2513
2543
  {
2514
2544
  ref: canvasRef,
@@ -2524,10 +2554,10 @@ var SpectrogramLabels = ({
2524
2554
  };
2525
2555
 
2526
2556
  // src/components/SmartScale.tsx
2527
- import { useContext as useContext8 } from "react";
2557
+ import { useContext as useContext9 } from "react";
2528
2558
 
2529
2559
  // src/components/TimeScale.tsx
2530
- import React15, { useRef as useRef8, useEffect as useEffect7, useLayoutEffect as useLayoutEffect4, useContext as useContext7, useMemo, useCallback as useCallback6 } from "react";
2560
+ import React16, { useLayoutEffect as useLayoutEffect4, useContext as useContext8, useMemo as useMemo2 } from "react";
2531
2561
  import styled21, { withTheme as withTheme2 } from "styled-components";
2532
2562
 
2533
2563
  // src/utils/conversions.ts
@@ -2551,7 +2581,8 @@ function secondsToPixels(seconds, samplesPerPixel, sampleRate) {
2551
2581
  }
2552
2582
 
2553
2583
  // src/components/TimeScale.tsx
2554
- import { jsx as jsx23, jsxs as jsxs10 } from "react/jsx-runtime";
2584
+ import { MAX_CANVAS_WIDTH as MAX_CANVAS_WIDTH3 } from "@waveform-playlist/core";
2585
+ import { jsx as jsx24, jsxs as jsxs10 } from "react/jsx-runtime";
2555
2586
  function formatTime2(milliseconds) {
2556
2587
  const seconds = Math.floor(milliseconds / 1e3);
2557
2588
  const s = seconds % 60;
@@ -2602,21 +2633,15 @@ var TimeScale = (props) => {
2602
2633
  secondStep,
2603
2634
  renderTimestamp
2604
2635
  } = props;
2605
- const canvasRefsMap = useRef8(/* @__PURE__ */ new Map());
2636
+ const { canvasRef, canvasMapRef } = useChunkedCanvasRefs();
2606
2637
  const {
2607
2638
  sampleRate,
2608
2639
  samplesPerPixel,
2609
2640
  timeScaleHeight,
2610
2641
  controls: { show: showControls, width: controlWidth }
2611
- } = useContext7(PlaylistInfoContext);
2642
+ } = useContext8(PlaylistInfoContext);
2612
2643
  const devicePixelRatio = useDevicePixelRatio();
2613
- const canvasRefCallback = useCallback6((canvas) => {
2614
- if (canvas !== null) {
2615
- const idx = parseInt(canvas.dataset.index, 10);
2616
- canvasRefsMap.current.set(idx, canvas);
2617
- }
2618
- }, []);
2619
- const { widthX, canvasInfo, timeMarkersWithPositions } = useMemo(() => {
2644
+ const { widthX, canvasInfo, timeMarkersWithPositions } = useMemo2(() => {
2620
2645
  const nextCanvasInfo = /* @__PURE__ */ new Map();
2621
2646
  const nextMarkers = [];
2622
2647
  const nextWidthX = secondsToPixels(duration / 1e3, samplesPerPixel, sampleRate);
@@ -2627,7 +2652,7 @@ var TimeScale = (props) => {
2627
2652
  if (counter % marker === 0) {
2628
2653
  const timeMs = counter;
2629
2654
  const timestamp = formatTime2(timeMs);
2630
- const element = renderTimestamp ? /* @__PURE__ */ jsx23(React15.Fragment, { children: renderTimestamp(timeMs, pix) }, `timestamp-${counter}`) : /* @__PURE__ */ jsx23(TimeStamp, { $left: pix, children: timestamp }, timestamp);
2655
+ const element = renderTimestamp ? /* @__PURE__ */ jsx24(React16.Fragment, { children: renderTimestamp(timeMs, pix) }, `timestamp-${counter}`) : /* @__PURE__ */ jsx24(TimeStamp, { $left: pix, children: timestamp }, timestamp);
2631
2656
  nextMarkers.push({ pix, element });
2632
2657
  nextCanvasInfo.set(pix, timeScaleHeight);
2633
2658
  } else if (counter % bigStep === 0) {
@@ -2642,28 +2667,21 @@ var TimeScale = (props) => {
2642
2667
  canvasInfo: nextCanvasInfo,
2643
2668
  timeMarkersWithPositions: nextMarkers
2644
2669
  };
2645
- }, [duration, samplesPerPixel, sampleRate, marker, bigStep, secondStep, renderTimestamp, timeScaleHeight]);
2646
- const visibleChunkKey = useScrollViewportSelector((viewport) => {
2647
- const totalChunks = Math.ceil(widthX / MAX_CANVAS_WIDTH);
2648
- const indices = [];
2649
- for (let i = 0; i < totalChunks; i++) {
2650
- const chunkLeft = i * MAX_CANVAS_WIDTH;
2651
- const chunkWidth = Math.min(widthX - chunkLeft, MAX_CANVAS_WIDTH);
2652
- if (viewport) {
2653
- const chunkEnd = chunkLeft + chunkWidth;
2654
- if (chunkEnd <= viewport.visibleStart || chunkLeft >= viewport.visibleEnd) {
2655
- continue;
2656
- }
2657
- }
2658
- indices.push(i);
2659
- }
2660
- return indices.join(",");
2661
- });
2662
- const visibleChunkIndices = visibleChunkKey ? visibleChunkKey.split(",").map(Number) : [];
2670
+ }, [
2671
+ duration,
2672
+ samplesPerPixel,
2673
+ sampleRate,
2674
+ marker,
2675
+ bigStep,
2676
+ secondStep,
2677
+ renderTimestamp,
2678
+ timeScaleHeight
2679
+ ]);
2680
+ const visibleChunkIndices = useVisibleChunkIndices(widthX, MAX_CANVAS_WIDTH3);
2663
2681
  const visibleChunks = visibleChunkIndices.map((i) => {
2664
- const chunkLeft = i * MAX_CANVAS_WIDTH;
2665
- const chunkWidth = Math.min(widthX - chunkLeft, MAX_CANVAS_WIDTH);
2666
- return /* @__PURE__ */ jsx23(
2682
+ const chunkLeft = i * MAX_CANVAS_WIDTH3;
2683
+ const chunkWidth = Math.min(widthX - chunkLeft, MAX_CANVAS_WIDTH3);
2684
+ return /* @__PURE__ */ jsx24(
2667
2685
  TimeTickChunk,
2668
2686
  {
2669
2687
  $cssWidth: chunkWidth,
@@ -2672,27 +2690,19 @@ var TimeScale = (props) => {
2672
2690
  width: chunkWidth * devicePixelRatio,
2673
2691
  height: timeScaleHeight * devicePixelRatio,
2674
2692
  "data-index": i,
2675
- ref: canvasRefCallback
2693
+ ref: canvasRef
2676
2694
  },
2677
2695
  `timescale-${i}`
2678
2696
  );
2679
2697
  });
2680
- const firstChunkLeft = visibleChunkIndices.length > 0 ? visibleChunkIndices[0] * MAX_CANVAS_WIDTH : 0;
2681
- const lastChunkRight = visibleChunkIndices.length > 0 ? (visibleChunkIndices[visibleChunkIndices.length - 1] + 1) * MAX_CANVAS_WIDTH : Infinity;
2698
+ const firstChunkLeft = visibleChunkIndices.length > 0 ? visibleChunkIndices[0] * MAX_CANVAS_WIDTH3 : 0;
2699
+ const lastChunkRight = visibleChunkIndices.length > 0 ? (visibleChunkIndices[visibleChunkIndices.length - 1] + 1) * MAX_CANVAS_WIDTH3 : Infinity;
2682
2700
  const visibleMarkers = visibleChunkIndices.length > 0 ? timeMarkersWithPositions.filter(({ pix }) => pix >= firstChunkLeft && pix < lastChunkRight).map(({ element }) => element) : timeMarkersWithPositions.map(({ element }) => element);
2683
- useEffect7(() => {
2684
- const currentMap = canvasRefsMap.current;
2685
- for (const [idx, canvas] of currentMap.entries()) {
2686
- if (!canvas.isConnected) {
2687
- currentMap.delete(idx);
2688
- }
2689
- }
2690
- });
2691
2701
  useLayoutEffect4(() => {
2692
- for (const [chunkIdx, canvas] of canvasRefsMap.current.entries()) {
2702
+ for (const [chunkIdx, canvas] of canvasMapRef.current.entries()) {
2693
2703
  const ctx = canvas.getContext("2d");
2694
2704
  if (!ctx) continue;
2695
- const chunkLeft = chunkIdx * MAX_CANVAS_WIDTH;
2705
+ const chunkLeft = chunkIdx * MAX_CANVAS_WIDTH3;
2696
2706
  const chunkWidth = canvas.width / devicePixelRatio;
2697
2707
  ctx.resetTransform();
2698
2708
  ctx.clearRect(0, 0, canvas.width, canvas.height);
@@ -2706,7 +2716,15 @@ var TimeScale = (props) => {
2706
2716
  ctx.fillRect(localX, scaleY, 1, scaleHeight);
2707
2717
  }
2708
2718
  }
2709
- }, [duration, devicePixelRatio, timeColor, timeScaleHeight, canvasInfo, visibleChunkKey]);
2719
+ }, [
2720
+ canvasMapRef,
2721
+ duration,
2722
+ devicePixelRatio,
2723
+ timeColor,
2724
+ timeScaleHeight,
2725
+ canvasInfo,
2726
+ visibleChunkIndices
2727
+ ]);
2710
2728
  return /* @__PURE__ */ jsxs10(
2711
2729
  PlaylistTimeScaleScroll,
2712
2730
  {
@@ -2723,7 +2741,7 @@ var TimeScale = (props) => {
2723
2741
  var StyledTimeScale = withTheme2(TimeScale);
2724
2742
 
2725
2743
  // src/components/SmartScale.tsx
2726
- import { jsx as jsx24 } from "react/jsx-runtime";
2744
+ import { jsx as jsx25 } from "react/jsx-runtime";
2727
2745
  var timeinfo = /* @__PURE__ */ new Map([
2728
2746
  [
2729
2747
  700,
@@ -2797,9 +2815,9 @@ function getScaleInfo(samplesPerPixel) {
2797
2815
  return config;
2798
2816
  }
2799
2817
  var SmartScale = ({ renderTimestamp }) => {
2800
- const { samplesPerPixel, duration } = useContext8(PlaylistInfoContext);
2818
+ const { samplesPerPixel, duration } = useContext9(PlaylistInfoContext);
2801
2819
  let config = getScaleInfo(samplesPerPixel);
2802
- return /* @__PURE__ */ jsx24(
2820
+ return /* @__PURE__ */ jsx25(
2803
2821
  StyledTimeScale,
2804
2822
  {
2805
2823
  marker: config.marker,
@@ -2813,7 +2831,7 @@ var SmartScale = ({ renderTimestamp }) => {
2813
2831
 
2814
2832
  // src/components/TimeFormatSelect.tsx
2815
2833
  import styled22 from "styled-components";
2816
- import { jsx as jsx25 } from "react/jsx-runtime";
2834
+ import { jsx as jsx26 } from "react/jsx-runtime";
2817
2835
  var SelectWrapper = styled22.div`
2818
2836
  display: inline-flex;
2819
2837
  align-items: center;
@@ -2836,7 +2854,7 @@ var TimeFormatSelect = ({
2836
2854
  const handleChange = (e) => {
2837
2855
  onChange(e.target.value);
2838
2856
  };
2839
- return /* @__PURE__ */ jsx25(SelectWrapper, { className, children: /* @__PURE__ */ jsx25(
2857
+ return /* @__PURE__ */ jsx26(SelectWrapper, { className, children: /* @__PURE__ */ jsx26(
2840
2858
  BaseSelect,
2841
2859
  {
2842
2860
  className: "time-format",
@@ -2844,14 +2862,14 @@ var TimeFormatSelect = ({
2844
2862
  onChange: handleChange,
2845
2863
  disabled,
2846
2864
  "aria-label": "Time format selection",
2847
- children: TIME_FORMAT_OPTIONS.map((option) => /* @__PURE__ */ jsx25("option", { value: option.value, children: option.label }, option.value))
2865
+ children: TIME_FORMAT_OPTIONS.map((option) => /* @__PURE__ */ jsx26("option", { value: option.value, children: option.label }, option.value))
2848
2866
  }
2849
2867
  ) });
2850
2868
  };
2851
2869
 
2852
2870
  // src/components/Track.tsx
2853
2871
  import styled23 from "styled-components";
2854
- import { jsx as jsx26, jsxs as jsxs11 } from "react/jsx-runtime";
2872
+ import { jsx as jsx27, jsxs as jsxs11 } from "react/jsx-runtime";
2855
2873
  var Container = styled23.div.attrs((props) => ({
2856
2874
  style: {
2857
2875
  height: `${props.$waveHeight * props.$numChannels + (props.$hasClipHeaders ? CLIP_HEADER_HEIGHT : 0)}px`
@@ -2917,15 +2935,8 @@ var Track = ({
2917
2935
  $hasClipHeaders: hasClipHeaders,
2918
2936
  $isSelected: isSelected,
2919
2937
  children: [
2920
- /* @__PURE__ */ jsx26(
2921
- ControlsWrapper,
2922
- {
2923
- $controlWidth: show ? controlWidth : 0,
2924
- $isSelected: isSelected,
2925
- children: controls
2926
- }
2927
- ),
2928
- /* @__PURE__ */ jsx26(
2938
+ /* @__PURE__ */ jsx27(ControlsWrapper, { $controlWidth: show ? controlWidth : 0, $isSelected: isSelected, children: controls }),
2939
+ /* @__PURE__ */ jsx27(
2929
2940
  ChannelContainer,
2930
2941
  {
2931
2942
  $controlWidth: show ? controlWidth : 0,
@@ -2956,8 +2967,11 @@ var Button = styled24.button.attrs({
2956
2967
  font-size: ${(props) => props.theme.fontSizeSmall};
2957
2968
  line-height: 1;
2958
2969
  border-radius: ${(props) => props.theme.borderRadius};
2959
- transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out,
2960
- border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
2970
+ transition:
2971
+ color 0.15s ease-in-out,
2972
+ background-color 0.15s ease-in-out,
2973
+ border-color 0.15s ease-in-out,
2974
+ box-shadow 0.15s ease-in-out;
2961
2975
  cursor: pointer;
2962
2976
 
2963
2977
  ${(props) => {
@@ -3033,7 +3047,7 @@ var ButtonGroup = styled25.div`
3033
3047
  // src/components/TrackControls/CloseButton.tsx
3034
3048
  import styled26 from "styled-components";
3035
3049
  import { X as XIcon } from "@phosphor-icons/react";
3036
- import { jsx as jsx27 } from "react/jsx-runtime";
3050
+ import { jsx as jsx28 } from "react/jsx-runtime";
3037
3051
  var StyledCloseButton = styled26.button`
3038
3052
  position: absolute;
3039
3053
  left: 0;
@@ -3048,17 +3062,16 @@ var StyledCloseButton = styled26.button`
3048
3062
  align-items: center;
3049
3063
  justify-content: center;
3050
3064
  opacity: 0.7;
3051
- transition: opacity 0.15s, color 0.15s;
3065
+ transition:
3066
+ opacity 0.15s,
3067
+ color 0.15s;
3052
3068
 
3053
3069
  &:hover {
3054
3070
  opacity: 1;
3055
3071
  color: #dc3545;
3056
3072
  }
3057
3073
  `;
3058
- var CloseButton = ({
3059
- onClick,
3060
- title = "Remove track"
3061
- }) => /* @__PURE__ */ jsx27(StyledCloseButton, { onClick, title, children: /* @__PURE__ */ jsx27(XIcon, { size: 12, weight: "bold" }) });
3074
+ var CloseButton = ({ onClick, title = "Remove track" }) => /* @__PURE__ */ jsx28(StyledCloseButton, { onClick, title, children: /* @__PURE__ */ jsx28(XIcon, { size: 12, weight: "bold" }) });
3062
3075
 
3063
3076
  // src/components/TrackControls/Controls.tsx
3064
3077
  import styled27 from "styled-components";
@@ -3094,23 +3107,23 @@ var Header = styled28.header`
3094
3107
 
3095
3108
  // src/components/TrackControls/VolumeDownIcon.tsx
3096
3109
  import { SpeakerLowIcon } from "@phosphor-icons/react";
3097
- import { jsx as jsx28 } from "react/jsx-runtime";
3098
- var VolumeDownIcon = (props) => /* @__PURE__ */ jsx28(SpeakerLowIcon, { weight: "light", ...props });
3110
+ import { jsx as jsx29 } from "react/jsx-runtime";
3111
+ var VolumeDownIcon = (props) => /* @__PURE__ */ jsx29(SpeakerLowIcon, { weight: "light", ...props });
3099
3112
 
3100
3113
  // src/components/TrackControls/VolumeUpIcon.tsx
3101
3114
  import { SpeakerHighIcon } from "@phosphor-icons/react";
3102
- import { jsx as jsx29 } from "react/jsx-runtime";
3103
- var VolumeUpIcon = (props) => /* @__PURE__ */ jsx29(SpeakerHighIcon, { weight: "light", ...props });
3115
+ import { jsx as jsx30 } from "react/jsx-runtime";
3116
+ var VolumeUpIcon = (props) => /* @__PURE__ */ jsx30(SpeakerHighIcon, { weight: "light", ...props });
3104
3117
 
3105
3118
  // src/components/TrackControls/TrashIcon.tsx
3106
3119
  import { TrashIcon as PhosphorTrashIcon } from "@phosphor-icons/react";
3107
- import { jsx as jsx30 } from "react/jsx-runtime";
3108
- var TrashIcon = (props) => /* @__PURE__ */ jsx30(PhosphorTrashIcon, { weight: "light", ...props });
3120
+ import { jsx as jsx31 } from "react/jsx-runtime";
3121
+ var TrashIcon = (props) => /* @__PURE__ */ jsx31(PhosphorTrashIcon, { weight: "light", ...props });
3109
3122
 
3110
3123
  // src/components/TrackControls/DotsIcon.tsx
3111
3124
  import { DotsThreeIcon } from "@phosphor-icons/react";
3112
- import { jsx as jsx31 } from "react/jsx-runtime";
3113
- var DotsIcon = (props) => /* @__PURE__ */ jsx31(DotsThreeIcon, { weight: "bold", ...props });
3125
+ import { jsx as jsx32 } from "react/jsx-runtime";
3126
+ var DotsIcon = (props) => /* @__PURE__ */ jsx32(DotsThreeIcon, { weight: "bold", ...props });
3114
3127
 
3115
3128
  // src/components/TrackControls/Slider.tsx
3116
3129
  import styled29 from "styled-components";
@@ -3178,10 +3191,10 @@ var SliderWrapper = styled30.label`
3178
3191
  `;
3179
3192
 
3180
3193
  // src/components/TrackMenu.tsx
3181
- import React17, { useState as useState6, useEffect as useEffect8, useRef as useRef9 } from "react";
3194
+ import React18, { useState as useState6, useEffect as useEffect7, useRef as useRef8 } from "react";
3182
3195
  import { createPortal } from "react-dom";
3183
3196
  import styled31 from "styled-components";
3184
- import { jsx as jsx32, jsxs as jsxs12 } from "react/jsx-runtime";
3197
+ import { jsx as jsx33, jsxs as jsxs12 } from "react/jsx-runtime";
3185
3198
  var MenuContainer = styled31.div`
3186
3199
  position: relative;
3187
3200
  display: inline-block;
@@ -3219,16 +3232,14 @@ var Divider = styled31.hr`
3219
3232
  border-top: 1px solid rgba(128, 128, 128, 0.3);
3220
3233
  margin: 0.35rem 0;
3221
3234
  `;
3222
- var TrackMenu = ({
3223
- items: itemsProp
3224
- }) => {
3235
+ var TrackMenu = ({ items: itemsProp }) => {
3225
3236
  const [open, setOpen] = useState6(false);
3226
3237
  const close = () => setOpen(false);
3227
3238
  const items = typeof itemsProp === "function" ? itemsProp(close) : itemsProp;
3228
3239
  const [dropdownPos, setDropdownPos] = useState6({ top: 0, left: 0 });
3229
- const buttonRef = useRef9(null);
3230
- const dropdownRef = useRef9(null);
3231
- useEffect8(() => {
3240
+ const buttonRef = useRef8(null);
3241
+ const dropdownRef = useRef8(null);
3242
+ useEffect7(() => {
3232
3243
  if (open && buttonRef.current) {
3233
3244
  const rect = buttonRef.current.getBoundingClientRect();
3234
3245
  setDropdownPos({
@@ -3237,7 +3248,7 @@ var TrackMenu = ({
3237
3248
  });
3238
3249
  }
3239
3250
  }, [open]);
3240
- useEffect8(() => {
3251
+ useEffect7(() => {
3241
3252
  if (!open) return;
3242
3253
  const handleClick = (e) => {
3243
3254
  const target = e.target;
@@ -3249,7 +3260,7 @@ var TrackMenu = ({
3249
3260
  return () => document.removeEventListener("mousedown", handleClick);
3250
3261
  }, [open]);
3251
3262
  return /* @__PURE__ */ jsxs12(MenuContainer, { children: [
3252
- /* @__PURE__ */ jsx32(
3263
+ /* @__PURE__ */ jsx33(
3253
3264
  MenuButton,
3254
3265
  {
3255
3266
  ref: buttonRef,
@@ -3260,19 +3271,19 @@ var TrackMenu = ({
3260
3271
  onMouseDown: (e) => e.stopPropagation(),
3261
3272
  title: "Track menu",
3262
3273
  "aria-label": "Track menu",
3263
- children: /* @__PURE__ */ jsx32(DotsIcon, { size: 16 })
3274
+ children: /* @__PURE__ */ jsx33(DotsIcon, { size: 16 })
3264
3275
  }
3265
3276
  ),
3266
3277
  open && typeof document !== "undefined" && createPortal(
3267
- /* @__PURE__ */ jsx32(
3278
+ /* @__PURE__ */ jsx33(
3268
3279
  Dropdown,
3269
3280
  {
3270
3281
  ref: dropdownRef,
3271
3282
  $top: dropdownPos.top,
3272
3283
  $left: dropdownPos.left,
3273
3284
  onMouseDown: (e) => e.stopPropagation(),
3274
- children: items.map((item, index) => /* @__PURE__ */ jsxs12(React17.Fragment, { children: [
3275
- index > 0 && /* @__PURE__ */ jsx32(Divider, {}),
3285
+ children: items.map((item, index) => /* @__PURE__ */ jsxs12(React18.Fragment, { children: [
3286
+ index > 0 && /* @__PURE__ */ jsx33(Divider, {}),
3276
3287
  item.content
3277
3288
  ] }, item.id))
3278
3289
  }
@@ -3303,6 +3314,7 @@ export {
3303
3314
  ClipBoundary,
3304
3315
  ClipHeader,
3305
3316
  ClipHeaderPresentational,
3317
+ ClipViewportOriginProvider,
3306
3318
  CloseButton,
3307
3319
  Controls,
3308
3320
  DevicePixelRatioProvider,
@@ -3312,7 +3324,6 @@ export {
3312
3324
  InlineLabel,
3313
3325
  LoopRegion,
3314
3326
  LoopRegionMarkers,
3315
- MAX_CANVAS_WIDTH,
3316
3327
  MasterVolumeControl,
3317
3328
  Playhead,
3318
3329
  PlayheadWithMarker,
@@ -3353,6 +3364,7 @@ export {
3353
3364
  samplesToSeconds,
3354
3365
  secondsToPixels,
3355
3366
  secondsToSamples,
3367
+ useClipViewportOrigin,
3356
3368
  useDevicePixelRatio,
3357
3369
  usePlaylistInfo,
3358
3370
  usePlayoutStatus,
@@ -3361,6 +3373,7 @@ export {
3361
3373
  useScrollViewportSelector,
3362
3374
  useTheme2 as useTheme,
3363
3375
  useTrackControls,
3376
+ useVisibleChunkIndices,
3364
3377
  waveformColorToCss
3365
3378
  };
3366
3379
  //# sourceMappingURL=index.mjs.map