@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.js CHANGED
@@ -51,6 +51,7 @@ __export(index_exports, {
51
51
  ClipBoundary: () => ClipBoundary,
52
52
  ClipHeader: () => ClipHeader,
53
53
  ClipHeaderPresentational: () => ClipHeaderPresentational,
54
+ ClipViewportOriginProvider: () => ClipViewportOriginProvider,
54
55
  CloseButton: () => CloseButton,
55
56
  Controls: () => Controls,
56
57
  DevicePixelRatioProvider: () => DevicePixelRatioProvider,
@@ -60,7 +61,6 @@ __export(index_exports, {
60
61
  InlineLabel: () => InlineLabel,
61
62
  LoopRegion: () => LoopRegion,
62
63
  LoopRegionMarkers: () => LoopRegionMarkers,
63
- MAX_CANVAS_WIDTH: () => MAX_CANVAS_WIDTH,
64
64
  MasterVolumeControl: () => MasterVolumeControl,
65
65
  Playhead: () => Playhead,
66
66
  PlayheadWithMarker: () => PlayheadWithMarker,
@@ -101,6 +101,7 @@ __export(index_exports, {
101
101
  samplesToSeconds: () => samplesToSeconds,
102
102
  secondsToPixels: () => secondsToPixels,
103
103
  secondsToSamples: () => secondsToSamples,
104
+ useClipViewportOrigin: () => useClipViewportOrigin,
104
105
  useDevicePixelRatio: () => useDevicePixelRatio,
105
106
  usePlaylistInfo: () => usePlaylistInfo,
106
107
  usePlayoutStatus: () => usePlayoutStatus,
@@ -109,6 +110,7 @@ __export(index_exports, {
109
110
  useScrollViewportSelector: () => useScrollViewportSelector,
110
111
  useTheme: () => useTheme2,
111
112
  useTrackControls: () => useTrackControls,
113
+ useVisibleChunkIndices: () => useVisibleChunkIndices,
112
114
  waveformColorToCss: () => waveformColorToCss
113
115
  });
114
116
  module.exports = __toCommonJS(index_exports);
@@ -123,10 +125,7 @@ var PositionDisplay = import_styled_components.default.span`
123
125
  color: ${(props) => props.theme?.textColor || "#333"};
124
126
  user-select: none;
125
127
  `;
126
- var AudioPosition = ({
127
- formattedTime,
128
- className
129
- }) => {
128
+ var AudioPosition = ({ formattedTime, className }) => {
130
129
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PositionDisplay, { className, "aria-label": "Audio position", children: formattedTime });
131
130
  };
132
131
 
@@ -146,7 +145,9 @@ var BaseButton = import_styled_components2.default.button`
146
145
  border-radius: ${(props) => props.theme.borderRadius};
147
146
  cursor: pointer;
148
147
  outline: none;
149
- transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out,
148
+ transition:
149
+ background-color 0.15s ease-in-out,
150
+ border-color 0.15s ease-in-out,
150
151
  box-shadow 0.15s ease-in-out;
151
152
 
152
153
  &:hover:not(:disabled) {
@@ -243,7 +244,9 @@ var BaseInput = import_styled_components5.default.input`
243
244
  border: 1px solid ${(props) => props.theme.inputBorder};
244
245
  border-radius: ${(props) => props.theme.borderRadius};
245
246
  outline: none;
246
- transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
247
+ transition:
248
+ border-color 0.15s ease-in-out,
249
+ box-shadow 0.15s ease-in-out;
247
250
 
248
251
  &::placeholder {
249
252
  color: ${(props) => props.theme.inputPlaceholder};
@@ -311,7 +314,9 @@ var BaseSelect = import_styled_components7.default.select`
311
314
  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");
312
315
  background-repeat: no-repeat;
313
316
  background-position: right 0.75rem center;
314
- transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
317
+ transition:
318
+ border-color 0.15s ease-in-out,
319
+ box-shadow 0.15s ease-in-out;
315
320
 
316
321
  &:focus {
317
322
  border-color: ${(props) => props.theme.inputFocusBorder};
@@ -357,7 +362,9 @@ var BaseSlider = import_styled_components8.default.input.attrs({ type: "range" }
357
362
  border-radius: 50%;
358
363
  cursor: pointer;
359
364
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
360
- transition: transform 0.15s ease, box-shadow 0.15s ease;
365
+ transition:
366
+ transform 0.15s ease,
367
+ box-shadow 0.15s ease;
361
368
  }
362
369
 
363
370
  &::-webkit-slider-thumb:hover {
@@ -374,7 +381,9 @@ var BaseSlider = import_styled_components8.default.input.attrs({ type: "range" }
374
381
  border-radius: 50%;
375
382
  cursor: pointer;
376
383
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
377
- transition: transform 0.15s ease, box-shadow 0.15s ease;
384
+ transition:
385
+ transform 0.15s ease,
386
+ box-shadow 0.15s ease;
378
387
  }
379
388
 
380
389
  &::-moz-range-thumb:hover {
@@ -442,7 +451,7 @@ var AutomaticScrollCheckbox = ({
442
451
  };
443
452
 
444
453
  // src/components/Channel.tsx
445
- var import_react2 = require("react");
454
+ var import_react4 = require("react");
446
455
  var import_styled_components9 = __toESM(require("styled-components"));
447
456
 
448
457
  // src/wfpl-theme.ts
@@ -637,10 +646,7 @@ var ViewportStoreContext = (0, import_react.createContext)(null);
637
646
  var EMPTY_SUBSCRIBE = () => () => {
638
647
  };
639
648
  var NULL_SNAPSHOT = () => null;
640
- var ScrollViewportProvider = ({
641
- containerRef,
642
- children
643
- }) => {
649
+ var ScrollViewportProvider = ({ containerRef, children }) => {
644
650
  const storeRef = (0, import_react.useRef)(null);
645
651
  if (storeRef.current === null) {
646
652
  storeRef.current = new ViewportStore();
@@ -695,12 +701,64 @@ function useScrollViewportSelector(selector) {
695
701
  () => selector(null)
696
702
  );
697
703
  }
704
+ function useVisibleChunkIndices(totalWidth, chunkWidth, originX = 0) {
705
+ const visibleChunkKey = useScrollViewportSelector((viewport) => {
706
+ const totalChunks = Math.ceil(totalWidth / chunkWidth);
707
+ const indices = [];
708
+ for (let i = 0; i < totalChunks; i++) {
709
+ const chunkLeft = i * chunkWidth;
710
+ const thisChunkWidth = Math.min(totalWidth - chunkLeft, chunkWidth);
711
+ if (viewport) {
712
+ const chunkLeftGlobal = originX + chunkLeft;
713
+ const chunkEndGlobal = chunkLeftGlobal + thisChunkWidth;
714
+ if (chunkEndGlobal <= viewport.visibleStart || chunkLeftGlobal >= viewport.visibleEnd) {
715
+ continue;
716
+ }
717
+ }
718
+ indices.push(i);
719
+ }
720
+ return indices.join(",");
721
+ });
722
+ return (0, import_react.useMemo)(
723
+ () => visibleChunkKey ? visibleChunkKey.split(",").map(Number) : [],
724
+ [visibleChunkKey]
725
+ );
726
+ }
698
727
 
699
- // src/constants.ts
700
- var MAX_CANVAS_WIDTH = 1e3;
728
+ // src/contexts/ClipViewportOrigin.tsx
729
+ var import_react2 = require("react");
730
+ var import_jsx_runtime4 = require("react/jsx-runtime");
731
+ var ClipViewportOriginContext = (0, import_react2.createContext)(0);
732
+ var ClipViewportOriginProvider = ({
733
+ originX,
734
+ children
735
+ }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ClipViewportOriginContext.Provider, { value: originX, children });
736
+ var useClipViewportOrigin = () => (0, import_react2.useContext)(ClipViewportOriginContext);
737
+
738
+ // src/hooks/useChunkedCanvasRefs.ts
739
+ var import_react3 = require("react");
740
+ function useChunkedCanvasRefs() {
741
+ const canvasMapRef = (0, import_react3.useRef)(/* @__PURE__ */ new Map());
742
+ const canvasRef = (0, import_react3.useCallback)((canvas) => {
743
+ if (canvas !== null) {
744
+ const idx = parseInt(canvas.dataset.index, 10);
745
+ canvasMapRef.current.set(idx, canvas);
746
+ }
747
+ }, []);
748
+ (0, import_react3.useEffect)(() => {
749
+ const map = canvasMapRef.current;
750
+ for (const [idx, canvas] of map.entries()) {
751
+ if (!canvas.isConnected) {
752
+ map.delete(idx);
753
+ }
754
+ }
755
+ });
756
+ return { canvasRef, canvasMapRef };
757
+ }
701
758
 
702
759
  // src/components/Channel.tsx
703
- var import_jsx_runtime4 = require("react/jsx-runtime");
760
+ var import_core = require("@waveform-playlist/core");
761
+ var import_jsx_runtime5 = require("react/jsx-runtime");
704
762
  function createCanvasFillStyle(ctx, color, width, height) {
705
763
  if (!isWaveformGradient(color)) {
706
764
  return color;
@@ -760,49 +818,13 @@ var Channel = (props) => {
760
818
  transparentBackground = false,
761
819
  drawMode = "inverted"
762
820
  } = props;
763
- const canvasesRef = (0, import_react2.useRef)([]);
764
- const visibleChunkKey = useScrollViewportSelector((viewport) => {
765
- const totalChunks = Math.ceil(length / MAX_CANVAS_WIDTH);
766
- const indices = [];
767
- for (let i = 0; i < totalChunks; i++) {
768
- const chunkLeft = i * MAX_CANVAS_WIDTH;
769
- const chunkWidth = Math.min(length - chunkLeft, MAX_CANVAS_WIDTH);
770
- if (viewport) {
771
- const chunkEnd = chunkLeft + chunkWidth;
772
- if (chunkEnd <= viewport.visibleStart || chunkLeft >= viewport.visibleEnd) {
773
- continue;
774
- }
775
- }
776
- indices.push(i);
777
- }
778
- return indices.join(",");
779
- });
780
- const visibleChunkIndices = visibleChunkKey ? visibleChunkKey.split(",").map(Number) : [];
781
- const canvasRef = (0, import_react2.useCallback)(
782
- (canvas) => {
783
- if (canvas !== null) {
784
- const index2 = parseInt(canvas.dataset.index, 10);
785
- canvasesRef.current[index2] = canvas;
786
- }
787
- },
788
- []
789
- );
790
- (0, import_react2.useEffect)(() => {
791
- const canvases = canvasesRef.current;
792
- for (let i = canvases.length - 1; i >= 0; i--) {
793
- if (canvases[i] && !canvases[i].isConnected) {
794
- delete canvases[i];
795
- }
796
- }
797
- });
798
- (0, import_react2.useLayoutEffect)(() => {
799
- const canvases = canvasesRef.current;
821
+ const { canvasRef, canvasMapRef } = useChunkedCanvasRefs();
822
+ const clipOriginX = useClipViewportOrigin();
823
+ const visibleChunkIndices = useVisibleChunkIndices(length, import_core.MAX_CANVAS_WIDTH, clipOriginX);
824
+ (0, import_react4.useLayoutEffect)(() => {
800
825
  const step = barWidth + barGap;
801
- for (let i = 0; i < canvases.length; i++) {
802
- const canvas = canvases[i];
803
- if (!canvas) continue;
804
- const canvasIdx = parseInt(canvas.dataset.index, 10);
805
- const globalPixelOffset = canvasIdx * MAX_CANVAS_WIDTH;
826
+ for (const [canvasIdx, canvas] of canvasMapRef.current.entries()) {
827
+ const globalPixelOffset = canvasIdx * import_core.MAX_CANVAS_WIDTH;
806
828
  const ctx = canvas.getContext("2d");
807
829
  const h2 = Math.floor(waveHeight / 2);
808
830
  const maxValue = 2 ** (bits - 1);
@@ -818,12 +840,7 @@ var Channel = (props) => {
818
840
  } else {
819
841
  fillColor = waveOutlineColor;
820
842
  }
821
- ctx.fillStyle = createCanvasFillStyle(
822
- ctx,
823
- fillColor,
824
- canvasWidth,
825
- waveHeight
826
- );
843
+ ctx.fillStyle = createCanvasFillStyle(ctx, fillColor, canvasWidth, waveHeight);
827
844
  const canvasStartGlobal = globalPixelOffset;
828
845
  const canvasEndGlobal = globalPixelOffset + canvasWidth;
829
846
  const firstBarGlobal = Math.floor((canvasStartGlobal - barWidth + step) / step) * step;
@@ -847,6 +864,7 @@ var Channel = (props) => {
847
864
  }
848
865
  }
849
866
  }, [
867
+ canvasMapRef,
850
868
  data,
851
869
  bits,
852
870
  waveHeight,
@@ -857,12 +875,12 @@ var Channel = (props) => {
857
875
  barWidth,
858
876
  barGap,
859
877
  drawMode,
860
- visibleChunkKey
878
+ visibleChunkIndices
861
879
  ]);
862
880
  const waveforms = visibleChunkIndices.map((i) => {
863
- const chunkLeft = i * MAX_CANVAS_WIDTH;
864
- const currentWidth = Math.min(length - chunkLeft, MAX_CANVAS_WIDTH);
865
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
881
+ const chunkLeft = i * import_core.MAX_CANVAS_WIDTH;
882
+ const currentWidth = Math.min(length - chunkLeft, import_core.MAX_CANVAS_WIDTH);
883
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
866
884
  Waveform,
867
885
  {
868
886
  $cssWidth: currentWidth,
@@ -878,7 +896,7 @@ var Channel = (props) => {
878
896
  });
879
897
  const bgColor = waveFillColor;
880
898
  const backgroundCss = transparentBackground ? "transparent" : waveformColorToCss(bgColor);
881
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
899
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
882
900
  Wrapper,
883
901
  {
884
902
  $index: index,
@@ -892,8 +910,8 @@ var Channel = (props) => {
892
910
  };
893
911
 
894
912
  // src/components/ErrorBoundary.tsx
895
- var import_react3 = __toESM(require("react"));
896
- var import_jsx_runtime5 = require("react/jsx-runtime");
913
+ var import_react5 = __toESM(require("react"));
914
+ var import_jsx_runtime6 = require("react/jsx-runtime");
897
915
  var errorContainerStyle = {
898
916
  padding: "16px",
899
917
  background: "#1a1a2e",
@@ -907,7 +925,7 @@ var errorContainerStyle = {
907
925
  alignItems: "center",
908
926
  justifyContent: "center"
909
927
  };
910
- var PlaylistErrorBoundary = class extends import_react3.default.Component {
928
+ var PlaylistErrorBoundary = class extends import_react5.default.Component {
911
929
  constructor(props) {
912
930
  super(props);
913
931
  this.state = { hasError: false, error: null };
@@ -923,7 +941,7 @@ var PlaylistErrorBoundary = class extends import_react3.default.Component {
923
941
  if (this.props.fallback) {
924
942
  return this.props.fallback;
925
943
  }
926
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: errorContainerStyle, children: "Waveform playlist encountered an error. Check console for details." });
944
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: errorContainerStyle, children: "Waveform playlist encountered an error. Check console for details." });
927
945
  }
928
946
  return this.props.children;
929
947
  }
@@ -931,12 +949,12 @@ var PlaylistErrorBoundary = class extends import_react3.default.Component {
931
949
 
932
950
  // src/components/Clip.tsx
933
951
  var import_styled_components13 = __toESM(require("styled-components"));
934
- var import_core = require("@dnd-kit/core");
952
+ var import_core2 = require("@dnd-kit/core");
935
953
  var import_utilities = require("@dnd-kit/utilities");
936
954
 
937
955
  // src/components/ClipHeader.tsx
938
956
  var import_styled_components10 = __toESM(require("styled-components"));
939
- var import_jsx_runtime6 = require("react/jsx-runtime");
957
+ var import_jsx_runtime7 = require("react/jsx-runtime");
940
958
  var CLIP_HEADER_HEIGHT = 22;
941
959
  var HeaderContainer = import_styled_components10.default.div`
942
960
  position: relative;
@@ -976,15 +994,7 @@ var ClipHeaderPresentational = ({
976
994
  trackName,
977
995
  isSelected = false
978
996
  }) => {
979
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
980
- HeaderContainer,
981
- {
982
- $isDragging: false,
983
- $interactive: false,
984
- $isSelected: isSelected,
985
- children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(TrackName, { children: trackName })
986
- }
987
- );
997
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(HeaderContainer, { $isDragging: false, $interactive: false, $isSelected: isSelected, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TrackName, { children: trackName }) });
988
998
  };
989
999
  var ClipHeader = ({
990
1000
  clipId,
@@ -996,16 +1006,10 @@ var ClipHeader = ({
996
1006
  dragHandleProps
997
1007
  }) => {
998
1008
  if (disableDrag || !dragHandleProps) {
999
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1000
- ClipHeaderPresentational,
1001
- {
1002
- trackName,
1003
- isSelected
1004
- }
1005
- );
1009
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ClipHeaderPresentational, { trackName, isSelected });
1006
1010
  }
1007
1011
  const { attributes, listeners, setActivatorNodeRef } = dragHandleProps;
1008
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1012
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1009
1013
  HeaderContainer,
1010
1014
  {
1011
1015
  ref: setActivatorNodeRef,
@@ -1014,15 +1018,15 @@ var ClipHeader = ({
1014
1018
  $isSelected: isSelected,
1015
1019
  ...listeners,
1016
1020
  ...attributes,
1017
- children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(TrackName, { children: trackName })
1021
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TrackName, { children: trackName })
1018
1022
  }
1019
1023
  );
1020
1024
  };
1021
1025
 
1022
1026
  // src/components/ClipBoundary.tsx
1023
- var import_react4 = __toESM(require("react"));
1027
+ var import_react6 = __toESM(require("react"));
1024
1028
  var import_styled_components11 = __toESM(require("styled-components"));
1025
- var import_jsx_runtime7 = require("react/jsx-runtime");
1029
+ var import_jsx_runtime8 = require("react/jsx-runtime");
1026
1030
  var CLIP_BOUNDARY_WIDTH = 8;
1027
1031
  var CLIP_BOUNDARY_WIDTH_TOUCH = 24;
1028
1032
  var BoundaryContainer = import_styled_components11.default.div`
@@ -1062,12 +1066,12 @@ var ClipBoundary = ({
1062
1066
  dragHandleProps,
1063
1067
  touchOptimized = false
1064
1068
  }) => {
1065
- const [isHovered, setIsHovered] = import_react4.default.useState(false);
1069
+ const [isHovered, setIsHovered] = import_react6.default.useState(false);
1066
1070
  if (!dragHandleProps) {
1067
1071
  return null;
1068
1072
  }
1069
1073
  const { attributes, listeners, setActivatorNodeRef, isDragging } = dragHandleProps;
1070
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1074
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1071
1075
  BoundaryContainer,
1072
1076
  {
1073
1077
  ref: setActivatorNodeRef,
@@ -1087,7 +1091,7 @@ var ClipBoundary = ({
1087
1091
 
1088
1092
  // src/components/FadeOverlay.tsx
1089
1093
  var import_styled_components12 = __toESM(require("styled-components"));
1090
- var import_jsx_runtime8 = require("react/jsx-runtime");
1094
+ var import_jsx_runtime9 = require("react/jsx-runtime");
1091
1095
  var FadeContainer = import_styled_components12.default.div.attrs((props) => ({
1092
1096
  style: {
1093
1097
  left: `${props.$left}px`,
@@ -1144,17 +1148,11 @@ var FadeOverlay = ({
1144
1148
  const theme = (0, import_styled_components12.useTheme)();
1145
1149
  if (width < 1) return null;
1146
1150
  const fillColor = color || theme?.fadeOverlayColor || "rgba(0, 0, 0, 0.4)";
1147
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(FadeContainer, { $left: left, $width: width, $type: type, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(FadeSvg, { $type: type, viewBox: `0 0 ${width} 100`, preserveAspectRatio: "none", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1148
- "path",
1149
- {
1150
- d: generateFadePath(width, 100, curveType),
1151
- fill: fillColor
1152
- }
1153
- ) }) });
1151
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(FadeContainer, { $left: left, $width: width, $type: type, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(FadeSvg, { $type: type, viewBox: `0 0 ${width} 100`, preserveAspectRatio: "none", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: generateFadePath(width, 100, curveType), fill: fillColor }) }) });
1154
1152
  };
1155
1153
 
1156
1154
  // src/components/Clip.tsx
1157
- var import_jsx_runtime9 = require("react/jsx-runtime");
1155
+ var import_jsx_runtime10 = require("react/jsx-runtime");
1158
1156
  var ClipContainer = import_styled_components13.default.div.attrs((props) => ({
1159
1157
  style: props.$isOverlay ? {} : {
1160
1158
  left: `${props.$left}px`,
@@ -1207,7 +1205,7 @@ var Clip = ({
1207
1205
  const width = endPixel - left;
1208
1206
  const enableDrag = showHeader && !disableHeaderDrag && !isOverlay;
1209
1207
  const draggableId = `clip-${trackIndex}-${clipIndex}`;
1210
- const { attributes, listeners, setNodeRef, setActivatorNodeRef, transform, isDragging } = (0, import_core.useDraggable)({
1208
+ const { attributes, listeners, setNodeRef, setActivatorNodeRef, transform, isDragging } = (0, import_core2.useDraggable)({
1211
1209
  id: draggableId,
1212
1210
  data: { clipId, trackIndex, clipIndex },
1213
1211
  disabled: !enableDrag
@@ -1218,7 +1216,7 @@ var Clip = ({
1218
1216
  listeners: leftBoundaryListeners,
1219
1217
  setActivatorNodeRef: setLeftBoundaryActivatorRef,
1220
1218
  isDragging: isLeftBoundaryDragging
1221
- } = (0, import_core.useDraggable)({
1219
+ } = (0, import_core2.useDraggable)({
1222
1220
  id: leftBoundaryId,
1223
1221
  data: { clipId, trackIndex, clipIndex, boundary: "left" },
1224
1222
  disabled: !enableDrag
@@ -1229,7 +1227,7 @@ var Clip = ({
1229
1227
  listeners: rightBoundaryListeners,
1230
1228
  setActivatorNodeRef: setRightBoundaryActivatorRef,
1231
1229
  isDragging: isRightBoundaryDragging
1232
- } = (0, import_core.useDraggable)({
1230
+ } = (0, import_core2.useDraggable)({
1233
1231
  id: rightBoundaryId,
1234
1232
  data: { clipId, trackIndex, clipIndex, boundary: "right" },
1235
1233
  disabled: !enableDrag
@@ -1239,7 +1237,7 @@ var Clip = ({
1239
1237
  zIndex: isDragging ? 100 : void 0
1240
1238
  // Below controls (z-index: 999) but above other clips
1241
1239
  } : void 0;
1242
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1240
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1243
1241
  ClipContainer,
1244
1242
  {
1245
1243
  ref: setNodeRef,
@@ -1252,7 +1250,7 @@ var Clip = ({
1252
1250
  "data-track-id": trackId,
1253
1251
  onMouseDown,
1254
1252
  children: [
1255
- showHeader && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1253
+ showHeader && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1256
1254
  ClipHeader,
1257
1255
  {
1258
1256
  clipId,
@@ -1264,9 +1262,9 @@ var Clip = ({
1264
1262
  dragHandleProps: enableDrag ? { attributes, listeners, setActivatorNodeRef } : void 0
1265
1263
  }
1266
1264
  ),
1267
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(ChannelsWrapper, { $isOverlay: isOverlay, children: [
1265
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ClipViewportOriginProvider, { originX: left, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(ChannelsWrapper, { $isOverlay: isOverlay, children: [
1268
1266
  children,
1269
- showFades && fadeIn && fadeIn.duration > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1267
+ showFades && fadeIn && fadeIn.duration > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1270
1268
  FadeOverlay,
1271
1269
  {
1272
1270
  left: 0,
@@ -1275,7 +1273,7 @@ var Clip = ({
1275
1273
  curveType: fadeIn.type
1276
1274
  }
1277
1275
  ),
1278
- showFades && fadeOut && fadeOut.duration > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1276
+ showFades && fadeOut && fadeOut.duration > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1279
1277
  FadeOverlay,
1280
1278
  {
1281
1279
  left: width - Math.floor(fadeOut.duration * sampleRate / samplesPerPixel),
@@ -1284,9 +1282,9 @@ var Clip = ({
1284
1282
  curveType: fadeOut.type
1285
1283
  }
1286
1284
  )
1287
- ] }),
1288
- showHeader && !disableHeaderDrag && !isOverlay && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
1289
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1285
+ ] }) }),
1286
+ showHeader && !disableHeaderDrag && !isOverlay && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
1287
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1290
1288
  ClipBoundary,
1291
1289
  {
1292
1290
  clipId,
@@ -1302,7 +1300,7 @@ var Clip = ({
1302
1300
  }
1303
1301
  }
1304
1302
  ),
1305
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1303
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1306
1304
  ClipBoundary,
1307
1305
  {
1308
1306
  clipId,
@@ -1326,7 +1324,7 @@ var Clip = ({
1326
1324
 
1327
1325
  // src/components/MasterVolumeControl.tsx
1328
1326
  var import_styled_components14 = __toESM(require("styled-components"));
1329
- var import_jsx_runtime10 = require("react/jsx-runtime");
1327
+ var import_jsx_runtime11 = require("react/jsx-runtime");
1330
1328
  var VolumeContainer = import_styled_components14.default.div`
1331
1329
  display: inline-flex;
1332
1330
  align-items: center;
@@ -1348,9 +1346,9 @@ var MasterVolumeControl = ({
1348
1346
  const handleChange = (e) => {
1349
1347
  onChange(parseFloat(e.target.value) / 100);
1350
1348
  };
1351
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(VolumeContainer, { className, children: [
1352
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(VolumeLabel, { htmlFor: "master-gain", children: "Master Volume" }),
1353
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1349
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(VolumeContainer, { className, children: [
1350
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(VolumeLabel, { htmlFor: "master-gain", children: "Master Volume" }),
1351
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1354
1352
  VolumeSlider,
1355
1353
  {
1356
1354
  min: "0",
@@ -1365,9 +1363,9 @@ var MasterVolumeControl = ({
1365
1363
  };
1366
1364
 
1367
1365
  // src/components/Playhead.tsx
1368
- var import_react5 = require("react");
1366
+ var import_react7 = require("react");
1369
1367
  var import_styled_components15 = __toESM(require("styled-components"));
1370
- var import_jsx_runtime11 = require("react/jsx-runtime");
1368
+ var import_jsx_runtime12 = require("react/jsx-runtime");
1371
1369
  var PlayheadLine = import_styled_components15.default.div.attrs((props) => ({
1372
1370
  style: {
1373
1371
  transform: `translate3d(${props.$position}px, 0, 0)`
@@ -1384,7 +1382,7 @@ var PlayheadLine = import_styled_components15.default.div.attrs((props) => ({
1384
1382
  will-change: transform;
1385
1383
  `;
1386
1384
  var Playhead = ({ position, color = "#ff0000" }) => {
1387
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(PlayheadLine, { $position: position, $color: color });
1385
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(PlayheadLine, { $position: position, $color: color });
1388
1386
  };
1389
1387
  var PlayheadWithMarkerContainer = import_styled_components15.default.div`
1390
1388
  position: absolute;
@@ -1424,9 +1422,9 @@ var PlayheadWithMarker = ({
1424
1422
  controlsOffset,
1425
1423
  getAudioContextTime
1426
1424
  }) => {
1427
- const containerRef = (0, import_react5.useRef)(null);
1428
- const animationFrameRef = (0, import_react5.useRef)(null);
1429
- (0, import_react5.useEffect)(() => {
1425
+ const containerRef = (0, import_react7.useRef)(null);
1426
+ const animationFrameRef = (0, import_react7.useRef)(null);
1427
+ (0, import_react7.useEffect)(() => {
1430
1428
  const updatePosition = () => {
1431
1429
  if (containerRef.current) {
1432
1430
  let time;
@@ -1454,24 +1452,33 @@ var PlayheadWithMarker = ({
1454
1452
  animationFrameRef.current = null;
1455
1453
  }
1456
1454
  };
1457
- }, [isPlaying, sampleRate, samplesPerPixel, controlsOffset, currentTimeRef, playbackStartTimeRef, audioStartPositionRef, getAudioContextTime]);
1458
- (0, import_react5.useEffect)(() => {
1455
+ }, [
1456
+ isPlaying,
1457
+ sampleRate,
1458
+ samplesPerPixel,
1459
+ controlsOffset,
1460
+ currentTimeRef,
1461
+ playbackStartTimeRef,
1462
+ audioStartPositionRef,
1463
+ getAudioContextTime
1464
+ ]);
1465
+ (0, import_react7.useEffect)(() => {
1459
1466
  if (!isPlaying && containerRef.current) {
1460
1467
  const time = currentTimeRef.current ?? 0;
1461
1468
  const pos = time * sampleRate / samplesPerPixel + controlsOffset;
1462
1469
  containerRef.current.style.transform = `translate3d(${pos}px, 0, 0)`;
1463
1470
  }
1464
1471
  });
1465
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(PlayheadWithMarkerContainer, { ref: containerRef, $color: color, children: [
1466
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(MarkerTriangle, { $color: color }),
1467
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(MarkerLine, { $color: color })
1472
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(PlayheadWithMarkerContainer, { ref: containerRef, $color: color, children: [
1473
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(MarkerTriangle, { $color: color }),
1474
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(MarkerLine, { $color: color })
1468
1475
  ] });
1469
1476
  };
1470
1477
 
1471
1478
  // src/components/Playlist.tsx
1472
1479
  var import_styled_components16 = __toESM(require("styled-components"));
1473
- var import_react6 = require("react");
1474
- var import_jsx_runtime12 = require("react/jsx-runtime");
1480
+ var import_react8 = require("react");
1481
+ var import_jsx_runtime13 = require("react/jsx-runtime");
1475
1482
  var Wrapper2 = import_styled_components16.default.div`
1476
1483
  overflow-y: hidden;
1477
1484
  overflow-x: auto;
@@ -1525,41 +1532,37 @@ var Playlist = ({
1525
1532
  isSelecting,
1526
1533
  "data-playlist-state": playlistState
1527
1534
  }) => {
1528
- const wrapperRef = (0, import_react6.useRef)(null);
1529
- const handleRef = (0, import_react6.useCallback)((el) => {
1530
- wrapperRef.current = el;
1531
- scrollContainerRef?.(el);
1532
- }, [scrollContainerRef]);
1533
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Wrapper2, { "data-scroll-container": "true", "data-playlist-state": playlistState, ref: handleRef, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ScrollViewportProvider, { containerRef: wrapperRef, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
1534
- ScrollContainer,
1535
- {
1536
- $backgroundColor: backgroundColor,
1537
- $width: scrollContainerWidth,
1538
- children: [
1539
- timescale && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(TimescaleWrapper, { $width: timescaleWidth, $backgroundColor: timescaleBackgroundColor, children: timescale }),
1540
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(TracksContainer, { $width: tracksWidth, $backgroundColor: backgroundColor, children: [
1541
- children,
1542
- (onTracksClick || onTracksMouseDown) && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1543
- ClickOverlay,
1544
- {
1545
- $controlsWidth: controlsWidth,
1546
- $isSelecting: isSelecting,
1547
- onClick: onTracksClick,
1548
- onMouseDown: onTracksMouseDown,
1549
- onMouseMove: onTracksMouseMove,
1550
- onMouseUp: onTracksMouseUp
1551
- }
1552
- )
1553
- ] })
1554
- ]
1555
- }
1556
- ) }) });
1535
+ const wrapperRef = (0, import_react8.useRef)(null);
1536
+ const handleRef = (0, import_react8.useCallback)(
1537
+ (el) => {
1538
+ wrapperRef.current = el;
1539
+ scrollContainerRef?.(el);
1540
+ },
1541
+ [scrollContainerRef]
1542
+ );
1543
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Wrapper2, { "data-scroll-container": "true", "data-playlist-state": playlistState, ref: handleRef, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ScrollViewportProvider, { containerRef: wrapperRef, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(ScrollContainer, { $backgroundColor: backgroundColor, $width: scrollContainerWidth, children: [
1544
+ timescale && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(TimescaleWrapper, { $width: timescaleWidth, $backgroundColor: timescaleBackgroundColor, children: timescale }),
1545
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(TracksContainer, { $width: tracksWidth, $backgroundColor: backgroundColor, children: [
1546
+ children,
1547
+ (onTracksClick || onTracksMouseDown) && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1548
+ ClickOverlay,
1549
+ {
1550
+ $controlsWidth: controlsWidth,
1551
+ $isSelecting: isSelecting,
1552
+ onClick: onTracksClick,
1553
+ onMouseDown: onTracksMouseDown,
1554
+ onMouseMove: onTracksMouseMove,
1555
+ onMouseUp: onTracksMouseUp
1556
+ }
1557
+ )
1558
+ ] })
1559
+ ] }) }) });
1557
1560
  };
1558
1561
  var StyledPlaylist = (0, import_styled_components16.withTheme)(Playlist);
1559
1562
 
1560
1563
  // src/components/Selection.tsx
1561
1564
  var import_styled_components17 = __toESM(require("styled-components"));
1562
- var import_jsx_runtime13 = require("react/jsx-runtime");
1565
+ var import_jsx_runtime14 = require("react/jsx-runtime");
1563
1566
  var SelectionOverlay = import_styled_components17.default.div.attrs((props) => ({
1564
1567
  style: {
1565
1568
  left: `${props.$left}px`,
@@ -1583,13 +1586,13 @@ var Selection = ({
1583
1586
  if (width <= 0) {
1584
1587
  return null;
1585
1588
  }
1586
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(SelectionOverlay, { $left: startPosition, $width: width, $color: color, "data-selection": true });
1589
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(SelectionOverlay, { $left: startPosition, $width: width, $color: color, "data-selection": true });
1587
1590
  };
1588
1591
 
1589
1592
  // src/components/LoopRegion.tsx
1590
- var import_react7 = require("react");
1593
+ var import_react9 = require("react");
1591
1594
  var import_styled_components18 = __toESM(require("styled-components"));
1592
- var import_jsx_runtime14 = require("react/jsx-runtime");
1595
+ var import_jsx_runtime15 = require("react/jsx-runtime");
1593
1596
  var LoopRegionOverlayDiv = import_styled_components18.default.div.attrs((props) => ({
1594
1597
  style: {
1595
1598
  left: `${props.$left}px`,
@@ -1638,8 +1641,8 @@ var LoopRegion = ({
1638
1641
  if (width <= 0) {
1639
1642
  return null;
1640
1643
  }
1641
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
1642
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1644
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
1645
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1643
1646
  LoopRegionOverlayDiv,
1644
1647
  {
1645
1648
  $left: startPosition,
@@ -1648,7 +1651,7 @@ var LoopRegion = ({
1648
1651
  "data-loop-region": true
1649
1652
  }
1650
1653
  ),
1651
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1654
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1652
1655
  LoopMarker,
1653
1656
  {
1654
1657
  $left: startPosition,
@@ -1657,7 +1660,7 @@ var LoopRegion = ({
1657
1660
  "data-loop-marker": "start"
1658
1661
  }
1659
1662
  ),
1660
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1663
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1661
1664
  LoopMarker,
1662
1665
  {
1663
1666
  $left: endPosition - 2,
@@ -1738,71 +1741,77 @@ var LoopRegionMarkers = ({
1738
1741
  minPosition = 0,
1739
1742
  maxPosition = Infinity
1740
1743
  }) => {
1741
- const [draggingMarker, setDraggingMarker] = (0, import_react7.useState)(null);
1742
- const dragStartX = (0, import_react7.useRef)(0);
1743
- const dragStartPosition = (0, import_react7.useRef)(0);
1744
- const dragStartEnd = (0, import_react7.useRef)(0);
1744
+ const [draggingMarker, setDraggingMarker] = (0, import_react9.useState)(null);
1745
+ const dragStartX = (0, import_react9.useRef)(0);
1746
+ const dragStartPosition = (0, import_react9.useRef)(0);
1747
+ const dragStartEnd = (0, import_react9.useRef)(0);
1745
1748
  const width = Math.max(0, endPosition - startPosition);
1746
- const handleMarkerMouseDown = (0, import_react7.useCallback)((e, marker) => {
1747
- e.preventDefault();
1748
- e.stopPropagation();
1749
- setDraggingMarker(marker);
1750
- dragStartX.current = e.clientX;
1751
- dragStartPosition.current = marker === "start" ? startPosition : endPosition;
1752
- const handleMouseMove = (moveEvent) => {
1753
- const delta = moveEvent.clientX - dragStartX.current;
1754
- const newPosition = dragStartPosition.current + delta;
1755
- if (marker === "start") {
1756
- const clampedPosition = Math.max(minPosition, Math.min(endPosition - 10, newPosition));
1757
- onLoopStartChange?.(clampedPosition);
1758
- } else {
1759
- const clampedPosition = Math.max(startPosition + 10, Math.min(maxPosition, newPosition));
1760
- onLoopEndChange?.(clampedPosition);
1761
- }
1762
- };
1763
- const handleMouseUp = () => {
1764
- setDraggingMarker(null);
1765
- document.removeEventListener("mousemove", handleMouseMove);
1766
- document.removeEventListener("mouseup", handleMouseUp);
1767
- };
1768
- document.addEventListener("mousemove", handleMouseMove);
1769
- document.addEventListener("mouseup", handleMouseUp);
1770
- }, [startPosition, endPosition, minPosition, maxPosition, onLoopStartChange, onLoopEndChange]);
1771
- const handleRegionMouseDown = (0, import_react7.useCallback)((e) => {
1772
- e.preventDefault();
1773
- e.stopPropagation();
1774
- setDraggingMarker("region");
1775
- dragStartX.current = e.clientX;
1776
- dragStartPosition.current = startPosition;
1777
- dragStartEnd.current = endPosition;
1778
- const regionWidth = endPosition - startPosition;
1779
- const handleMouseMove = (moveEvent) => {
1780
- const delta = moveEvent.clientX - dragStartX.current;
1781
- let newStart = dragStartPosition.current + delta;
1782
- let newEnd = dragStartEnd.current + delta;
1783
- if (newStart < minPosition) {
1784
- newStart = minPosition;
1785
- newEnd = minPosition + regionWidth;
1786
- }
1787
- if (newEnd > maxPosition) {
1788
- newEnd = maxPosition;
1789
- newStart = maxPosition - regionWidth;
1790
- }
1791
- onLoopRegionMove?.(newStart, newEnd);
1792
- };
1793
- const handleMouseUp = () => {
1794
- setDraggingMarker(null);
1795
- document.removeEventListener("mousemove", handleMouseMove);
1796
- document.removeEventListener("mouseup", handleMouseUp);
1797
- };
1798
- document.addEventListener("mousemove", handleMouseMove);
1799
- document.addEventListener("mouseup", handleMouseUp);
1800
- }, [startPosition, endPosition, minPosition, maxPosition, onLoopRegionMove]);
1749
+ const handleMarkerMouseDown = (0, import_react9.useCallback)(
1750
+ (e, marker) => {
1751
+ e.preventDefault();
1752
+ e.stopPropagation();
1753
+ setDraggingMarker(marker);
1754
+ dragStartX.current = e.clientX;
1755
+ dragStartPosition.current = marker === "start" ? startPosition : endPosition;
1756
+ const handleMouseMove = (moveEvent) => {
1757
+ const delta = moveEvent.clientX - dragStartX.current;
1758
+ const newPosition = dragStartPosition.current + delta;
1759
+ if (marker === "start") {
1760
+ const clampedPosition = Math.max(minPosition, Math.min(endPosition - 10, newPosition));
1761
+ onLoopStartChange?.(clampedPosition);
1762
+ } else {
1763
+ const clampedPosition = Math.max(startPosition + 10, Math.min(maxPosition, newPosition));
1764
+ onLoopEndChange?.(clampedPosition);
1765
+ }
1766
+ };
1767
+ const handleMouseUp = () => {
1768
+ setDraggingMarker(null);
1769
+ document.removeEventListener("mousemove", handleMouseMove);
1770
+ document.removeEventListener("mouseup", handleMouseUp);
1771
+ };
1772
+ document.addEventListener("mousemove", handleMouseMove);
1773
+ document.addEventListener("mouseup", handleMouseUp);
1774
+ },
1775
+ [startPosition, endPosition, minPosition, maxPosition, onLoopStartChange, onLoopEndChange]
1776
+ );
1777
+ const handleRegionMouseDown = (0, import_react9.useCallback)(
1778
+ (e) => {
1779
+ e.preventDefault();
1780
+ e.stopPropagation();
1781
+ setDraggingMarker("region");
1782
+ dragStartX.current = e.clientX;
1783
+ dragStartPosition.current = startPosition;
1784
+ dragStartEnd.current = endPosition;
1785
+ const regionWidth = endPosition - startPosition;
1786
+ const handleMouseMove = (moveEvent) => {
1787
+ const delta = moveEvent.clientX - dragStartX.current;
1788
+ let newStart = dragStartPosition.current + delta;
1789
+ let newEnd = dragStartEnd.current + delta;
1790
+ if (newStart < minPosition) {
1791
+ newStart = minPosition;
1792
+ newEnd = minPosition + regionWidth;
1793
+ }
1794
+ if (newEnd > maxPosition) {
1795
+ newEnd = maxPosition;
1796
+ newStart = maxPosition - regionWidth;
1797
+ }
1798
+ onLoopRegionMove?.(newStart, newEnd);
1799
+ };
1800
+ const handleMouseUp = () => {
1801
+ setDraggingMarker(null);
1802
+ document.removeEventListener("mousemove", handleMouseMove);
1803
+ document.removeEventListener("mouseup", handleMouseUp);
1804
+ };
1805
+ document.addEventListener("mousemove", handleMouseMove);
1806
+ document.addEventListener("mouseup", handleMouseUp);
1807
+ },
1808
+ [startPosition, endPosition, minPosition, maxPosition, onLoopRegionMove]
1809
+ );
1801
1810
  if (width <= 0) {
1802
1811
  return null;
1803
1812
  }
1804
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
1805
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1813
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
1814
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1806
1815
  TimescaleLoopShade,
1807
1816
  {
1808
1817
  $left: startPosition,
@@ -1813,7 +1822,7 @@ var LoopRegionMarkers = ({
1813
1822
  "data-loop-region-timescale": true
1814
1823
  }
1815
1824
  ),
1816
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1825
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1817
1826
  DraggableMarkerHandle,
1818
1827
  {
1819
1828
  $left: startPosition,
@@ -1824,7 +1833,7 @@ var LoopRegionMarkers = ({
1824
1833
  "data-loop-marker-handle": "start"
1825
1834
  }
1826
1835
  ),
1827
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1836
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1828
1837
  DraggableMarkerHandle,
1829
1838
  {
1830
1839
  $left: endPosition,
@@ -1859,46 +1868,49 @@ var TimescaleLoopRegion = ({
1859
1868
  maxPosition = Infinity,
1860
1869
  controlsOffset = 0
1861
1870
  }) => {
1862
- const [, setIsCreating] = (0, import_react7.useState)(false);
1863
- const createStartX = (0, import_react7.useRef)(0);
1864
- const containerRef = (0, import_react7.useRef)(null);
1871
+ const [, setIsCreating] = (0, import_react9.useState)(false);
1872
+ const createStartX = (0, import_react9.useRef)(0);
1873
+ const containerRef = (0, import_react9.useRef)(null);
1865
1874
  const hasLoopRegion = endPosition > startPosition;
1866
- const handleBackgroundMouseDown = (0, import_react7.useCallback)((e) => {
1867
- const target = e.target;
1868
- if (target.closest("[data-loop-marker-handle]") || target.closest("[data-loop-region-timescale]")) {
1869
- return;
1870
- }
1871
- e.preventDefault();
1872
- setIsCreating(true);
1873
- const rect = containerRef.current?.getBoundingClientRect();
1874
- if (!rect) return;
1875
- const clickX = e.clientX - rect.left;
1876
- const clampedX = Math.max(minPosition, Math.min(maxPosition, clickX));
1877
- createStartX.current = clampedX;
1878
- onLoopRegionChange?.(clampedX, clampedX);
1879
- const handleMouseMove = (moveEvent) => {
1880
- const currentX = moveEvent.clientX - rect.left;
1881
- const clampedCurrentX = Math.max(minPosition, Math.min(maxPosition, currentX));
1882
- const newStart = Math.min(createStartX.current, clampedCurrentX);
1883
- const newEnd = Math.max(createStartX.current, clampedCurrentX);
1884
- onLoopRegionChange?.(newStart, newEnd);
1885
- };
1886
- const handleMouseUp = () => {
1887
- setIsCreating(false);
1888
- document.removeEventListener("mousemove", handleMouseMove);
1889
- document.removeEventListener("mouseup", handleMouseUp);
1890
- };
1891
- document.addEventListener("mousemove", handleMouseMove);
1892
- document.addEventListener("mouseup", handleMouseUp);
1893
- }, [minPosition, maxPosition, onLoopRegionChange]);
1894
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1875
+ const handleBackgroundMouseDown = (0, import_react9.useCallback)(
1876
+ (e) => {
1877
+ const target = e.target;
1878
+ if (target.closest("[data-loop-marker-handle]") || target.closest("[data-loop-region-timescale]")) {
1879
+ return;
1880
+ }
1881
+ e.preventDefault();
1882
+ setIsCreating(true);
1883
+ const rect = containerRef.current?.getBoundingClientRect();
1884
+ if (!rect) return;
1885
+ const clickX = e.clientX - rect.left;
1886
+ const clampedX = Math.max(minPosition, Math.min(maxPosition, clickX));
1887
+ createStartX.current = clampedX;
1888
+ onLoopRegionChange?.(clampedX, clampedX);
1889
+ const handleMouseMove = (moveEvent) => {
1890
+ const currentX = moveEvent.clientX - rect.left;
1891
+ const clampedCurrentX = Math.max(minPosition, Math.min(maxPosition, currentX));
1892
+ const newStart = Math.min(createStartX.current, clampedCurrentX);
1893
+ const newEnd = Math.max(createStartX.current, clampedCurrentX);
1894
+ onLoopRegionChange?.(newStart, newEnd);
1895
+ };
1896
+ const handleMouseUp = () => {
1897
+ setIsCreating(false);
1898
+ document.removeEventListener("mousemove", handleMouseMove);
1899
+ document.removeEventListener("mouseup", handleMouseUp);
1900
+ };
1901
+ document.addEventListener("mousemove", handleMouseMove);
1902
+ document.addEventListener("mouseup", handleMouseUp);
1903
+ },
1904
+ [minPosition, maxPosition, onLoopRegionChange]
1905
+ );
1906
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1895
1907
  TimescaleLoopCreator,
1896
1908
  {
1897
1909
  ref: containerRef,
1898
1910
  $leftOffset: controlsOffset,
1899
1911
  onMouseDown: handleBackgroundMouseDown,
1900
1912
  "data-timescale-loop-creator": true,
1901
- children: hasLoopRegion && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1913
+ children: hasLoopRegion && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1902
1914
  LoopRegionMarkers,
1903
1915
  {
1904
1916
  startPosition,
@@ -1917,10 +1929,10 @@ var TimescaleLoopRegion = ({
1917
1929
  };
1918
1930
 
1919
1931
  // src/components/SelectionTimeInputs.tsx
1920
- var import_react9 = require("react");
1932
+ var import_react11 = require("react");
1921
1933
 
1922
1934
  // src/components/TimeInput.tsx
1923
- var import_react8 = require("react");
1935
+ var import_react10 = require("react");
1924
1936
 
1925
1937
  // src/utils/timeFormat.ts
1926
1938
  function clockFormat(seconds, decimals) {
@@ -1970,7 +1982,7 @@ function parseTime(timeStr, format) {
1970
1982
  }
1971
1983
 
1972
1984
  // src/components/TimeInput.tsx
1973
- var import_jsx_runtime15 = require("react/jsx-runtime");
1985
+ var import_jsx_runtime16 = require("react/jsx-runtime");
1974
1986
  var TimeInput = ({
1975
1987
  id,
1976
1988
  label,
@@ -1980,8 +1992,8 @@ var TimeInput = ({
1980
1992
  onChange,
1981
1993
  readOnly = false
1982
1994
  }) => {
1983
- const [displayValue, setDisplayValue] = (0, import_react8.useState)("");
1984
- (0, import_react8.useEffect)(() => {
1995
+ const [displayValue, setDisplayValue] = (0, import_react10.useState)("");
1996
+ (0, import_react10.useEffect)(() => {
1985
1997
  const formatted = formatTime(value, format);
1986
1998
  setDisplayValue(formatted);
1987
1999
  }, [value, format, id]);
@@ -2001,9 +2013,9 @@ var TimeInput = ({
2001
2013
  e.currentTarget.blur();
2002
2014
  }
2003
2015
  };
2004
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
2005
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ScreenReaderOnly, { as: "label", htmlFor: id, children: label }),
2006
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2016
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
2017
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ScreenReaderOnly, { as: "label", htmlFor: id, children: label }),
2018
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2007
2019
  BaseInput,
2008
2020
  {
2009
2021
  type: "text",
@@ -2020,15 +2032,15 @@ var TimeInput = ({
2020
2032
  };
2021
2033
 
2022
2034
  // src/components/SelectionTimeInputs.tsx
2023
- var import_jsx_runtime16 = require("react/jsx-runtime");
2035
+ var import_jsx_runtime17 = require("react/jsx-runtime");
2024
2036
  var SelectionTimeInputs = ({
2025
2037
  selectionStart,
2026
2038
  selectionEnd,
2027
2039
  onSelectionChange,
2028
2040
  className
2029
2041
  }) => {
2030
- const [timeFormat, setTimeFormat] = (0, import_react9.useState)("hh:mm:ss.uuu");
2031
- (0, import_react9.useEffect)(() => {
2042
+ const [timeFormat, setTimeFormat] = (0, import_react11.useState)("hh:mm:ss.uuu");
2043
+ (0, import_react11.useEffect)(() => {
2032
2044
  const timeFormatSelect = document.querySelector(".time-format");
2033
2045
  const handleFormatChange = () => {
2034
2046
  if (timeFormatSelect) {
@@ -2053,8 +2065,8 @@ var SelectionTimeInputs = ({
2053
2065
  onSelectionChange(selectionStart, value);
2054
2066
  }
2055
2067
  };
2056
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className, children: [
2057
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2068
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className, children: [
2069
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2058
2070
  TimeInput,
2059
2071
  {
2060
2072
  id: "audio_start",
@@ -2065,7 +2077,7 @@ var SelectionTimeInputs = ({
2065
2077
  onChange: handleStartChange
2066
2078
  }
2067
2079
  ),
2068
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2080
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2069
2081
  TimeInput,
2070
2082
  {
2071
2083
  id: "audio_end",
@@ -2080,14 +2092,14 @@ var SelectionTimeInputs = ({
2080
2092
  };
2081
2093
 
2082
2094
  // src/contexts/DevicePixelRatio.tsx
2083
- var import_react10 = require("react");
2084
- var import_jsx_runtime17 = require("react/jsx-runtime");
2095
+ var import_react12 = require("react");
2096
+ var import_jsx_runtime18 = require("react/jsx-runtime");
2085
2097
  function getScale() {
2086
2098
  return window.devicePixelRatio;
2087
2099
  }
2088
- var DevicePixelRatioContext = (0, import_react10.createContext)(getScale());
2100
+ var DevicePixelRatioContext = (0, import_react12.createContext)(getScale());
2089
2101
  var DevicePixelRatioProvider = ({ children }) => {
2090
- const [scale, setScale] = (0, import_react10.useState)(getScale());
2102
+ const [scale, setScale] = (0, import_react12.useState)(getScale());
2091
2103
  matchMedia(`(resolution: ${getScale()}dppx)`).addEventListener(
2092
2104
  "change",
2093
2105
  () => {
@@ -2095,13 +2107,13 @@ var DevicePixelRatioProvider = ({ children }) => {
2095
2107
  },
2096
2108
  { once: true }
2097
2109
  );
2098
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DevicePixelRatioContext.Provider, { value: Math.ceil(scale), children });
2110
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(DevicePixelRatioContext.Provider, { value: Math.ceil(scale), children });
2099
2111
  };
2100
- var useDevicePixelRatio = () => (0, import_react10.useContext)(DevicePixelRatioContext);
2112
+ var useDevicePixelRatio = () => (0, import_react12.useContext)(DevicePixelRatioContext);
2101
2113
 
2102
2114
  // src/contexts/PlaylistInfo.tsx
2103
- var import_react11 = require("react");
2104
- var PlaylistInfoContext = (0, import_react11.createContext)({
2115
+ var import_react13 = require("react");
2116
+ var PlaylistInfoContext = (0, import_react13.createContext)({
2105
2117
  sampleRate: 48e3,
2106
2118
  samplesPerPixel: 1e3,
2107
2119
  zoomLevels: [1e3, 1500, 2e3, 2500],
@@ -2115,22 +2127,22 @@ var PlaylistInfoContext = (0, import_react11.createContext)({
2115
2127
  barWidth: 1,
2116
2128
  barGap: 0
2117
2129
  });
2118
- var usePlaylistInfo = () => (0, import_react11.useContext)(PlaylistInfoContext);
2130
+ var usePlaylistInfo = () => (0, import_react13.useContext)(PlaylistInfoContext);
2119
2131
 
2120
2132
  // src/contexts/Theme.tsx
2121
- var import_react12 = require("react");
2133
+ var import_react14 = require("react");
2122
2134
  var import_styled_components19 = require("styled-components");
2123
- var useTheme2 = () => (0, import_react12.useContext)(import_styled_components19.ThemeContext);
2135
+ var useTheme2 = () => (0, import_react14.useContext)(import_styled_components19.ThemeContext);
2124
2136
 
2125
2137
  // src/contexts/TrackControls.tsx
2126
- var import_react13 = require("react");
2127
- var import_jsx_runtime18 = require("react/jsx-runtime");
2128
- var TrackControlsContext = (0, import_react13.createContext)(/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react13.Fragment, {}));
2129
- var useTrackControls = () => (0, import_react13.useContext)(TrackControlsContext);
2138
+ var import_react15 = require("react");
2139
+ var import_jsx_runtime19 = require("react/jsx-runtime");
2140
+ var TrackControlsContext = (0, import_react15.createContext)(/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react15.Fragment, {}));
2141
+ var useTrackControls = () => (0, import_react15.useContext)(TrackControlsContext);
2130
2142
 
2131
2143
  // src/contexts/Playout.tsx
2132
- var import_react14 = require("react");
2133
- var import_jsx_runtime19 = require("react/jsx-runtime");
2144
+ var import_react16 = require("react");
2145
+ var import_jsx_runtime20 = require("react/jsx-runtime");
2134
2146
  var defaultProgress = 0;
2135
2147
  var defaultIsPlaying = false;
2136
2148
  var defaultSelectionStart = 0;
@@ -2141,8 +2153,8 @@ var defaultPlayout = {
2141
2153
  selectionStart: defaultSelectionStart,
2142
2154
  selectionEnd: defaultSelectionEnd
2143
2155
  };
2144
- var PlayoutStatusContext = (0, import_react14.createContext)(defaultPlayout);
2145
- var PlayoutStatusUpdateContext = (0, import_react14.createContext)({
2156
+ var PlayoutStatusContext = (0, import_react16.createContext)(defaultPlayout);
2157
+ var PlayoutStatusUpdateContext = (0, import_react16.createContext)({
2146
2158
  setIsPlaying: () => {
2147
2159
  },
2148
2160
  setProgress: () => {
@@ -2151,23 +2163,24 @@ var PlayoutStatusUpdateContext = (0, import_react14.createContext)({
2151
2163
  }
2152
2164
  });
2153
2165
  var PlayoutProvider = ({ children }) => {
2154
- const [isPlaying, setIsPlaying] = (0, import_react14.useState)(defaultIsPlaying);
2155
- const [progress, setProgress] = (0, import_react14.useState)(defaultProgress);
2156
- const [selectionStart, setSelectionStart] = (0, import_react14.useState)(defaultSelectionStart);
2157
- const [selectionEnd, setSelectionEnd] = (0, import_react14.useState)(defaultSelectionEnd);
2166
+ const [isPlaying, setIsPlaying] = (0, import_react16.useState)(defaultIsPlaying);
2167
+ const [progress, setProgress] = (0, import_react16.useState)(defaultProgress);
2168
+ const [selectionStart, setSelectionStart] = (0, import_react16.useState)(defaultSelectionStart);
2169
+ const [selectionEnd, setSelectionEnd] = (0, import_react16.useState)(defaultSelectionEnd);
2158
2170
  const setSelection = (start, end) => {
2159
2171
  setSelectionStart(start);
2160
2172
  setSelectionEnd(end);
2161
2173
  };
2162
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(PlayoutStatusUpdateContext.Provider, { value: { setIsPlaying, setProgress, setSelection }, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(PlayoutStatusContext.Provider, { value: { isPlaying, progress, selectionStart, selectionEnd }, children }) });
2174
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(PlayoutStatusUpdateContext.Provider, { value: { setIsPlaying, setProgress, setSelection }, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(PlayoutStatusContext.Provider, { value: { isPlaying, progress, selectionStart, selectionEnd }, children }) });
2163
2175
  };
2164
- var usePlayoutStatus = () => (0, import_react14.useContext)(PlayoutStatusContext);
2165
- var usePlayoutStatusUpdate = () => (0, import_react14.useContext)(PlayoutStatusUpdateContext);
2176
+ var usePlayoutStatus = () => (0, import_react16.useContext)(PlayoutStatusContext);
2177
+ var usePlayoutStatusUpdate = () => (0, import_react16.useContext)(PlayoutStatusUpdateContext);
2166
2178
 
2167
2179
  // src/components/SpectrogramChannel.tsx
2168
- var import_react15 = require("react");
2180
+ var import_react17 = require("react");
2169
2181
  var import_styled_components20 = __toESM(require("styled-components"));
2170
- var import_jsx_runtime20 = require("react/jsx-runtime");
2182
+ var import_core3 = require("@waveform-playlist/core");
2183
+ var import_jsx_runtime21 = require("react/jsx-runtime");
2171
2184
  var LINEAR_FREQUENCY_SCALE = (f, minF, maxF) => (f - minF) / (maxF - minF);
2172
2185
  var Wrapper3 = import_styled_components20.default.div.attrs((props) => ({
2173
2186
  style: {
@@ -2220,60 +2233,52 @@ var SpectrogramChannel = ({
2220
2233
  onCanvasesReady
2221
2234
  }) => {
2222
2235
  const channelIndex = channelIndexProp ?? index;
2223
- const canvasesRef = (0, import_react15.useRef)([]);
2224
- const registeredIdsRef = (0, import_react15.useRef)([]);
2225
- const transferredCanvasesRef = (0, import_react15.useRef)(/* @__PURE__ */ new WeakSet());
2226
- const workerApiRef = (0, import_react15.useRef)(workerApi);
2227
- const onCanvasesReadyRef = (0, import_react15.useRef)(onCanvasesReady);
2236
+ const { canvasRef, canvasMapRef } = useChunkedCanvasRefs();
2237
+ const registeredIdsRef = (0, import_react17.useRef)([]);
2238
+ const transferredCanvasesRef = (0, import_react17.useRef)(/* @__PURE__ */ new WeakSet());
2239
+ const workerApiRef = (0, import_react17.useRef)(workerApi);
2240
+ const onCanvasesReadyRef = (0, import_react17.useRef)(onCanvasesReady);
2228
2241
  const isWorkerMode = !!(workerApi && clipId);
2229
- const visibleChunkKey = useScrollViewportSelector((viewport) => {
2230
- const totalChunks = Math.ceil(length / MAX_CANVAS_WIDTH);
2231
- const indices = [];
2232
- for (let i = 0; i < totalChunks; i++) {
2233
- const chunkLeft = i * MAX_CANVAS_WIDTH;
2234
- const chunkWidth = Math.min(length - chunkLeft, MAX_CANVAS_WIDTH);
2235
- if (viewport) {
2236
- const chunkEnd = chunkLeft + chunkWidth;
2237
- if (chunkEnd <= viewport.visibleStart || chunkLeft >= viewport.visibleEnd) {
2238
- continue;
2239
- }
2240
- }
2241
- indices.push(i);
2242
- }
2243
- return indices.join(",");
2244
- });
2245
- const visibleChunkIndices = visibleChunkKey ? visibleChunkKey.split(",").map(Number) : [];
2246
- const canvasRef = (0, import_react15.useCallback)(
2247
- (canvas) => {
2248
- if (canvas !== null) {
2249
- const idx = parseInt(canvas.dataset.index, 10);
2250
- canvasesRef.current[idx] = canvas;
2251
- }
2252
- },
2253
- []
2254
- );
2242
+ const clipOriginX = useClipViewportOrigin();
2243
+ const visibleChunkIndices = useVisibleChunkIndices(length, import_core3.MAX_CANVAS_WIDTH, clipOriginX);
2255
2244
  const lut = colorLUT ?? DEFAULT_COLOR_LUT;
2256
2245
  const maxF = maxFrequency ?? (data ? data.sampleRate / 2 : 22050);
2257
2246
  const scaleFn = frequencyScaleFn ?? LINEAR_FREQUENCY_SCALE;
2258
2247
  const hasCustomFrequencyScale = Boolean(frequencyScaleFn);
2259
- (0, import_react15.useEffect)(() => {
2248
+ (0, import_react17.useEffect)(() => {
2260
2249
  workerApiRef.current = workerApi;
2261
2250
  }, [workerApi]);
2262
- (0, import_react15.useEffect)(() => {
2251
+ (0, import_react17.useEffect)(() => {
2263
2252
  onCanvasesReadyRef.current = onCanvasesReady;
2264
2253
  }, [onCanvasesReady]);
2265
- (0, import_react15.useEffect)(() => {
2254
+ (0, import_react17.useEffect)(() => {
2266
2255
  if (!isWorkerMode) return;
2267
2256
  const currentWorkerApi = workerApiRef.current;
2268
2257
  if (!currentWorkerApi || !clipId) return;
2269
- const canvases2 = canvasesRef.current;
2258
+ const previousCount = registeredIdsRef.current.length;
2259
+ const remaining = [];
2260
+ for (const id of registeredIdsRef.current) {
2261
+ const match = id.match(/chunk(\d+)$/);
2262
+ if (!match) {
2263
+ remaining.push(id);
2264
+ continue;
2265
+ }
2266
+ const chunkIdx = parseInt(match[1], 10);
2267
+ const canvas = canvasMapRef.current.get(chunkIdx);
2268
+ if (canvas && canvas.isConnected) {
2269
+ remaining.push(id);
2270
+ } else {
2271
+ try {
2272
+ currentWorkerApi.unregisterCanvas(id);
2273
+ } catch (err) {
2274
+ console.warn(`[spectrogram] unregisterCanvas failed for ${id}:`, err);
2275
+ }
2276
+ }
2277
+ }
2278
+ registeredIdsRef.current = remaining;
2270
2279
  const newIds = [];
2271
- const newWidths = [];
2272
- for (let i = 0; i < canvases2.length; i++) {
2273
- const canvas = canvases2[i];
2274
- if (!canvas) continue;
2280
+ for (const [canvasIdx, canvas] of canvasMapRef.current.entries()) {
2275
2281
  if (transferredCanvasesRef.current.has(canvas)) continue;
2276
- const canvasIdx = parseInt(canvas.dataset.index, 10);
2277
2282
  const canvasId = `${clipId}-ch${channelIndex}-chunk${canvasIdx}`;
2278
2283
  let offscreen;
2279
2284
  try {
@@ -2286,7 +2291,6 @@ var SpectrogramChannel = ({
2286
2291
  try {
2287
2292
  currentWorkerApi.registerCanvas(canvasId, offscreen);
2288
2293
  newIds.push(canvasId);
2289
- newWidths.push(Math.min(length - canvasIdx * MAX_CANVAS_WIDTH, MAX_CANVAS_WIDTH));
2290
2294
  } catch (err) {
2291
2295
  console.warn(`[spectrogram] registerCanvas failed for ${canvasId}:`, err);
2292
2296
  continue;
@@ -2294,35 +2298,23 @@ var SpectrogramChannel = ({
2294
2298
  }
2295
2299
  if (newIds.length > 0) {
2296
2300
  registeredIdsRef.current = [...registeredIdsRef.current, ...newIds];
2297
- onCanvasesReadyRef.current?.(newIds, newWidths);
2298
2301
  }
2299
- }, [isWorkerMode, clipId, channelIndex, length, visibleChunkKey]);
2300
- (0, import_react15.useEffect)(() => {
2301
- if (!isWorkerMode) return;
2302
- const currentWorkerApi = workerApiRef.current;
2303
- if (!currentWorkerApi) return;
2304
- const remaining = [];
2305
- for (const id of registeredIdsRef.current) {
2306
- const match = id.match(/chunk(\d+)$/);
2307
- if (!match) {
2308
- remaining.push(id);
2309
- continue;
2310
- }
2311
- const chunkIdx = parseInt(match[1], 10);
2312
- const canvas = canvasesRef.current[chunkIdx];
2313
- if (canvas && canvas.isConnected) {
2314
- remaining.push(id);
2315
- } else {
2316
- try {
2317
- currentWorkerApi.unregisterCanvas(id);
2318
- } catch (err) {
2319
- console.warn(`[spectrogram] unregisterCanvas failed for ${id}:`, err);
2302
+ const canvasSetChanged = newIds.length > 0 || remaining.length < previousCount;
2303
+ if (canvasSetChanged) {
2304
+ const allIds = registeredIdsRef.current;
2305
+ const allWidths = allIds.map((id) => {
2306
+ const match = id.match(/chunk(\d+)$/);
2307
+ if (!match) {
2308
+ console.warn(`[spectrogram] Unexpected canvas ID format: ${id}`);
2309
+ return import_core3.MAX_CANVAS_WIDTH;
2320
2310
  }
2321
- }
2311
+ const chunkIdx = parseInt(match[1], 10);
2312
+ return Math.min(length - chunkIdx * import_core3.MAX_CANVAS_WIDTH, import_core3.MAX_CANVAS_WIDTH);
2313
+ });
2314
+ onCanvasesReadyRef.current?.(allIds, allWidths);
2322
2315
  }
2323
- registeredIdsRef.current = remaining;
2324
- });
2325
- (0, import_react15.useEffect)(() => {
2316
+ }, [canvasMapRef, isWorkerMode, clipId, channelIndex, length, visibleChunkIndices]);
2317
+ (0, import_react17.useEffect)(() => {
2326
2318
  return () => {
2327
2319
  const api = workerApiRef.current;
2328
2320
  if (!api) return;
@@ -2336,17 +2328,20 @@ var SpectrogramChannel = ({
2336
2328
  registeredIdsRef.current = [];
2337
2329
  };
2338
2330
  }, []);
2339
- (0, import_react15.useLayoutEffect)(() => {
2331
+ (0, import_react17.useLayoutEffect)(() => {
2340
2332
  if (isWorkerMode || !data) return;
2341
- const canvases2 = canvasesRef.current;
2342
- const { frequencyBinCount, frameCount, hopSize, sampleRate, gainDb, rangeDb: rawRangeDb } = data;
2333
+ const {
2334
+ frequencyBinCount,
2335
+ frameCount,
2336
+ hopSize,
2337
+ sampleRate,
2338
+ gainDb,
2339
+ rangeDb: rawRangeDb
2340
+ } = data;
2343
2341
  const rangeDb = rawRangeDb === 0 ? 1 : rawRangeDb;
2344
2342
  const binToFreq = (bin) => bin / frequencyBinCount * (sampleRate / 2);
2345
- for (let i = 0; i < canvases2.length; i++) {
2346
- const canvas = canvases2[i];
2347
- if (!canvas) continue;
2348
- const canvasIdx = parseInt(canvas.dataset.index, 10);
2349
- const globalPixelOffset = canvasIdx * MAX_CANVAS_WIDTH;
2343
+ for (const [canvasIdx, canvas] of canvasMapRef.current.entries()) {
2344
+ const globalPixelOffset = canvasIdx * import_core3.MAX_CANVAS_WIDTH;
2350
2345
  const ctx = canvas.getContext("2d");
2351
2346
  if (!ctx) continue;
2352
2347
  const canvasWidth = canvas.width / devicePixelRatio;
@@ -2406,11 +2401,25 @@ var SpectrogramChannel = ({
2406
2401
  ctx.drawImage(tmpCanvas, 0, 0, canvas.width, canvas.height);
2407
2402
  }
2408
2403
  }
2409
- }, [isWorkerMode, data, length, waveHeight, devicePixelRatio, samplesPerPixel, lut, minFrequency, maxF, scaleFn, hasCustomFrequencyScale, visibleChunkKey]);
2404
+ }, [
2405
+ canvasMapRef,
2406
+ isWorkerMode,
2407
+ data,
2408
+ length,
2409
+ waveHeight,
2410
+ devicePixelRatio,
2411
+ samplesPerPixel,
2412
+ lut,
2413
+ minFrequency,
2414
+ maxF,
2415
+ scaleFn,
2416
+ hasCustomFrequencyScale,
2417
+ visibleChunkIndices
2418
+ ]);
2410
2419
  const canvases = visibleChunkIndices.map((i) => {
2411
- const chunkLeft = i * MAX_CANVAS_WIDTH;
2412
- const currentWidth = Math.min(length - chunkLeft, MAX_CANVAS_WIDTH);
2413
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
2420
+ const chunkLeft = i * import_core3.MAX_CANVAS_WIDTH;
2421
+ const currentWidth = Math.min(length - chunkLeft, import_core3.MAX_CANVAS_WIDTH);
2422
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2414
2423
  SpectrogramCanvas,
2415
2424
  {
2416
2425
  $cssWidth: currentWidth,
@@ -2424,11 +2433,11 @@ var SpectrogramChannel = ({
2424
2433
  `${length}-${i}`
2425
2434
  );
2426
2435
  });
2427
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Wrapper3, { $index: index, $cssWidth: length, $waveHeight: waveHeight, children: canvases });
2436
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Wrapper3, { $index: index, $cssWidth: length, $waveHeight: waveHeight, children: canvases });
2428
2437
  };
2429
2438
 
2430
2439
  // src/components/SmartChannel.tsx
2431
- var import_jsx_runtime21 = require("react/jsx-runtime");
2440
+ var import_jsx_runtime22 = require("react/jsx-runtime");
2432
2441
  var SmartChannel = ({
2433
2442
  isSelected,
2434
2443
  transparentBackground,
@@ -2453,7 +2462,7 @@ var SmartChannel = ({
2453
2462
  const drawMode = theme?.waveformDrawMode || "inverted";
2454
2463
  const hasSpectrogram = spectrogramData || spectrogramWorkerApi;
2455
2464
  if (renderMode === "spectrogram" && hasSpectrogram) {
2456
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2465
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2457
2466
  SpectrogramChannel,
2458
2467
  {
2459
2468
  index: props.index,
@@ -2474,8 +2483,8 @@ var SmartChannel = ({
2474
2483
  }
2475
2484
  if (renderMode === "both" && hasSpectrogram) {
2476
2485
  const halfHeight = Math.floor(waveHeight / 2);
2477
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
2478
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2486
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
2487
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2479
2488
  SpectrogramChannel,
2480
2489
  {
2481
2490
  index: props.index * 2,
@@ -2494,24 +2503,35 @@ var SmartChannel = ({
2494
2503
  onCanvasesReady: spectrogramOnCanvasesReady
2495
2504
  }
2496
2505
  ),
2497
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { style: { position: "absolute", top: (props.index * 2 + 1) * halfHeight, width: props.length, height: halfHeight }, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2498
- Channel,
2506
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2507
+ "div",
2499
2508
  {
2500
- ...props,
2501
- index: 0,
2502
- waveOutlineColor,
2503
- waveFillColor,
2504
- waveHeight: halfHeight,
2505
- devicePixelRatio,
2506
- barWidth,
2507
- barGap,
2508
- transparentBackground,
2509
- drawMode
2509
+ style: {
2510
+ position: "absolute",
2511
+ top: (props.index * 2 + 1) * halfHeight,
2512
+ width: props.length,
2513
+ height: halfHeight
2514
+ },
2515
+ children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2516
+ Channel,
2517
+ {
2518
+ ...props,
2519
+ index: 0,
2520
+ waveOutlineColor,
2521
+ waveFillColor,
2522
+ waveHeight: halfHeight,
2523
+ devicePixelRatio,
2524
+ barWidth,
2525
+ barGap,
2526
+ transparentBackground,
2527
+ drawMode
2528
+ }
2529
+ )
2510
2530
  }
2511
- ) })
2531
+ )
2512
2532
  ] });
2513
2533
  }
2514
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2534
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2515
2535
  Channel,
2516
2536
  {
2517
2537
  ...props,
@@ -2528,9 +2548,9 @@ var SmartChannel = ({
2528
2548
  };
2529
2549
 
2530
2550
  // src/components/SpectrogramLabels.tsx
2531
- var import_react16 = require("react");
2551
+ var import_react18 = require("react");
2532
2552
  var import_styled_components21 = __toESM(require("styled-components"));
2533
- var import_jsx_runtime22 = require("react/jsx-runtime");
2553
+ var import_jsx_runtime23 = require("react/jsx-runtime");
2534
2554
  var LABELS_WIDTH = 72;
2535
2555
  var LabelsStickyWrapper = import_styled_components21.default.div`
2536
2556
  position: sticky;
@@ -2580,12 +2600,12 @@ var SpectrogramLabels = ({
2580
2600
  renderMode = "spectrogram",
2581
2601
  hasClipHeaders = false
2582
2602
  }) => {
2583
- const canvasRef = (0, import_react16.useRef)(null);
2603
+ const canvasRef = (0, import_react18.useRef)(null);
2584
2604
  const devicePixelRatio = useDevicePixelRatio();
2585
2605
  const spectrogramHeight = renderMode === "both" ? Math.floor(waveHeight / 2) : waveHeight;
2586
2606
  const totalHeight = numChannels * waveHeight;
2587
2607
  const clipHeaderOffset = hasClipHeaders ? 22 : 0;
2588
- (0, import_react16.useLayoutEffect)(() => {
2608
+ (0, import_react18.useLayoutEffect)(() => {
2589
2609
  const canvas = canvasRef.current;
2590
2610
  if (!canvas) return;
2591
2611
  const ctx = canvas.getContext("2d");
@@ -2611,8 +2631,19 @@ var SpectrogramLabels = ({
2611
2631
  ctx.fillText(text, padding, y);
2612
2632
  }
2613
2633
  }
2614
- }, [waveHeight, numChannels, frequencyScaleFn, minFrequency, maxFrequency, labelsColor, labelsBackground, devicePixelRatio, spectrogramHeight, clipHeaderOffset]);
2615
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(LabelsStickyWrapper, { $height: totalHeight + clipHeaderOffset, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2634
+ }, [
2635
+ waveHeight,
2636
+ numChannels,
2637
+ frequencyScaleFn,
2638
+ minFrequency,
2639
+ maxFrequency,
2640
+ labelsColor,
2641
+ labelsBackground,
2642
+ devicePixelRatio,
2643
+ spectrogramHeight,
2644
+ clipHeaderOffset
2645
+ ]);
2646
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(LabelsStickyWrapper, { $height: totalHeight + clipHeaderOffset, children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2616
2647
  "canvas",
2617
2648
  {
2618
2649
  ref: canvasRef,
@@ -2628,10 +2659,10 @@ var SpectrogramLabels = ({
2628
2659
  };
2629
2660
 
2630
2661
  // src/components/SmartScale.tsx
2631
- var import_react18 = require("react");
2662
+ var import_react20 = require("react");
2632
2663
 
2633
2664
  // src/components/TimeScale.tsx
2634
- var import_react17 = __toESM(require("react"));
2665
+ var import_react19 = __toESM(require("react"));
2635
2666
  var import_styled_components22 = __toESM(require("styled-components"));
2636
2667
 
2637
2668
  // src/utils/conversions.ts
@@ -2655,7 +2686,8 @@ function secondsToPixels(seconds, samplesPerPixel, sampleRate) {
2655
2686
  }
2656
2687
 
2657
2688
  // src/components/TimeScale.tsx
2658
- var import_jsx_runtime23 = require("react/jsx-runtime");
2689
+ var import_core4 = require("@waveform-playlist/core");
2690
+ var import_jsx_runtime24 = require("react/jsx-runtime");
2659
2691
  function formatTime2(milliseconds) {
2660
2692
  const seconds = Math.floor(milliseconds / 1e3);
2661
2693
  const s = seconds % 60;
@@ -2706,21 +2738,15 @@ var TimeScale = (props) => {
2706
2738
  secondStep,
2707
2739
  renderTimestamp
2708
2740
  } = props;
2709
- const canvasRefsMap = (0, import_react17.useRef)(/* @__PURE__ */ new Map());
2741
+ const { canvasRef, canvasMapRef } = useChunkedCanvasRefs();
2710
2742
  const {
2711
2743
  sampleRate,
2712
2744
  samplesPerPixel,
2713
2745
  timeScaleHeight,
2714
2746
  controls: { show: showControls, width: controlWidth }
2715
- } = (0, import_react17.useContext)(PlaylistInfoContext);
2747
+ } = (0, import_react19.useContext)(PlaylistInfoContext);
2716
2748
  const devicePixelRatio = useDevicePixelRatio();
2717
- const canvasRefCallback = (0, import_react17.useCallback)((canvas) => {
2718
- if (canvas !== null) {
2719
- const idx = parseInt(canvas.dataset.index, 10);
2720
- canvasRefsMap.current.set(idx, canvas);
2721
- }
2722
- }, []);
2723
- const { widthX, canvasInfo, timeMarkersWithPositions } = (0, import_react17.useMemo)(() => {
2749
+ const { widthX, canvasInfo, timeMarkersWithPositions } = (0, import_react19.useMemo)(() => {
2724
2750
  const nextCanvasInfo = /* @__PURE__ */ new Map();
2725
2751
  const nextMarkers = [];
2726
2752
  const nextWidthX = secondsToPixels(duration / 1e3, samplesPerPixel, sampleRate);
@@ -2731,7 +2757,7 @@ var TimeScale = (props) => {
2731
2757
  if (counter % marker === 0) {
2732
2758
  const timeMs = counter;
2733
2759
  const timestamp = formatTime2(timeMs);
2734
- const element = renderTimestamp ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_react17.default.Fragment, { children: renderTimestamp(timeMs, pix) }, `timestamp-${counter}`) : /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(TimeStamp, { $left: pix, children: timestamp }, timestamp);
2760
+ const element = renderTimestamp ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_react19.default.Fragment, { children: renderTimestamp(timeMs, pix) }, `timestamp-${counter}`) : /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(TimeStamp, { $left: pix, children: timestamp }, timestamp);
2735
2761
  nextMarkers.push({ pix, element });
2736
2762
  nextCanvasInfo.set(pix, timeScaleHeight);
2737
2763
  } else if (counter % bigStep === 0) {
@@ -2746,28 +2772,21 @@ var TimeScale = (props) => {
2746
2772
  canvasInfo: nextCanvasInfo,
2747
2773
  timeMarkersWithPositions: nextMarkers
2748
2774
  };
2749
- }, [duration, samplesPerPixel, sampleRate, marker, bigStep, secondStep, renderTimestamp, timeScaleHeight]);
2750
- const visibleChunkKey = useScrollViewportSelector((viewport) => {
2751
- const totalChunks = Math.ceil(widthX / MAX_CANVAS_WIDTH);
2752
- const indices = [];
2753
- for (let i = 0; i < totalChunks; i++) {
2754
- const chunkLeft = i * MAX_CANVAS_WIDTH;
2755
- const chunkWidth = Math.min(widthX - chunkLeft, MAX_CANVAS_WIDTH);
2756
- if (viewport) {
2757
- const chunkEnd = chunkLeft + chunkWidth;
2758
- if (chunkEnd <= viewport.visibleStart || chunkLeft >= viewport.visibleEnd) {
2759
- continue;
2760
- }
2761
- }
2762
- indices.push(i);
2763
- }
2764
- return indices.join(",");
2765
- });
2766
- const visibleChunkIndices = visibleChunkKey ? visibleChunkKey.split(",").map(Number) : [];
2775
+ }, [
2776
+ duration,
2777
+ samplesPerPixel,
2778
+ sampleRate,
2779
+ marker,
2780
+ bigStep,
2781
+ secondStep,
2782
+ renderTimestamp,
2783
+ timeScaleHeight
2784
+ ]);
2785
+ const visibleChunkIndices = useVisibleChunkIndices(widthX, import_core4.MAX_CANVAS_WIDTH);
2767
2786
  const visibleChunks = visibleChunkIndices.map((i) => {
2768
- const chunkLeft = i * MAX_CANVAS_WIDTH;
2769
- const chunkWidth = Math.min(widthX - chunkLeft, MAX_CANVAS_WIDTH);
2770
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2787
+ const chunkLeft = i * import_core4.MAX_CANVAS_WIDTH;
2788
+ const chunkWidth = Math.min(widthX - chunkLeft, import_core4.MAX_CANVAS_WIDTH);
2789
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2771
2790
  TimeTickChunk,
2772
2791
  {
2773
2792
  $cssWidth: chunkWidth,
@@ -2776,27 +2795,19 @@ var TimeScale = (props) => {
2776
2795
  width: chunkWidth * devicePixelRatio,
2777
2796
  height: timeScaleHeight * devicePixelRatio,
2778
2797
  "data-index": i,
2779
- ref: canvasRefCallback
2798
+ ref: canvasRef
2780
2799
  },
2781
2800
  `timescale-${i}`
2782
2801
  );
2783
2802
  });
2784
- const firstChunkLeft = visibleChunkIndices.length > 0 ? visibleChunkIndices[0] * MAX_CANVAS_WIDTH : 0;
2785
- const lastChunkRight = visibleChunkIndices.length > 0 ? (visibleChunkIndices[visibleChunkIndices.length - 1] + 1) * MAX_CANVAS_WIDTH : Infinity;
2803
+ const firstChunkLeft = visibleChunkIndices.length > 0 ? visibleChunkIndices[0] * import_core4.MAX_CANVAS_WIDTH : 0;
2804
+ const lastChunkRight = visibleChunkIndices.length > 0 ? (visibleChunkIndices[visibleChunkIndices.length - 1] + 1) * import_core4.MAX_CANVAS_WIDTH : Infinity;
2786
2805
  const visibleMarkers = visibleChunkIndices.length > 0 ? timeMarkersWithPositions.filter(({ pix }) => pix >= firstChunkLeft && pix < lastChunkRight).map(({ element }) => element) : timeMarkersWithPositions.map(({ element }) => element);
2787
- (0, import_react17.useEffect)(() => {
2788
- const currentMap = canvasRefsMap.current;
2789
- for (const [idx, canvas] of currentMap.entries()) {
2790
- if (!canvas.isConnected) {
2791
- currentMap.delete(idx);
2792
- }
2793
- }
2794
- });
2795
- (0, import_react17.useLayoutEffect)(() => {
2796
- for (const [chunkIdx, canvas] of canvasRefsMap.current.entries()) {
2806
+ (0, import_react19.useLayoutEffect)(() => {
2807
+ for (const [chunkIdx, canvas] of canvasMapRef.current.entries()) {
2797
2808
  const ctx = canvas.getContext("2d");
2798
2809
  if (!ctx) continue;
2799
- const chunkLeft = chunkIdx * MAX_CANVAS_WIDTH;
2810
+ const chunkLeft = chunkIdx * import_core4.MAX_CANVAS_WIDTH;
2800
2811
  const chunkWidth = canvas.width / devicePixelRatio;
2801
2812
  ctx.resetTransform();
2802
2813
  ctx.clearRect(0, 0, canvas.width, canvas.height);
@@ -2810,8 +2821,16 @@ var TimeScale = (props) => {
2810
2821
  ctx.fillRect(localX, scaleY, 1, scaleHeight);
2811
2822
  }
2812
2823
  }
2813
- }, [duration, devicePixelRatio, timeColor, timeScaleHeight, canvasInfo, visibleChunkKey]);
2814
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
2824
+ }, [
2825
+ canvasMapRef,
2826
+ duration,
2827
+ devicePixelRatio,
2828
+ timeColor,
2829
+ timeScaleHeight,
2830
+ canvasInfo,
2831
+ visibleChunkIndices
2832
+ ]);
2833
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
2815
2834
  PlaylistTimeScaleScroll,
2816
2835
  {
2817
2836
  $cssWidth: widthX,
@@ -2827,7 +2846,7 @@ var TimeScale = (props) => {
2827
2846
  var StyledTimeScale = (0, import_styled_components22.withTheme)(TimeScale);
2828
2847
 
2829
2848
  // src/components/SmartScale.tsx
2830
- var import_jsx_runtime24 = require("react/jsx-runtime");
2849
+ var import_jsx_runtime25 = require("react/jsx-runtime");
2831
2850
  var timeinfo = /* @__PURE__ */ new Map([
2832
2851
  [
2833
2852
  700,
@@ -2901,9 +2920,9 @@ function getScaleInfo(samplesPerPixel) {
2901
2920
  return config;
2902
2921
  }
2903
2922
  var SmartScale = ({ renderTimestamp }) => {
2904
- const { samplesPerPixel, duration } = (0, import_react18.useContext)(PlaylistInfoContext);
2923
+ const { samplesPerPixel, duration } = (0, import_react20.useContext)(PlaylistInfoContext);
2905
2924
  let config = getScaleInfo(samplesPerPixel);
2906
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2925
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
2907
2926
  StyledTimeScale,
2908
2927
  {
2909
2928
  marker: config.marker,
@@ -2917,7 +2936,7 @@ var SmartScale = ({ renderTimestamp }) => {
2917
2936
 
2918
2937
  // src/components/TimeFormatSelect.tsx
2919
2938
  var import_styled_components23 = __toESM(require("styled-components"));
2920
- var import_jsx_runtime25 = require("react/jsx-runtime");
2939
+ var import_jsx_runtime26 = require("react/jsx-runtime");
2921
2940
  var SelectWrapper = import_styled_components23.default.div`
2922
2941
  display: inline-flex;
2923
2942
  align-items: center;
@@ -2940,7 +2959,7 @@ var TimeFormatSelect = ({
2940
2959
  const handleChange = (e) => {
2941
2960
  onChange(e.target.value);
2942
2961
  };
2943
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(SelectWrapper, { className, children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
2962
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(SelectWrapper, { className, children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2944
2963
  BaseSelect,
2945
2964
  {
2946
2965
  className: "time-format",
@@ -2948,14 +2967,14 @@ var TimeFormatSelect = ({
2948
2967
  onChange: handleChange,
2949
2968
  disabled,
2950
2969
  "aria-label": "Time format selection",
2951
- children: TIME_FORMAT_OPTIONS.map((option) => /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("option", { value: option.value, children: option.label }, option.value))
2970
+ children: TIME_FORMAT_OPTIONS.map((option) => /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("option", { value: option.value, children: option.label }, option.value))
2952
2971
  }
2953
2972
  ) });
2954
2973
  };
2955
2974
 
2956
2975
  // src/components/Track.tsx
2957
2976
  var import_styled_components24 = __toESM(require("styled-components"));
2958
- var import_jsx_runtime26 = require("react/jsx-runtime");
2977
+ var import_jsx_runtime27 = require("react/jsx-runtime");
2959
2978
  var Container = import_styled_components24.default.div.attrs((props) => ({
2960
2979
  style: {
2961
2980
  height: `${props.$waveHeight * props.$numChannels + (props.$hasClipHeaders ? CLIP_HEADER_HEIGHT : 0)}px`
@@ -3010,7 +3029,7 @@ var Track = ({
3010
3029
  controls: { show, width: controlWidth }
3011
3030
  } = usePlaylistInfo();
3012
3031
  const controls = useTrackControls();
3013
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
3032
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
3014
3033
  Container,
3015
3034
  {
3016
3035
  $numChannels: numChannels,
@@ -3021,15 +3040,8 @@ var Track = ({
3021
3040
  $hasClipHeaders: hasClipHeaders,
3022
3041
  $isSelected: isSelected,
3023
3042
  children: [
3024
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
3025
- ControlsWrapper,
3026
- {
3027
- $controlWidth: show ? controlWidth : 0,
3028
- $isSelected: isSelected,
3029
- children: controls
3030
- }
3031
- ),
3032
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
3043
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(ControlsWrapper, { $controlWidth: show ? controlWidth : 0, $isSelected: isSelected, children: controls }),
3044
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3033
3045
  ChannelContainer,
3034
3046
  {
3035
3047
  $controlWidth: show ? controlWidth : 0,
@@ -3060,8 +3072,11 @@ var Button = import_styled_components25.default.button.attrs({
3060
3072
  font-size: ${(props) => props.theme.fontSizeSmall};
3061
3073
  line-height: 1;
3062
3074
  border-radius: ${(props) => props.theme.borderRadius};
3063
- transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out,
3064
- border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
3075
+ transition:
3076
+ color 0.15s ease-in-out,
3077
+ background-color 0.15s ease-in-out,
3078
+ border-color 0.15s ease-in-out,
3079
+ box-shadow 0.15s ease-in-out;
3065
3080
  cursor: pointer;
3066
3081
 
3067
3082
  ${(props) => {
@@ -3136,8 +3151,8 @@ var ButtonGroup = import_styled_components26.default.div`
3136
3151
 
3137
3152
  // src/components/TrackControls/CloseButton.tsx
3138
3153
  var import_styled_components27 = __toESM(require("styled-components"));
3139
- var import_react19 = require("@phosphor-icons/react");
3140
- var import_jsx_runtime27 = require("react/jsx-runtime");
3154
+ var import_react21 = require("@phosphor-icons/react");
3155
+ var import_jsx_runtime28 = require("react/jsx-runtime");
3141
3156
  var StyledCloseButton = import_styled_components27.default.button`
3142
3157
  position: absolute;
3143
3158
  left: 0;
@@ -3152,17 +3167,16 @@ var StyledCloseButton = import_styled_components27.default.button`
3152
3167
  align-items: center;
3153
3168
  justify-content: center;
3154
3169
  opacity: 0.7;
3155
- transition: opacity 0.15s, color 0.15s;
3170
+ transition:
3171
+ opacity 0.15s,
3172
+ color 0.15s;
3156
3173
 
3157
3174
  &:hover {
3158
3175
  opacity: 1;
3159
3176
  color: #dc3545;
3160
3177
  }
3161
3178
  `;
3162
- var CloseButton = ({
3163
- onClick,
3164
- title = "Remove track"
3165
- }) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(StyledCloseButton, { onClick, title, children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react19.X, { size: 12, weight: "bold" }) });
3179
+ var CloseButton = ({ onClick, title = "Remove track" }) => /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(StyledCloseButton, { onClick, title, children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_react21.X, { size: 12, weight: "bold" }) });
3166
3180
 
3167
3181
  // src/components/TrackControls/Controls.tsx
3168
3182
  var import_styled_components28 = __toESM(require("styled-components"));
@@ -3197,24 +3211,24 @@ var Header = import_styled_components29.default.header`
3197
3211
  `;
3198
3212
 
3199
3213
  // src/components/TrackControls/VolumeDownIcon.tsx
3200
- var import_react20 = require("@phosphor-icons/react");
3201
- var import_jsx_runtime28 = require("react/jsx-runtime");
3202
- var VolumeDownIcon = (props) => /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_react20.SpeakerLowIcon, { weight: "light", ...props });
3214
+ var import_react22 = require("@phosphor-icons/react");
3215
+ var import_jsx_runtime29 = require("react/jsx-runtime");
3216
+ var VolumeDownIcon = (props) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_react22.SpeakerLowIcon, { weight: "light", ...props });
3203
3217
 
3204
3218
  // src/components/TrackControls/VolumeUpIcon.tsx
3205
- var import_react21 = require("@phosphor-icons/react");
3206
- var import_jsx_runtime29 = require("react/jsx-runtime");
3207
- var VolumeUpIcon = (props) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_react21.SpeakerHighIcon, { weight: "light", ...props });
3219
+ var import_react23 = require("@phosphor-icons/react");
3220
+ var import_jsx_runtime30 = require("react/jsx-runtime");
3221
+ var VolumeUpIcon = (props) => /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_react23.SpeakerHighIcon, { weight: "light", ...props });
3208
3222
 
3209
3223
  // src/components/TrackControls/TrashIcon.tsx
3210
- var import_react22 = require("@phosphor-icons/react");
3211
- var import_jsx_runtime30 = require("react/jsx-runtime");
3212
- var TrashIcon = (props) => /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_react22.TrashIcon, { weight: "light", ...props });
3224
+ var import_react24 = require("@phosphor-icons/react");
3225
+ var import_jsx_runtime31 = require("react/jsx-runtime");
3226
+ var TrashIcon = (props) => /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_react24.TrashIcon, { weight: "light", ...props });
3213
3227
 
3214
3228
  // src/components/TrackControls/DotsIcon.tsx
3215
- var import_react23 = require("@phosphor-icons/react");
3216
- var import_jsx_runtime31 = require("react/jsx-runtime");
3217
- var DotsIcon = (props) => /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_react23.DotsThreeIcon, { weight: "bold", ...props });
3229
+ var import_react25 = require("@phosphor-icons/react");
3230
+ var import_jsx_runtime32 = require("react/jsx-runtime");
3231
+ var DotsIcon = (props) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_react25.DotsThreeIcon, { weight: "bold", ...props });
3218
3232
 
3219
3233
  // src/components/TrackControls/Slider.tsx
3220
3234
  var import_styled_components30 = __toESM(require("styled-components"));
@@ -3282,10 +3296,10 @@ var SliderWrapper = import_styled_components31.default.label`
3282
3296
  `;
3283
3297
 
3284
3298
  // src/components/TrackMenu.tsx
3285
- var import_react24 = __toESM(require("react"));
3299
+ var import_react26 = __toESM(require("react"));
3286
3300
  var import_react_dom = require("react-dom");
3287
3301
  var import_styled_components32 = __toESM(require("styled-components"));
3288
- var import_jsx_runtime32 = require("react/jsx-runtime");
3302
+ var import_jsx_runtime33 = require("react/jsx-runtime");
3289
3303
  var MenuContainer = import_styled_components32.default.div`
3290
3304
  position: relative;
3291
3305
  display: inline-block;
@@ -3323,16 +3337,14 @@ var Divider = import_styled_components32.default.hr`
3323
3337
  border-top: 1px solid rgba(128, 128, 128, 0.3);
3324
3338
  margin: 0.35rem 0;
3325
3339
  `;
3326
- var TrackMenu = ({
3327
- items: itemsProp
3328
- }) => {
3329
- const [open, setOpen] = (0, import_react24.useState)(false);
3340
+ var TrackMenu = ({ items: itemsProp }) => {
3341
+ const [open, setOpen] = (0, import_react26.useState)(false);
3330
3342
  const close = () => setOpen(false);
3331
3343
  const items = typeof itemsProp === "function" ? itemsProp(close) : itemsProp;
3332
- const [dropdownPos, setDropdownPos] = (0, import_react24.useState)({ top: 0, left: 0 });
3333
- const buttonRef = (0, import_react24.useRef)(null);
3334
- const dropdownRef = (0, import_react24.useRef)(null);
3335
- (0, import_react24.useEffect)(() => {
3344
+ const [dropdownPos, setDropdownPos] = (0, import_react26.useState)({ top: 0, left: 0 });
3345
+ const buttonRef = (0, import_react26.useRef)(null);
3346
+ const dropdownRef = (0, import_react26.useRef)(null);
3347
+ (0, import_react26.useEffect)(() => {
3336
3348
  if (open && buttonRef.current) {
3337
3349
  const rect = buttonRef.current.getBoundingClientRect();
3338
3350
  setDropdownPos({
@@ -3341,7 +3353,7 @@ var TrackMenu = ({
3341
3353
  });
3342
3354
  }
3343
3355
  }, [open]);
3344
- (0, import_react24.useEffect)(() => {
3356
+ (0, import_react26.useEffect)(() => {
3345
3357
  if (!open) return;
3346
3358
  const handleClick = (e) => {
3347
3359
  const target = e.target;
@@ -3352,8 +3364,8 @@ var TrackMenu = ({
3352
3364
  document.addEventListener("mousedown", handleClick);
3353
3365
  return () => document.removeEventListener("mousedown", handleClick);
3354
3366
  }, [open]);
3355
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(MenuContainer, { children: [
3356
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
3367
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(MenuContainer, { children: [
3368
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
3357
3369
  MenuButton,
3358
3370
  {
3359
3371
  ref: buttonRef,
@@ -3364,19 +3376,19 @@ var TrackMenu = ({
3364
3376
  onMouseDown: (e) => e.stopPropagation(),
3365
3377
  title: "Track menu",
3366
3378
  "aria-label": "Track menu",
3367
- children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(DotsIcon, { size: 16 })
3379
+ children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(DotsIcon, { size: 16 })
3368
3380
  }
3369
3381
  ),
3370
3382
  open && typeof document !== "undefined" && (0, import_react_dom.createPortal)(
3371
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
3383
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
3372
3384
  Dropdown,
3373
3385
  {
3374
3386
  ref: dropdownRef,
3375
3387
  $top: dropdownPos.top,
3376
3388
  $left: dropdownPos.left,
3377
3389
  onMouseDown: (e) => e.stopPropagation(),
3378
- children: items.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(import_react24.default.Fragment, { children: [
3379
- index > 0 && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(Divider, {}),
3390
+ children: items.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(import_react26.default.Fragment, { children: [
3391
+ index > 0 && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(Divider, {}),
3380
3392
  item.content
3381
3393
  ] }, item.id))
3382
3394
  }
@@ -3408,6 +3420,7 @@ var TrackMenu = ({
3408
3420
  ClipBoundary,
3409
3421
  ClipHeader,
3410
3422
  ClipHeaderPresentational,
3423
+ ClipViewportOriginProvider,
3411
3424
  CloseButton,
3412
3425
  Controls,
3413
3426
  DevicePixelRatioProvider,
@@ -3417,7 +3430,6 @@ var TrackMenu = ({
3417
3430
  InlineLabel,
3418
3431
  LoopRegion,
3419
3432
  LoopRegionMarkers,
3420
- MAX_CANVAS_WIDTH,
3421
3433
  MasterVolumeControl,
3422
3434
  Playhead,
3423
3435
  PlayheadWithMarker,
@@ -3458,6 +3470,7 @@ var TrackMenu = ({
3458
3470
  samplesToSeconds,
3459
3471
  secondsToPixels,
3460
3472
  secondsToSamples,
3473
+ useClipViewportOrigin,
3461
3474
  useDevicePixelRatio,
3462
3475
  usePlaylistInfo,
3463
3476
  usePlayoutStatus,
@@ -3466,6 +3479,7 @@ var TrackMenu = ({
3466
3479
  useScrollViewportSelector,
3467
3480
  useTheme,
3468
3481
  useTrackControls,
3482
+ useVisibleChunkIndices,
3469
3483
  waveformColorToCss
3470
3484
  });
3471
3485
  //# sourceMappingURL=index.js.map