@waveform-playlist/ui-components 6.0.2 → 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,13 +60,16 @@ __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,
66
67
  Playlist: () => Playlist,
68
+ PlaylistErrorBoundary: () => PlaylistErrorBoundary,
67
69
  PlaylistInfoContext: () => PlaylistInfoContext,
68
70
  PlayoutProvider: () => PlayoutProvider,
69
71
  ScreenReaderOnly: () => ScreenReaderOnly,
72
+ ScrollViewportProvider: () => ScrollViewportProvider,
70
73
  Selection: () => Selection,
71
74
  SelectionTimeInputs: () => SelectionTimeInputs,
72
75
  Slider: () => Slider,
@@ -102,6 +105,8 @@ __export(index_exports, {
102
105
  usePlaylistInfo: () => usePlaylistInfo,
103
106
  usePlayoutStatus: () => usePlayoutStatus,
104
107
  usePlayoutStatusUpdate: () => usePlayoutStatusUpdate,
108
+ useScrollViewport: () => useScrollViewport,
109
+ useScrollViewportSelector: () => useScrollViewportSelector,
105
110
  useTheme: () => useTheme2,
106
111
  useTrackControls: () => useTrackControls,
107
112
  waveformColorToCss: () => waveformColorToCss
@@ -437,7 +442,7 @@ var AutomaticScrollCheckbox = ({
437
442
  };
438
443
 
439
444
  // src/components/Channel.tsx
440
- var import_react = require("react");
445
+ var import_react2 = require("react");
441
446
  var import_styled_components9 = __toESM(require("styled-components"));
442
447
 
443
448
  // src/wfpl-theme.ts
@@ -597,9 +602,105 @@ var darkTheme = {
597
602
  fontSizeSmall: "12px"
598
603
  };
599
604
 
600
- // src/components/Channel.tsx
605
+ // src/contexts/ScrollViewport.tsx
606
+ var import_react = require("react");
601
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
602
700
  var MAX_CANVAS_WIDTH = 1e3;
701
+
702
+ // src/components/Channel.tsx
703
+ var import_jsx_runtime4 = require("react/jsx-runtime");
603
704
  function createCanvasFillStyle(ctx, color, width, height) {
604
705
  if (!isWaveformGradient(color)) {
605
706
  return color;
@@ -618,11 +719,12 @@ function createCanvasFillStyle(ctx, color, width, height) {
618
719
  var Waveform = import_styled_components9.default.canvas.attrs((props) => ({
619
720
  style: {
620
721
  width: `${props.$cssWidth}px`,
621
- height: `${props.$waveHeight}px`
722
+ height: `${props.$waveHeight}px`,
723
+ left: `${props.$left}px`
622
724
  }
623
725
  }))`
624
- float: left;
625
- position: relative;
726
+ position: absolute;
727
+ top: 0;
626
728
  /* Promote to own compositing layer for smoother scrolling */
627
729
  will-change: transform;
628
730
  /* Disable image rendering interpolation */
@@ -658,8 +760,25 @@ var Channel = (props) => {
658
760
  transparentBackground = false,
659
761
  drawMode = "inverted"
660
762
  } = props;
661
- const canvasesRef = (0, import_react.useRef)([]);
662
- 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)(
663
782
  (canvas) => {
664
783
  if (canvas !== null) {
665
784
  const index2 = parseInt(canvas.dataset.index, 10);
@@ -668,12 +787,22 @@ var Channel = (props) => {
668
787
  },
669
788
  []
670
789
  );
671
- (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)(() => {
672
799
  const canvases = canvasesRef.current;
673
800
  const step = barWidth + barGap;
674
- let globalPixelOffset = 0;
675
801
  for (let i = 0; i < canvases.length; i++) {
676
802
  const canvas = canvases[i];
803
+ if (!canvas) continue;
804
+ const canvasIdx = parseInt(canvas.dataset.index, 10);
805
+ const globalPixelOffset = canvasIdx * MAX_CANVAS_WIDTH;
677
806
  const ctx = canvas.getContext("2d");
678
807
  const h2 = Math.floor(waveHeight / 2);
679
808
  const maxValue = 2 ** (bits - 1);
@@ -716,7 +845,6 @@ var Channel = (props) => {
716
845
  }
717
846
  }
718
847
  }
719
- globalPixelOffset += canvas.width / devicePixelRatio;
720
848
  }
721
849
  }, [
722
850
  data,
@@ -728,32 +856,29 @@ var Channel = (props) => {
728
856
  length,
729
857
  barWidth,
730
858
  barGap,
731
- drawMode
859
+ drawMode,
860
+ visibleChunkKey
732
861
  ]);
733
- let totalWidth = length;
734
- let waveformCount = 0;
735
- const waveforms = [];
736
- while (totalWidth > 0) {
737
- const currentWidth = Math.min(totalWidth, MAX_CANVAS_WIDTH);
738
- 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)(
739
866
  Waveform,
740
867
  {
741
868
  $cssWidth: currentWidth,
869
+ $left: chunkLeft,
742
870
  width: currentWidth * devicePixelRatio,
743
871
  height: waveHeight * devicePixelRatio,
744
872
  $waveHeight: waveHeight,
745
- "data-index": waveformCount,
873
+ "data-index": i,
746
874
  ref: canvasRef
747
875
  },
748
- `${length}-${waveformCount}`
876
+ `${length}-${i}`
749
877
  );
750
- waveforms.push(waveform);
751
- totalWidth -= currentWidth;
752
- waveformCount += 1;
753
- }
878
+ });
754
879
  const bgColor = waveFillColor;
755
880
  const backgroundCss = transparentBackground ? "transparent" : waveformColorToCss(bgColor);
756
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
881
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
757
882
  Wrapper,
758
883
  {
759
884
  $index: index,
@@ -766,6 +891,44 @@ var Channel = (props) => {
766
891
  );
767
892
  };
768
893
 
894
+ // src/components/ErrorBoundary.tsx
895
+ var import_react3 = __toESM(require("react"));
896
+ var import_jsx_runtime5 = require("react/jsx-runtime");
897
+ var errorContainerStyle = {
898
+ padding: "16px",
899
+ background: "#1a1a2e",
900
+ color: "#e0e0e0",
901
+ border: "1px solid #d08070",
902
+ borderRadius: "4px",
903
+ fontFamily: "monospace",
904
+ fontSize: "13px",
905
+ minHeight: "60px",
906
+ display: "flex",
907
+ alignItems: "center",
908
+ justifyContent: "center"
909
+ };
910
+ var PlaylistErrorBoundary = class extends import_react3.default.Component {
911
+ constructor(props) {
912
+ super(props);
913
+ this.state = { hasError: false, error: null };
914
+ }
915
+ static getDerivedStateFromError(error) {
916
+ return { hasError: true, error };
917
+ }
918
+ componentDidCatch(error, errorInfo) {
919
+ console.error("[waveform-playlist] Render error:", error, errorInfo.componentStack);
920
+ }
921
+ render() {
922
+ if (this.state.hasError) {
923
+ if (this.props.fallback) {
924
+ return this.props.fallback;
925
+ }
926
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: errorContainerStyle, children: "Waveform playlist encountered an error. Check console for details." });
927
+ }
928
+ return this.props.children;
929
+ }
930
+ };
931
+
769
932
  // src/components/Clip.tsx
770
933
  var import_styled_components13 = __toESM(require("styled-components"));
771
934
  var import_core = require("@dnd-kit/core");
@@ -773,7 +936,7 @@ var import_utilities = require("@dnd-kit/utilities");
773
936
 
774
937
  // src/components/ClipHeader.tsx
775
938
  var import_styled_components10 = __toESM(require("styled-components"));
776
- var import_jsx_runtime4 = require("react/jsx-runtime");
939
+ var import_jsx_runtime6 = require("react/jsx-runtime");
777
940
  var CLIP_HEADER_HEIGHT = 22;
778
941
  var HeaderContainer = import_styled_components10.default.div`
779
942
  position: relative;
@@ -813,27 +976,27 @@ var ClipHeaderPresentational = ({
813
976
  trackName,
814
977
  isSelected = false
815
978
  }) => {
816
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
979
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
817
980
  HeaderContainer,
818
981
  {
819
982
  $isDragging: false,
820
983
  $interactive: false,
821
984
  $isSelected: isSelected,
822
- children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(TrackName, { children: trackName })
985
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(TrackName, { children: trackName })
823
986
  }
824
987
  );
825
988
  };
826
989
  var ClipHeader = ({
827
990
  clipId,
828
- trackIndex,
829
- clipIndex,
991
+ trackIndex: _trackIndex,
992
+ clipIndex: _clipIndex,
830
993
  trackName,
831
994
  isSelected = false,
832
995
  disableDrag = false,
833
996
  dragHandleProps
834
997
  }) => {
835
998
  if (disableDrag || !dragHandleProps) {
836
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
999
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
837
1000
  ClipHeaderPresentational,
838
1001
  {
839
1002
  trackName,
@@ -842,7 +1005,7 @@ var ClipHeader = ({
842
1005
  );
843
1006
  }
844
1007
  const { attributes, listeners, setActivatorNodeRef } = dragHandleProps;
845
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1008
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
846
1009
  HeaderContainer,
847
1010
  {
848
1011
  ref: setActivatorNodeRef,
@@ -851,15 +1014,15 @@ var ClipHeader = ({
851
1014
  $isSelected: isSelected,
852
1015
  ...listeners,
853
1016
  ...attributes,
854
- children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(TrackName, { children: trackName })
1017
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(TrackName, { children: trackName })
855
1018
  }
856
1019
  );
857
1020
  };
858
1021
 
859
1022
  // src/components/ClipBoundary.tsx
860
- var import_react2 = __toESM(require("react"));
1023
+ var import_react4 = __toESM(require("react"));
861
1024
  var import_styled_components11 = __toESM(require("styled-components"));
862
- var import_jsx_runtime5 = require("react/jsx-runtime");
1025
+ var import_jsx_runtime7 = require("react/jsx-runtime");
863
1026
  var CLIP_BOUNDARY_WIDTH = 8;
864
1027
  var CLIP_BOUNDARY_WIDTH_TOUCH = 24;
865
1028
  var BoundaryContainer = import_styled_components11.default.div`
@@ -893,18 +1056,18 @@ var BoundaryContainer = import_styled_components11.default.div`
893
1056
  `;
894
1057
  var ClipBoundary = ({
895
1058
  clipId,
896
- trackIndex,
897
- clipIndex,
1059
+ trackIndex: _trackIndex,
1060
+ clipIndex: _clipIndex,
898
1061
  edge,
899
1062
  dragHandleProps,
900
1063
  touchOptimized = false
901
1064
  }) => {
902
- const [isHovered, setIsHovered] = import_react2.default.useState(false);
1065
+ const [isHovered, setIsHovered] = import_react4.default.useState(false);
903
1066
  if (!dragHandleProps) {
904
1067
  return null;
905
1068
  }
906
1069
  const { attributes, listeners, setActivatorNodeRef, isDragging } = dragHandleProps;
907
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1070
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
908
1071
  BoundaryContainer,
909
1072
  {
910
1073
  ref: setActivatorNodeRef,
@@ -924,7 +1087,7 @@ var ClipBoundary = ({
924
1087
 
925
1088
  // src/components/FadeOverlay.tsx
926
1089
  var import_styled_components12 = __toESM(require("styled-components"));
927
- var import_jsx_runtime6 = require("react/jsx-runtime");
1090
+ var import_jsx_runtime8 = require("react/jsx-runtime");
928
1091
  var FadeContainer = import_styled_components12.default.div.attrs((props) => ({
929
1092
  style: {
930
1093
  left: `${props.$left}px`,
@@ -981,7 +1144,7 @@ var FadeOverlay = ({
981
1144
  const theme = (0, import_styled_components12.useTheme)();
982
1145
  if (width < 1) return null;
983
1146
  const fillColor = color || theme?.fadeOverlayColor || "rgba(0, 0, 0, 0.4)";
984
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(FadeContainer, { $left: left, $width: width, $type: type, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(FadeSvg, { $type: type, viewBox: `0 0 ${width} 100`, preserveAspectRatio: "none", children: /* @__PURE__ */ (0, import_jsx_runtime6.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)(
985
1148
  "path",
986
1149
  {
987
1150
  d: generateFadePath(width, 100, curveType),
@@ -991,7 +1154,7 @@ var FadeOverlay = ({
991
1154
  };
992
1155
 
993
1156
  // src/components/Clip.tsx
994
- var import_jsx_runtime7 = require("react/jsx-runtime");
1157
+ var import_jsx_runtime9 = require("react/jsx-runtime");
995
1158
  var ClipContainer = import_styled_components13.default.div.attrs((props) => ({
996
1159
  style: props.$isOverlay ? {} : {
997
1160
  left: `${props.$left}px`,
@@ -1076,7 +1239,7 @@ var Clip = ({
1076
1239
  zIndex: isDragging ? 100 : void 0
1077
1240
  // Below controls (z-index: 999) but above other clips
1078
1241
  } : void 0;
1079
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1242
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1080
1243
  ClipContainer,
1081
1244
  {
1082
1245
  ref: setNodeRef,
@@ -1089,7 +1252,7 @@ var Clip = ({
1089
1252
  "data-track-id": trackId,
1090
1253
  onMouseDown,
1091
1254
  children: [
1092
- showHeader && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1255
+ showHeader && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1093
1256
  ClipHeader,
1094
1257
  {
1095
1258
  clipId,
@@ -1101,9 +1264,9 @@ var Clip = ({
1101
1264
  dragHandleProps: enableDrag ? { attributes, listeners, setActivatorNodeRef } : void 0
1102
1265
  }
1103
1266
  ),
1104
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(ChannelsWrapper, { $isOverlay: isOverlay, children: [
1267
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(ChannelsWrapper, { $isOverlay: isOverlay, children: [
1105
1268
  children,
1106
- showFades && fadeIn && fadeIn.duration > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1269
+ showFades && fadeIn && fadeIn.duration > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1107
1270
  FadeOverlay,
1108
1271
  {
1109
1272
  left: 0,
@@ -1112,7 +1275,7 @@ var Clip = ({
1112
1275
  curveType: fadeIn.type
1113
1276
  }
1114
1277
  ),
1115
- showFades && fadeOut && fadeOut.duration > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1278
+ showFades && fadeOut && fadeOut.duration > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1116
1279
  FadeOverlay,
1117
1280
  {
1118
1281
  left: width - Math.floor(fadeOut.duration * sampleRate / samplesPerPixel),
@@ -1122,8 +1285,8 @@ var Clip = ({
1122
1285
  }
1123
1286
  )
1124
1287
  ] }),
1125
- showHeader && !disableHeaderDrag && !isOverlay && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
1126
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1288
+ showHeader && !disableHeaderDrag && !isOverlay && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
1289
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1127
1290
  ClipBoundary,
1128
1291
  {
1129
1292
  clipId,
@@ -1139,7 +1302,7 @@ var Clip = ({
1139
1302
  }
1140
1303
  }
1141
1304
  ),
1142
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1305
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1143
1306
  ClipBoundary,
1144
1307
  {
1145
1308
  clipId,
@@ -1163,7 +1326,7 @@ var Clip = ({
1163
1326
 
1164
1327
  // src/components/MasterVolumeControl.tsx
1165
1328
  var import_styled_components14 = __toESM(require("styled-components"));
1166
- var import_jsx_runtime8 = require("react/jsx-runtime");
1329
+ var import_jsx_runtime10 = require("react/jsx-runtime");
1167
1330
  var VolumeContainer = import_styled_components14.default.div`
1168
1331
  display: inline-flex;
1169
1332
  align-items: center;
@@ -1185,9 +1348,9 @@ var MasterVolumeControl = ({
1185
1348
  const handleChange = (e) => {
1186
1349
  onChange(parseFloat(e.target.value) / 100);
1187
1350
  };
1188
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(VolumeContainer, { className, children: [
1189
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(VolumeLabel, { htmlFor: "master-gain", children: "Master Volume" }),
1190
- /* @__PURE__ */ (0, import_jsx_runtime8.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)(
1191
1354
  VolumeSlider,
1192
1355
  {
1193
1356
  min: "0",
@@ -1202,9 +1365,9 @@ var MasterVolumeControl = ({
1202
1365
  };
1203
1366
 
1204
1367
  // src/components/Playhead.tsx
1205
- var import_react3 = require("react");
1368
+ var import_react5 = require("react");
1206
1369
  var import_styled_components15 = __toESM(require("styled-components"));
1207
- var import_jsx_runtime9 = require("react/jsx-runtime");
1370
+ var import_jsx_runtime11 = require("react/jsx-runtime");
1208
1371
  var PlayheadLine = import_styled_components15.default.div.attrs((props) => ({
1209
1372
  style: {
1210
1373
  transform: `translate3d(${props.$position}px, 0, 0)`
@@ -1221,7 +1384,7 @@ var PlayheadLine = import_styled_components15.default.div.attrs((props) => ({
1221
1384
  will-change: transform;
1222
1385
  `;
1223
1386
  var Playhead = ({ position, color = "#ff0000" }) => {
1224
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(PlayheadLine, { $position: position, $color: color });
1387
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(PlayheadLine, { $position: position, $color: color });
1225
1388
  };
1226
1389
  var PlayheadWithMarkerContainer = import_styled_components15.default.div`
1227
1390
  position: absolute;
@@ -1261,9 +1424,9 @@ var PlayheadWithMarker = ({
1261
1424
  controlsOffset,
1262
1425
  getAudioContextTime
1263
1426
  }) => {
1264
- const containerRef = (0, import_react3.useRef)(null);
1265
- const animationFrameRef = (0, import_react3.useRef)(null);
1266
- (0, import_react3.useEffect)(() => {
1427
+ const containerRef = (0, import_react5.useRef)(null);
1428
+ const animationFrameRef = (0, import_react5.useRef)(null);
1429
+ (0, import_react5.useEffect)(() => {
1267
1430
  const updatePosition = () => {
1268
1431
  if (containerRef.current) {
1269
1432
  let time;
@@ -1292,22 +1455,23 @@ var PlayheadWithMarker = ({
1292
1455
  }
1293
1456
  };
1294
1457
  }, [isPlaying, sampleRate, samplesPerPixel, controlsOffset, currentTimeRef, playbackStartTimeRef, audioStartPositionRef, getAudioContextTime]);
1295
- (0, import_react3.useEffect)(() => {
1458
+ (0, import_react5.useEffect)(() => {
1296
1459
  if (!isPlaying && containerRef.current) {
1297
1460
  const time = currentTimeRef.current ?? 0;
1298
1461
  const pos = time * sampleRate / samplesPerPixel + controlsOffset;
1299
1462
  containerRef.current.style.transform = `translate3d(${pos}px, 0, 0)`;
1300
1463
  }
1301
1464
  });
1302
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(PlayheadWithMarkerContainer, { ref: containerRef, $color: color, children: [
1303
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(MarkerTriangle, { $color: color }),
1304
- /* @__PURE__ */ (0, import_jsx_runtime9.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 })
1305
1468
  ] });
1306
1469
  };
1307
1470
 
1308
1471
  // src/components/Playlist.tsx
1309
1472
  var import_styled_components16 = __toESM(require("styled-components"));
1310
- var import_jsx_runtime10 = require("react/jsx-runtime");
1473
+ var import_react6 = require("react");
1474
+ var import_jsx_runtime12 = require("react/jsx-runtime");
1311
1475
  var Wrapper2 = import_styled_components16.default.div`
1312
1476
  overflow-y: hidden;
1313
1477
  overflow-x: auto;
@@ -1361,16 +1525,21 @@ var Playlist = ({
1361
1525
  isSelecting,
1362
1526
  "data-playlist-state": playlistState
1363
1527
  }) => {
1364
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Wrapper2, { "data-scroll-container": "true", "data-playlist-state": playlistState, ref: scrollContainerRef, children: /* @__PURE__ */ (0, import_jsx_runtime10.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)(
1365
1534
  ScrollContainer,
1366
1535
  {
1367
1536
  $backgroundColor: backgroundColor,
1368
1537
  $width: scrollContainerWidth,
1369
1538
  children: [
1370
- timescale && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(TimescaleWrapper, { $width: timescaleWidth, $backgroundColor: timescaleBackgroundColor, children: timescale }),
1371
- /* @__PURE__ */ (0, import_jsx_runtime10.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: [
1372
1541
  children,
1373
- (onTracksClick || onTracksMouseDown) && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1542
+ (onTracksClick || onTracksMouseDown) && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1374
1543
  ClickOverlay,
1375
1544
  {
1376
1545
  $controlsWidth: controlsWidth,
@@ -1384,13 +1553,13 @@ var Playlist = ({
1384
1553
  ] })
1385
1554
  ]
1386
1555
  }
1387
- ) });
1556
+ ) }) });
1388
1557
  };
1389
1558
  var StyledPlaylist = (0, import_styled_components16.withTheme)(Playlist);
1390
1559
 
1391
1560
  // src/components/Selection.tsx
1392
1561
  var import_styled_components17 = __toESM(require("styled-components"));
1393
- var import_jsx_runtime11 = require("react/jsx-runtime");
1562
+ var import_jsx_runtime13 = require("react/jsx-runtime");
1394
1563
  var SelectionOverlay = import_styled_components17.default.div.attrs((props) => ({
1395
1564
  style: {
1396
1565
  left: `${props.$left}px`,
@@ -1414,13 +1583,13 @@ var Selection = ({
1414
1583
  if (width <= 0) {
1415
1584
  return null;
1416
1585
  }
1417
- return /* @__PURE__ */ (0, import_jsx_runtime11.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 });
1418
1587
  };
1419
1588
 
1420
1589
  // src/components/LoopRegion.tsx
1421
- var import_react4 = require("react");
1590
+ var import_react7 = require("react");
1422
1591
  var import_styled_components18 = __toESM(require("styled-components"));
1423
- var import_jsx_runtime12 = require("react/jsx-runtime");
1592
+ var import_jsx_runtime14 = require("react/jsx-runtime");
1424
1593
  var LoopRegionOverlayDiv = import_styled_components18.default.div.attrs((props) => ({
1425
1594
  style: {
1426
1595
  left: `${props.$left}px`,
@@ -1469,8 +1638,8 @@ var LoopRegion = ({
1469
1638
  if (width <= 0) {
1470
1639
  return null;
1471
1640
  }
1472
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
1473
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1641
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
1642
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1474
1643
  LoopRegionOverlayDiv,
1475
1644
  {
1476
1645
  $left: startPosition,
@@ -1479,7 +1648,7 @@ var LoopRegion = ({
1479
1648
  "data-loop-region": true
1480
1649
  }
1481
1650
  ),
1482
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1651
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1483
1652
  LoopMarker,
1484
1653
  {
1485
1654
  $left: startPosition,
@@ -1488,7 +1657,7 @@ var LoopRegion = ({
1488
1657
  "data-loop-marker": "start"
1489
1658
  }
1490
1659
  ),
1491
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1660
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1492
1661
  LoopMarker,
1493
1662
  {
1494
1663
  $left: endPosition - 2,
@@ -1569,12 +1738,12 @@ var LoopRegionMarkers = ({
1569
1738
  minPosition = 0,
1570
1739
  maxPosition = Infinity
1571
1740
  }) => {
1572
- const [draggingMarker, setDraggingMarker] = (0, import_react4.useState)(null);
1573
- const dragStartX = (0, import_react4.useRef)(0);
1574
- const dragStartPosition = (0, import_react4.useRef)(0);
1575
- const dragStartEnd = (0, import_react4.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);
1576
1745
  const width = Math.max(0, endPosition - startPosition);
1577
- const handleMarkerMouseDown = (0, import_react4.useCallback)((e, marker) => {
1746
+ const handleMarkerMouseDown = (0, import_react7.useCallback)((e, marker) => {
1578
1747
  e.preventDefault();
1579
1748
  e.stopPropagation();
1580
1749
  setDraggingMarker(marker);
@@ -1599,7 +1768,7 @@ var LoopRegionMarkers = ({
1599
1768
  document.addEventListener("mousemove", handleMouseMove);
1600
1769
  document.addEventListener("mouseup", handleMouseUp);
1601
1770
  }, [startPosition, endPosition, minPosition, maxPosition, onLoopStartChange, onLoopEndChange]);
1602
- const handleRegionMouseDown = (0, import_react4.useCallback)((e) => {
1771
+ const handleRegionMouseDown = (0, import_react7.useCallback)((e) => {
1603
1772
  e.preventDefault();
1604
1773
  e.stopPropagation();
1605
1774
  setDraggingMarker("region");
@@ -1632,8 +1801,8 @@ var LoopRegionMarkers = ({
1632
1801
  if (width <= 0) {
1633
1802
  return null;
1634
1803
  }
1635
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
1636
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1804
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
1805
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1637
1806
  TimescaleLoopShade,
1638
1807
  {
1639
1808
  $left: startPosition,
@@ -1644,7 +1813,7 @@ var LoopRegionMarkers = ({
1644
1813
  "data-loop-region-timescale": true
1645
1814
  }
1646
1815
  ),
1647
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1816
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1648
1817
  DraggableMarkerHandle,
1649
1818
  {
1650
1819
  $left: startPosition,
@@ -1655,7 +1824,7 @@ var LoopRegionMarkers = ({
1655
1824
  "data-loop-marker-handle": "start"
1656
1825
  }
1657
1826
  ),
1658
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1827
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1659
1828
  DraggableMarkerHandle,
1660
1829
  {
1661
1830
  $left: endPosition,
@@ -1690,11 +1859,11 @@ var TimescaleLoopRegion = ({
1690
1859
  maxPosition = Infinity,
1691
1860
  controlsOffset = 0
1692
1861
  }) => {
1693
- const [isCreating, setIsCreating] = (0, import_react4.useState)(false);
1694
- const createStartX = (0, import_react4.useRef)(0);
1695
- const containerRef = (0, import_react4.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);
1696
1865
  const hasLoopRegion = endPosition > startPosition;
1697
- const handleBackgroundMouseDown = (0, import_react4.useCallback)((e) => {
1866
+ const handleBackgroundMouseDown = (0, import_react7.useCallback)((e) => {
1698
1867
  const target = e.target;
1699
1868
  if (target.closest("[data-loop-marker-handle]") || target.closest("[data-loop-region-timescale]")) {
1700
1869
  return;
@@ -1722,14 +1891,14 @@ var TimescaleLoopRegion = ({
1722
1891
  document.addEventListener("mousemove", handleMouseMove);
1723
1892
  document.addEventListener("mouseup", handleMouseUp);
1724
1893
  }, [minPosition, maxPosition, onLoopRegionChange]);
1725
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1894
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1726
1895
  TimescaleLoopCreator,
1727
1896
  {
1728
1897
  ref: containerRef,
1729
1898
  $leftOffset: controlsOffset,
1730
1899
  onMouseDown: handleBackgroundMouseDown,
1731
1900
  "data-timescale-loop-creator": true,
1732
- children: hasLoopRegion && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1901
+ children: hasLoopRegion && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1733
1902
  LoopRegionMarkers,
1734
1903
  {
1735
1904
  startPosition,
@@ -1748,10 +1917,10 @@ var TimescaleLoopRegion = ({
1748
1917
  };
1749
1918
 
1750
1919
  // src/components/SelectionTimeInputs.tsx
1751
- var import_react6 = require("react");
1920
+ var import_react9 = require("react");
1752
1921
 
1753
1922
  // src/components/TimeInput.tsx
1754
- var import_react5 = require("react");
1923
+ var import_react8 = require("react");
1755
1924
 
1756
1925
  // src/utils/timeFormat.ts
1757
1926
  function clockFormat(seconds, decimals) {
@@ -1801,7 +1970,7 @@ function parseTime(timeStr, format) {
1801
1970
  }
1802
1971
 
1803
1972
  // src/components/TimeInput.tsx
1804
- var import_jsx_runtime13 = require("react/jsx-runtime");
1973
+ var import_jsx_runtime15 = require("react/jsx-runtime");
1805
1974
  var TimeInput = ({
1806
1975
  id,
1807
1976
  label,
@@ -1811,8 +1980,8 @@ var TimeInput = ({
1811
1980
  onChange,
1812
1981
  readOnly = false
1813
1982
  }) => {
1814
- const [displayValue, setDisplayValue] = (0, import_react5.useState)("");
1815
- (0, import_react5.useEffect)(() => {
1983
+ const [displayValue, setDisplayValue] = (0, import_react8.useState)("");
1984
+ (0, import_react8.useEffect)(() => {
1816
1985
  const formatted = formatTime(value, format);
1817
1986
  setDisplayValue(formatted);
1818
1987
  }, [value, format, id]);
@@ -1832,9 +2001,9 @@ var TimeInput = ({
1832
2001
  e.currentTarget.blur();
1833
2002
  }
1834
2003
  };
1835
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
1836
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ScreenReaderOnly, { as: "label", htmlFor: id, children: label }),
1837
- /* @__PURE__ */ (0, import_jsx_runtime13.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)(
1838
2007
  BaseInput,
1839
2008
  {
1840
2009
  type: "text",
@@ -1851,15 +2020,15 @@ var TimeInput = ({
1851
2020
  };
1852
2021
 
1853
2022
  // src/components/SelectionTimeInputs.tsx
1854
- var import_jsx_runtime14 = require("react/jsx-runtime");
2023
+ var import_jsx_runtime16 = require("react/jsx-runtime");
1855
2024
  var SelectionTimeInputs = ({
1856
2025
  selectionStart,
1857
2026
  selectionEnd,
1858
2027
  onSelectionChange,
1859
2028
  className
1860
2029
  }) => {
1861
- const [timeFormat, setTimeFormat] = (0, import_react6.useState)("hh:mm:ss.uuu");
1862
- (0, import_react6.useEffect)(() => {
2030
+ const [timeFormat, setTimeFormat] = (0, import_react9.useState)("hh:mm:ss.uuu");
2031
+ (0, import_react9.useEffect)(() => {
1863
2032
  const timeFormatSelect = document.querySelector(".time-format");
1864
2033
  const handleFormatChange = () => {
1865
2034
  if (timeFormatSelect) {
@@ -1884,8 +2053,8 @@ var SelectionTimeInputs = ({
1884
2053
  onSelectionChange(selectionStart, value);
1885
2054
  }
1886
2055
  };
1887
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
1888
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2056
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className, children: [
2057
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
1889
2058
  TimeInput,
1890
2059
  {
1891
2060
  id: "audio_start",
@@ -1896,7 +2065,7 @@ var SelectionTimeInputs = ({
1896
2065
  onChange: handleStartChange
1897
2066
  }
1898
2067
  ),
1899
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2068
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
1900
2069
  TimeInput,
1901
2070
  {
1902
2071
  id: "audio_end",
@@ -1911,14 +2080,14 @@ var SelectionTimeInputs = ({
1911
2080
  };
1912
2081
 
1913
2082
  // src/contexts/DevicePixelRatio.tsx
1914
- var import_react7 = require("react");
1915
- var import_jsx_runtime15 = require("react/jsx-runtime");
2083
+ var import_react10 = require("react");
2084
+ var import_jsx_runtime17 = require("react/jsx-runtime");
1916
2085
  function getScale() {
1917
2086
  return window.devicePixelRatio;
1918
2087
  }
1919
- var DevicePixelRatioContext = (0, import_react7.createContext)(getScale());
2088
+ var DevicePixelRatioContext = (0, import_react10.createContext)(getScale());
1920
2089
  var DevicePixelRatioProvider = ({ children }) => {
1921
- const [scale, setScale] = (0, import_react7.useState)(getScale());
2090
+ const [scale, setScale] = (0, import_react10.useState)(getScale());
1922
2091
  matchMedia(`(resolution: ${getScale()}dppx)`).addEventListener(
1923
2092
  "change",
1924
2093
  () => {
@@ -1926,13 +2095,13 @@ var DevicePixelRatioProvider = ({ children }) => {
1926
2095
  },
1927
2096
  { once: true }
1928
2097
  );
1929
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(DevicePixelRatioContext.Provider, { value: Math.ceil(scale), children });
2098
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DevicePixelRatioContext.Provider, { value: Math.ceil(scale), children });
1930
2099
  };
1931
- var useDevicePixelRatio = () => (0, import_react7.useContext)(DevicePixelRatioContext);
2100
+ var useDevicePixelRatio = () => (0, import_react10.useContext)(DevicePixelRatioContext);
1932
2101
 
1933
2102
  // src/contexts/PlaylistInfo.tsx
1934
- var import_react8 = require("react");
1935
- var PlaylistInfoContext = (0, import_react8.createContext)({
2103
+ var import_react11 = require("react");
2104
+ var PlaylistInfoContext = (0, import_react11.createContext)({
1936
2105
  sampleRate: 48e3,
1937
2106
  samplesPerPixel: 1e3,
1938
2107
  zoomLevels: [1e3, 1500, 2e3, 2500],
@@ -1946,22 +2115,22 @@ var PlaylistInfoContext = (0, import_react8.createContext)({
1946
2115
  barWidth: 1,
1947
2116
  barGap: 0
1948
2117
  });
1949
- var usePlaylistInfo = () => (0, import_react8.useContext)(PlaylistInfoContext);
2118
+ var usePlaylistInfo = () => (0, import_react11.useContext)(PlaylistInfoContext);
1950
2119
 
1951
2120
  // src/contexts/Theme.tsx
1952
- var import_react9 = require("react");
2121
+ var import_react12 = require("react");
1953
2122
  var import_styled_components19 = require("styled-components");
1954
- var useTheme2 = () => (0, import_react9.useContext)(import_styled_components19.ThemeContext);
2123
+ var useTheme2 = () => (0, import_react12.useContext)(import_styled_components19.ThemeContext);
1955
2124
 
1956
2125
  // src/contexts/TrackControls.tsx
1957
- var import_react10 = require("react");
1958
- var import_jsx_runtime16 = require("react/jsx-runtime");
1959
- var TrackControlsContext = (0, import_react10.createContext)(/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_react10.Fragment, {}));
1960
- var useTrackControls = () => (0, import_react10.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);
1961
2130
 
1962
2131
  // src/contexts/Playout.tsx
1963
- var import_react11 = require("react");
1964
- var import_jsx_runtime17 = require("react/jsx-runtime");
2132
+ var import_react14 = require("react");
2133
+ var import_jsx_runtime19 = require("react/jsx-runtime");
1965
2134
  var defaultProgress = 0;
1966
2135
  var defaultIsPlaying = false;
1967
2136
  var defaultSelectionStart = 0;
@@ -1972,8 +2141,8 @@ var defaultPlayout = {
1972
2141
  selectionStart: defaultSelectionStart,
1973
2142
  selectionEnd: defaultSelectionEnd
1974
2143
  };
1975
- var PlayoutStatusContext = (0, import_react11.createContext)(defaultPlayout);
1976
- var PlayoutStatusUpdateContext = (0, import_react11.createContext)({
2144
+ var PlayoutStatusContext = (0, import_react14.createContext)(defaultPlayout);
2145
+ var PlayoutStatusUpdateContext = (0, import_react14.createContext)({
1977
2146
  setIsPlaying: () => {
1978
2147
  },
1979
2148
  setProgress: () => {
@@ -1982,24 +2151,24 @@ var PlayoutStatusUpdateContext = (0, import_react11.createContext)({
1982
2151
  }
1983
2152
  });
1984
2153
  var PlayoutProvider = ({ children }) => {
1985
- const [isPlaying, setIsPlaying] = (0, import_react11.useState)(defaultIsPlaying);
1986
- const [progress, setProgress] = (0, import_react11.useState)(defaultProgress);
1987
- const [selectionStart, setSelectionStart] = (0, import_react11.useState)(defaultSelectionStart);
1988
- const [selectionEnd, setSelectionEnd] = (0, import_react11.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);
1989
2158
  const setSelection = (start, end) => {
1990
2159
  setSelectionStart(start);
1991
2160
  setSelectionEnd(end);
1992
2161
  };
1993
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(PlayoutStatusUpdateContext.Provider, { value: { setIsPlaying, setProgress, setSelection }, children: /* @__PURE__ */ (0, import_jsx_runtime17.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 }) });
1994
2163
  };
1995
- var usePlayoutStatus = () => (0, import_react11.useContext)(PlayoutStatusContext);
1996
- var usePlayoutStatusUpdate = () => (0, import_react11.useContext)(PlayoutStatusUpdateContext);
2164
+ var usePlayoutStatus = () => (0, import_react14.useContext)(PlayoutStatusContext);
2165
+ var usePlayoutStatusUpdate = () => (0, import_react14.useContext)(PlayoutStatusUpdateContext);
1997
2166
 
1998
2167
  // src/components/SpectrogramChannel.tsx
1999
- var import_react12 = require("react");
2168
+ var import_react15 = require("react");
2000
2169
  var import_styled_components20 = __toESM(require("styled-components"));
2001
- var import_jsx_runtime18 = require("react/jsx-runtime");
2002
- 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);
2003
2172
  var Wrapper3 = import_styled_components20.default.div.attrs((props) => ({
2004
2173
  style: {
2005
2174
  top: `${props.$waveHeight * props.$index}px`,
@@ -2015,11 +2184,13 @@ var Wrapper3 = import_styled_components20.default.div.attrs((props) => ({
2015
2184
  var SpectrogramCanvas = import_styled_components20.default.canvas.attrs((props) => ({
2016
2185
  style: {
2017
2186
  width: `${props.$cssWidth}px`,
2018
- height: `${props.$waveHeight}px`
2187
+ height: `${props.$waveHeight}px`,
2188
+ left: `${props.$left}px`
2019
2189
  }
2020
2190
  }))`
2021
- float: left;
2022
- position: relative;
2191
+ position: absolute;
2192
+ top: 0;
2193
+ /* Promote to own compositing layer for smoother scrolling */
2023
2194
  will-change: transform;
2024
2195
  image-rendering: pixelated;
2025
2196
  image-rendering: crisp-edges;
@@ -2031,6 +2202,7 @@ function defaultGetColorMap() {
2031
2202
  }
2032
2203
  return lut;
2033
2204
  }
2205
+ var DEFAULT_COLOR_LUT = defaultGetColorMap();
2034
2206
  var SpectrogramChannel = ({
2035
2207
  index,
2036
2208
  channelIndex: channelIndexProp,
@@ -2048,11 +2220,30 @@ var SpectrogramChannel = ({
2048
2220
  onCanvasesReady
2049
2221
  }) => {
2050
2222
  const channelIndex = channelIndexProp ?? index;
2051
- const canvasesRef = (0, import_react12.useRef)([]);
2052
- const registeredIdsRef = (0, import_react12.useRef)([]);
2053
- const transferredCanvasesRef = (0, import_react12.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);
2054
2228
  const isWorkerMode = !!(workerApi && clipId);
2055
- const canvasRef = (0, import_react12.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)(
2056
2247
  (canvas) => {
2057
2248
  if (canvas !== null) {
2058
2249
  const idx = parseInt(canvas.dataset.index, 10);
@@ -2061,53 +2252,101 @@ var SpectrogramChannel = ({
2061
2252
  },
2062
2253
  []
2063
2254
  );
2064
- (0, import_react12.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)(() => {
2065
2266
  if (!isWorkerMode) return;
2066
- const canvasCount2 = Math.ceil(length / MAX_CANVAS_WIDTH2);
2067
- canvasesRef.current.length = canvasCount2;
2267
+ const currentWorkerApi = workerApiRef.current;
2268
+ if (!currentWorkerApi || !clipId) return;
2068
2269
  const canvases2 = canvasesRef.current;
2069
- const ids = [];
2070
- const widths = [];
2270
+ const newIds = [];
2271
+ const newWidths = [];
2071
2272
  for (let i = 0; i < canvases2.length; i++) {
2072
2273
  const canvas = canvases2[i];
2073
2274
  if (!canvas) continue;
2074
2275
  if (transferredCanvasesRef.current.has(canvas)) continue;
2075
- 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;
2076
2279
  try {
2077
- const offscreen = canvas.transferControlToOffscreen();
2078
- workerApi.registerCanvas(canvasId, offscreen);
2079
- transferredCanvasesRef.current.add(canvas);
2080
- ids.push(canvasId);
2081
- widths.push(Math.min(length - i * MAX_CANVAS_WIDTH2, MAX_CANVAS_WIDTH2));
2280
+ offscreen = canvas.transferControlToOffscreen();
2082
2281
  } catch (err) {
2083
2282
  console.warn(`[spectrogram] transferControlToOffscreen failed for ${canvasId}:`, err);
2084
2283
  continue;
2085
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
+ }
2086
2294
  }
2087
- registeredIdsRef.current = ids;
2088
- if (ids.length > 0 && onCanvasesReady) {
2089
- onCanvasesReady(ids, widths);
2295
+ if (newIds.length > 0) {
2296
+ registeredIdsRef.current = [...registeredIdsRef.current, ...newIds];
2297
+ onCanvasesReadyRef.current?.(newIds, newWidths);
2090
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)(() => {
2091
2326
  return () => {
2327
+ const api = workerApiRef.current;
2328
+ if (!api) return;
2092
2329
  for (const id of registeredIdsRef.current) {
2093
- workerApi.unregisterCanvas(id);
2330
+ try {
2331
+ api.unregisterCanvas(id);
2332
+ } catch (err) {
2333
+ console.warn(`[spectrogram] unregisterCanvas failed for ${id}:`, err);
2334
+ }
2094
2335
  }
2095
2336
  registeredIdsRef.current = [];
2096
2337
  };
2097
- }, [isWorkerMode, clipId, channelIndex, length]);
2098
- const lut = colorLUT ?? defaultGetColorMap();
2099
- const maxF = maxFrequency ?? (data ? data.sampleRate / 2 : 22050);
2100
- const scaleFn = frequencyScaleFn ?? ((f, minF, maxF2) => (f - minF) / (maxF2 - minF));
2101
- (0, import_react12.useLayoutEffect)(() => {
2338
+ }, []);
2339
+ (0, import_react15.useLayoutEffect)(() => {
2102
2340
  if (isWorkerMode || !data) return;
2103
2341
  const canvases2 = canvasesRef.current;
2104
2342
  const { frequencyBinCount, frameCount, hopSize, sampleRate, gainDb, rangeDb: rawRangeDb } = data;
2105
2343
  const rangeDb = rawRangeDb === 0 ? 1 : rawRangeDb;
2106
- let globalPixelOffset = 0;
2107
2344
  const binToFreq = (bin) => bin / frequencyBinCount * (sampleRate / 2);
2108
- for (let canvasIdx = 0; canvasIdx < canvases2.length; canvasIdx++) {
2109
- const canvas = canvases2[canvasIdx];
2345
+ for (let i = 0; i < canvases2.length; i++) {
2346
+ const canvas = canvases2[i];
2110
2347
  if (!canvas) continue;
2348
+ const canvasIdx = parseInt(canvas.dataset.index, 10);
2349
+ const globalPixelOffset = canvasIdx * MAX_CANVAS_WIDTH;
2111
2350
  const ctx = canvas.getContext("2d");
2112
2351
  if (!ctx) continue;
2113
2352
  const canvasWidth = canvas.width / devicePixelRatio;
@@ -2127,7 +2366,7 @@ var SpectrogramChannel = ({
2127
2366
  for (let y = 0; y < canvasHeight; y++) {
2128
2367
  const normalizedY = 1 - y / canvasHeight;
2129
2368
  let bin = Math.floor(normalizedY * frequencyBinCount);
2130
- if (frequencyScaleFn) {
2369
+ if (hasCustomFrequencyScale) {
2131
2370
  let lo = 0;
2132
2371
  let hi = frequencyBinCount - 1;
2133
2372
  while (lo < hi) {
@@ -2166,36 +2405,30 @@ var SpectrogramChannel = ({
2166
2405
  ctx.imageSmoothingEnabled = false;
2167
2406
  ctx.drawImage(tmpCanvas, 0, 0, canvas.width, canvas.height);
2168
2407
  }
2169
- globalPixelOffset += canvasWidth;
2170
2408
  }
2171
- }, [isWorkerMode, data, length, waveHeight, devicePixelRatio, samplesPerPixel, lut, frequencyScaleFn, minFrequency, maxF, scaleFn]);
2172
- let totalWidth = length;
2173
- let canvasCount = 0;
2174
- const canvases = [];
2175
- while (totalWidth > 0) {
2176
- const currentWidth = Math.min(totalWidth, MAX_CANVAS_WIDTH2);
2177
- canvases.push(
2178
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2179
- SpectrogramCanvas,
2180
- {
2181
- $cssWidth: currentWidth,
2182
- width: currentWidth * devicePixelRatio,
2183
- height: waveHeight * devicePixelRatio,
2184
- $waveHeight: waveHeight,
2185
- "data-index": canvasCount,
2186
- ref: canvasRef
2187
- },
2188
- `${length}-${canvasCount}`
2189
- )
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}`
2190
2425
  );
2191
- totalWidth -= currentWidth;
2192
- canvasCount++;
2193
- }
2194
- return /* @__PURE__ */ (0, import_jsx_runtime18.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 });
2195
2428
  };
2196
2429
 
2197
2430
  // src/components/SmartChannel.tsx
2198
- var import_jsx_runtime19 = require("react/jsx-runtime");
2431
+ var import_jsx_runtime21 = require("react/jsx-runtime");
2199
2432
  var SmartChannel = ({
2200
2433
  isSelected,
2201
2434
  transparentBackground,
@@ -2220,7 +2453,7 @@ var SmartChannel = ({
2220
2453
  const drawMode = theme?.waveformDrawMode || "inverted";
2221
2454
  const hasSpectrogram = spectrogramData || spectrogramWorkerApi;
2222
2455
  if (renderMode === "spectrogram" && hasSpectrogram) {
2223
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2456
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2224
2457
  SpectrogramChannel,
2225
2458
  {
2226
2459
  index: props.index,
@@ -2241,8 +2474,8 @@ var SmartChannel = ({
2241
2474
  }
2242
2475
  if (renderMode === "both" && hasSpectrogram) {
2243
2476
  const halfHeight = Math.floor(waveHeight / 2);
2244
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_jsx_runtime19.Fragment, { children: [
2245
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2477
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
2478
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2246
2479
  SpectrogramChannel,
2247
2480
  {
2248
2481
  index: props.index * 2,
@@ -2261,11 +2494,10 @@ var SmartChannel = ({
2261
2494
  onCanvasesReady: spectrogramOnCanvasesReady
2262
2495
  }
2263
2496
  ),
2264
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { style: { position: "absolute", top: (props.index * 2 + 1) * halfHeight, width: props.length, height: halfHeight }, children: /* @__PURE__ */ (0, import_jsx_runtime19.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)(
2265
2498
  Channel,
2266
2499
  {
2267
2500
  ...props,
2268
- ...theme,
2269
2501
  index: 0,
2270
2502
  waveOutlineColor,
2271
2503
  waveFillColor,
@@ -2279,11 +2511,10 @@ var SmartChannel = ({
2279
2511
  ) })
2280
2512
  ] });
2281
2513
  }
2282
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2514
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2283
2515
  Channel,
2284
2516
  {
2285
2517
  ...props,
2286
- ...theme,
2287
2518
  waveOutlineColor,
2288
2519
  waveFillColor,
2289
2520
  waveHeight,
@@ -2297,9 +2528,9 @@ var SmartChannel = ({
2297
2528
  };
2298
2529
 
2299
2530
  // src/components/SpectrogramLabels.tsx
2300
- var import_react13 = require("react");
2531
+ var import_react16 = require("react");
2301
2532
  var import_styled_components21 = __toESM(require("styled-components"));
2302
- var import_jsx_runtime20 = require("react/jsx-runtime");
2533
+ var import_jsx_runtime22 = require("react/jsx-runtime");
2303
2534
  var LABELS_WIDTH = 72;
2304
2535
  var LabelsStickyWrapper = import_styled_components21.default.div`
2305
2536
  position: sticky;
@@ -2349,12 +2580,12 @@ var SpectrogramLabels = ({
2349
2580
  renderMode = "spectrogram",
2350
2581
  hasClipHeaders = false
2351
2582
  }) => {
2352
- const canvasRef = (0, import_react13.useRef)(null);
2583
+ const canvasRef = (0, import_react16.useRef)(null);
2353
2584
  const devicePixelRatio = useDevicePixelRatio();
2354
2585
  const spectrogramHeight = renderMode === "both" ? Math.floor(waveHeight / 2) : waveHeight;
2355
2586
  const totalHeight = numChannels * waveHeight;
2356
2587
  const clipHeaderOffset = hasClipHeaders ? 22 : 0;
2357
- (0, import_react13.useLayoutEffect)(() => {
2588
+ (0, import_react16.useLayoutEffect)(() => {
2358
2589
  const canvas = canvasRef.current;
2359
2590
  if (!canvas) return;
2360
2591
  const ctx = canvas.getContext("2d");
@@ -2381,7 +2612,7 @@ var SpectrogramLabels = ({
2381
2612
  }
2382
2613
  }
2383
2614
  }, [waveHeight, numChannels, frequencyScaleFn, minFrequency, maxFrequency, labelsColor, labelsBackground, devicePixelRatio, spectrogramHeight, clipHeaderOffset]);
2384
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(LabelsStickyWrapper, { $height: totalHeight + clipHeaderOffset, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
2615
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(LabelsStickyWrapper, { $height: totalHeight + clipHeaderOffset, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2385
2616
  "canvas",
2386
2617
  {
2387
2618
  ref: canvasRef,
@@ -2397,10 +2628,10 @@ var SpectrogramLabels = ({
2397
2628
  };
2398
2629
 
2399
2630
  // src/components/SmartScale.tsx
2400
- var import_react15 = require("react");
2631
+ var import_react18 = require("react");
2401
2632
 
2402
2633
  // src/components/TimeScale.tsx
2403
- var import_react14 = __toESM(require("react"));
2634
+ var import_react17 = __toESM(require("react"));
2404
2635
  var import_styled_components22 = __toESM(require("styled-components"));
2405
2636
 
2406
2637
  // src/utils/conversions.ts
@@ -2424,7 +2655,7 @@ function secondsToPixels(seconds, samplesPerPixel, sampleRate) {
2424
2655
  }
2425
2656
 
2426
2657
  // src/components/TimeScale.tsx
2427
- var import_jsx_runtime21 = require("react/jsx-runtime");
2658
+ var import_jsx_runtime23 = require("react/jsx-runtime");
2428
2659
  function formatTime2(milliseconds) {
2429
2660
  const seconds = Math.floor(milliseconds / 1e3);
2430
2661
  const s = seconds % 60;
@@ -2443,16 +2674,17 @@ var PlaylistTimeScaleScroll = import_styled_components22.default.div.attrs((prop
2443
2674
  border-bottom: 1px solid ${(props) => props.theme.timeColor};
2444
2675
  box-sizing: border-box;
2445
2676
  `;
2446
- var TimeTicks = import_styled_components22.default.canvas.attrs((props) => ({
2677
+ var TimeTickChunk = import_styled_components22.default.canvas.attrs((props) => ({
2447
2678
  style: {
2448
2679
  width: `${props.$cssWidth}px`,
2449
- height: `${props.$timeScaleHeight}px`
2680
+ height: `${props.$timeScaleHeight}px`,
2681
+ left: `${props.$left}px`
2450
2682
  }
2451
2683
  }))`
2452
2684
  position: absolute;
2453
- left: 0;
2454
- right: 0;
2455
2685
  bottom: 0;
2686
+ /* Promote to own compositing layer for smoother scrolling */
2687
+ will-change: transform;
2456
2688
  `;
2457
2689
  var TimeStamp = import_styled_components22.default.div.attrs((props) => ({
2458
2690
  style: {
@@ -2474,78 +2706,120 @@ var TimeScale = (props) => {
2474
2706
  secondStep,
2475
2707
  renderTimestamp
2476
2708
  } = props;
2477
- const canvasInfo = /* @__PURE__ */ new Map();
2478
- const timeMarkers = [];
2479
- const canvasRef = (0, import_react14.useRef)(null);
2709
+ const canvasRefsMap = (0, import_react17.useRef)(/* @__PURE__ */ new Map());
2480
2710
  const {
2481
2711
  sampleRate,
2482
2712
  samplesPerPixel,
2483
2713
  timeScaleHeight,
2484
2714
  controls: { show: showControls, width: controlWidth }
2485
- } = (0, import_react14.useContext)(PlaylistInfoContext);
2715
+ } = (0, import_react17.useContext)(PlaylistInfoContext);
2486
2716
  const devicePixelRatio = useDevicePixelRatio();
2487
- (0, import_react14.useEffect)(() => {
2488
- if (canvasRef.current !== null) {
2489
- const canvas = canvasRef.current;
2490
- const ctx = canvas.getContext("2d");
2491
- if (ctx) {
2492
- ctx.resetTransform();
2493
- ctx.clearRect(0, 0, canvas.width, canvas.height);
2494
- ctx.imageSmoothingEnabled = false;
2495
- ctx.fillStyle = timeColor;
2496
- ctx.scale(devicePixelRatio, devicePixelRatio);
2497
- for (const [pixLeft, scaleHeight] of canvasInfo.entries()) {
2498
- const scaleY = timeScaleHeight - scaleHeight;
2499
- 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;
2500
2760
  }
2501
2761
  }
2762
+ indices.push(i);
2502
2763
  }
2503
- }, [
2504
- duration,
2505
- devicePixelRatio,
2506
- timeColor,
2507
- timeScaleHeight,
2508
- bigStep,
2509
- secondStep,
2510
- marker,
2511
- canvasInfo
2512
- ]);
2513
- const widthX = secondsToPixels(duration / 1e3, samplesPerPixel, sampleRate);
2514
- const pixPerSec = sampleRate / samplesPerPixel;
2515
- let counter = 0;
2516
- for (let i = 0; i < widthX; i += pixPerSec * secondStep / 1e3) {
2517
- const pix = Math.floor(i);
2518
- if (counter % marker === 0) {
2519
- const timeMs = counter;
2520
- const timestamp = formatTime2(timeMs);
2521
- const timestampContent = renderTimestamp ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_react14.default.Fragment, { children: renderTimestamp(timeMs, pix) }, `timestamp-${counter}`) : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TimeStamp, { $left: pix, children: timestamp }, timestamp);
2522
- timeMarkers.push(timestampContent);
2523
- canvasInfo.set(pix, timeScaleHeight);
2524
- } else if (counter % bigStep === 0) {
2525
- canvasInfo.set(pix, Math.floor(timeScaleHeight / 2));
2526
- } else if (counter % secondStep === 0) {
2527
- 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
+ }
2528
2793
  }
2529
- counter += secondStep;
2530
- }
2531
- return /* @__PURE__ */ (0, import_jsx_runtime21.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)(
2532
2815
  PlaylistTimeScaleScroll,
2533
2816
  {
2534
2817
  $cssWidth: widthX,
2535
2818
  $controlWidth: showControls ? controlWidth : 0,
2536
2819
  $timeScaleHeight: timeScaleHeight,
2537
2820
  children: [
2538
- timeMarkers,
2539
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2540
- TimeTicks,
2541
- {
2542
- $cssWidth: widthX,
2543
- $timeScaleHeight: timeScaleHeight,
2544
- width: widthX * devicePixelRatio,
2545
- height: timeScaleHeight * devicePixelRatio,
2546
- ref: canvasRef
2547
- }
2548
- )
2821
+ visibleMarkers,
2822
+ visibleChunks
2549
2823
  ]
2550
2824
  }
2551
2825
  );
@@ -2553,7 +2827,7 @@ var TimeScale = (props) => {
2553
2827
  var StyledTimeScale = (0, import_styled_components22.withTheme)(TimeScale);
2554
2828
 
2555
2829
  // src/components/SmartScale.tsx
2556
- var import_jsx_runtime22 = require("react/jsx-runtime");
2830
+ var import_jsx_runtime24 = require("react/jsx-runtime");
2557
2831
  var timeinfo = /* @__PURE__ */ new Map([
2558
2832
  [
2559
2833
  700,
@@ -2627,9 +2901,9 @@ function getScaleInfo(samplesPerPixel) {
2627
2901
  return config;
2628
2902
  }
2629
2903
  var SmartScale = ({ renderTimestamp }) => {
2630
- const { samplesPerPixel, duration } = (0, import_react15.useContext)(PlaylistInfoContext);
2904
+ const { samplesPerPixel, duration } = (0, import_react18.useContext)(PlaylistInfoContext);
2631
2905
  let config = getScaleInfo(samplesPerPixel);
2632
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2906
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2633
2907
  StyledTimeScale,
2634
2908
  {
2635
2909
  marker: config.marker,
@@ -2643,7 +2917,7 @@ var SmartScale = ({ renderTimestamp }) => {
2643
2917
 
2644
2918
  // src/components/TimeFormatSelect.tsx
2645
2919
  var import_styled_components23 = __toESM(require("styled-components"));
2646
- var import_jsx_runtime23 = require("react/jsx-runtime");
2920
+ var import_jsx_runtime25 = require("react/jsx-runtime");
2647
2921
  var SelectWrapper = import_styled_components23.default.div`
2648
2922
  display: inline-flex;
2649
2923
  align-items: center;
@@ -2666,7 +2940,7 @@ var TimeFormatSelect = ({
2666
2940
  const handleChange = (e) => {
2667
2941
  onChange(e.target.value);
2668
2942
  };
2669
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(SelectWrapper, { className, children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2943
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(SelectWrapper, { className, children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
2670
2944
  BaseSelect,
2671
2945
  {
2672
2946
  className: "time-format",
@@ -2674,14 +2948,14 @@ var TimeFormatSelect = ({
2674
2948
  onChange: handleChange,
2675
2949
  disabled,
2676
2950
  "aria-label": "Time format selection",
2677
- children: TIME_FORMAT_OPTIONS.map((option) => /* @__PURE__ */ (0, import_jsx_runtime23.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))
2678
2952
  }
2679
2953
  ) });
2680
2954
  };
2681
2955
 
2682
2956
  // src/components/Track.tsx
2683
2957
  var import_styled_components24 = __toESM(require("styled-components"));
2684
- var import_jsx_runtime24 = require("react/jsx-runtime");
2958
+ var import_jsx_runtime26 = require("react/jsx-runtime");
2685
2959
  var Container = import_styled_components24.default.div.attrs((props) => ({
2686
2960
  style: {
2687
2961
  height: `${props.$waveHeight * props.$numChannels + (props.$hasClipHeaders ? CLIP_HEADER_HEIGHT : 0)}px`
@@ -2736,7 +3010,7 @@ var Track = ({
2736
3010
  controls: { show, width: controlWidth }
2737
3011
  } = usePlaylistInfo();
2738
3012
  const controls = useTrackControls();
2739
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
3013
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
2740
3014
  Container,
2741
3015
  {
2742
3016
  $numChannels: numChannels,
@@ -2747,7 +3021,7 @@ var Track = ({
2747
3021
  $hasClipHeaders: hasClipHeaders,
2748
3022
  $isSelected: isSelected,
2749
3023
  children: [
2750
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
3024
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2751
3025
  ControlsWrapper,
2752
3026
  {
2753
3027
  $controlWidth: show ? controlWidth : 0,
@@ -2755,7 +3029,7 @@ var Track = ({
2755
3029
  children: controls
2756
3030
  }
2757
3031
  ),
2758
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
3032
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2759
3033
  ChannelContainer,
2760
3034
  {
2761
3035
  $controlWidth: show ? controlWidth : 0,
@@ -2862,8 +3136,8 @@ var ButtonGroup = import_styled_components26.default.div`
2862
3136
 
2863
3137
  // src/components/TrackControls/CloseButton.tsx
2864
3138
  var import_styled_components27 = __toESM(require("styled-components"));
2865
- var import_react16 = require("@phosphor-icons/react");
2866
- var import_jsx_runtime25 = require("react/jsx-runtime");
3139
+ var import_react19 = require("@phosphor-icons/react");
3140
+ var import_jsx_runtime27 = require("react/jsx-runtime");
2867
3141
  var StyledCloseButton = import_styled_components27.default.button`
2868
3142
  position: absolute;
2869
3143
  left: 0;
@@ -2888,7 +3162,7 @@ var StyledCloseButton = import_styled_components27.default.button`
2888
3162
  var CloseButton = ({
2889
3163
  onClick,
2890
3164
  title = "Remove track"
2891
- }) => /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(StyledCloseButton, { onClick, title, children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_react16.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" }) });
2892
3166
 
2893
3167
  // src/components/TrackControls/Controls.tsx
2894
3168
  var import_styled_components28 = __toESM(require("styled-components"));
@@ -2923,24 +3197,24 @@ var Header = import_styled_components29.default.header`
2923
3197
  `;
2924
3198
 
2925
3199
  // src/components/TrackControls/VolumeDownIcon.tsx
2926
- var import_react17 = require("@phosphor-icons/react");
2927
- var import_jsx_runtime26 = require("react/jsx-runtime");
2928
- var VolumeDownIcon = (props) => /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_react17.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 });
2929
3203
 
2930
3204
  // src/components/TrackControls/VolumeUpIcon.tsx
2931
- var import_react18 = require("@phosphor-icons/react");
2932
- var import_jsx_runtime27 = require("react/jsx-runtime");
2933
- var VolumeUpIcon = (props) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react18.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 });
2934
3208
 
2935
3209
  // src/components/TrackControls/TrashIcon.tsx
2936
- var import_react19 = require("@phosphor-icons/react");
2937
- var import_jsx_runtime28 = require("react/jsx-runtime");
2938
- var TrashIcon = (props) => /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_react19.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 });
2939
3213
 
2940
3214
  // src/components/TrackControls/DotsIcon.tsx
2941
- var import_react20 = require("@phosphor-icons/react");
2942
- var import_jsx_runtime29 = require("react/jsx-runtime");
2943
- var DotsIcon = (props) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_react20.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 });
2944
3218
 
2945
3219
  // src/components/TrackControls/Slider.tsx
2946
3220
  var import_styled_components30 = __toESM(require("styled-components"));
@@ -3008,10 +3282,10 @@ var SliderWrapper = import_styled_components31.default.label`
3008
3282
  `;
3009
3283
 
3010
3284
  // src/components/TrackMenu.tsx
3011
- var import_react21 = __toESM(require("react"));
3285
+ var import_react24 = __toESM(require("react"));
3012
3286
  var import_react_dom = require("react-dom");
3013
3287
  var import_styled_components32 = __toESM(require("styled-components"));
3014
- var import_jsx_runtime30 = require("react/jsx-runtime");
3288
+ var import_jsx_runtime32 = require("react/jsx-runtime");
3015
3289
  var MenuContainer = import_styled_components32.default.div`
3016
3290
  position: relative;
3017
3291
  display: inline-block;
@@ -3052,13 +3326,13 @@ var Divider = import_styled_components32.default.hr`
3052
3326
  var TrackMenu = ({
3053
3327
  items: itemsProp
3054
3328
  }) => {
3055
- const [open, setOpen] = (0, import_react21.useState)(false);
3329
+ const [open, setOpen] = (0, import_react24.useState)(false);
3056
3330
  const close = () => setOpen(false);
3057
3331
  const items = typeof itemsProp === "function" ? itemsProp(close) : itemsProp;
3058
- const [dropdownPos, setDropdownPos] = (0, import_react21.useState)({ top: 0, left: 0 });
3059
- const buttonRef = (0, import_react21.useRef)(null);
3060
- const dropdownRef = (0, import_react21.useRef)(null);
3061
- (0, import_react21.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)(() => {
3062
3336
  if (open && buttonRef.current) {
3063
3337
  const rect = buttonRef.current.getBoundingClientRect();
3064
3338
  setDropdownPos({
@@ -3067,7 +3341,7 @@ var TrackMenu = ({
3067
3341
  });
3068
3342
  }
3069
3343
  }, [open]);
3070
- (0, import_react21.useEffect)(() => {
3344
+ (0, import_react24.useEffect)(() => {
3071
3345
  if (!open) return;
3072
3346
  const handleClick = (e) => {
3073
3347
  const target = e.target;
@@ -3078,8 +3352,8 @@ var TrackMenu = ({
3078
3352
  document.addEventListener("mousedown", handleClick);
3079
3353
  return () => document.removeEventListener("mousedown", handleClick);
3080
3354
  }, [open]);
3081
- return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(MenuContainer, { children: [
3082
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
3355
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(MenuContainer, { children: [
3356
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
3083
3357
  MenuButton,
3084
3358
  {
3085
3359
  ref: buttonRef,
@@ -3090,19 +3364,19 @@ var TrackMenu = ({
3090
3364
  onMouseDown: (e) => e.stopPropagation(),
3091
3365
  title: "Track menu",
3092
3366
  "aria-label": "Track menu",
3093
- children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(DotsIcon, { size: 16 })
3367
+ children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(DotsIcon, { size: 16 })
3094
3368
  }
3095
3369
  ),
3096
3370
  open && typeof document !== "undefined" && (0, import_react_dom.createPortal)(
3097
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
3371
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
3098
3372
  Dropdown,
3099
3373
  {
3100
3374
  ref: dropdownRef,
3101
3375
  $top: dropdownPos.top,
3102
3376
  $left: dropdownPos.left,
3103
3377
  onMouseDown: (e) => e.stopPropagation(),
3104
- children: items.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(import_react21.default.Fragment, { children: [
3105
- index > 0 && /* @__PURE__ */ (0, import_jsx_runtime30.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, {}),
3106
3380
  item.content
3107
3381
  ] }, item.id))
3108
3382
  }
@@ -3143,13 +3417,16 @@ var TrackMenu = ({
3143
3417
  InlineLabel,
3144
3418
  LoopRegion,
3145
3419
  LoopRegionMarkers,
3420
+ MAX_CANVAS_WIDTH,
3146
3421
  MasterVolumeControl,
3147
3422
  Playhead,
3148
3423
  PlayheadWithMarker,
3149
3424
  Playlist,
3425
+ PlaylistErrorBoundary,
3150
3426
  PlaylistInfoContext,
3151
3427
  PlayoutProvider,
3152
3428
  ScreenReaderOnly,
3429
+ ScrollViewportProvider,
3153
3430
  Selection,
3154
3431
  SelectionTimeInputs,
3155
3432
  Slider,
@@ -3185,6 +3462,8 @@ var TrackMenu = ({
3185
3462
  usePlaylistInfo,
3186
3463
  usePlayoutStatus,
3187
3464
  usePlayoutStatusUpdate,
3465
+ useScrollViewport,
3466
+ useScrollViewportSelector,
3188
3467
  useTheme,
3189
3468
  useTrackControls,
3190
3469
  waveformColorToCss