@waveform-playlist/ui-components 7.0.0 → 7.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -60,6 +60,7 @@ __export(index_exports, {
60
60
  InlineLabel: () => InlineLabel,
61
61
  LoopRegion: () => LoopRegion,
62
62
  LoopRegionMarkers: () => LoopRegionMarkers,
63
+ MAX_CANVAS_WIDTH: () => MAX_CANVAS_WIDTH,
63
64
  MasterVolumeControl: () => MasterVolumeControl,
64
65
  Playhead: () => Playhead,
65
66
  PlayheadWithMarker: () => PlayheadWithMarker,
@@ -68,6 +69,7 @@ __export(index_exports, {
68
69
  PlaylistInfoContext: () => PlaylistInfoContext,
69
70
  PlayoutProvider: () => PlayoutProvider,
70
71
  ScreenReaderOnly: () => ScreenReaderOnly,
72
+ ScrollViewportProvider: () => ScrollViewportProvider,
71
73
  Selection: () => Selection,
72
74
  SelectionTimeInputs: () => SelectionTimeInputs,
73
75
  Slider: () => Slider,
@@ -103,6 +105,8 @@ __export(index_exports, {
103
105
  usePlaylistInfo: () => usePlaylistInfo,
104
106
  usePlayoutStatus: () => usePlayoutStatus,
105
107
  usePlayoutStatusUpdate: () => usePlayoutStatusUpdate,
108
+ useScrollViewport: () => useScrollViewport,
109
+ useScrollViewportSelector: () => useScrollViewportSelector,
106
110
  useTheme: () => useTheme2,
107
111
  useTrackControls: () => useTrackControls,
108
112
  waveformColorToCss: () => waveformColorToCss
@@ -438,7 +442,7 @@ var AutomaticScrollCheckbox = ({
438
442
  };
439
443
 
440
444
  // src/components/Channel.tsx
441
- var import_react = require("react");
445
+ var import_react2 = require("react");
442
446
  var import_styled_components9 = __toESM(require("styled-components"));
443
447
 
444
448
  // src/wfpl-theme.ts
@@ -598,9 +602,105 @@ var darkTheme = {
598
602
  fontSizeSmall: "12px"
599
603
  };
600
604
 
601
- // src/components/Channel.tsx
605
+ // src/contexts/ScrollViewport.tsx
606
+ var import_react = require("react");
602
607
  var import_jsx_runtime3 = require("react/jsx-runtime");
608
+ var ViewportStore = class {
609
+ constructor() {
610
+ this._state = null;
611
+ this._listeners = /* @__PURE__ */ new Set();
612
+ this.subscribe = (callback) => {
613
+ this._listeners.add(callback);
614
+ return () => this._listeners.delete(callback);
615
+ };
616
+ this.getSnapshot = () => this._state;
617
+ }
618
+ /**
619
+ * Update viewport state. Applies a 100px scroll threshold to skip updates
620
+ * that don't affect chunk visibility (1000px chunks with 1.5× overscan buffer).
621
+ * Only notifies listeners when the state actually changes.
622
+ */
623
+ update(scrollLeft, containerWidth) {
624
+ const buffer = containerWidth * 1.5;
625
+ const visibleStart = Math.max(0, scrollLeft - buffer);
626
+ const visibleEnd = scrollLeft + containerWidth + buffer;
627
+ if (this._state && this._state.containerWidth === containerWidth && Math.abs(this._state.scrollLeft - scrollLeft) < 100) {
628
+ return;
629
+ }
630
+ this._state = { scrollLeft, containerWidth, visibleStart, visibleEnd };
631
+ for (const listener of this._listeners) {
632
+ listener();
633
+ }
634
+ }
635
+ };
636
+ var ViewportStoreContext = (0, import_react.createContext)(null);
637
+ var EMPTY_SUBSCRIBE = () => () => {
638
+ };
639
+ var NULL_SNAPSHOT = () => null;
640
+ var ScrollViewportProvider = ({
641
+ containerRef,
642
+ children
643
+ }) => {
644
+ const storeRef = (0, import_react.useRef)(null);
645
+ if (storeRef.current === null) {
646
+ storeRef.current = new ViewportStore();
647
+ }
648
+ const store = storeRef.current;
649
+ const rafIdRef = (0, import_react.useRef)(null);
650
+ const measure = (0, import_react.useCallback)(() => {
651
+ const el = containerRef.current;
652
+ if (!el) return;
653
+ store.update(el.scrollLeft, el.clientWidth);
654
+ }, [containerRef, store]);
655
+ const scheduleUpdate = (0, import_react.useCallback)(() => {
656
+ if (rafIdRef.current !== null) return;
657
+ rafIdRef.current = requestAnimationFrame(() => {
658
+ rafIdRef.current = null;
659
+ measure();
660
+ });
661
+ }, [measure]);
662
+ (0, import_react.useEffect)(() => {
663
+ const el = containerRef.current;
664
+ if (!el) return;
665
+ measure();
666
+ el.addEventListener("scroll", scheduleUpdate, { passive: true });
667
+ const resizeObserver = new ResizeObserver(() => {
668
+ scheduleUpdate();
669
+ });
670
+ resizeObserver.observe(el);
671
+ return () => {
672
+ el.removeEventListener("scroll", scheduleUpdate);
673
+ resizeObserver.disconnect();
674
+ if (rafIdRef.current !== null) {
675
+ cancelAnimationFrame(rafIdRef.current);
676
+ rafIdRef.current = null;
677
+ }
678
+ };
679
+ }, [containerRef, measure, scheduleUpdate]);
680
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ViewportStoreContext.Provider, { value: store, children });
681
+ };
682
+ var useScrollViewport = () => {
683
+ const store = (0, import_react.useContext)(ViewportStoreContext);
684
+ return (0, import_react.useSyncExternalStore)(
685
+ store ? store.subscribe : EMPTY_SUBSCRIBE,
686
+ store ? store.getSnapshot : NULL_SNAPSHOT,
687
+ NULL_SNAPSHOT
688
+ );
689
+ };
690
+ function useScrollViewportSelector(selector) {
691
+ const store = (0, import_react.useContext)(ViewportStoreContext);
692
+ return (0, import_react.useSyncExternalStore)(
693
+ store ? store.subscribe : EMPTY_SUBSCRIBE,
694
+ () => selector(store ? store.getSnapshot() : null),
695
+ () => selector(null)
696
+ );
697
+ }
698
+
699
+ // src/constants.ts
603
700
  var MAX_CANVAS_WIDTH = 1e3;
701
+
702
+ // src/components/Channel.tsx
703
+ var import_jsx_runtime4 = require("react/jsx-runtime");
604
704
  function createCanvasFillStyle(ctx, color, width, height) {
605
705
  if (!isWaveformGradient(color)) {
606
706
  return color;
@@ -619,11 +719,12 @@ function createCanvasFillStyle(ctx, color, width, height) {
619
719
  var Waveform = import_styled_components9.default.canvas.attrs((props) => ({
620
720
  style: {
621
721
  width: `${props.$cssWidth}px`,
622
- height: `${props.$waveHeight}px`
722
+ height: `${props.$waveHeight}px`,
723
+ left: `${props.$left}px`
623
724
  }
624
725
  }))`
625
- float: left;
626
- position: relative;
726
+ position: absolute;
727
+ top: 0;
627
728
  /* Promote to own compositing layer for smoother scrolling */
628
729
  will-change: transform;
629
730
  /* Disable image rendering interpolation */
@@ -659,8 +760,25 @@ var Channel = (props) => {
659
760
  transparentBackground = false,
660
761
  drawMode = "inverted"
661
762
  } = props;
662
- const canvasesRef = (0, import_react.useRef)([]);
663
- const canvasRef = (0, import_react.useCallback)(
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)(
664
782
  (canvas) => {
665
783
  if (canvas !== null) {
666
784
  const index2 = parseInt(canvas.dataset.index, 10);
@@ -669,12 +787,22 @@ var Channel = (props) => {
669
787
  },
670
788
  []
671
789
  );
672
- (0, import_react.useLayoutEffect)(() => {
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)(() => {
673
799
  const canvases = canvasesRef.current;
674
800
  const step = barWidth + barGap;
675
- let globalPixelOffset = 0;
676
801
  for (let i = 0; i < canvases.length; i++) {
677
802
  const canvas = canvases[i];
803
+ if (!canvas) continue;
804
+ const canvasIdx = parseInt(canvas.dataset.index, 10);
805
+ const globalPixelOffset = canvasIdx * MAX_CANVAS_WIDTH;
678
806
  const ctx = canvas.getContext("2d");
679
807
  const h2 = Math.floor(waveHeight / 2);
680
808
  const maxValue = 2 ** (bits - 1);
@@ -717,7 +845,6 @@ var Channel = (props) => {
717
845
  }
718
846
  }
719
847
  }
720
- globalPixelOffset += canvas.width / devicePixelRatio;
721
848
  }
722
849
  }, [
723
850
  data,
@@ -729,32 +856,29 @@ var Channel = (props) => {
729
856
  length,
730
857
  barWidth,
731
858
  barGap,
732
- drawMode
859
+ drawMode,
860
+ visibleChunkKey
733
861
  ]);
734
- let totalWidth = length;
735
- let waveformCount = 0;
736
- const waveforms = [];
737
- while (totalWidth > 0) {
738
- const currentWidth = Math.min(totalWidth, MAX_CANVAS_WIDTH);
739
- const waveform = /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
862
+ 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)(
740
866
  Waveform,
741
867
  {
742
868
  $cssWidth: currentWidth,
869
+ $left: chunkLeft,
743
870
  width: currentWidth * devicePixelRatio,
744
871
  height: waveHeight * devicePixelRatio,
745
872
  $waveHeight: waveHeight,
746
- "data-index": waveformCount,
873
+ "data-index": i,
747
874
  ref: canvasRef
748
875
  },
749
- `${length}-${waveformCount}`
876
+ `${length}-${i}`
750
877
  );
751
- waveforms.push(waveform);
752
- totalWidth -= currentWidth;
753
- waveformCount += 1;
754
- }
878
+ });
755
879
  const bgColor = waveFillColor;
756
880
  const backgroundCss = transparentBackground ? "transparent" : waveformColorToCss(bgColor);
757
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
881
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
758
882
  Wrapper,
759
883
  {
760
884
  $index: index,
@@ -768,8 +892,8 @@ var Channel = (props) => {
768
892
  };
769
893
 
770
894
  // src/components/ErrorBoundary.tsx
771
- var import_react2 = __toESM(require("react"));
772
- var import_jsx_runtime4 = require("react/jsx-runtime");
895
+ var import_react3 = __toESM(require("react"));
896
+ var import_jsx_runtime5 = require("react/jsx-runtime");
773
897
  var errorContainerStyle = {
774
898
  padding: "16px",
775
899
  background: "#1a1a2e",
@@ -783,7 +907,7 @@ var errorContainerStyle = {
783
907
  alignItems: "center",
784
908
  justifyContent: "center"
785
909
  };
786
- var PlaylistErrorBoundary = class extends import_react2.default.Component {
910
+ var PlaylistErrorBoundary = class extends import_react3.default.Component {
787
911
  constructor(props) {
788
912
  super(props);
789
913
  this.state = { hasError: false, error: null };
@@ -799,7 +923,7 @@ var PlaylistErrorBoundary = class extends import_react2.default.Component {
799
923
  if (this.props.fallback) {
800
924
  return this.props.fallback;
801
925
  }
802
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: errorContainerStyle, children: "Waveform playlist encountered an error. Check console for details." });
926
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: errorContainerStyle, children: "Waveform playlist encountered an error. Check console for details." });
803
927
  }
804
928
  return this.props.children;
805
929
  }
@@ -812,7 +936,7 @@ var import_utilities = require("@dnd-kit/utilities");
812
936
 
813
937
  // src/components/ClipHeader.tsx
814
938
  var import_styled_components10 = __toESM(require("styled-components"));
815
- var import_jsx_runtime5 = require("react/jsx-runtime");
939
+ var import_jsx_runtime6 = require("react/jsx-runtime");
816
940
  var CLIP_HEADER_HEIGHT = 22;
817
941
  var HeaderContainer = import_styled_components10.default.div`
818
942
  position: relative;
@@ -852,27 +976,27 @@ var ClipHeaderPresentational = ({
852
976
  trackName,
853
977
  isSelected = false
854
978
  }) => {
855
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
979
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
856
980
  HeaderContainer,
857
981
  {
858
982
  $isDragging: false,
859
983
  $interactive: false,
860
984
  $isSelected: isSelected,
861
- children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(TrackName, { children: trackName })
985
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(TrackName, { children: trackName })
862
986
  }
863
987
  );
864
988
  };
865
989
  var ClipHeader = ({
866
990
  clipId,
867
- trackIndex,
868
- clipIndex,
991
+ trackIndex: _trackIndex,
992
+ clipIndex: _clipIndex,
869
993
  trackName,
870
994
  isSelected = false,
871
995
  disableDrag = false,
872
996
  dragHandleProps
873
997
  }) => {
874
998
  if (disableDrag || !dragHandleProps) {
875
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
999
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
876
1000
  ClipHeaderPresentational,
877
1001
  {
878
1002
  trackName,
@@ -881,7 +1005,7 @@ var ClipHeader = ({
881
1005
  );
882
1006
  }
883
1007
  const { attributes, listeners, setActivatorNodeRef } = dragHandleProps;
884
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1008
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
885
1009
  HeaderContainer,
886
1010
  {
887
1011
  ref: setActivatorNodeRef,
@@ -890,15 +1014,15 @@ var ClipHeader = ({
890
1014
  $isSelected: isSelected,
891
1015
  ...listeners,
892
1016
  ...attributes,
893
- children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(TrackName, { children: trackName })
1017
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(TrackName, { children: trackName })
894
1018
  }
895
1019
  );
896
1020
  };
897
1021
 
898
1022
  // src/components/ClipBoundary.tsx
899
- var import_react3 = __toESM(require("react"));
1023
+ var import_react4 = __toESM(require("react"));
900
1024
  var import_styled_components11 = __toESM(require("styled-components"));
901
- var import_jsx_runtime6 = require("react/jsx-runtime");
1025
+ var import_jsx_runtime7 = require("react/jsx-runtime");
902
1026
  var CLIP_BOUNDARY_WIDTH = 8;
903
1027
  var CLIP_BOUNDARY_WIDTH_TOUCH = 24;
904
1028
  var BoundaryContainer = import_styled_components11.default.div`
@@ -932,18 +1056,18 @@ var BoundaryContainer = import_styled_components11.default.div`
932
1056
  `;
933
1057
  var ClipBoundary = ({
934
1058
  clipId,
935
- trackIndex,
936
- clipIndex,
1059
+ trackIndex: _trackIndex,
1060
+ clipIndex: _clipIndex,
937
1061
  edge,
938
1062
  dragHandleProps,
939
1063
  touchOptimized = false
940
1064
  }) => {
941
- const [isHovered, setIsHovered] = import_react3.default.useState(false);
1065
+ const [isHovered, setIsHovered] = import_react4.default.useState(false);
942
1066
  if (!dragHandleProps) {
943
1067
  return null;
944
1068
  }
945
1069
  const { attributes, listeners, setActivatorNodeRef, isDragging } = dragHandleProps;
946
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1070
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
947
1071
  BoundaryContainer,
948
1072
  {
949
1073
  ref: setActivatorNodeRef,
@@ -963,7 +1087,7 @@ var ClipBoundary = ({
963
1087
 
964
1088
  // src/components/FadeOverlay.tsx
965
1089
  var import_styled_components12 = __toESM(require("styled-components"));
966
- var import_jsx_runtime7 = require("react/jsx-runtime");
1090
+ var import_jsx_runtime8 = require("react/jsx-runtime");
967
1091
  var FadeContainer = import_styled_components12.default.div.attrs((props) => ({
968
1092
  style: {
969
1093
  left: `${props.$left}px`,
@@ -1020,7 +1144,7 @@ var FadeOverlay = ({
1020
1144
  const theme = (0, import_styled_components12.useTheme)();
1021
1145
  if (width < 1) return null;
1022
1146
  const fillColor = color || theme?.fadeOverlayColor || "rgba(0, 0, 0, 0.4)";
1023
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(FadeContainer, { $left: left, $width: width, $type: type, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(FadeSvg, { $type: type, viewBox: `0 0 ${width} 100`, preserveAspectRatio: "none", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
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)(
1024
1148
  "path",
1025
1149
  {
1026
1150
  d: generateFadePath(width, 100, curveType),
@@ -1030,7 +1154,7 @@ var FadeOverlay = ({
1030
1154
  };
1031
1155
 
1032
1156
  // src/components/Clip.tsx
1033
- var import_jsx_runtime8 = require("react/jsx-runtime");
1157
+ var import_jsx_runtime9 = require("react/jsx-runtime");
1034
1158
  var ClipContainer = import_styled_components13.default.div.attrs((props) => ({
1035
1159
  style: props.$isOverlay ? {} : {
1036
1160
  left: `${props.$left}px`,
@@ -1115,7 +1239,7 @@ var Clip = ({
1115
1239
  zIndex: isDragging ? 100 : void 0
1116
1240
  // Below controls (z-index: 999) but above other clips
1117
1241
  } : void 0;
1118
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1242
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1119
1243
  ClipContainer,
1120
1244
  {
1121
1245
  ref: setNodeRef,
@@ -1128,7 +1252,7 @@ var Clip = ({
1128
1252
  "data-track-id": trackId,
1129
1253
  onMouseDown,
1130
1254
  children: [
1131
- showHeader && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1255
+ showHeader && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1132
1256
  ClipHeader,
1133
1257
  {
1134
1258
  clipId,
@@ -1140,9 +1264,9 @@ var Clip = ({
1140
1264
  dragHandleProps: enableDrag ? { attributes, listeners, setActivatorNodeRef } : void 0
1141
1265
  }
1142
1266
  ),
1143
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(ChannelsWrapper, { $isOverlay: isOverlay, children: [
1267
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(ChannelsWrapper, { $isOverlay: isOverlay, children: [
1144
1268
  children,
1145
- showFades && fadeIn && fadeIn.duration > 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1269
+ showFades && fadeIn && fadeIn.duration > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1146
1270
  FadeOverlay,
1147
1271
  {
1148
1272
  left: 0,
@@ -1151,7 +1275,7 @@ var Clip = ({
1151
1275
  curveType: fadeIn.type
1152
1276
  }
1153
1277
  ),
1154
- showFades && fadeOut && fadeOut.duration > 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1278
+ showFades && fadeOut && fadeOut.duration > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1155
1279
  FadeOverlay,
1156
1280
  {
1157
1281
  left: width - Math.floor(fadeOut.duration * sampleRate / samplesPerPixel),
@@ -1161,8 +1285,8 @@ var Clip = ({
1161
1285
  }
1162
1286
  )
1163
1287
  ] }),
1164
- showHeader && !disableHeaderDrag && !isOverlay && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
1165
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1288
+ showHeader && !disableHeaderDrag && !isOverlay && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
1289
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1166
1290
  ClipBoundary,
1167
1291
  {
1168
1292
  clipId,
@@ -1178,7 +1302,7 @@ var Clip = ({
1178
1302
  }
1179
1303
  }
1180
1304
  ),
1181
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1305
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1182
1306
  ClipBoundary,
1183
1307
  {
1184
1308
  clipId,
@@ -1202,7 +1326,7 @@ var Clip = ({
1202
1326
 
1203
1327
  // src/components/MasterVolumeControl.tsx
1204
1328
  var import_styled_components14 = __toESM(require("styled-components"));
1205
- var import_jsx_runtime9 = require("react/jsx-runtime");
1329
+ var import_jsx_runtime10 = require("react/jsx-runtime");
1206
1330
  var VolumeContainer = import_styled_components14.default.div`
1207
1331
  display: inline-flex;
1208
1332
  align-items: center;
@@ -1224,9 +1348,9 @@ var MasterVolumeControl = ({
1224
1348
  const handleChange = (e) => {
1225
1349
  onChange(parseFloat(e.target.value) / 100);
1226
1350
  };
1227
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(VolumeContainer, { className, children: [
1228
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(VolumeLabel, { htmlFor: "master-gain", children: "Master Volume" }),
1229
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
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)(
1230
1354
  VolumeSlider,
1231
1355
  {
1232
1356
  min: "0",
@@ -1241,9 +1365,9 @@ var MasterVolumeControl = ({
1241
1365
  };
1242
1366
 
1243
1367
  // src/components/Playhead.tsx
1244
- var import_react4 = require("react");
1368
+ var import_react5 = require("react");
1245
1369
  var import_styled_components15 = __toESM(require("styled-components"));
1246
- var import_jsx_runtime10 = require("react/jsx-runtime");
1370
+ var import_jsx_runtime11 = require("react/jsx-runtime");
1247
1371
  var PlayheadLine = import_styled_components15.default.div.attrs((props) => ({
1248
1372
  style: {
1249
1373
  transform: `translate3d(${props.$position}px, 0, 0)`
@@ -1260,7 +1384,7 @@ var PlayheadLine = import_styled_components15.default.div.attrs((props) => ({
1260
1384
  will-change: transform;
1261
1385
  `;
1262
1386
  var Playhead = ({ position, color = "#ff0000" }) => {
1263
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PlayheadLine, { $position: position, $color: color });
1387
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(PlayheadLine, { $position: position, $color: color });
1264
1388
  };
1265
1389
  var PlayheadWithMarkerContainer = import_styled_components15.default.div`
1266
1390
  position: absolute;
@@ -1300,9 +1424,9 @@ var PlayheadWithMarker = ({
1300
1424
  controlsOffset,
1301
1425
  getAudioContextTime
1302
1426
  }) => {
1303
- const containerRef = (0, import_react4.useRef)(null);
1304
- const animationFrameRef = (0, import_react4.useRef)(null);
1305
- (0, import_react4.useEffect)(() => {
1427
+ const containerRef = (0, import_react5.useRef)(null);
1428
+ const animationFrameRef = (0, import_react5.useRef)(null);
1429
+ (0, import_react5.useEffect)(() => {
1306
1430
  const updatePosition = () => {
1307
1431
  if (containerRef.current) {
1308
1432
  let time;
@@ -1331,22 +1455,23 @@ var PlayheadWithMarker = ({
1331
1455
  }
1332
1456
  };
1333
1457
  }, [isPlaying, sampleRate, samplesPerPixel, controlsOffset, currentTimeRef, playbackStartTimeRef, audioStartPositionRef, getAudioContextTime]);
1334
- (0, import_react4.useEffect)(() => {
1458
+ (0, import_react5.useEffect)(() => {
1335
1459
  if (!isPlaying && containerRef.current) {
1336
1460
  const time = currentTimeRef.current ?? 0;
1337
1461
  const pos = time * sampleRate / samplesPerPixel + controlsOffset;
1338
1462
  containerRef.current.style.transform = `translate3d(${pos}px, 0, 0)`;
1339
1463
  }
1340
1464
  });
1341
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(PlayheadWithMarkerContainer, { ref: containerRef, $color: color, children: [
1342
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MarkerTriangle, { $color: color }),
1343
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MarkerLine, { $color: color })
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 })
1344
1468
  ] });
1345
1469
  };
1346
1470
 
1347
1471
  // src/components/Playlist.tsx
1348
1472
  var import_styled_components16 = __toESM(require("styled-components"));
1349
- var import_jsx_runtime11 = require("react/jsx-runtime");
1473
+ var import_react6 = require("react");
1474
+ var import_jsx_runtime12 = require("react/jsx-runtime");
1350
1475
  var Wrapper2 = import_styled_components16.default.div`
1351
1476
  overflow-y: hidden;
1352
1477
  overflow-x: auto;
@@ -1400,16 +1525,21 @@ var Playlist = ({
1400
1525
  isSelecting,
1401
1526
  "data-playlist-state": playlistState
1402
1527
  }) => {
1403
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Wrapper2, { "data-scroll-container": "true", "data-playlist-state": playlistState, ref: scrollContainerRef, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
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)(
1404
1534
  ScrollContainer,
1405
1535
  {
1406
1536
  $backgroundColor: backgroundColor,
1407
1537
  $width: scrollContainerWidth,
1408
1538
  children: [
1409
- timescale && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(TimescaleWrapper, { $width: timescaleWidth, $backgroundColor: timescaleBackgroundColor, children: timescale }),
1410
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(TracksContainer, { $width: tracksWidth, $backgroundColor: backgroundColor, 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: [
1411
1541
  children,
1412
- (onTracksClick || onTracksMouseDown) && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1542
+ (onTracksClick || onTracksMouseDown) && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1413
1543
  ClickOverlay,
1414
1544
  {
1415
1545
  $controlsWidth: controlsWidth,
@@ -1423,13 +1553,13 @@ var Playlist = ({
1423
1553
  ] })
1424
1554
  ]
1425
1555
  }
1426
- ) });
1556
+ ) }) });
1427
1557
  };
1428
1558
  var StyledPlaylist = (0, import_styled_components16.withTheme)(Playlist);
1429
1559
 
1430
1560
  // src/components/Selection.tsx
1431
1561
  var import_styled_components17 = __toESM(require("styled-components"));
1432
- var import_jsx_runtime12 = require("react/jsx-runtime");
1562
+ var import_jsx_runtime13 = require("react/jsx-runtime");
1433
1563
  var SelectionOverlay = import_styled_components17.default.div.attrs((props) => ({
1434
1564
  style: {
1435
1565
  left: `${props.$left}px`,
@@ -1453,13 +1583,13 @@ var Selection = ({
1453
1583
  if (width <= 0) {
1454
1584
  return null;
1455
1585
  }
1456
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(SelectionOverlay, { $left: startPosition, $width: width, $color: color, "data-selection": true });
1586
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(SelectionOverlay, { $left: startPosition, $width: width, $color: color, "data-selection": true });
1457
1587
  };
1458
1588
 
1459
1589
  // src/components/LoopRegion.tsx
1460
- var import_react5 = require("react");
1590
+ var import_react7 = require("react");
1461
1591
  var import_styled_components18 = __toESM(require("styled-components"));
1462
- var import_jsx_runtime13 = require("react/jsx-runtime");
1592
+ var import_jsx_runtime14 = require("react/jsx-runtime");
1463
1593
  var LoopRegionOverlayDiv = import_styled_components18.default.div.attrs((props) => ({
1464
1594
  style: {
1465
1595
  left: `${props.$left}px`,
@@ -1508,8 +1638,8 @@ var LoopRegion = ({
1508
1638
  if (width <= 0) {
1509
1639
  return null;
1510
1640
  }
1511
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
1512
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1641
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
1642
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1513
1643
  LoopRegionOverlayDiv,
1514
1644
  {
1515
1645
  $left: startPosition,
@@ -1518,7 +1648,7 @@ var LoopRegion = ({
1518
1648
  "data-loop-region": true
1519
1649
  }
1520
1650
  ),
1521
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1651
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1522
1652
  LoopMarker,
1523
1653
  {
1524
1654
  $left: startPosition,
@@ -1527,7 +1657,7 @@ var LoopRegion = ({
1527
1657
  "data-loop-marker": "start"
1528
1658
  }
1529
1659
  ),
1530
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1660
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1531
1661
  LoopMarker,
1532
1662
  {
1533
1663
  $left: endPosition - 2,
@@ -1608,12 +1738,12 @@ var LoopRegionMarkers = ({
1608
1738
  minPosition = 0,
1609
1739
  maxPosition = Infinity
1610
1740
  }) => {
1611
- const [draggingMarker, setDraggingMarker] = (0, import_react5.useState)(null);
1612
- const dragStartX = (0, import_react5.useRef)(0);
1613
- const dragStartPosition = (0, import_react5.useRef)(0);
1614
- const dragStartEnd = (0, import_react5.useRef)(0);
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);
1615
1745
  const width = Math.max(0, endPosition - startPosition);
1616
- const handleMarkerMouseDown = (0, import_react5.useCallback)((e, marker) => {
1746
+ const handleMarkerMouseDown = (0, import_react7.useCallback)((e, marker) => {
1617
1747
  e.preventDefault();
1618
1748
  e.stopPropagation();
1619
1749
  setDraggingMarker(marker);
@@ -1638,7 +1768,7 @@ var LoopRegionMarkers = ({
1638
1768
  document.addEventListener("mousemove", handleMouseMove);
1639
1769
  document.addEventListener("mouseup", handleMouseUp);
1640
1770
  }, [startPosition, endPosition, minPosition, maxPosition, onLoopStartChange, onLoopEndChange]);
1641
- const handleRegionMouseDown = (0, import_react5.useCallback)((e) => {
1771
+ const handleRegionMouseDown = (0, import_react7.useCallback)((e) => {
1642
1772
  e.preventDefault();
1643
1773
  e.stopPropagation();
1644
1774
  setDraggingMarker("region");
@@ -1671,8 +1801,8 @@ var LoopRegionMarkers = ({
1671
1801
  if (width <= 0) {
1672
1802
  return null;
1673
1803
  }
1674
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
1675
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1804
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
1805
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1676
1806
  TimescaleLoopShade,
1677
1807
  {
1678
1808
  $left: startPosition,
@@ -1683,7 +1813,7 @@ var LoopRegionMarkers = ({
1683
1813
  "data-loop-region-timescale": true
1684
1814
  }
1685
1815
  ),
1686
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1816
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1687
1817
  DraggableMarkerHandle,
1688
1818
  {
1689
1819
  $left: startPosition,
@@ -1694,7 +1824,7 @@ var LoopRegionMarkers = ({
1694
1824
  "data-loop-marker-handle": "start"
1695
1825
  }
1696
1826
  ),
1697
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1827
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1698
1828
  DraggableMarkerHandle,
1699
1829
  {
1700
1830
  $left: endPosition,
@@ -1729,11 +1859,11 @@ var TimescaleLoopRegion = ({
1729
1859
  maxPosition = Infinity,
1730
1860
  controlsOffset = 0
1731
1861
  }) => {
1732
- const [isCreating, setIsCreating] = (0, import_react5.useState)(false);
1733
- const createStartX = (0, import_react5.useRef)(0);
1734
- const containerRef = (0, import_react5.useRef)(null);
1862
+ const [, setIsCreating] = (0, import_react7.useState)(false);
1863
+ const createStartX = (0, import_react7.useRef)(0);
1864
+ const containerRef = (0, import_react7.useRef)(null);
1735
1865
  const hasLoopRegion = endPosition > startPosition;
1736
- const handleBackgroundMouseDown = (0, import_react5.useCallback)((e) => {
1866
+ const handleBackgroundMouseDown = (0, import_react7.useCallback)((e) => {
1737
1867
  const target = e.target;
1738
1868
  if (target.closest("[data-loop-marker-handle]") || target.closest("[data-loop-region-timescale]")) {
1739
1869
  return;
@@ -1761,14 +1891,14 @@ var TimescaleLoopRegion = ({
1761
1891
  document.addEventListener("mousemove", handleMouseMove);
1762
1892
  document.addEventListener("mouseup", handleMouseUp);
1763
1893
  }, [minPosition, maxPosition, onLoopRegionChange]);
1764
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1894
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1765
1895
  TimescaleLoopCreator,
1766
1896
  {
1767
1897
  ref: containerRef,
1768
1898
  $leftOffset: controlsOffset,
1769
1899
  onMouseDown: handleBackgroundMouseDown,
1770
1900
  "data-timescale-loop-creator": true,
1771
- children: hasLoopRegion && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1901
+ children: hasLoopRegion && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1772
1902
  LoopRegionMarkers,
1773
1903
  {
1774
1904
  startPosition,
@@ -1787,10 +1917,10 @@ var TimescaleLoopRegion = ({
1787
1917
  };
1788
1918
 
1789
1919
  // src/components/SelectionTimeInputs.tsx
1790
- var import_react7 = require("react");
1920
+ var import_react9 = require("react");
1791
1921
 
1792
1922
  // src/components/TimeInput.tsx
1793
- var import_react6 = require("react");
1923
+ var import_react8 = require("react");
1794
1924
 
1795
1925
  // src/utils/timeFormat.ts
1796
1926
  function clockFormat(seconds, decimals) {
@@ -1840,7 +1970,7 @@ function parseTime(timeStr, format) {
1840
1970
  }
1841
1971
 
1842
1972
  // src/components/TimeInput.tsx
1843
- var import_jsx_runtime14 = require("react/jsx-runtime");
1973
+ var import_jsx_runtime15 = require("react/jsx-runtime");
1844
1974
  var TimeInput = ({
1845
1975
  id,
1846
1976
  label,
@@ -1850,8 +1980,8 @@ var TimeInput = ({
1850
1980
  onChange,
1851
1981
  readOnly = false
1852
1982
  }) => {
1853
- const [displayValue, setDisplayValue] = (0, import_react6.useState)("");
1854
- (0, import_react6.useEffect)(() => {
1983
+ const [displayValue, setDisplayValue] = (0, import_react8.useState)("");
1984
+ (0, import_react8.useEffect)(() => {
1855
1985
  const formatted = formatTime(value, format);
1856
1986
  setDisplayValue(formatted);
1857
1987
  }, [value, format, id]);
@@ -1871,9 +2001,9 @@ var TimeInput = ({
1871
2001
  e.currentTarget.blur();
1872
2002
  }
1873
2003
  };
1874
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
1875
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ScreenReaderOnly, { as: "label", htmlFor: id, children: label }),
1876
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
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)(
1877
2007
  BaseInput,
1878
2008
  {
1879
2009
  type: "text",
@@ -1890,15 +2020,15 @@ var TimeInput = ({
1890
2020
  };
1891
2021
 
1892
2022
  // src/components/SelectionTimeInputs.tsx
1893
- var import_jsx_runtime15 = require("react/jsx-runtime");
2023
+ var import_jsx_runtime16 = require("react/jsx-runtime");
1894
2024
  var SelectionTimeInputs = ({
1895
2025
  selectionStart,
1896
2026
  selectionEnd,
1897
2027
  onSelectionChange,
1898
2028
  className
1899
2029
  }) => {
1900
- const [timeFormat, setTimeFormat] = (0, import_react7.useState)("hh:mm:ss.uuu");
1901
- (0, import_react7.useEffect)(() => {
2030
+ const [timeFormat, setTimeFormat] = (0, import_react9.useState)("hh:mm:ss.uuu");
2031
+ (0, import_react9.useEffect)(() => {
1902
2032
  const timeFormatSelect = document.querySelector(".time-format");
1903
2033
  const handleFormatChange = () => {
1904
2034
  if (timeFormatSelect) {
@@ -1923,8 +2053,8 @@ var SelectionTimeInputs = ({
1923
2053
  onSelectionChange(selectionStart, value);
1924
2054
  }
1925
2055
  };
1926
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
1927
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2056
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className, children: [
2057
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
1928
2058
  TimeInput,
1929
2059
  {
1930
2060
  id: "audio_start",
@@ -1935,7 +2065,7 @@ var SelectionTimeInputs = ({
1935
2065
  onChange: handleStartChange
1936
2066
  }
1937
2067
  ),
1938
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2068
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
1939
2069
  TimeInput,
1940
2070
  {
1941
2071
  id: "audio_end",
@@ -1950,14 +2080,14 @@ var SelectionTimeInputs = ({
1950
2080
  };
1951
2081
 
1952
2082
  // src/contexts/DevicePixelRatio.tsx
1953
- var import_react8 = require("react");
1954
- var import_jsx_runtime16 = require("react/jsx-runtime");
2083
+ var import_react10 = require("react");
2084
+ var import_jsx_runtime17 = require("react/jsx-runtime");
1955
2085
  function getScale() {
1956
2086
  return window.devicePixelRatio;
1957
2087
  }
1958
- var DevicePixelRatioContext = (0, import_react8.createContext)(getScale());
2088
+ var DevicePixelRatioContext = (0, import_react10.createContext)(getScale());
1959
2089
  var DevicePixelRatioProvider = ({ children }) => {
1960
- const [scale, setScale] = (0, import_react8.useState)(getScale());
2090
+ const [scale, setScale] = (0, import_react10.useState)(getScale());
1961
2091
  matchMedia(`(resolution: ${getScale()}dppx)`).addEventListener(
1962
2092
  "change",
1963
2093
  () => {
@@ -1965,13 +2095,13 @@ var DevicePixelRatioProvider = ({ children }) => {
1965
2095
  },
1966
2096
  { once: true }
1967
2097
  );
1968
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DevicePixelRatioContext.Provider, { value: Math.ceil(scale), children });
2098
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DevicePixelRatioContext.Provider, { value: Math.ceil(scale), children });
1969
2099
  };
1970
- var useDevicePixelRatio = () => (0, import_react8.useContext)(DevicePixelRatioContext);
2100
+ var useDevicePixelRatio = () => (0, import_react10.useContext)(DevicePixelRatioContext);
1971
2101
 
1972
2102
  // src/contexts/PlaylistInfo.tsx
1973
- var import_react9 = require("react");
1974
- var PlaylistInfoContext = (0, import_react9.createContext)({
2103
+ var import_react11 = require("react");
2104
+ var PlaylistInfoContext = (0, import_react11.createContext)({
1975
2105
  sampleRate: 48e3,
1976
2106
  samplesPerPixel: 1e3,
1977
2107
  zoomLevels: [1e3, 1500, 2e3, 2500],
@@ -1985,22 +2115,22 @@ var PlaylistInfoContext = (0, import_react9.createContext)({
1985
2115
  barWidth: 1,
1986
2116
  barGap: 0
1987
2117
  });
1988
- var usePlaylistInfo = () => (0, import_react9.useContext)(PlaylistInfoContext);
2118
+ var usePlaylistInfo = () => (0, import_react11.useContext)(PlaylistInfoContext);
1989
2119
 
1990
2120
  // src/contexts/Theme.tsx
1991
- var import_react10 = require("react");
2121
+ var import_react12 = require("react");
1992
2122
  var import_styled_components19 = require("styled-components");
1993
- var useTheme2 = () => (0, import_react10.useContext)(import_styled_components19.ThemeContext);
2123
+ var useTheme2 = () => (0, import_react12.useContext)(import_styled_components19.ThemeContext);
1994
2124
 
1995
2125
  // src/contexts/TrackControls.tsx
1996
- var import_react11 = require("react");
1997
- var import_jsx_runtime17 = require("react/jsx-runtime");
1998
- var TrackControlsContext = (0, import_react11.createContext)(/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_react11.Fragment, {}));
1999
- var useTrackControls = () => (0, import_react11.useContext)(TrackControlsContext);
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);
2000
2130
 
2001
2131
  // src/contexts/Playout.tsx
2002
- var import_react12 = require("react");
2003
- var import_jsx_runtime18 = require("react/jsx-runtime");
2132
+ var import_react14 = require("react");
2133
+ var import_jsx_runtime19 = require("react/jsx-runtime");
2004
2134
  var defaultProgress = 0;
2005
2135
  var defaultIsPlaying = false;
2006
2136
  var defaultSelectionStart = 0;
@@ -2011,8 +2141,8 @@ var defaultPlayout = {
2011
2141
  selectionStart: defaultSelectionStart,
2012
2142
  selectionEnd: defaultSelectionEnd
2013
2143
  };
2014
- var PlayoutStatusContext = (0, import_react12.createContext)(defaultPlayout);
2015
- var PlayoutStatusUpdateContext = (0, import_react12.createContext)({
2144
+ var PlayoutStatusContext = (0, import_react14.createContext)(defaultPlayout);
2145
+ var PlayoutStatusUpdateContext = (0, import_react14.createContext)({
2016
2146
  setIsPlaying: () => {
2017
2147
  },
2018
2148
  setProgress: () => {
@@ -2021,24 +2151,24 @@ var PlayoutStatusUpdateContext = (0, import_react12.createContext)({
2021
2151
  }
2022
2152
  });
2023
2153
  var PlayoutProvider = ({ children }) => {
2024
- const [isPlaying, setIsPlaying] = (0, import_react12.useState)(defaultIsPlaying);
2025
- const [progress, setProgress] = (0, import_react12.useState)(defaultProgress);
2026
- const [selectionStart, setSelectionStart] = (0, import_react12.useState)(defaultSelectionStart);
2027
- const [selectionEnd, setSelectionEnd] = (0, import_react12.useState)(defaultSelectionEnd);
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);
2028
2158
  const setSelection = (start, end) => {
2029
2159
  setSelectionStart(start);
2030
2160
  setSelectionEnd(end);
2031
2161
  };
2032
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(PlayoutStatusUpdateContext.Provider, { value: { setIsPlaying, setProgress, setSelection }, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(PlayoutStatusContext.Provider, { value: { isPlaying, progress, selectionStart, selectionEnd }, children }) });
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 }) });
2033
2163
  };
2034
- var usePlayoutStatus = () => (0, import_react12.useContext)(PlayoutStatusContext);
2035
- var usePlayoutStatusUpdate = () => (0, import_react12.useContext)(PlayoutStatusUpdateContext);
2164
+ var usePlayoutStatus = () => (0, import_react14.useContext)(PlayoutStatusContext);
2165
+ var usePlayoutStatusUpdate = () => (0, import_react14.useContext)(PlayoutStatusUpdateContext);
2036
2166
 
2037
2167
  // src/components/SpectrogramChannel.tsx
2038
- var import_react13 = require("react");
2168
+ var import_react15 = require("react");
2039
2169
  var import_styled_components20 = __toESM(require("styled-components"));
2040
- var import_jsx_runtime19 = require("react/jsx-runtime");
2041
- var MAX_CANVAS_WIDTH2 = 1e3;
2170
+ var import_jsx_runtime20 = require("react/jsx-runtime");
2171
+ var LINEAR_FREQUENCY_SCALE = (f, minF, maxF) => (f - minF) / (maxF - minF);
2042
2172
  var Wrapper3 = import_styled_components20.default.div.attrs((props) => ({
2043
2173
  style: {
2044
2174
  top: `${props.$waveHeight * props.$index}px`,
@@ -2054,11 +2184,13 @@ var Wrapper3 = import_styled_components20.default.div.attrs((props) => ({
2054
2184
  var SpectrogramCanvas = import_styled_components20.default.canvas.attrs((props) => ({
2055
2185
  style: {
2056
2186
  width: `${props.$cssWidth}px`,
2057
- height: `${props.$waveHeight}px`
2187
+ height: `${props.$waveHeight}px`,
2188
+ left: `${props.$left}px`
2058
2189
  }
2059
2190
  }))`
2060
- float: left;
2061
- position: relative;
2191
+ position: absolute;
2192
+ top: 0;
2193
+ /* Promote to own compositing layer for smoother scrolling */
2062
2194
  will-change: transform;
2063
2195
  image-rendering: pixelated;
2064
2196
  image-rendering: crisp-edges;
@@ -2070,6 +2202,7 @@ function defaultGetColorMap() {
2070
2202
  }
2071
2203
  return lut;
2072
2204
  }
2205
+ var DEFAULT_COLOR_LUT = defaultGetColorMap();
2073
2206
  var SpectrogramChannel = ({
2074
2207
  index,
2075
2208
  channelIndex: channelIndexProp,
@@ -2087,11 +2220,30 @@ var SpectrogramChannel = ({
2087
2220
  onCanvasesReady
2088
2221
  }) => {
2089
2222
  const channelIndex = channelIndexProp ?? index;
2090
- const canvasesRef = (0, import_react13.useRef)([]);
2091
- const registeredIdsRef = (0, import_react13.useRef)([]);
2092
- const transferredCanvasesRef = (0, import_react13.useRef)(/* @__PURE__ */ new WeakSet());
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);
2093
2228
  const isWorkerMode = !!(workerApi && clipId);
2094
- const canvasRef = (0, import_react13.useCallback)(
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)(
2095
2247
  (canvas) => {
2096
2248
  if (canvas !== null) {
2097
2249
  const idx = parseInt(canvas.dataset.index, 10);
@@ -2100,53 +2252,101 @@ var SpectrogramChannel = ({
2100
2252
  },
2101
2253
  []
2102
2254
  );
2103
- (0, import_react13.useEffect)(() => {
2255
+ const lut = colorLUT ?? DEFAULT_COLOR_LUT;
2256
+ const maxF = maxFrequency ?? (data ? data.sampleRate / 2 : 22050);
2257
+ const scaleFn = frequencyScaleFn ?? LINEAR_FREQUENCY_SCALE;
2258
+ const hasCustomFrequencyScale = Boolean(frequencyScaleFn);
2259
+ (0, import_react15.useEffect)(() => {
2260
+ workerApiRef.current = workerApi;
2261
+ }, [workerApi]);
2262
+ (0, import_react15.useEffect)(() => {
2263
+ onCanvasesReadyRef.current = onCanvasesReady;
2264
+ }, [onCanvasesReady]);
2265
+ (0, import_react15.useEffect)(() => {
2104
2266
  if (!isWorkerMode) return;
2105
- const canvasCount2 = Math.ceil(length / MAX_CANVAS_WIDTH2);
2106
- canvasesRef.current.length = canvasCount2;
2267
+ const currentWorkerApi = workerApiRef.current;
2268
+ if (!currentWorkerApi || !clipId) return;
2107
2269
  const canvases2 = canvasesRef.current;
2108
- const ids = [];
2109
- const widths = [];
2270
+ const newIds = [];
2271
+ const newWidths = [];
2110
2272
  for (let i = 0; i < canvases2.length; i++) {
2111
2273
  const canvas = canvases2[i];
2112
2274
  if (!canvas) continue;
2113
2275
  if (transferredCanvasesRef.current.has(canvas)) continue;
2114
- const canvasId = `${clipId}-ch${channelIndex}-chunk${i}`;
2276
+ const canvasIdx = parseInt(canvas.dataset.index, 10);
2277
+ const canvasId = `${clipId}-ch${channelIndex}-chunk${canvasIdx}`;
2278
+ let offscreen;
2115
2279
  try {
2116
- const offscreen = canvas.transferControlToOffscreen();
2117
- workerApi.registerCanvas(canvasId, offscreen);
2118
- transferredCanvasesRef.current.add(canvas);
2119
- ids.push(canvasId);
2120
- widths.push(Math.min(length - i * MAX_CANVAS_WIDTH2, MAX_CANVAS_WIDTH2));
2280
+ offscreen = canvas.transferControlToOffscreen();
2121
2281
  } catch (err) {
2122
2282
  console.warn(`[spectrogram] transferControlToOffscreen failed for ${canvasId}:`, err);
2123
2283
  continue;
2124
2284
  }
2285
+ transferredCanvasesRef.current.add(canvas);
2286
+ try {
2287
+ currentWorkerApi.registerCanvas(canvasId, offscreen);
2288
+ newIds.push(canvasId);
2289
+ newWidths.push(Math.min(length - canvasIdx * MAX_CANVAS_WIDTH, MAX_CANVAS_WIDTH));
2290
+ } catch (err) {
2291
+ console.warn(`[spectrogram] registerCanvas failed for ${canvasId}:`, err);
2292
+ continue;
2293
+ }
2125
2294
  }
2126
- registeredIdsRef.current = ids;
2127
- if (ids.length > 0 && onCanvasesReady) {
2128
- onCanvasesReady(ids, widths);
2295
+ if (newIds.length > 0) {
2296
+ registeredIdsRef.current = [...registeredIdsRef.current, ...newIds];
2297
+ onCanvasesReadyRef.current?.(newIds, newWidths);
2129
2298
  }
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);
2320
+ }
2321
+ }
2322
+ }
2323
+ registeredIdsRef.current = remaining;
2324
+ });
2325
+ (0, import_react15.useEffect)(() => {
2130
2326
  return () => {
2327
+ const api = workerApiRef.current;
2328
+ if (!api) return;
2131
2329
  for (const id of registeredIdsRef.current) {
2132
- workerApi.unregisterCanvas(id);
2330
+ try {
2331
+ api.unregisterCanvas(id);
2332
+ } catch (err) {
2333
+ console.warn(`[spectrogram] unregisterCanvas failed for ${id}:`, err);
2334
+ }
2133
2335
  }
2134
2336
  registeredIdsRef.current = [];
2135
2337
  };
2136
- }, [isWorkerMode, clipId, channelIndex, length]);
2137
- const lut = colorLUT ?? defaultGetColorMap();
2138
- const maxF = maxFrequency ?? (data ? data.sampleRate / 2 : 22050);
2139
- const scaleFn = frequencyScaleFn ?? ((f, minF, maxF2) => (f - minF) / (maxF2 - minF));
2140
- (0, import_react13.useLayoutEffect)(() => {
2338
+ }, []);
2339
+ (0, import_react15.useLayoutEffect)(() => {
2141
2340
  if (isWorkerMode || !data) return;
2142
2341
  const canvases2 = canvasesRef.current;
2143
2342
  const { frequencyBinCount, frameCount, hopSize, sampleRate, gainDb, rangeDb: rawRangeDb } = data;
2144
2343
  const rangeDb = rawRangeDb === 0 ? 1 : rawRangeDb;
2145
- let globalPixelOffset = 0;
2146
2344
  const binToFreq = (bin) => bin / frequencyBinCount * (sampleRate / 2);
2147
- for (let canvasIdx = 0; canvasIdx < canvases2.length; canvasIdx++) {
2148
- const canvas = canvases2[canvasIdx];
2345
+ for (let i = 0; i < canvases2.length; i++) {
2346
+ const canvas = canvases2[i];
2149
2347
  if (!canvas) continue;
2348
+ const canvasIdx = parseInt(canvas.dataset.index, 10);
2349
+ const globalPixelOffset = canvasIdx * MAX_CANVAS_WIDTH;
2150
2350
  const ctx = canvas.getContext("2d");
2151
2351
  if (!ctx) continue;
2152
2352
  const canvasWidth = canvas.width / devicePixelRatio;
@@ -2166,7 +2366,7 @@ var SpectrogramChannel = ({
2166
2366
  for (let y = 0; y < canvasHeight; y++) {
2167
2367
  const normalizedY = 1 - y / canvasHeight;
2168
2368
  let bin = Math.floor(normalizedY * frequencyBinCount);
2169
- if (frequencyScaleFn) {
2369
+ if (hasCustomFrequencyScale) {
2170
2370
  let lo = 0;
2171
2371
  let hi = frequencyBinCount - 1;
2172
2372
  while (lo < hi) {
@@ -2205,36 +2405,30 @@ var SpectrogramChannel = ({
2205
2405
  ctx.imageSmoothingEnabled = false;
2206
2406
  ctx.drawImage(tmpCanvas, 0, 0, canvas.width, canvas.height);
2207
2407
  }
2208
- globalPixelOffset += canvasWidth;
2209
2408
  }
2210
- }, [isWorkerMode, data, length, waveHeight, devicePixelRatio, samplesPerPixel, lut, frequencyScaleFn, minFrequency, maxF, scaleFn]);
2211
- let totalWidth = length;
2212
- let canvasCount = 0;
2213
- const canvases = [];
2214
- while (totalWidth > 0) {
2215
- const currentWidth = Math.min(totalWidth, MAX_CANVAS_WIDTH2);
2216
- canvases.push(
2217
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2218
- SpectrogramCanvas,
2219
- {
2220
- $cssWidth: currentWidth,
2221
- width: currentWidth * devicePixelRatio,
2222
- height: waveHeight * devicePixelRatio,
2223
- $waveHeight: waveHeight,
2224
- "data-index": canvasCount,
2225
- ref: canvasRef
2226
- },
2227
- `${length}-${canvasCount}`
2228
- )
2409
+ }, [isWorkerMode, data, length, waveHeight, devicePixelRatio, samplesPerPixel, lut, minFrequency, maxF, scaleFn, hasCustomFrequencyScale, visibleChunkKey]);
2410
+ 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)(
2414
+ SpectrogramCanvas,
2415
+ {
2416
+ $cssWidth: currentWidth,
2417
+ $left: chunkLeft,
2418
+ width: currentWidth * devicePixelRatio,
2419
+ height: waveHeight * devicePixelRatio,
2420
+ $waveHeight: waveHeight,
2421
+ "data-index": i,
2422
+ ref: canvasRef
2423
+ },
2424
+ `${length}-${i}`
2229
2425
  );
2230
- totalWidth -= currentWidth;
2231
- canvasCount++;
2232
- }
2233
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Wrapper3, { $index: index, $cssWidth: length, $waveHeight: waveHeight, children: canvases });
2426
+ });
2427
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Wrapper3, { $index: index, $cssWidth: length, $waveHeight: waveHeight, children: canvases });
2234
2428
  };
2235
2429
 
2236
2430
  // src/components/SmartChannel.tsx
2237
- var import_jsx_runtime20 = require("react/jsx-runtime");
2431
+ var import_jsx_runtime21 = require("react/jsx-runtime");
2238
2432
  var SmartChannel = ({
2239
2433
  isSelected,
2240
2434
  transparentBackground,
@@ -2259,7 +2453,7 @@ var SmartChannel = ({
2259
2453
  const drawMode = theme?.waveformDrawMode || "inverted";
2260
2454
  const hasSpectrogram = spectrogramData || spectrogramWorkerApi;
2261
2455
  if (renderMode === "spectrogram" && hasSpectrogram) {
2262
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
2456
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2263
2457
  SpectrogramChannel,
2264
2458
  {
2265
2459
  index: props.index,
@@ -2280,8 +2474,8 @@ var SmartChannel = ({
2280
2474
  }
2281
2475
  if (renderMode === "both" && hasSpectrogram) {
2282
2476
  const halfHeight = Math.floor(waveHeight / 2);
2283
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
2284
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
2477
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
2478
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2285
2479
  SpectrogramChannel,
2286
2480
  {
2287
2481
  index: props.index * 2,
@@ -2300,7 +2494,7 @@ var SmartChannel = ({
2300
2494
  onCanvasesReady: spectrogramOnCanvasesReady
2301
2495
  }
2302
2496
  ),
2303
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { style: { position: "absolute", top: (props.index * 2 + 1) * halfHeight, width: props.length, height: halfHeight }, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
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)(
2304
2498
  Channel,
2305
2499
  {
2306
2500
  ...props,
@@ -2317,7 +2511,7 @@ var SmartChannel = ({
2317
2511
  ) })
2318
2512
  ] });
2319
2513
  }
2320
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
2514
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2321
2515
  Channel,
2322
2516
  {
2323
2517
  ...props,
@@ -2334,9 +2528,9 @@ var SmartChannel = ({
2334
2528
  };
2335
2529
 
2336
2530
  // src/components/SpectrogramLabels.tsx
2337
- var import_react14 = require("react");
2531
+ var import_react16 = require("react");
2338
2532
  var import_styled_components21 = __toESM(require("styled-components"));
2339
- var import_jsx_runtime21 = require("react/jsx-runtime");
2533
+ var import_jsx_runtime22 = require("react/jsx-runtime");
2340
2534
  var LABELS_WIDTH = 72;
2341
2535
  var LabelsStickyWrapper = import_styled_components21.default.div`
2342
2536
  position: sticky;
@@ -2386,12 +2580,12 @@ var SpectrogramLabels = ({
2386
2580
  renderMode = "spectrogram",
2387
2581
  hasClipHeaders = false
2388
2582
  }) => {
2389
- const canvasRef = (0, import_react14.useRef)(null);
2583
+ const canvasRef = (0, import_react16.useRef)(null);
2390
2584
  const devicePixelRatio = useDevicePixelRatio();
2391
2585
  const spectrogramHeight = renderMode === "both" ? Math.floor(waveHeight / 2) : waveHeight;
2392
2586
  const totalHeight = numChannels * waveHeight;
2393
2587
  const clipHeaderOffset = hasClipHeaders ? 22 : 0;
2394
- (0, import_react14.useLayoutEffect)(() => {
2588
+ (0, import_react16.useLayoutEffect)(() => {
2395
2589
  const canvas = canvasRef.current;
2396
2590
  if (!canvas) return;
2397
2591
  const ctx = canvas.getContext("2d");
@@ -2418,7 +2612,7 @@ var SpectrogramLabels = ({
2418
2612
  }
2419
2613
  }
2420
2614
  }, [waveHeight, numChannels, frequencyScaleFn, minFrequency, maxFrequency, labelsColor, labelsBackground, devicePixelRatio, spectrogramHeight, clipHeaderOffset]);
2421
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(LabelsStickyWrapper, { $height: totalHeight + clipHeaderOffset, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2615
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(LabelsStickyWrapper, { $height: totalHeight + clipHeaderOffset, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2422
2616
  "canvas",
2423
2617
  {
2424
2618
  ref: canvasRef,
@@ -2434,10 +2628,10 @@ var SpectrogramLabels = ({
2434
2628
  };
2435
2629
 
2436
2630
  // src/components/SmartScale.tsx
2437
- var import_react16 = require("react");
2631
+ var import_react18 = require("react");
2438
2632
 
2439
2633
  // src/components/TimeScale.tsx
2440
- var import_react15 = __toESM(require("react"));
2634
+ var import_react17 = __toESM(require("react"));
2441
2635
  var import_styled_components22 = __toESM(require("styled-components"));
2442
2636
 
2443
2637
  // src/utils/conversions.ts
@@ -2461,7 +2655,7 @@ function secondsToPixels(seconds, samplesPerPixel, sampleRate) {
2461
2655
  }
2462
2656
 
2463
2657
  // src/components/TimeScale.tsx
2464
- var import_jsx_runtime22 = require("react/jsx-runtime");
2658
+ var import_jsx_runtime23 = require("react/jsx-runtime");
2465
2659
  function formatTime2(milliseconds) {
2466
2660
  const seconds = Math.floor(milliseconds / 1e3);
2467
2661
  const s = seconds % 60;
@@ -2480,16 +2674,17 @@ var PlaylistTimeScaleScroll = import_styled_components22.default.div.attrs((prop
2480
2674
  border-bottom: 1px solid ${(props) => props.theme.timeColor};
2481
2675
  box-sizing: border-box;
2482
2676
  `;
2483
- var TimeTicks = import_styled_components22.default.canvas.attrs((props) => ({
2677
+ var TimeTickChunk = import_styled_components22.default.canvas.attrs((props) => ({
2484
2678
  style: {
2485
2679
  width: `${props.$cssWidth}px`,
2486
- height: `${props.$timeScaleHeight}px`
2680
+ height: `${props.$timeScaleHeight}px`,
2681
+ left: `${props.$left}px`
2487
2682
  }
2488
2683
  }))`
2489
2684
  position: absolute;
2490
- left: 0;
2491
- right: 0;
2492
2685
  bottom: 0;
2686
+ /* Promote to own compositing layer for smoother scrolling */
2687
+ will-change: transform;
2493
2688
  `;
2494
2689
  var TimeStamp = import_styled_components22.default.div.attrs((props) => ({
2495
2690
  style: {
@@ -2511,78 +2706,120 @@ var TimeScale = (props) => {
2511
2706
  secondStep,
2512
2707
  renderTimestamp
2513
2708
  } = props;
2514
- const canvasInfo = /* @__PURE__ */ new Map();
2515
- const timeMarkers = [];
2516
- const canvasRef = (0, import_react15.useRef)(null);
2709
+ const canvasRefsMap = (0, import_react17.useRef)(/* @__PURE__ */ new Map());
2517
2710
  const {
2518
2711
  sampleRate,
2519
2712
  samplesPerPixel,
2520
2713
  timeScaleHeight,
2521
2714
  controls: { show: showControls, width: controlWidth }
2522
- } = (0, import_react15.useContext)(PlaylistInfoContext);
2715
+ } = (0, import_react17.useContext)(PlaylistInfoContext);
2523
2716
  const devicePixelRatio = useDevicePixelRatio();
2524
- (0, import_react15.useEffect)(() => {
2525
- if (canvasRef.current !== null) {
2526
- const canvas = canvasRef.current;
2527
- const ctx = canvas.getContext("2d");
2528
- if (ctx) {
2529
- ctx.resetTransform();
2530
- ctx.clearRect(0, 0, canvas.width, canvas.height);
2531
- ctx.imageSmoothingEnabled = false;
2532
- ctx.fillStyle = timeColor;
2533
- ctx.scale(devicePixelRatio, devicePixelRatio);
2534
- for (const [pixLeft, scaleHeight] of canvasInfo.entries()) {
2535
- const scaleY = timeScaleHeight - scaleHeight;
2536
- ctx.fillRect(pixLeft, scaleY, 1, scaleHeight);
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)(() => {
2724
+ const nextCanvasInfo = /* @__PURE__ */ new Map();
2725
+ const nextMarkers = [];
2726
+ const nextWidthX = secondsToPixels(duration / 1e3, samplesPerPixel, sampleRate);
2727
+ const pixPerSec = sampleRate / samplesPerPixel;
2728
+ let counter = 0;
2729
+ for (let i = 0; i < nextWidthX; i += pixPerSec * secondStep / 1e3) {
2730
+ const pix = Math.floor(i);
2731
+ if (counter % marker === 0) {
2732
+ const timeMs = counter;
2733
+ 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);
2735
+ nextMarkers.push({ pix, element });
2736
+ nextCanvasInfo.set(pix, timeScaleHeight);
2737
+ } else if (counter % bigStep === 0) {
2738
+ nextCanvasInfo.set(pix, Math.floor(timeScaleHeight / 2));
2739
+ } else if (counter % secondStep === 0) {
2740
+ nextCanvasInfo.set(pix, Math.floor(timeScaleHeight / 5));
2741
+ }
2742
+ counter += secondStep;
2743
+ }
2744
+ return {
2745
+ widthX: nextWidthX,
2746
+ canvasInfo: nextCanvasInfo,
2747
+ timeMarkersWithPositions: nextMarkers
2748
+ };
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;
2537
2760
  }
2538
2761
  }
2762
+ indices.push(i);
2539
2763
  }
2540
- }, [
2541
- duration,
2542
- devicePixelRatio,
2543
- timeColor,
2544
- timeScaleHeight,
2545
- bigStep,
2546
- secondStep,
2547
- marker,
2548
- canvasInfo
2549
- ]);
2550
- const widthX = secondsToPixels(duration / 1e3, samplesPerPixel, sampleRate);
2551
- const pixPerSec = sampleRate / samplesPerPixel;
2552
- let counter = 0;
2553
- for (let i = 0; i < widthX; i += pixPerSec * secondStep / 1e3) {
2554
- const pix = Math.floor(i);
2555
- if (counter % marker === 0) {
2556
- const timeMs = counter;
2557
- const timestamp = formatTime2(timeMs);
2558
- const timestampContent = renderTimestamp ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react15.default.Fragment, { children: renderTimestamp(timeMs, pix) }, `timestamp-${counter}`) : /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TimeStamp, { $left: pix, children: timestamp }, timestamp);
2559
- timeMarkers.push(timestampContent);
2560
- canvasInfo.set(pix, timeScaleHeight);
2561
- } else if (counter % bigStep === 0) {
2562
- canvasInfo.set(pix, Math.floor(timeScaleHeight / 2));
2563
- } else if (counter % secondStep === 0) {
2564
- canvasInfo.set(pix, Math.floor(timeScaleHeight / 5));
2764
+ return indices.join(",");
2765
+ });
2766
+ const visibleChunkIndices = visibleChunkKey ? visibleChunkKey.split(",").map(Number) : [];
2767
+ 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)(
2771
+ TimeTickChunk,
2772
+ {
2773
+ $cssWidth: chunkWidth,
2774
+ $left: chunkLeft,
2775
+ $timeScaleHeight: timeScaleHeight,
2776
+ width: chunkWidth * devicePixelRatio,
2777
+ height: timeScaleHeight * devicePixelRatio,
2778
+ "data-index": i,
2779
+ ref: canvasRefCallback
2780
+ },
2781
+ `timescale-${i}`
2782
+ );
2783
+ });
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;
2786
+ 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
+ }
2565
2793
  }
2566
- counter += secondStep;
2567
- }
2568
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
2794
+ });
2795
+ (0, import_react17.useLayoutEffect)(() => {
2796
+ for (const [chunkIdx, canvas] of canvasRefsMap.current.entries()) {
2797
+ const ctx = canvas.getContext("2d");
2798
+ if (!ctx) continue;
2799
+ const chunkLeft = chunkIdx * MAX_CANVAS_WIDTH;
2800
+ const chunkWidth = canvas.width / devicePixelRatio;
2801
+ ctx.resetTransform();
2802
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
2803
+ ctx.imageSmoothingEnabled = false;
2804
+ ctx.fillStyle = timeColor;
2805
+ ctx.scale(devicePixelRatio, devicePixelRatio);
2806
+ for (const [pixLeft, scaleHeight] of canvasInfo.entries()) {
2807
+ if (pixLeft < chunkLeft || pixLeft >= chunkLeft + chunkWidth) continue;
2808
+ const localX = pixLeft - chunkLeft;
2809
+ const scaleY = timeScaleHeight - scaleHeight;
2810
+ ctx.fillRect(localX, scaleY, 1, scaleHeight);
2811
+ }
2812
+ }
2813
+ }, [duration, devicePixelRatio, timeColor, timeScaleHeight, canvasInfo, visibleChunkKey]);
2814
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
2569
2815
  PlaylistTimeScaleScroll,
2570
2816
  {
2571
2817
  $cssWidth: widthX,
2572
2818
  $controlWidth: showControls ? controlWidth : 0,
2573
2819
  $timeScaleHeight: timeScaleHeight,
2574
2820
  children: [
2575
- timeMarkers,
2576
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2577
- TimeTicks,
2578
- {
2579
- $cssWidth: widthX,
2580
- $timeScaleHeight: timeScaleHeight,
2581
- width: widthX * devicePixelRatio,
2582
- height: timeScaleHeight * devicePixelRatio,
2583
- ref: canvasRef
2584
- }
2585
- )
2821
+ visibleMarkers,
2822
+ visibleChunks
2586
2823
  ]
2587
2824
  }
2588
2825
  );
@@ -2590,7 +2827,7 @@ var TimeScale = (props) => {
2590
2827
  var StyledTimeScale = (0, import_styled_components22.withTheme)(TimeScale);
2591
2828
 
2592
2829
  // src/components/SmartScale.tsx
2593
- var import_jsx_runtime23 = require("react/jsx-runtime");
2830
+ var import_jsx_runtime24 = require("react/jsx-runtime");
2594
2831
  var timeinfo = /* @__PURE__ */ new Map([
2595
2832
  [
2596
2833
  700,
@@ -2664,9 +2901,9 @@ function getScaleInfo(samplesPerPixel) {
2664
2901
  return config;
2665
2902
  }
2666
2903
  var SmartScale = ({ renderTimestamp }) => {
2667
- const { samplesPerPixel, duration } = (0, import_react16.useContext)(PlaylistInfoContext);
2904
+ const { samplesPerPixel, duration } = (0, import_react18.useContext)(PlaylistInfoContext);
2668
2905
  let config = getScaleInfo(samplesPerPixel);
2669
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2906
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2670
2907
  StyledTimeScale,
2671
2908
  {
2672
2909
  marker: config.marker,
@@ -2680,7 +2917,7 @@ var SmartScale = ({ renderTimestamp }) => {
2680
2917
 
2681
2918
  // src/components/TimeFormatSelect.tsx
2682
2919
  var import_styled_components23 = __toESM(require("styled-components"));
2683
- var import_jsx_runtime24 = require("react/jsx-runtime");
2920
+ var import_jsx_runtime25 = require("react/jsx-runtime");
2684
2921
  var SelectWrapper = import_styled_components23.default.div`
2685
2922
  display: inline-flex;
2686
2923
  align-items: center;
@@ -2703,7 +2940,7 @@ var TimeFormatSelect = ({
2703
2940
  const handleChange = (e) => {
2704
2941
  onChange(e.target.value);
2705
2942
  };
2706
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(SelectWrapper, { className, children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2943
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(SelectWrapper, { className, children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
2707
2944
  BaseSelect,
2708
2945
  {
2709
2946
  className: "time-format",
@@ -2711,14 +2948,14 @@ var TimeFormatSelect = ({
2711
2948
  onChange: handleChange,
2712
2949
  disabled,
2713
2950
  "aria-label": "Time format selection",
2714
- children: TIME_FORMAT_OPTIONS.map((option) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("option", { value: option.value, children: option.label }, option.value))
2951
+ children: TIME_FORMAT_OPTIONS.map((option) => /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("option", { value: option.value, children: option.label }, option.value))
2715
2952
  }
2716
2953
  ) });
2717
2954
  };
2718
2955
 
2719
2956
  // src/components/Track.tsx
2720
2957
  var import_styled_components24 = __toESM(require("styled-components"));
2721
- var import_jsx_runtime25 = require("react/jsx-runtime");
2958
+ var import_jsx_runtime26 = require("react/jsx-runtime");
2722
2959
  var Container = import_styled_components24.default.div.attrs((props) => ({
2723
2960
  style: {
2724
2961
  height: `${props.$waveHeight * props.$numChannels + (props.$hasClipHeaders ? CLIP_HEADER_HEIGHT : 0)}px`
@@ -2773,7 +3010,7 @@ var Track = ({
2773
3010
  controls: { show, width: controlWidth }
2774
3011
  } = usePlaylistInfo();
2775
3012
  const controls = useTrackControls();
2776
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
3013
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
2777
3014
  Container,
2778
3015
  {
2779
3016
  $numChannels: numChannels,
@@ -2784,7 +3021,7 @@ var Track = ({
2784
3021
  $hasClipHeaders: hasClipHeaders,
2785
3022
  $isSelected: isSelected,
2786
3023
  children: [
2787
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
3024
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2788
3025
  ControlsWrapper,
2789
3026
  {
2790
3027
  $controlWidth: show ? controlWidth : 0,
@@ -2792,7 +3029,7 @@ var Track = ({
2792
3029
  children: controls
2793
3030
  }
2794
3031
  ),
2795
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
3032
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2796
3033
  ChannelContainer,
2797
3034
  {
2798
3035
  $controlWidth: show ? controlWidth : 0,
@@ -2899,8 +3136,8 @@ var ButtonGroup = import_styled_components26.default.div`
2899
3136
 
2900
3137
  // src/components/TrackControls/CloseButton.tsx
2901
3138
  var import_styled_components27 = __toESM(require("styled-components"));
2902
- var import_react17 = require("@phosphor-icons/react");
2903
- var import_jsx_runtime26 = require("react/jsx-runtime");
3139
+ var import_react19 = require("@phosphor-icons/react");
3140
+ var import_jsx_runtime27 = require("react/jsx-runtime");
2904
3141
  var StyledCloseButton = import_styled_components27.default.button`
2905
3142
  position: absolute;
2906
3143
  left: 0;
@@ -2925,7 +3162,7 @@ var StyledCloseButton = import_styled_components27.default.button`
2925
3162
  var CloseButton = ({
2926
3163
  onClick,
2927
3164
  title = "Remove track"
2928
- }) => /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(StyledCloseButton, { onClick, title, children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_react17.X, { size: 12, weight: "bold" }) });
3165
+ }) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(StyledCloseButton, { onClick, title, children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react19.X, { size: 12, weight: "bold" }) });
2929
3166
 
2930
3167
  // src/components/TrackControls/Controls.tsx
2931
3168
  var import_styled_components28 = __toESM(require("styled-components"));
@@ -2960,24 +3197,24 @@ var Header = import_styled_components29.default.header`
2960
3197
  `;
2961
3198
 
2962
3199
  // src/components/TrackControls/VolumeDownIcon.tsx
2963
- var import_react18 = require("@phosphor-icons/react");
2964
- var import_jsx_runtime27 = require("react/jsx-runtime");
2965
- var VolumeDownIcon = (props) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react18.SpeakerLowIcon, { weight: "light", ...props });
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 });
2966
3203
 
2967
3204
  // src/components/TrackControls/VolumeUpIcon.tsx
2968
- var import_react19 = require("@phosphor-icons/react");
2969
- var import_jsx_runtime28 = require("react/jsx-runtime");
2970
- var VolumeUpIcon = (props) => /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_react19.SpeakerHighIcon, { weight: "light", ...props });
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 });
2971
3208
 
2972
3209
  // src/components/TrackControls/TrashIcon.tsx
2973
- var import_react20 = require("@phosphor-icons/react");
2974
- var import_jsx_runtime29 = require("react/jsx-runtime");
2975
- var TrashIcon = (props) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_react20.TrashIcon, { weight: "light", ...props });
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 });
2976
3213
 
2977
3214
  // src/components/TrackControls/DotsIcon.tsx
2978
- var import_react21 = require("@phosphor-icons/react");
2979
- var import_jsx_runtime30 = require("react/jsx-runtime");
2980
- var DotsIcon = (props) => /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_react21.DotsThreeIcon, { weight: "bold", ...props });
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 });
2981
3218
 
2982
3219
  // src/components/TrackControls/Slider.tsx
2983
3220
  var import_styled_components30 = __toESM(require("styled-components"));
@@ -3045,10 +3282,10 @@ var SliderWrapper = import_styled_components31.default.label`
3045
3282
  `;
3046
3283
 
3047
3284
  // src/components/TrackMenu.tsx
3048
- var import_react22 = __toESM(require("react"));
3285
+ var import_react24 = __toESM(require("react"));
3049
3286
  var import_react_dom = require("react-dom");
3050
3287
  var import_styled_components32 = __toESM(require("styled-components"));
3051
- var import_jsx_runtime31 = require("react/jsx-runtime");
3288
+ var import_jsx_runtime32 = require("react/jsx-runtime");
3052
3289
  var MenuContainer = import_styled_components32.default.div`
3053
3290
  position: relative;
3054
3291
  display: inline-block;
@@ -3089,13 +3326,13 @@ var Divider = import_styled_components32.default.hr`
3089
3326
  var TrackMenu = ({
3090
3327
  items: itemsProp
3091
3328
  }) => {
3092
- const [open, setOpen] = (0, import_react22.useState)(false);
3329
+ const [open, setOpen] = (0, import_react24.useState)(false);
3093
3330
  const close = () => setOpen(false);
3094
3331
  const items = typeof itemsProp === "function" ? itemsProp(close) : itemsProp;
3095
- const [dropdownPos, setDropdownPos] = (0, import_react22.useState)({ top: 0, left: 0 });
3096
- const buttonRef = (0, import_react22.useRef)(null);
3097
- const dropdownRef = (0, import_react22.useRef)(null);
3098
- (0, import_react22.useEffect)(() => {
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)(() => {
3099
3336
  if (open && buttonRef.current) {
3100
3337
  const rect = buttonRef.current.getBoundingClientRect();
3101
3338
  setDropdownPos({
@@ -3104,7 +3341,7 @@ var TrackMenu = ({
3104
3341
  });
3105
3342
  }
3106
3343
  }, [open]);
3107
- (0, import_react22.useEffect)(() => {
3344
+ (0, import_react24.useEffect)(() => {
3108
3345
  if (!open) return;
3109
3346
  const handleClick = (e) => {
3110
3347
  const target = e.target;
@@ -3115,8 +3352,8 @@ var TrackMenu = ({
3115
3352
  document.addEventListener("mousedown", handleClick);
3116
3353
  return () => document.removeEventListener("mousedown", handleClick);
3117
3354
  }, [open]);
3118
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(MenuContainer, { children: [
3119
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
3355
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(MenuContainer, { children: [
3356
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
3120
3357
  MenuButton,
3121
3358
  {
3122
3359
  ref: buttonRef,
@@ -3127,19 +3364,19 @@ var TrackMenu = ({
3127
3364
  onMouseDown: (e) => e.stopPropagation(),
3128
3365
  title: "Track menu",
3129
3366
  "aria-label": "Track menu",
3130
- children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(DotsIcon, { size: 16 })
3367
+ children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(DotsIcon, { size: 16 })
3131
3368
  }
3132
3369
  ),
3133
3370
  open && typeof document !== "undefined" && (0, import_react_dom.createPortal)(
3134
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
3371
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
3135
3372
  Dropdown,
3136
3373
  {
3137
3374
  ref: dropdownRef,
3138
3375
  $top: dropdownPos.top,
3139
3376
  $left: dropdownPos.left,
3140
3377
  onMouseDown: (e) => e.stopPropagation(),
3141
- children: items.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(import_react22.default.Fragment, { children: [
3142
- index > 0 && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(Divider, {}),
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, {}),
3143
3380
  item.content
3144
3381
  ] }, item.id))
3145
3382
  }
@@ -3180,6 +3417,7 @@ var TrackMenu = ({
3180
3417
  InlineLabel,
3181
3418
  LoopRegion,
3182
3419
  LoopRegionMarkers,
3420
+ MAX_CANVAS_WIDTH,
3183
3421
  MasterVolumeControl,
3184
3422
  Playhead,
3185
3423
  PlayheadWithMarker,
@@ -3188,6 +3426,7 @@ var TrackMenu = ({
3188
3426
  PlaylistInfoContext,
3189
3427
  PlayoutProvider,
3190
3428
  ScreenReaderOnly,
3429
+ ScrollViewportProvider,
3191
3430
  Selection,
3192
3431
  SelectionTimeInputs,
3193
3432
  Slider,
@@ -3223,6 +3462,8 @@ var TrackMenu = ({
3223
3462
  usePlaylistInfo,
3224
3463
  usePlayoutStatus,
3225
3464
  usePlayoutStatusUpdate,
3465
+ useScrollViewport,
3466
+ useScrollViewportSelector,
3226
3467
  useTheme,
3227
3468
  useTrackControls,
3228
3469
  waveformColorToCss