@waveform-playlist/ui-components 7.1.2 → 8.0.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
@@ -51,6 +51,7 @@ __export(index_exports, {
51
51
  ClipBoundary: () => ClipBoundary,
52
52
  ClipHeader: () => ClipHeader,
53
53
  ClipHeaderPresentational: () => ClipHeaderPresentational,
54
+ ClipViewportOriginProvider: () => ClipViewportOriginProvider,
54
55
  CloseButton: () => CloseButton,
55
56
  Controls: () => Controls,
56
57
  DevicePixelRatioProvider: () => DevicePixelRatioProvider,
@@ -100,6 +101,7 @@ __export(index_exports, {
100
101
  samplesToSeconds: () => samplesToSeconds,
101
102
  secondsToPixels: () => secondsToPixels,
102
103
  secondsToSamples: () => secondsToSamples,
104
+ useClipViewportOrigin: () => useClipViewportOrigin,
103
105
  useDevicePixelRatio: () => useDevicePixelRatio,
104
106
  usePlaylistInfo: () => usePlaylistInfo,
105
107
  usePlayoutStatus: () => usePlayoutStatus,
@@ -123,10 +125,7 @@ var PositionDisplay = import_styled_components.default.span`
123
125
  color: ${(props) => props.theme?.textColor || "#333"};
124
126
  user-select: none;
125
127
  `;
126
- var AudioPosition = ({
127
- formattedTime,
128
- className
129
- }) => {
128
+ var AudioPosition = ({ formattedTime, className }) => {
130
129
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PositionDisplay, { className, "aria-label": "Audio position", children: formattedTime });
131
130
  };
132
131
 
@@ -146,7 +145,9 @@ var BaseButton = import_styled_components2.default.button`
146
145
  border-radius: ${(props) => props.theme.borderRadius};
147
146
  cursor: pointer;
148
147
  outline: none;
149
- transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out,
148
+ transition:
149
+ background-color 0.15s ease-in-out,
150
+ border-color 0.15s ease-in-out,
150
151
  box-shadow 0.15s ease-in-out;
151
152
 
152
153
  &:hover:not(:disabled) {
@@ -243,7 +244,9 @@ var BaseInput = import_styled_components5.default.input`
243
244
  border: 1px solid ${(props) => props.theme.inputBorder};
244
245
  border-radius: ${(props) => props.theme.borderRadius};
245
246
  outline: none;
246
- transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
247
+ transition:
248
+ border-color 0.15s ease-in-out,
249
+ box-shadow 0.15s ease-in-out;
247
250
 
248
251
  &::placeholder {
249
252
  color: ${(props) => props.theme.inputPlaceholder};
@@ -311,7 +314,9 @@ var BaseSelect = import_styled_components7.default.select`
311
314
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23666' d='M6 8L1 3h10z'/%3E%3C/svg%3E");
312
315
  background-repeat: no-repeat;
313
316
  background-position: right 0.75rem center;
314
- transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
317
+ transition:
318
+ border-color 0.15s ease-in-out,
319
+ box-shadow 0.15s ease-in-out;
315
320
 
316
321
  &:focus {
317
322
  border-color: ${(props) => props.theme.inputFocusBorder};
@@ -357,7 +362,9 @@ var BaseSlider = import_styled_components8.default.input.attrs({ type: "range" }
357
362
  border-radius: 50%;
358
363
  cursor: pointer;
359
364
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
360
- transition: transform 0.15s ease, box-shadow 0.15s ease;
365
+ transition:
366
+ transform 0.15s ease,
367
+ box-shadow 0.15s ease;
361
368
  }
362
369
 
363
370
  &::-webkit-slider-thumb:hover {
@@ -374,7 +381,9 @@ var BaseSlider = import_styled_components8.default.input.attrs({ type: "range" }
374
381
  border-radius: 50%;
375
382
  cursor: pointer;
376
383
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
377
- transition: transform 0.15s ease, box-shadow 0.15s ease;
384
+ transition:
385
+ transform 0.15s ease,
386
+ box-shadow 0.15s ease;
378
387
  }
379
388
 
380
389
  &::-moz-range-thumb:hover {
@@ -442,7 +451,7 @@ var AutomaticScrollCheckbox = ({
442
451
  };
443
452
 
444
453
  // src/components/Channel.tsx
445
- var import_react3 = require("react");
454
+ var import_react4 = require("react");
446
455
  var import_styled_components9 = __toESM(require("styled-components"));
447
456
 
448
457
  // src/wfpl-theme.ts
@@ -637,10 +646,7 @@ var ViewportStoreContext = (0, import_react.createContext)(null);
637
646
  var EMPTY_SUBSCRIBE = () => () => {
638
647
  };
639
648
  var NULL_SNAPSHOT = () => null;
640
- var ScrollViewportProvider = ({
641
- containerRef,
642
- children
643
- }) => {
649
+ var ScrollViewportProvider = ({ containerRef, children }) => {
644
650
  const storeRef = (0, import_react.useRef)(null);
645
651
  if (storeRef.current === null) {
646
652
  storeRef.current = new ViewportStore();
@@ -695,7 +701,7 @@ function useScrollViewportSelector(selector) {
695
701
  () => selector(null)
696
702
  );
697
703
  }
698
- function useVisibleChunkIndices(totalWidth, chunkWidth) {
704
+ function useVisibleChunkIndices(totalWidth, chunkWidth, originX = 0) {
699
705
  const visibleChunkKey = useScrollViewportSelector((viewport) => {
700
706
  const totalChunks = Math.ceil(totalWidth / chunkWidth);
701
707
  const indices = [];
@@ -703,8 +709,9 @@ function useVisibleChunkIndices(totalWidth, chunkWidth) {
703
709
  const chunkLeft = i * chunkWidth;
704
710
  const thisChunkWidth = Math.min(totalWidth - chunkLeft, chunkWidth);
705
711
  if (viewport) {
706
- const chunkEnd = chunkLeft + thisChunkWidth;
707
- if (chunkEnd <= viewport.visibleStart || chunkLeft >= viewport.visibleEnd) {
712
+ const chunkLeftGlobal = originX + chunkLeft;
713
+ const chunkEndGlobal = chunkLeftGlobal + thisChunkWidth;
714
+ if (chunkEndGlobal <= viewport.visibleStart || chunkLeftGlobal >= viewport.visibleEnd) {
708
715
  continue;
709
716
  }
710
717
  }
@@ -718,17 +725,27 @@ function useVisibleChunkIndices(totalWidth, chunkWidth) {
718
725
  );
719
726
  }
720
727
 
721
- // src/hooks/useChunkedCanvasRefs.ts
728
+ // src/contexts/ClipViewportOrigin.tsx
722
729
  var import_react2 = require("react");
730
+ var import_jsx_runtime4 = require("react/jsx-runtime");
731
+ var ClipViewportOriginContext = (0, import_react2.createContext)(0);
732
+ var ClipViewportOriginProvider = ({
733
+ originX,
734
+ children
735
+ }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ClipViewportOriginContext.Provider, { value: originX, children });
736
+ var useClipViewportOrigin = () => (0, import_react2.useContext)(ClipViewportOriginContext);
737
+
738
+ // src/hooks/useChunkedCanvasRefs.ts
739
+ var import_react3 = require("react");
723
740
  function useChunkedCanvasRefs() {
724
- const canvasMapRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
725
- const canvasRef = (0, import_react2.useCallback)((canvas) => {
741
+ const canvasMapRef = (0, import_react3.useRef)(/* @__PURE__ */ new Map());
742
+ const canvasRef = (0, import_react3.useCallback)((canvas) => {
726
743
  if (canvas !== null) {
727
744
  const idx = parseInt(canvas.dataset.index, 10);
728
745
  canvasMapRef.current.set(idx, canvas);
729
746
  }
730
747
  }, []);
731
- (0, import_react2.useEffect)(() => {
748
+ (0, import_react3.useEffect)(() => {
732
749
  const map = canvasMapRef.current;
733
750
  for (const [idx, canvas] of map.entries()) {
734
751
  if (!canvas.isConnected) {
@@ -741,7 +758,7 @@ function useChunkedCanvasRefs() {
741
758
 
742
759
  // src/components/Channel.tsx
743
760
  var import_core = require("@waveform-playlist/core");
744
- var import_jsx_runtime4 = require("react/jsx-runtime");
761
+ var import_jsx_runtime5 = require("react/jsx-runtime");
745
762
  function createCanvasFillStyle(ctx, color, width, height) {
746
763
  if (!isWaveformGradient(color)) {
747
764
  return color;
@@ -802,8 +819,9 @@ var Channel = (props) => {
802
819
  drawMode = "inverted"
803
820
  } = props;
804
821
  const { canvasRef, canvasMapRef } = useChunkedCanvasRefs();
805
- const visibleChunkIndices = useVisibleChunkIndices(length, import_core.MAX_CANVAS_WIDTH);
806
- (0, import_react3.useLayoutEffect)(() => {
822
+ const clipOriginX = useClipViewportOrigin();
823
+ const visibleChunkIndices = useVisibleChunkIndices(length, import_core.MAX_CANVAS_WIDTH, clipOriginX);
824
+ (0, import_react4.useLayoutEffect)(() => {
807
825
  const step = barWidth + barGap;
808
826
  for (const [canvasIdx, canvas] of canvasMapRef.current.entries()) {
809
827
  const globalPixelOffset = canvasIdx * import_core.MAX_CANVAS_WIDTH;
@@ -822,12 +840,7 @@ var Channel = (props) => {
822
840
  } else {
823
841
  fillColor = waveOutlineColor;
824
842
  }
825
- ctx.fillStyle = createCanvasFillStyle(
826
- ctx,
827
- fillColor,
828
- canvasWidth,
829
- waveHeight
830
- );
843
+ ctx.fillStyle = createCanvasFillStyle(ctx, fillColor, canvasWidth, waveHeight);
831
844
  const canvasStartGlobal = globalPixelOffset;
832
845
  const canvasEndGlobal = globalPixelOffset + canvasWidth;
833
846
  const firstBarGlobal = Math.floor((canvasStartGlobal - barWidth + step) / step) * step;
@@ -867,7 +880,7 @@ var Channel = (props) => {
867
880
  const waveforms = visibleChunkIndices.map((i) => {
868
881
  const chunkLeft = i * import_core.MAX_CANVAS_WIDTH;
869
882
  const currentWidth = Math.min(length - chunkLeft, import_core.MAX_CANVAS_WIDTH);
870
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
883
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
871
884
  Waveform,
872
885
  {
873
886
  $cssWidth: currentWidth,
@@ -883,7 +896,7 @@ var Channel = (props) => {
883
896
  });
884
897
  const bgColor = waveFillColor;
885
898
  const backgroundCss = transparentBackground ? "transparent" : waveformColorToCss(bgColor);
886
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
899
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
887
900
  Wrapper,
888
901
  {
889
902
  $index: index,
@@ -897,8 +910,8 @@ var Channel = (props) => {
897
910
  };
898
911
 
899
912
  // src/components/ErrorBoundary.tsx
900
- var import_react4 = __toESM(require("react"));
901
- var import_jsx_runtime5 = require("react/jsx-runtime");
913
+ var import_react5 = __toESM(require("react"));
914
+ var import_jsx_runtime6 = require("react/jsx-runtime");
902
915
  var errorContainerStyle = {
903
916
  padding: "16px",
904
917
  background: "#1a1a2e",
@@ -912,7 +925,7 @@ var errorContainerStyle = {
912
925
  alignItems: "center",
913
926
  justifyContent: "center"
914
927
  };
915
- var PlaylistErrorBoundary = class extends import_react4.default.Component {
928
+ var PlaylistErrorBoundary = class extends import_react5.default.Component {
916
929
  constructor(props) {
917
930
  super(props);
918
931
  this.state = { hasError: false, error: null };
@@ -928,7 +941,7 @@ var PlaylistErrorBoundary = class extends import_react4.default.Component {
928
941
  if (this.props.fallback) {
929
942
  return this.props.fallback;
930
943
  }
931
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: errorContainerStyle, children: "Waveform playlist encountered an error. Check console for details." });
944
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: errorContainerStyle, children: "Waveform playlist encountered an error. Check console for details." });
932
945
  }
933
946
  return this.props.children;
934
947
  }
@@ -941,7 +954,7 @@ var import_utilities = require("@dnd-kit/utilities");
941
954
 
942
955
  // src/components/ClipHeader.tsx
943
956
  var import_styled_components10 = __toESM(require("styled-components"));
944
- var import_jsx_runtime6 = require("react/jsx-runtime");
957
+ var import_jsx_runtime7 = require("react/jsx-runtime");
945
958
  var CLIP_HEADER_HEIGHT = 22;
946
959
  var HeaderContainer = import_styled_components10.default.div`
947
960
  position: relative;
@@ -981,15 +994,7 @@ var ClipHeaderPresentational = ({
981
994
  trackName,
982
995
  isSelected = false
983
996
  }) => {
984
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
985
- HeaderContainer,
986
- {
987
- $isDragging: false,
988
- $interactive: false,
989
- $isSelected: isSelected,
990
- children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(TrackName, { children: trackName })
991
- }
992
- );
997
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(HeaderContainer, { $isDragging: false, $interactive: false, $isSelected: isSelected, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TrackName, { children: trackName }) });
993
998
  };
994
999
  var ClipHeader = ({
995
1000
  clipId,
@@ -1001,16 +1006,10 @@ var ClipHeader = ({
1001
1006
  dragHandleProps
1002
1007
  }) => {
1003
1008
  if (disableDrag || !dragHandleProps) {
1004
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1005
- ClipHeaderPresentational,
1006
- {
1007
- trackName,
1008
- isSelected
1009
- }
1010
- );
1009
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ClipHeaderPresentational, { trackName, isSelected });
1011
1010
  }
1012
1011
  const { attributes, listeners, setActivatorNodeRef } = dragHandleProps;
1013
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1012
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1014
1013
  HeaderContainer,
1015
1014
  {
1016
1015
  ref: setActivatorNodeRef,
@@ -1019,15 +1018,15 @@ var ClipHeader = ({
1019
1018
  $isSelected: isSelected,
1020
1019
  ...listeners,
1021
1020
  ...attributes,
1022
- children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(TrackName, { children: trackName })
1021
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TrackName, { children: trackName })
1023
1022
  }
1024
1023
  );
1025
1024
  };
1026
1025
 
1027
1026
  // src/components/ClipBoundary.tsx
1028
- var import_react5 = __toESM(require("react"));
1027
+ var import_react6 = __toESM(require("react"));
1029
1028
  var import_styled_components11 = __toESM(require("styled-components"));
1030
- var import_jsx_runtime7 = require("react/jsx-runtime");
1029
+ var import_jsx_runtime8 = require("react/jsx-runtime");
1031
1030
  var CLIP_BOUNDARY_WIDTH = 8;
1032
1031
  var CLIP_BOUNDARY_WIDTH_TOUCH = 24;
1033
1032
  var BoundaryContainer = import_styled_components11.default.div`
@@ -1067,12 +1066,12 @@ var ClipBoundary = ({
1067
1066
  dragHandleProps,
1068
1067
  touchOptimized = false
1069
1068
  }) => {
1070
- const [isHovered, setIsHovered] = import_react5.default.useState(false);
1069
+ const [isHovered, setIsHovered] = import_react6.default.useState(false);
1071
1070
  if (!dragHandleProps) {
1072
1071
  return null;
1073
1072
  }
1074
1073
  const { attributes, listeners, setActivatorNodeRef, isDragging } = dragHandleProps;
1075
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1074
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1076
1075
  BoundaryContainer,
1077
1076
  {
1078
1077
  ref: setActivatorNodeRef,
@@ -1092,7 +1091,7 @@ var ClipBoundary = ({
1092
1091
 
1093
1092
  // src/components/FadeOverlay.tsx
1094
1093
  var import_styled_components12 = __toESM(require("styled-components"));
1095
- var import_jsx_runtime8 = require("react/jsx-runtime");
1094
+ var import_jsx_runtime9 = require("react/jsx-runtime");
1096
1095
  var FadeContainer = import_styled_components12.default.div.attrs((props) => ({
1097
1096
  style: {
1098
1097
  left: `${props.$left}px`,
@@ -1149,17 +1148,11 @@ var FadeOverlay = ({
1149
1148
  const theme = (0, import_styled_components12.useTheme)();
1150
1149
  if (width < 1) return null;
1151
1150
  const fillColor = color || theme?.fadeOverlayColor || "rgba(0, 0, 0, 0.4)";
1152
- 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)(
1153
- "path",
1154
- {
1155
- d: generateFadePath(width, 100, curveType),
1156
- fill: fillColor
1157
- }
1158
- ) }) });
1151
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(FadeContainer, { $left: left, $width: width, $type: type, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(FadeSvg, { $type: type, viewBox: `0 0 ${width} 100`, preserveAspectRatio: "none", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: generateFadePath(width, 100, curveType), fill: fillColor }) }) });
1159
1152
  };
1160
1153
 
1161
1154
  // src/components/Clip.tsx
1162
- var import_jsx_runtime9 = require("react/jsx-runtime");
1155
+ var import_jsx_runtime10 = require("react/jsx-runtime");
1163
1156
  var ClipContainer = import_styled_components13.default.div.attrs((props) => ({
1164
1157
  style: props.$isOverlay ? {} : {
1165
1158
  left: `${props.$left}px`,
@@ -1244,7 +1237,7 @@ var Clip = ({
1244
1237
  zIndex: isDragging ? 100 : void 0
1245
1238
  // Below controls (z-index: 999) but above other clips
1246
1239
  } : void 0;
1247
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1240
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1248
1241
  ClipContainer,
1249
1242
  {
1250
1243
  ref: setNodeRef,
@@ -1257,7 +1250,7 @@ var Clip = ({
1257
1250
  "data-track-id": trackId,
1258
1251
  onMouseDown,
1259
1252
  children: [
1260
- showHeader && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1253
+ showHeader && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1261
1254
  ClipHeader,
1262
1255
  {
1263
1256
  clipId,
@@ -1269,9 +1262,9 @@ var Clip = ({
1269
1262
  dragHandleProps: enableDrag ? { attributes, listeners, setActivatorNodeRef } : void 0
1270
1263
  }
1271
1264
  ),
1272
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(ChannelsWrapper, { $isOverlay: isOverlay, children: [
1265
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ClipViewportOriginProvider, { originX: left, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(ChannelsWrapper, { $isOverlay: isOverlay, children: [
1273
1266
  children,
1274
- showFades && fadeIn && fadeIn.duration > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1267
+ showFades && fadeIn && fadeIn.duration > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1275
1268
  FadeOverlay,
1276
1269
  {
1277
1270
  left: 0,
@@ -1280,7 +1273,7 @@ var Clip = ({
1280
1273
  curveType: fadeIn.type
1281
1274
  }
1282
1275
  ),
1283
- showFades && fadeOut && fadeOut.duration > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1276
+ showFades && fadeOut && fadeOut.duration > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1284
1277
  FadeOverlay,
1285
1278
  {
1286
1279
  left: width - Math.floor(fadeOut.duration * sampleRate / samplesPerPixel),
@@ -1289,9 +1282,9 @@ var Clip = ({
1289
1282
  curveType: fadeOut.type
1290
1283
  }
1291
1284
  )
1292
- ] }),
1293
- showHeader && !disableHeaderDrag && !isOverlay && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
1294
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1285
+ ] }) }),
1286
+ showHeader && !disableHeaderDrag && !isOverlay && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
1287
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1295
1288
  ClipBoundary,
1296
1289
  {
1297
1290
  clipId,
@@ -1307,7 +1300,7 @@ var Clip = ({
1307
1300
  }
1308
1301
  }
1309
1302
  ),
1310
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1303
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1311
1304
  ClipBoundary,
1312
1305
  {
1313
1306
  clipId,
@@ -1331,7 +1324,7 @@ var Clip = ({
1331
1324
 
1332
1325
  // src/components/MasterVolumeControl.tsx
1333
1326
  var import_styled_components14 = __toESM(require("styled-components"));
1334
- var import_jsx_runtime10 = require("react/jsx-runtime");
1327
+ var import_jsx_runtime11 = require("react/jsx-runtime");
1335
1328
  var VolumeContainer = import_styled_components14.default.div`
1336
1329
  display: inline-flex;
1337
1330
  align-items: center;
@@ -1353,9 +1346,9 @@ var MasterVolumeControl = ({
1353
1346
  const handleChange = (e) => {
1354
1347
  onChange(parseFloat(e.target.value) / 100);
1355
1348
  };
1356
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(VolumeContainer, { className, children: [
1357
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(VolumeLabel, { htmlFor: "master-gain", children: "Master Volume" }),
1358
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1349
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(VolumeContainer, { className, children: [
1350
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(VolumeLabel, { htmlFor: "master-gain", children: "Master Volume" }),
1351
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1359
1352
  VolumeSlider,
1360
1353
  {
1361
1354
  min: "0",
@@ -1370,9 +1363,9 @@ var MasterVolumeControl = ({
1370
1363
  };
1371
1364
 
1372
1365
  // src/components/Playhead.tsx
1373
- var import_react6 = require("react");
1366
+ var import_react7 = require("react");
1374
1367
  var import_styled_components15 = __toESM(require("styled-components"));
1375
- var import_jsx_runtime11 = require("react/jsx-runtime");
1368
+ var import_jsx_runtime12 = require("react/jsx-runtime");
1376
1369
  var PlayheadLine = import_styled_components15.default.div.attrs((props) => ({
1377
1370
  style: {
1378
1371
  transform: `translate3d(${props.$position}px, 0, 0)`
@@ -1389,7 +1382,7 @@ var PlayheadLine = import_styled_components15.default.div.attrs((props) => ({
1389
1382
  will-change: transform;
1390
1383
  `;
1391
1384
  var Playhead = ({ position, color = "#ff0000" }) => {
1392
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(PlayheadLine, { $position: position, $color: color });
1385
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(PlayheadLine, { $position: position, $color: color });
1393
1386
  };
1394
1387
  var PlayheadWithMarkerContainer = import_styled_components15.default.div`
1395
1388
  position: absolute;
@@ -1429,9 +1422,9 @@ var PlayheadWithMarker = ({
1429
1422
  controlsOffset,
1430
1423
  getAudioContextTime
1431
1424
  }) => {
1432
- const containerRef = (0, import_react6.useRef)(null);
1433
- const animationFrameRef = (0, import_react6.useRef)(null);
1434
- (0, import_react6.useEffect)(() => {
1425
+ const containerRef = (0, import_react7.useRef)(null);
1426
+ const animationFrameRef = (0, import_react7.useRef)(null);
1427
+ (0, import_react7.useEffect)(() => {
1435
1428
  const updatePosition = () => {
1436
1429
  if (containerRef.current) {
1437
1430
  let time;
@@ -1459,24 +1452,33 @@ var PlayheadWithMarker = ({
1459
1452
  animationFrameRef.current = null;
1460
1453
  }
1461
1454
  };
1462
- }, [isPlaying, sampleRate, samplesPerPixel, controlsOffset, currentTimeRef, playbackStartTimeRef, audioStartPositionRef, getAudioContextTime]);
1463
- (0, import_react6.useEffect)(() => {
1455
+ }, [
1456
+ isPlaying,
1457
+ sampleRate,
1458
+ samplesPerPixel,
1459
+ controlsOffset,
1460
+ currentTimeRef,
1461
+ playbackStartTimeRef,
1462
+ audioStartPositionRef,
1463
+ getAudioContextTime
1464
+ ]);
1465
+ (0, import_react7.useEffect)(() => {
1464
1466
  if (!isPlaying && containerRef.current) {
1465
1467
  const time = currentTimeRef.current ?? 0;
1466
1468
  const pos = time * sampleRate / samplesPerPixel + controlsOffset;
1467
1469
  containerRef.current.style.transform = `translate3d(${pos}px, 0, 0)`;
1468
1470
  }
1469
1471
  });
1470
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(PlayheadWithMarkerContainer, { ref: containerRef, $color: color, children: [
1471
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(MarkerTriangle, { $color: color }),
1472
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(MarkerLine, { $color: color })
1472
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(PlayheadWithMarkerContainer, { ref: containerRef, $color: color, children: [
1473
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(MarkerTriangle, { $color: color }),
1474
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(MarkerLine, { $color: color })
1473
1475
  ] });
1474
1476
  };
1475
1477
 
1476
1478
  // src/components/Playlist.tsx
1477
1479
  var import_styled_components16 = __toESM(require("styled-components"));
1478
- var import_react7 = require("react");
1479
- var import_jsx_runtime12 = require("react/jsx-runtime");
1480
+ var import_react8 = require("react");
1481
+ var import_jsx_runtime13 = require("react/jsx-runtime");
1480
1482
  var Wrapper2 = import_styled_components16.default.div`
1481
1483
  overflow-y: hidden;
1482
1484
  overflow-x: auto;
@@ -1530,41 +1532,37 @@ var Playlist = ({
1530
1532
  isSelecting,
1531
1533
  "data-playlist-state": playlistState
1532
1534
  }) => {
1533
- const wrapperRef = (0, import_react7.useRef)(null);
1534
- const handleRef = (0, import_react7.useCallback)((el) => {
1535
- wrapperRef.current = el;
1536
- scrollContainerRef?.(el);
1537
- }, [scrollContainerRef]);
1538
- 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)(
1539
- ScrollContainer,
1540
- {
1541
- $backgroundColor: backgroundColor,
1542
- $width: scrollContainerWidth,
1543
- children: [
1544
- timescale && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(TimescaleWrapper, { $width: timescaleWidth, $backgroundColor: timescaleBackgroundColor, children: timescale }),
1545
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(TracksContainer, { $width: tracksWidth, $backgroundColor: backgroundColor, children: [
1546
- children,
1547
- (onTracksClick || onTracksMouseDown) && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1548
- ClickOverlay,
1549
- {
1550
- $controlsWidth: controlsWidth,
1551
- $isSelecting: isSelecting,
1552
- onClick: onTracksClick,
1553
- onMouseDown: onTracksMouseDown,
1554
- onMouseMove: onTracksMouseMove,
1555
- onMouseUp: onTracksMouseUp
1556
- }
1557
- )
1558
- ] })
1559
- ]
1560
- }
1561
- ) }) });
1535
+ const wrapperRef = (0, import_react8.useRef)(null);
1536
+ const handleRef = (0, import_react8.useCallback)(
1537
+ (el) => {
1538
+ wrapperRef.current = el;
1539
+ scrollContainerRef?.(el);
1540
+ },
1541
+ [scrollContainerRef]
1542
+ );
1543
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Wrapper2, { "data-scroll-container": "true", "data-playlist-state": playlistState, ref: handleRef, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ScrollViewportProvider, { containerRef: wrapperRef, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(ScrollContainer, { $backgroundColor: backgroundColor, $width: scrollContainerWidth, children: [
1544
+ timescale && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(TimescaleWrapper, { $width: timescaleWidth, $backgroundColor: timescaleBackgroundColor, children: timescale }),
1545
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(TracksContainer, { $width: tracksWidth, $backgroundColor: backgroundColor, children: [
1546
+ children,
1547
+ (onTracksClick || onTracksMouseDown) && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1548
+ ClickOverlay,
1549
+ {
1550
+ $controlsWidth: controlsWidth,
1551
+ $isSelecting: isSelecting,
1552
+ onClick: onTracksClick,
1553
+ onMouseDown: onTracksMouseDown,
1554
+ onMouseMove: onTracksMouseMove,
1555
+ onMouseUp: onTracksMouseUp
1556
+ }
1557
+ )
1558
+ ] })
1559
+ ] }) }) });
1562
1560
  };
1563
1561
  var StyledPlaylist = (0, import_styled_components16.withTheme)(Playlist);
1564
1562
 
1565
1563
  // src/components/Selection.tsx
1566
1564
  var import_styled_components17 = __toESM(require("styled-components"));
1567
- var import_jsx_runtime13 = require("react/jsx-runtime");
1565
+ var import_jsx_runtime14 = require("react/jsx-runtime");
1568
1566
  var SelectionOverlay = import_styled_components17.default.div.attrs((props) => ({
1569
1567
  style: {
1570
1568
  left: `${props.$left}px`,
@@ -1588,13 +1586,13 @@ var Selection = ({
1588
1586
  if (width <= 0) {
1589
1587
  return null;
1590
1588
  }
1591
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(SelectionOverlay, { $left: startPosition, $width: width, $color: color, "data-selection": true });
1589
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(SelectionOverlay, { $left: startPosition, $width: width, $color: color, "data-selection": true });
1592
1590
  };
1593
1591
 
1594
1592
  // src/components/LoopRegion.tsx
1595
- var import_react8 = require("react");
1593
+ var import_react9 = require("react");
1596
1594
  var import_styled_components18 = __toESM(require("styled-components"));
1597
- var import_jsx_runtime14 = require("react/jsx-runtime");
1595
+ var import_jsx_runtime15 = require("react/jsx-runtime");
1598
1596
  var LoopRegionOverlayDiv = import_styled_components18.default.div.attrs((props) => ({
1599
1597
  style: {
1600
1598
  left: `${props.$left}px`,
@@ -1643,8 +1641,8 @@ var LoopRegion = ({
1643
1641
  if (width <= 0) {
1644
1642
  return null;
1645
1643
  }
1646
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
1647
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1644
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
1645
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1648
1646
  LoopRegionOverlayDiv,
1649
1647
  {
1650
1648
  $left: startPosition,
@@ -1653,7 +1651,7 @@ var LoopRegion = ({
1653
1651
  "data-loop-region": true
1654
1652
  }
1655
1653
  ),
1656
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1654
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1657
1655
  LoopMarker,
1658
1656
  {
1659
1657
  $left: startPosition,
@@ -1662,7 +1660,7 @@ var LoopRegion = ({
1662
1660
  "data-loop-marker": "start"
1663
1661
  }
1664
1662
  ),
1665
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1663
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1666
1664
  LoopMarker,
1667
1665
  {
1668
1666
  $left: endPosition - 2,
@@ -1743,71 +1741,77 @@ var LoopRegionMarkers = ({
1743
1741
  minPosition = 0,
1744
1742
  maxPosition = Infinity
1745
1743
  }) => {
1746
- const [draggingMarker, setDraggingMarker] = (0, import_react8.useState)(null);
1747
- const dragStartX = (0, import_react8.useRef)(0);
1748
- const dragStartPosition = (0, import_react8.useRef)(0);
1749
- const dragStartEnd = (0, import_react8.useRef)(0);
1744
+ const [draggingMarker, setDraggingMarker] = (0, import_react9.useState)(null);
1745
+ const dragStartX = (0, import_react9.useRef)(0);
1746
+ const dragStartPosition = (0, import_react9.useRef)(0);
1747
+ const dragStartEnd = (0, import_react9.useRef)(0);
1750
1748
  const width = Math.max(0, endPosition - startPosition);
1751
- const handleMarkerMouseDown = (0, import_react8.useCallback)((e, marker) => {
1752
- e.preventDefault();
1753
- e.stopPropagation();
1754
- setDraggingMarker(marker);
1755
- dragStartX.current = e.clientX;
1756
- dragStartPosition.current = marker === "start" ? startPosition : endPosition;
1757
- const handleMouseMove = (moveEvent) => {
1758
- const delta = moveEvent.clientX - dragStartX.current;
1759
- const newPosition = dragStartPosition.current + delta;
1760
- if (marker === "start") {
1761
- const clampedPosition = Math.max(minPosition, Math.min(endPosition - 10, newPosition));
1762
- onLoopStartChange?.(clampedPosition);
1763
- } else {
1764
- const clampedPosition = Math.max(startPosition + 10, Math.min(maxPosition, newPosition));
1765
- onLoopEndChange?.(clampedPosition);
1766
- }
1767
- };
1768
- const handleMouseUp = () => {
1769
- setDraggingMarker(null);
1770
- document.removeEventListener("mousemove", handleMouseMove);
1771
- document.removeEventListener("mouseup", handleMouseUp);
1772
- };
1773
- document.addEventListener("mousemove", handleMouseMove);
1774
- document.addEventListener("mouseup", handleMouseUp);
1775
- }, [startPosition, endPosition, minPosition, maxPosition, onLoopStartChange, onLoopEndChange]);
1776
- const handleRegionMouseDown = (0, import_react8.useCallback)((e) => {
1777
- e.preventDefault();
1778
- e.stopPropagation();
1779
- setDraggingMarker("region");
1780
- dragStartX.current = e.clientX;
1781
- dragStartPosition.current = startPosition;
1782
- dragStartEnd.current = endPosition;
1783
- const regionWidth = endPosition - startPosition;
1784
- const handleMouseMove = (moveEvent) => {
1785
- const delta = moveEvent.clientX - dragStartX.current;
1786
- let newStart = dragStartPosition.current + delta;
1787
- let newEnd = dragStartEnd.current + delta;
1788
- if (newStart < minPosition) {
1789
- newStart = minPosition;
1790
- newEnd = minPosition + regionWidth;
1791
- }
1792
- if (newEnd > maxPosition) {
1793
- newEnd = maxPosition;
1794
- newStart = maxPosition - regionWidth;
1795
- }
1796
- onLoopRegionMove?.(newStart, newEnd);
1797
- };
1798
- const handleMouseUp = () => {
1799
- setDraggingMarker(null);
1800
- document.removeEventListener("mousemove", handleMouseMove);
1801
- document.removeEventListener("mouseup", handleMouseUp);
1802
- };
1803
- document.addEventListener("mousemove", handleMouseMove);
1804
- document.addEventListener("mouseup", handleMouseUp);
1805
- }, [startPosition, endPosition, minPosition, maxPosition, onLoopRegionMove]);
1749
+ const handleMarkerMouseDown = (0, import_react9.useCallback)(
1750
+ (e, marker) => {
1751
+ e.preventDefault();
1752
+ e.stopPropagation();
1753
+ setDraggingMarker(marker);
1754
+ dragStartX.current = e.clientX;
1755
+ dragStartPosition.current = marker === "start" ? startPosition : endPosition;
1756
+ const handleMouseMove = (moveEvent) => {
1757
+ const delta = moveEvent.clientX - dragStartX.current;
1758
+ const newPosition = dragStartPosition.current + delta;
1759
+ if (marker === "start") {
1760
+ const clampedPosition = Math.max(minPosition, Math.min(endPosition - 10, newPosition));
1761
+ onLoopStartChange?.(clampedPosition);
1762
+ } else {
1763
+ const clampedPosition = Math.max(startPosition + 10, Math.min(maxPosition, newPosition));
1764
+ onLoopEndChange?.(clampedPosition);
1765
+ }
1766
+ };
1767
+ const handleMouseUp = () => {
1768
+ setDraggingMarker(null);
1769
+ document.removeEventListener("mousemove", handleMouseMove);
1770
+ document.removeEventListener("mouseup", handleMouseUp);
1771
+ };
1772
+ document.addEventListener("mousemove", handleMouseMove);
1773
+ document.addEventListener("mouseup", handleMouseUp);
1774
+ },
1775
+ [startPosition, endPosition, minPosition, maxPosition, onLoopStartChange, onLoopEndChange]
1776
+ );
1777
+ const handleRegionMouseDown = (0, import_react9.useCallback)(
1778
+ (e) => {
1779
+ e.preventDefault();
1780
+ e.stopPropagation();
1781
+ setDraggingMarker("region");
1782
+ dragStartX.current = e.clientX;
1783
+ dragStartPosition.current = startPosition;
1784
+ dragStartEnd.current = endPosition;
1785
+ const regionWidth = endPosition - startPosition;
1786
+ const handleMouseMove = (moveEvent) => {
1787
+ const delta = moveEvent.clientX - dragStartX.current;
1788
+ let newStart = dragStartPosition.current + delta;
1789
+ let newEnd = dragStartEnd.current + delta;
1790
+ if (newStart < minPosition) {
1791
+ newStart = minPosition;
1792
+ newEnd = minPosition + regionWidth;
1793
+ }
1794
+ if (newEnd > maxPosition) {
1795
+ newEnd = maxPosition;
1796
+ newStart = maxPosition - regionWidth;
1797
+ }
1798
+ onLoopRegionMove?.(newStart, newEnd);
1799
+ };
1800
+ const handleMouseUp = () => {
1801
+ setDraggingMarker(null);
1802
+ document.removeEventListener("mousemove", handleMouseMove);
1803
+ document.removeEventListener("mouseup", handleMouseUp);
1804
+ };
1805
+ document.addEventListener("mousemove", handleMouseMove);
1806
+ document.addEventListener("mouseup", handleMouseUp);
1807
+ },
1808
+ [startPosition, endPosition, minPosition, maxPosition, onLoopRegionMove]
1809
+ );
1806
1810
  if (width <= 0) {
1807
1811
  return null;
1808
1812
  }
1809
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
1810
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1813
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
1814
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1811
1815
  TimescaleLoopShade,
1812
1816
  {
1813
1817
  $left: startPosition,
@@ -1818,7 +1822,7 @@ var LoopRegionMarkers = ({
1818
1822
  "data-loop-region-timescale": true
1819
1823
  }
1820
1824
  ),
1821
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1825
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1822
1826
  DraggableMarkerHandle,
1823
1827
  {
1824
1828
  $left: startPosition,
@@ -1829,7 +1833,7 @@ var LoopRegionMarkers = ({
1829
1833
  "data-loop-marker-handle": "start"
1830
1834
  }
1831
1835
  ),
1832
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1836
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1833
1837
  DraggableMarkerHandle,
1834
1838
  {
1835
1839
  $left: endPosition,
@@ -1864,46 +1868,49 @@ var TimescaleLoopRegion = ({
1864
1868
  maxPosition = Infinity,
1865
1869
  controlsOffset = 0
1866
1870
  }) => {
1867
- const [, setIsCreating] = (0, import_react8.useState)(false);
1868
- const createStartX = (0, import_react8.useRef)(0);
1869
- const containerRef = (0, import_react8.useRef)(null);
1871
+ const [, setIsCreating] = (0, import_react9.useState)(false);
1872
+ const createStartX = (0, import_react9.useRef)(0);
1873
+ const containerRef = (0, import_react9.useRef)(null);
1870
1874
  const hasLoopRegion = endPosition > startPosition;
1871
- const handleBackgroundMouseDown = (0, import_react8.useCallback)((e) => {
1872
- const target = e.target;
1873
- if (target.closest("[data-loop-marker-handle]") || target.closest("[data-loop-region-timescale]")) {
1874
- return;
1875
- }
1876
- e.preventDefault();
1877
- setIsCreating(true);
1878
- const rect = containerRef.current?.getBoundingClientRect();
1879
- if (!rect) return;
1880
- const clickX = e.clientX - rect.left;
1881
- const clampedX = Math.max(minPosition, Math.min(maxPosition, clickX));
1882
- createStartX.current = clampedX;
1883
- onLoopRegionChange?.(clampedX, clampedX);
1884
- const handleMouseMove = (moveEvent) => {
1885
- const currentX = moveEvent.clientX - rect.left;
1886
- const clampedCurrentX = Math.max(minPosition, Math.min(maxPosition, currentX));
1887
- const newStart = Math.min(createStartX.current, clampedCurrentX);
1888
- const newEnd = Math.max(createStartX.current, clampedCurrentX);
1889
- onLoopRegionChange?.(newStart, newEnd);
1890
- };
1891
- const handleMouseUp = () => {
1892
- setIsCreating(false);
1893
- document.removeEventListener("mousemove", handleMouseMove);
1894
- document.removeEventListener("mouseup", handleMouseUp);
1895
- };
1896
- document.addEventListener("mousemove", handleMouseMove);
1897
- document.addEventListener("mouseup", handleMouseUp);
1898
- }, [minPosition, maxPosition, onLoopRegionChange]);
1899
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1875
+ const handleBackgroundMouseDown = (0, import_react9.useCallback)(
1876
+ (e) => {
1877
+ const target = e.target;
1878
+ if (target.closest("[data-loop-marker-handle]") || target.closest("[data-loop-region-timescale]")) {
1879
+ return;
1880
+ }
1881
+ e.preventDefault();
1882
+ setIsCreating(true);
1883
+ const rect = containerRef.current?.getBoundingClientRect();
1884
+ if (!rect) return;
1885
+ const clickX = e.clientX - rect.left;
1886
+ const clampedX = Math.max(minPosition, Math.min(maxPosition, clickX));
1887
+ createStartX.current = clampedX;
1888
+ onLoopRegionChange?.(clampedX, clampedX);
1889
+ const handleMouseMove = (moveEvent) => {
1890
+ const currentX = moveEvent.clientX - rect.left;
1891
+ const clampedCurrentX = Math.max(minPosition, Math.min(maxPosition, currentX));
1892
+ const newStart = Math.min(createStartX.current, clampedCurrentX);
1893
+ const newEnd = Math.max(createStartX.current, clampedCurrentX);
1894
+ onLoopRegionChange?.(newStart, newEnd);
1895
+ };
1896
+ const handleMouseUp = () => {
1897
+ setIsCreating(false);
1898
+ document.removeEventListener("mousemove", handleMouseMove);
1899
+ document.removeEventListener("mouseup", handleMouseUp);
1900
+ };
1901
+ document.addEventListener("mousemove", handleMouseMove);
1902
+ document.addEventListener("mouseup", handleMouseUp);
1903
+ },
1904
+ [minPosition, maxPosition, onLoopRegionChange]
1905
+ );
1906
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1900
1907
  TimescaleLoopCreator,
1901
1908
  {
1902
1909
  ref: containerRef,
1903
1910
  $leftOffset: controlsOffset,
1904
1911
  onMouseDown: handleBackgroundMouseDown,
1905
1912
  "data-timescale-loop-creator": true,
1906
- children: hasLoopRegion && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1913
+ children: hasLoopRegion && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1907
1914
  LoopRegionMarkers,
1908
1915
  {
1909
1916
  startPosition,
@@ -1922,10 +1929,10 @@ var TimescaleLoopRegion = ({
1922
1929
  };
1923
1930
 
1924
1931
  // src/components/SelectionTimeInputs.tsx
1925
- var import_react10 = require("react");
1932
+ var import_react11 = require("react");
1926
1933
 
1927
1934
  // src/components/TimeInput.tsx
1928
- var import_react9 = require("react");
1935
+ var import_react10 = require("react");
1929
1936
 
1930
1937
  // src/utils/timeFormat.ts
1931
1938
  function clockFormat(seconds, decimals) {
@@ -1975,7 +1982,7 @@ function parseTime(timeStr, format) {
1975
1982
  }
1976
1983
 
1977
1984
  // src/components/TimeInput.tsx
1978
- var import_jsx_runtime15 = require("react/jsx-runtime");
1985
+ var import_jsx_runtime16 = require("react/jsx-runtime");
1979
1986
  var TimeInput = ({
1980
1987
  id,
1981
1988
  label,
@@ -1985,8 +1992,8 @@ var TimeInput = ({
1985
1992
  onChange,
1986
1993
  readOnly = false
1987
1994
  }) => {
1988
- const [displayValue, setDisplayValue] = (0, import_react9.useState)("");
1989
- (0, import_react9.useEffect)(() => {
1995
+ const [displayValue, setDisplayValue] = (0, import_react10.useState)("");
1996
+ (0, import_react10.useEffect)(() => {
1990
1997
  const formatted = formatTime(value, format);
1991
1998
  setDisplayValue(formatted);
1992
1999
  }, [value, format, id]);
@@ -2006,9 +2013,9 @@ var TimeInput = ({
2006
2013
  e.currentTarget.blur();
2007
2014
  }
2008
2015
  };
2009
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
2010
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ScreenReaderOnly, { as: "label", htmlFor: id, children: label }),
2011
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2016
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
2017
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ScreenReaderOnly, { as: "label", htmlFor: id, children: label }),
2018
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2012
2019
  BaseInput,
2013
2020
  {
2014
2021
  type: "text",
@@ -2025,15 +2032,15 @@ var TimeInput = ({
2025
2032
  };
2026
2033
 
2027
2034
  // src/components/SelectionTimeInputs.tsx
2028
- var import_jsx_runtime16 = require("react/jsx-runtime");
2035
+ var import_jsx_runtime17 = require("react/jsx-runtime");
2029
2036
  var SelectionTimeInputs = ({
2030
2037
  selectionStart,
2031
2038
  selectionEnd,
2032
2039
  onSelectionChange,
2033
2040
  className
2034
2041
  }) => {
2035
- const [timeFormat, setTimeFormat] = (0, import_react10.useState)("hh:mm:ss.uuu");
2036
- (0, import_react10.useEffect)(() => {
2042
+ const [timeFormat, setTimeFormat] = (0, import_react11.useState)("hh:mm:ss.uuu");
2043
+ (0, import_react11.useEffect)(() => {
2037
2044
  const timeFormatSelect = document.querySelector(".time-format");
2038
2045
  const handleFormatChange = () => {
2039
2046
  if (timeFormatSelect) {
@@ -2058,8 +2065,8 @@ var SelectionTimeInputs = ({
2058
2065
  onSelectionChange(selectionStart, value);
2059
2066
  }
2060
2067
  };
2061
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className, children: [
2062
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2068
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className, children: [
2069
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2063
2070
  TimeInput,
2064
2071
  {
2065
2072
  id: "audio_start",
@@ -2070,7 +2077,7 @@ var SelectionTimeInputs = ({
2070
2077
  onChange: handleStartChange
2071
2078
  }
2072
2079
  ),
2073
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2080
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2074
2081
  TimeInput,
2075
2082
  {
2076
2083
  id: "audio_end",
@@ -2085,14 +2092,14 @@ var SelectionTimeInputs = ({
2085
2092
  };
2086
2093
 
2087
2094
  // src/contexts/DevicePixelRatio.tsx
2088
- var import_react11 = require("react");
2089
- var import_jsx_runtime17 = require("react/jsx-runtime");
2095
+ var import_react12 = require("react");
2096
+ var import_jsx_runtime18 = require("react/jsx-runtime");
2090
2097
  function getScale() {
2091
2098
  return window.devicePixelRatio;
2092
2099
  }
2093
- var DevicePixelRatioContext = (0, import_react11.createContext)(getScale());
2100
+ var DevicePixelRatioContext = (0, import_react12.createContext)(getScale());
2094
2101
  var DevicePixelRatioProvider = ({ children }) => {
2095
- const [scale, setScale] = (0, import_react11.useState)(getScale());
2102
+ const [scale, setScale] = (0, import_react12.useState)(getScale());
2096
2103
  matchMedia(`(resolution: ${getScale()}dppx)`).addEventListener(
2097
2104
  "change",
2098
2105
  () => {
@@ -2100,13 +2107,13 @@ var DevicePixelRatioProvider = ({ children }) => {
2100
2107
  },
2101
2108
  { once: true }
2102
2109
  );
2103
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DevicePixelRatioContext.Provider, { value: Math.ceil(scale), children });
2110
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(DevicePixelRatioContext.Provider, { value: Math.ceil(scale), children });
2104
2111
  };
2105
- var useDevicePixelRatio = () => (0, import_react11.useContext)(DevicePixelRatioContext);
2112
+ var useDevicePixelRatio = () => (0, import_react12.useContext)(DevicePixelRatioContext);
2106
2113
 
2107
2114
  // src/contexts/PlaylistInfo.tsx
2108
- var import_react12 = require("react");
2109
- var PlaylistInfoContext = (0, import_react12.createContext)({
2115
+ var import_react13 = require("react");
2116
+ var PlaylistInfoContext = (0, import_react13.createContext)({
2110
2117
  sampleRate: 48e3,
2111
2118
  samplesPerPixel: 1e3,
2112
2119
  zoomLevels: [1e3, 1500, 2e3, 2500],
@@ -2120,22 +2127,22 @@ var PlaylistInfoContext = (0, import_react12.createContext)({
2120
2127
  barWidth: 1,
2121
2128
  barGap: 0
2122
2129
  });
2123
- var usePlaylistInfo = () => (0, import_react12.useContext)(PlaylistInfoContext);
2130
+ var usePlaylistInfo = () => (0, import_react13.useContext)(PlaylistInfoContext);
2124
2131
 
2125
2132
  // src/contexts/Theme.tsx
2126
- var import_react13 = require("react");
2133
+ var import_react14 = require("react");
2127
2134
  var import_styled_components19 = require("styled-components");
2128
- var useTheme2 = () => (0, import_react13.useContext)(import_styled_components19.ThemeContext);
2135
+ var useTheme2 = () => (0, import_react14.useContext)(import_styled_components19.ThemeContext);
2129
2136
 
2130
2137
  // src/contexts/TrackControls.tsx
2131
- var import_react14 = require("react");
2132
- var import_jsx_runtime18 = require("react/jsx-runtime");
2133
- var TrackControlsContext = (0, import_react14.createContext)(/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react14.Fragment, {}));
2134
- var useTrackControls = () => (0, import_react14.useContext)(TrackControlsContext);
2135
-
2136
- // src/contexts/Playout.tsx
2137
2138
  var import_react15 = require("react");
2138
2139
  var import_jsx_runtime19 = require("react/jsx-runtime");
2140
+ var TrackControlsContext = (0, import_react15.createContext)(/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react15.Fragment, {}));
2141
+ var useTrackControls = () => (0, import_react15.useContext)(TrackControlsContext);
2142
+
2143
+ // src/contexts/Playout.tsx
2144
+ var import_react16 = require("react");
2145
+ var import_jsx_runtime20 = require("react/jsx-runtime");
2139
2146
  var defaultProgress = 0;
2140
2147
  var defaultIsPlaying = false;
2141
2148
  var defaultSelectionStart = 0;
@@ -2146,8 +2153,8 @@ var defaultPlayout = {
2146
2153
  selectionStart: defaultSelectionStart,
2147
2154
  selectionEnd: defaultSelectionEnd
2148
2155
  };
2149
- var PlayoutStatusContext = (0, import_react15.createContext)(defaultPlayout);
2150
- var PlayoutStatusUpdateContext = (0, import_react15.createContext)({
2156
+ var PlayoutStatusContext = (0, import_react16.createContext)(defaultPlayout);
2157
+ var PlayoutStatusUpdateContext = (0, import_react16.createContext)({
2151
2158
  setIsPlaying: () => {
2152
2159
  },
2153
2160
  setProgress: () => {
@@ -2156,24 +2163,24 @@ var PlayoutStatusUpdateContext = (0, import_react15.createContext)({
2156
2163
  }
2157
2164
  });
2158
2165
  var PlayoutProvider = ({ children }) => {
2159
- const [isPlaying, setIsPlaying] = (0, import_react15.useState)(defaultIsPlaying);
2160
- const [progress, setProgress] = (0, import_react15.useState)(defaultProgress);
2161
- const [selectionStart, setSelectionStart] = (0, import_react15.useState)(defaultSelectionStart);
2162
- const [selectionEnd, setSelectionEnd] = (0, import_react15.useState)(defaultSelectionEnd);
2166
+ const [isPlaying, setIsPlaying] = (0, import_react16.useState)(defaultIsPlaying);
2167
+ const [progress, setProgress] = (0, import_react16.useState)(defaultProgress);
2168
+ const [selectionStart, setSelectionStart] = (0, import_react16.useState)(defaultSelectionStart);
2169
+ const [selectionEnd, setSelectionEnd] = (0, import_react16.useState)(defaultSelectionEnd);
2163
2170
  const setSelection = (start, end) => {
2164
2171
  setSelectionStart(start);
2165
2172
  setSelectionEnd(end);
2166
2173
  };
2167
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(PlayoutStatusUpdateContext.Provider, { value: { setIsPlaying, setProgress, setSelection }, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(PlayoutStatusContext.Provider, { value: { isPlaying, progress, selectionStart, selectionEnd }, children }) });
2174
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(PlayoutStatusUpdateContext.Provider, { value: { setIsPlaying, setProgress, setSelection }, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(PlayoutStatusContext.Provider, { value: { isPlaying, progress, selectionStart, selectionEnd }, children }) });
2168
2175
  };
2169
- var usePlayoutStatus = () => (0, import_react15.useContext)(PlayoutStatusContext);
2170
- var usePlayoutStatusUpdate = () => (0, import_react15.useContext)(PlayoutStatusUpdateContext);
2176
+ var usePlayoutStatus = () => (0, import_react16.useContext)(PlayoutStatusContext);
2177
+ var usePlayoutStatusUpdate = () => (0, import_react16.useContext)(PlayoutStatusUpdateContext);
2171
2178
 
2172
2179
  // src/components/SpectrogramChannel.tsx
2173
- var import_react16 = require("react");
2180
+ var import_react17 = require("react");
2174
2181
  var import_styled_components20 = __toESM(require("styled-components"));
2175
2182
  var import_core3 = require("@waveform-playlist/core");
2176
- var import_jsx_runtime20 = require("react/jsx-runtime");
2183
+ var import_jsx_runtime21 = require("react/jsx-runtime");
2177
2184
  var LINEAR_FREQUENCY_SCALE = (f, minF, maxF) => (f - minF) / (maxF - minF);
2178
2185
  var Wrapper3 = import_styled_components20.default.div.attrs((props) => ({
2179
2186
  style: {
@@ -2227,23 +2234,24 @@ var SpectrogramChannel = ({
2227
2234
  }) => {
2228
2235
  const channelIndex = channelIndexProp ?? index;
2229
2236
  const { canvasRef, canvasMapRef } = useChunkedCanvasRefs();
2230
- const registeredIdsRef = (0, import_react16.useRef)([]);
2231
- const transferredCanvasesRef = (0, import_react16.useRef)(/* @__PURE__ */ new WeakSet());
2232
- const workerApiRef = (0, import_react16.useRef)(workerApi);
2233
- const onCanvasesReadyRef = (0, import_react16.useRef)(onCanvasesReady);
2237
+ const registeredIdsRef = (0, import_react17.useRef)([]);
2238
+ const transferredCanvasesRef = (0, import_react17.useRef)(/* @__PURE__ */ new WeakSet());
2239
+ const workerApiRef = (0, import_react17.useRef)(workerApi);
2240
+ const onCanvasesReadyRef = (0, import_react17.useRef)(onCanvasesReady);
2234
2241
  const isWorkerMode = !!(workerApi && clipId);
2235
- const visibleChunkIndices = useVisibleChunkIndices(length, import_core3.MAX_CANVAS_WIDTH);
2242
+ const clipOriginX = useClipViewportOrigin();
2243
+ const visibleChunkIndices = useVisibleChunkIndices(length, import_core3.MAX_CANVAS_WIDTH, clipOriginX);
2236
2244
  const lut = colorLUT ?? DEFAULT_COLOR_LUT;
2237
2245
  const maxF = maxFrequency ?? (data ? data.sampleRate / 2 : 22050);
2238
2246
  const scaleFn = frequencyScaleFn ?? LINEAR_FREQUENCY_SCALE;
2239
2247
  const hasCustomFrequencyScale = Boolean(frequencyScaleFn);
2240
- (0, import_react16.useEffect)(() => {
2248
+ (0, import_react17.useEffect)(() => {
2241
2249
  workerApiRef.current = workerApi;
2242
2250
  }, [workerApi]);
2243
- (0, import_react16.useEffect)(() => {
2251
+ (0, import_react17.useEffect)(() => {
2244
2252
  onCanvasesReadyRef.current = onCanvasesReady;
2245
2253
  }, [onCanvasesReady]);
2246
- (0, import_react16.useEffect)(() => {
2254
+ (0, import_react17.useEffect)(() => {
2247
2255
  if (!isWorkerMode) return;
2248
2256
  const currentWorkerApi = workerApiRef.current;
2249
2257
  if (!currentWorkerApi || !clipId) return;
@@ -2306,7 +2314,7 @@ var SpectrogramChannel = ({
2306
2314
  onCanvasesReadyRef.current?.(allIds, allWidths);
2307
2315
  }
2308
2316
  }, [canvasMapRef, isWorkerMode, clipId, channelIndex, length, visibleChunkIndices]);
2309
- (0, import_react16.useEffect)(() => {
2317
+ (0, import_react17.useEffect)(() => {
2310
2318
  return () => {
2311
2319
  const api = workerApiRef.current;
2312
2320
  if (!api) return;
@@ -2320,9 +2328,16 @@ var SpectrogramChannel = ({
2320
2328
  registeredIdsRef.current = [];
2321
2329
  };
2322
2330
  }, []);
2323
- (0, import_react16.useLayoutEffect)(() => {
2331
+ (0, import_react17.useLayoutEffect)(() => {
2324
2332
  if (isWorkerMode || !data) return;
2325
- const { frequencyBinCount, frameCount, hopSize, sampleRate, gainDb, rangeDb: rawRangeDb } = data;
2333
+ const {
2334
+ frequencyBinCount,
2335
+ frameCount,
2336
+ hopSize,
2337
+ sampleRate,
2338
+ gainDb,
2339
+ rangeDb: rawRangeDb
2340
+ } = data;
2326
2341
  const rangeDb = rawRangeDb === 0 ? 1 : rawRangeDb;
2327
2342
  const binToFreq = (bin) => bin / frequencyBinCount * (sampleRate / 2);
2328
2343
  for (const [canvasIdx, canvas] of canvasMapRef.current.entries()) {
@@ -2386,11 +2401,25 @@ var SpectrogramChannel = ({
2386
2401
  ctx.drawImage(tmpCanvas, 0, 0, canvas.width, canvas.height);
2387
2402
  }
2388
2403
  }
2389
- }, [canvasMapRef, isWorkerMode, data, length, waveHeight, devicePixelRatio, samplesPerPixel, lut, minFrequency, maxF, scaleFn, hasCustomFrequencyScale, visibleChunkIndices]);
2404
+ }, [
2405
+ canvasMapRef,
2406
+ isWorkerMode,
2407
+ data,
2408
+ length,
2409
+ waveHeight,
2410
+ devicePixelRatio,
2411
+ samplesPerPixel,
2412
+ lut,
2413
+ minFrequency,
2414
+ maxF,
2415
+ scaleFn,
2416
+ hasCustomFrequencyScale,
2417
+ visibleChunkIndices
2418
+ ]);
2390
2419
  const canvases = visibleChunkIndices.map((i) => {
2391
2420
  const chunkLeft = i * import_core3.MAX_CANVAS_WIDTH;
2392
2421
  const currentWidth = Math.min(length - chunkLeft, import_core3.MAX_CANVAS_WIDTH);
2393
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
2422
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2394
2423
  SpectrogramCanvas,
2395
2424
  {
2396
2425
  $cssWidth: currentWidth,
@@ -2404,11 +2433,11 @@ var SpectrogramChannel = ({
2404
2433
  `${length}-${i}`
2405
2434
  );
2406
2435
  });
2407
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Wrapper3, { $index: index, $cssWidth: length, $waveHeight: waveHeight, children: canvases });
2436
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Wrapper3, { $index: index, $cssWidth: length, $waveHeight: waveHeight, children: canvases });
2408
2437
  };
2409
2438
 
2410
2439
  // src/components/SmartChannel.tsx
2411
- var import_jsx_runtime21 = require("react/jsx-runtime");
2440
+ var import_jsx_runtime22 = require("react/jsx-runtime");
2412
2441
  var SmartChannel = ({
2413
2442
  isSelected,
2414
2443
  transparentBackground,
@@ -2433,7 +2462,7 @@ var SmartChannel = ({
2433
2462
  const drawMode = theme?.waveformDrawMode || "inverted";
2434
2463
  const hasSpectrogram = spectrogramData || spectrogramWorkerApi;
2435
2464
  if (renderMode === "spectrogram" && hasSpectrogram) {
2436
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2465
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2437
2466
  SpectrogramChannel,
2438
2467
  {
2439
2468
  index: props.index,
@@ -2454,8 +2483,8 @@ var SmartChannel = ({
2454
2483
  }
2455
2484
  if (renderMode === "both" && hasSpectrogram) {
2456
2485
  const halfHeight = Math.floor(waveHeight / 2);
2457
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
2458
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2486
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
2487
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2459
2488
  SpectrogramChannel,
2460
2489
  {
2461
2490
  index: props.index * 2,
@@ -2474,24 +2503,35 @@ var SmartChannel = ({
2474
2503
  onCanvasesReady: spectrogramOnCanvasesReady
2475
2504
  }
2476
2505
  ),
2477
- /* @__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)(
2478
- Channel,
2506
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2507
+ "div",
2479
2508
  {
2480
- ...props,
2481
- index: 0,
2482
- waveOutlineColor,
2483
- waveFillColor,
2484
- waveHeight: halfHeight,
2485
- devicePixelRatio,
2486
- barWidth,
2487
- barGap,
2488
- transparentBackground,
2489
- drawMode
2509
+ style: {
2510
+ position: "absolute",
2511
+ top: (props.index * 2 + 1) * halfHeight,
2512
+ width: props.length,
2513
+ height: halfHeight
2514
+ },
2515
+ children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2516
+ Channel,
2517
+ {
2518
+ ...props,
2519
+ index: 0,
2520
+ waveOutlineColor,
2521
+ waveFillColor,
2522
+ waveHeight: halfHeight,
2523
+ devicePixelRatio,
2524
+ barWidth,
2525
+ barGap,
2526
+ transparentBackground,
2527
+ drawMode
2528
+ }
2529
+ )
2490
2530
  }
2491
- ) })
2531
+ )
2492
2532
  ] });
2493
2533
  }
2494
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2534
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2495
2535
  Channel,
2496
2536
  {
2497
2537
  ...props,
@@ -2508,9 +2548,9 @@ var SmartChannel = ({
2508
2548
  };
2509
2549
 
2510
2550
  // src/components/SpectrogramLabels.tsx
2511
- var import_react17 = require("react");
2551
+ var import_react18 = require("react");
2512
2552
  var import_styled_components21 = __toESM(require("styled-components"));
2513
- var import_jsx_runtime22 = require("react/jsx-runtime");
2553
+ var import_jsx_runtime23 = require("react/jsx-runtime");
2514
2554
  var LABELS_WIDTH = 72;
2515
2555
  var LabelsStickyWrapper = import_styled_components21.default.div`
2516
2556
  position: sticky;
@@ -2560,12 +2600,12 @@ var SpectrogramLabels = ({
2560
2600
  renderMode = "spectrogram",
2561
2601
  hasClipHeaders = false
2562
2602
  }) => {
2563
- const canvasRef = (0, import_react17.useRef)(null);
2603
+ const canvasRef = (0, import_react18.useRef)(null);
2564
2604
  const devicePixelRatio = useDevicePixelRatio();
2565
2605
  const spectrogramHeight = renderMode === "both" ? Math.floor(waveHeight / 2) : waveHeight;
2566
2606
  const totalHeight = numChannels * waveHeight;
2567
2607
  const clipHeaderOffset = hasClipHeaders ? 22 : 0;
2568
- (0, import_react17.useLayoutEffect)(() => {
2608
+ (0, import_react18.useLayoutEffect)(() => {
2569
2609
  const canvas = canvasRef.current;
2570
2610
  if (!canvas) return;
2571
2611
  const ctx = canvas.getContext("2d");
@@ -2591,8 +2631,19 @@ var SpectrogramLabels = ({
2591
2631
  ctx.fillText(text, padding, y);
2592
2632
  }
2593
2633
  }
2594
- }, [waveHeight, numChannels, frequencyScaleFn, minFrequency, maxFrequency, labelsColor, labelsBackground, devicePixelRatio, spectrogramHeight, clipHeaderOffset]);
2595
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(LabelsStickyWrapper, { $height: totalHeight + clipHeaderOffset, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2634
+ }, [
2635
+ waveHeight,
2636
+ numChannels,
2637
+ frequencyScaleFn,
2638
+ minFrequency,
2639
+ maxFrequency,
2640
+ labelsColor,
2641
+ labelsBackground,
2642
+ devicePixelRatio,
2643
+ spectrogramHeight,
2644
+ clipHeaderOffset
2645
+ ]);
2646
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(LabelsStickyWrapper, { $height: totalHeight + clipHeaderOffset, children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2596
2647
  "canvas",
2597
2648
  {
2598
2649
  ref: canvasRef,
@@ -2608,10 +2659,10 @@ var SpectrogramLabels = ({
2608
2659
  };
2609
2660
 
2610
2661
  // src/components/SmartScale.tsx
2611
- var import_react19 = require("react");
2662
+ var import_react20 = require("react");
2612
2663
 
2613
2664
  // src/components/TimeScale.tsx
2614
- var import_react18 = __toESM(require("react"));
2665
+ var import_react19 = __toESM(require("react"));
2615
2666
  var import_styled_components22 = __toESM(require("styled-components"));
2616
2667
 
2617
2668
  // src/utils/conversions.ts
@@ -2636,7 +2687,7 @@ function secondsToPixels(seconds, samplesPerPixel, sampleRate) {
2636
2687
 
2637
2688
  // src/components/TimeScale.tsx
2638
2689
  var import_core4 = require("@waveform-playlist/core");
2639
- var import_jsx_runtime23 = require("react/jsx-runtime");
2690
+ var import_jsx_runtime24 = require("react/jsx-runtime");
2640
2691
  function formatTime2(milliseconds) {
2641
2692
  const seconds = Math.floor(milliseconds / 1e3);
2642
2693
  const s = seconds % 60;
@@ -2693,9 +2744,9 @@ var TimeScale = (props) => {
2693
2744
  samplesPerPixel,
2694
2745
  timeScaleHeight,
2695
2746
  controls: { show: showControls, width: controlWidth }
2696
- } = (0, import_react18.useContext)(PlaylistInfoContext);
2747
+ } = (0, import_react19.useContext)(PlaylistInfoContext);
2697
2748
  const devicePixelRatio = useDevicePixelRatio();
2698
- const { widthX, canvasInfo, timeMarkersWithPositions } = (0, import_react18.useMemo)(() => {
2749
+ const { widthX, canvasInfo, timeMarkersWithPositions } = (0, import_react19.useMemo)(() => {
2699
2750
  const nextCanvasInfo = /* @__PURE__ */ new Map();
2700
2751
  const nextMarkers = [];
2701
2752
  const nextWidthX = secondsToPixels(duration / 1e3, samplesPerPixel, sampleRate);
@@ -2706,7 +2757,7 @@ var TimeScale = (props) => {
2706
2757
  if (counter % marker === 0) {
2707
2758
  const timeMs = counter;
2708
2759
  const timestamp = formatTime2(timeMs);
2709
- const element = renderTimestamp ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_react18.default.Fragment, { children: renderTimestamp(timeMs, pix) }, `timestamp-${counter}`) : /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(TimeStamp, { $left: pix, children: timestamp }, timestamp);
2760
+ const element = renderTimestamp ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_react19.default.Fragment, { children: renderTimestamp(timeMs, pix) }, `timestamp-${counter}`) : /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(TimeStamp, { $left: pix, children: timestamp }, timestamp);
2710
2761
  nextMarkers.push({ pix, element });
2711
2762
  nextCanvasInfo.set(pix, timeScaleHeight);
2712
2763
  } else if (counter % bigStep === 0) {
@@ -2721,12 +2772,21 @@ var TimeScale = (props) => {
2721
2772
  canvasInfo: nextCanvasInfo,
2722
2773
  timeMarkersWithPositions: nextMarkers
2723
2774
  };
2724
- }, [duration, samplesPerPixel, sampleRate, marker, bigStep, secondStep, renderTimestamp, timeScaleHeight]);
2775
+ }, [
2776
+ duration,
2777
+ samplesPerPixel,
2778
+ sampleRate,
2779
+ marker,
2780
+ bigStep,
2781
+ secondStep,
2782
+ renderTimestamp,
2783
+ timeScaleHeight
2784
+ ]);
2725
2785
  const visibleChunkIndices = useVisibleChunkIndices(widthX, import_core4.MAX_CANVAS_WIDTH);
2726
2786
  const visibleChunks = visibleChunkIndices.map((i) => {
2727
2787
  const chunkLeft = i * import_core4.MAX_CANVAS_WIDTH;
2728
2788
  const chunkWidth = Math.min(widthX - chunkLeft, import_core4.MAX_CANVAS_WIDTH);
2729
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2789
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2730
2790
  TimeTickChunk,
2731
2791
  {
2732
2792
  $cssWidth: chunkWidth,
@@ -2743,7 +2803,7 @@ var TimeScale = (props) => {
2743
2803
  const firstChunkLeft = visibleChunkIndices.length > 0 ? visibleChunkIndices[0] * import_core4.MAX_CANVAS_WIDTH : 0;
2744
2804
  const lastChunkRight = visibleChunkIndices.length > 0 ? (visibleChunkIndices[visibleChunkIndices.length - 1] + 1) * import_core4.MAX_CANVAS_WIDTH : Infinity;
2745
2805
  const visibleMarkers = visibleChunkIndices.length > 0 ? timeMarkersWithPositions.filter(({ pix }) => pix >= firstChunkLeft && pix < lastChunkRight).map(({ element }) => element) : timeMarkersWithPositions.map(({ element }) => element);
2746
- (0, import_react18.useLayoutEffect)(() => {
2806
+ (0, import_react19.useLayoutEffect)(() => {
2747
2807
  for (const [chunkIdx, canvas] of canvasMapRef.current.entries()) {
2748
2808
  const ctx = canvas.getContext("2d");
2749
2809
  if (!ctx) continue;
@@ -2761,8 +2821,16 @@ var TimeScale = (props) => {
2761
2821
  ctx.fillRect(localX, scaleY, 1, scaleHeight);
2762
2822
  }
2763
2823
  }
2764
- }, [canvasMapRef, duration, devicePixelRatio, timeColor, timeScaleHeight, canvasInfo, visibleChunkIndices]);
2765
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
2824
+ }, [
2825
+ canvasMapRef,
2826
+ duration,
2827
+ devicePixelRatio,
2828
+ timeColor,
2829
+ timeScaleHeight,
2830
+ canvasInfo,
2831
+ visibleChunkIndices
2832
+ ]);
2833
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
2766
2834
  PlaylistTimeScaleScroll,
2767
2835
  {
2768
2836
  $cssWidth: widthX,
@@ -2778,7 +2846,7 @@ var TimeScale = (props) => {
2778
2846
  var StyledTimeScale = (0, import_styled_components22.withTheme)(TimeScale);
2779
2847
 
2780
2848
  // src/components/SmartScale.tsx
2781
- var import_jsx_runtime24 = require("react/jsx-runtime");
2849
+ var import_jsx_runtime25 = require("react/jsx-runtime");
2782
2850
  var timeinfo = /* @__PURE__ */ new Map([
2783
2851
  [
2784
2852
  700,
@@ -2852,9 +2920,9 @@ function getScaleInfo(samplesPerPixel) {
2852
2920
  return config;
2853
2921
  }
2854
2922
  var SmartScale = ({ renderTimestamp }) => {
2855
- const { samplesPerPixel, duration } = (0, import_react19.useContext)(PlaylistInfoContext);
2923
+ const { samplesPerPixel, duration } = (0, import_react20.useContext)(PlaylistInfoContext);
2856
2924
  let config = getScaleInfo(samplesPerPixel);
2857
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2925
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
2858
2926
  StyledTimeScale,
2859
2927
  {
2860
2928
  marker: config.marker,
@@ -2868,7 +2936,7 @@ var SmartScale = ({ renderTimestamp }) => {
2868
2936
 
2869
2937
  // src/components/TimeFormatSelect.tsx
2870
2938
  var import_styled_components23 = __toESM(require("styled-components"));
2871
- var import_jsx_runtime25 = require("react/jsx-runtime");
2939
+ var import_jsx_runtime26 = require("react/jsx-runtime");
2872
2940
  var SelectWrapper = import_styled_components23.default.div`
2873
2941
  display: inline-flex;
2874
2942
  align-items: center;
@@ -2891,7 +2959,7 @@ var TimeFormatSelect = ({
2891
2959
  const handleChange = (e) => {
2892
2960
  onChange(e.target.value);
2893
2961
  };
2894
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(SelectWrapper, { className, children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
2962
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(SelectWrapper, { className, children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2895
2963
  BaseSelect,
2896
2964
  {
2897
2965
  className: "time-format",
@@ -2899,14 +2967,14 @@ var TimeFormatSelect = ({
2899
2967
  onChange: handleChange,
2900
2968
  disabled,
2901
2969
  "aria-label": "Time format selection",
2902
- children: TIME_FORMAT_OPTIONS.map((option) => /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("option", { value: option.value, children: option.label }, option.value))
2970
+ children: TIME_FORMAT_OPTIONS.map((option) => /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("option", { value: option.value, children: option.label }, option.value))
2903
2971
  }
2904
2972
  ) });
2905
2973
  };
2906
2974
 
2907
2975
  // src/components/Track.tsx
2908
2976
  var import_styled_components24 = __toESM(require("styled-components"));
2909
- var import_jsx_runtime26 = require("react/jsx-runtime");
2977
+ var import_jsx_runtime27 = require("react/jsx-runtime");
2910
2978
  var Container = import_styled_components24.default.div.attrs((props) => ({
2911
2979
  style: {
2912
2980
  height: `${props.$waveHeight * props.$numChannels + (props.$hasClipHeaders ? CLIP_HEADER_HEIGHT : 0)}px`
@@ -2961,7 +3029,7 @@ var Track = ({
2961
3029
  controls: { show, width: controlWidth }
2962
3030
  } = usePlaylistInfo();
2963
3031
  const controls = useTrackControls();
2964
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
3032
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
2965
3033
  Container,
2966
3034
  {
2967
3035
  $numChannels: numChannels,
@@ -2972,15 +3040,8 @@ var Track = ({
2972
3040
  $hasClipHeaders: hasClipHeaders,
2973
3041
  $isSelected: isSelected,
2974
3042
  children: [
2975
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2976
- ControlsWrapper,
2977
- {
2978
- $controlWidth: show ? controlWidth : 0,
2979
- $isSelected: isSelected,
2980
- children: controls
2981
- }
2982
- ),
2983
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
3043
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(ControlsWrapper, { $controlWidth: show ? controlWidth : 0, $isSelected: isSelected, children: controls }),
3044
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
2984
3045
  ChannelContainer,
2985
3046
  {
2986
3047
  $controlWidth: show ? controlWidth : 0,
@@ -3011,8 +3072,11 @@ var Button = import_styled_components25.default.button.attrs({
3011
3072
  font-size: ${(props) => props.theme.fontSizeSmall};
3012
3073
  line-height: 1;
3013
3074
  border-radius: ${(props) => props.theme.borderRadius};
3014
- transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out,
3015
- border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
3075
+ transition:
3076
+ color 0.15s ease-in-out,
3077
+ background-color 0.15s ease-in-out,
3078
+ border-color 0.15s ease-in-out,
3079
+ box-shadow 0.15s ease-in-out;
3016
3080
  cursor: pointer;
3017
3081
 
3018
3082
  ${(props) => {
@@ -3087,8 +3151,8 @@ var ButtonGroup = import_styled_components26.default.div`
3087
3151
 
3088
3152
  // src/components/TrackControls/CloseButton.tsx
3089
3153
  var import_styled_components27 = __toESM(require("styled-components"));
3090
- var import_react20 = require("@phosphor-icons/react");
3091
- var import_jsx_runtime27 = require("react/jsx-runtime");
3154
+ var import_react21 = require("@phosphor-icons/react");
3155
+ var import_jsx_runtime28 = require("react/jsx-runtime");
3092
3156
  var StyledCloseButton = import_styled_components27.default.button`
3093
3157
  position: absolute;
3094
3158
  left: 0;
@@ -3103,17 +3167,16 @@ var StyledCloseButton = import_styled_components27.default.button`
3103
3167
  align-items: center;
3104
3168
  justify-content: center;
3105
3169
  opacity: 0.7;
3106
- transition: opacity 0.15s, color 0.15s;
3170
+ transition:
3171
+ opacity 0.15s,
3172
+ color 0.15s;
3107
3173
 
3108
3174
  &:hover {
3109
3175
  opacity: 1;
3110
3176
  color: #dc3545;
3111
3177
  }
3112
3178
  `;
3113
- var CloseButton = ({
3114
- onClick,
3115
- title = "Remove track"
3116
- }) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(StyledCloseButton, { onClick, title, children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react20.X, { size: 12, weight: "bold" }) });
3179
+ var CloseButton = ({ onClick, title = "Remove track" }) => /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(StyledCloseButton, { onClick, title, children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_react21.X, { size: 12, weight: "bold" }) });
3117
3180
 
3118
3181
  // src/components/TrackControls/Controls.tsx
3119
3182
  var import_styled_components28 = __toESM(require("styled-components"));
@@ -3148,24 +3211,24 @@ var Header = import_styled_components29.default.header`
3148
3211
  `;
3149
3212
 
3150
3213
  // src/components/TrackControls/VolumeDownIcon.tsx
3151
- var import_react21 = require("@phosphor-icons/react");
3152
- var import_jsx_runtime28 = require("react/jsx-runtime");
3153
- var VolumeDownIcon = (props) => /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_react21.SpeakerLowIcon, { weight: "light", ...props });
3154
-
3155
- // src/components/TrackControls/VolumeUpIcon.tsx
3156
3214
  var import_react22 = require("@phosphor-icons/react");
3157
3215
  var import_jsx_runtime29 = require("react/jsx-runtime");
3158
- var VolumeUpIcon = (props) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_react22.SpeakerHighIcon, { weight: "light", ...props });
3216
+ var VolumeDownIcon = (props) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_react22.SpeakerLowIcon, { weight: "light", ...props });
3159
3217
 
3160
- // src/components/TrackControls/TrashIcon.tsx
3218
+ // src/components/TrackControls/VolumeUpIcon.tsx
3161
3219
  var import_react23 = require("@phosphor-icons/react");
3162
3220
  var import_jsx_runtime30 = require("react/jsx-runtime");
3163
- var TrashIcon = (props) => /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_react23.TrashIcon, { weight: "light", ...props });
3221
+ var VolumeUpIcon = (props) => /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_react23.SpeakerHighIcon, { weight: "light", ...props });
3164
3222
 
3165
- // src/components/TrackControls/DotsIcon.tsx
3223
+ // src/components/TrackControls/TrashIcon.tsx
3166
3224
  var import_react24 = require("@phosphor-icons/react");
3167
3225
  var import_jsx_runtime31 = require("react/jsx-runtime");
3168
- var DotsIcon = (props) => /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_react24.DotsThreeIcon, { weight: "bold", ...props });
3226
+ var TrashIcon = (props) => /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_react24.TrashIcon, { weight: "light", ...props });
3227
+
3228
+ // src/components/TrackControls/DotsIcon.tsx
3229
+ var import_react25 = require("@phosphor-icons/react");
3230
+ var import_jsx_runtime32 = require("react/jsx-runtime");
3231
+ var DotsIcon = (props) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_react25.DotsThreeIcon, { weight: "bold", ...props });
3169
3232
 
3170
3233
  // src/components/TrackControls/Slider.tsx
3171
3234
  var import_styled_components30 = __toESM(require("styled-components"));
@@ -3233,10 +3296,10 @@ var SliderWrapper = import_styled_components31.default.label`
3233
3296
  `;
3234
3297
 
3235
3298
  // src/components/TrackMenu.tsx
3236
- var import_react25 = __toESM(require("react"));
3299
+ var import_react26 = __toESM(require("react"));
3237
3300
  var import_react_dom = require("react-dom");
3238
3301
  var import_styled_components32 = __toESM(require("styled-components"));
3239
- var import_jsx_runtime32 = require("react/jsx-runtime");
3302
+ var import_jsx_runtime33 = require("react/jsx-runtime");
3240
3303
  var MenuContainer = import_styled_components32.default.div`
3241
3304
  position: relative;
3242
3305
  display: inline-block;
@@ -3274,16 +3337,14 @@ var Divider = import_styled_components32.default.hr`
3274
3337
  border-top: 1px solid rgba(128, 128, 128, 0.3);
3275
3338
  margin: 0.35rem 0;
3276
3339
  `;
3277
- var TrackMenu = ({
3278
- items: itemsProp
3279
- }) => {
3280
- const [open, setOpen] = (0, import_react25.useState)(false);
3340
+ var TrackMenu = ({ items: itemsProp }) => {
3341
+ const [open, setOpen] = (0, import_react26.useState)(false);
3281
3342
  const close = () => setOpen(false);
3282
3343
  const items = typeof itemsProp === "function" ? itemsProp(close) : itemsProp;
3283
- const [dropdownPos, setDropdownPos] = (0, import_react25.useState)({ top: 0, left: 0 });
3284
- const buttonRef = (0, import_react25.useRef)(null);
3285
- const dropdownRef = (0, import_react25.useRef)(null);
3286
- (0, import_react25.useEffect)(() => {
3344
+ const [dropdownPos, setDropdownPos] = (0, import_react26.useState)({ top: 0, left: 0 });
3345
+ const buttonRef = (0, import_react26.useRef)(null);
3346
+ const dropdownRef = (0, import_react26.useRef)(null);
3347
+ (0, import_react26.useEffect)(() => {
3287
3348
  if (open && buttonRef.current) {
3288
3349
  const rect = buttonRef.current.getBoundingClientRect();
3289
3350
  setDropdownPos({
@@ -3292,7 +3353,7 @@ var TrackMenu = ({
3292
3353
  });
3293
3354
  }
3294
3355
  }, [open]);
3295
- (0, import_react25.useEffect)(() => {
3356
+ (0, import_react26.useEffect)(() => {
3296
3357
  if (!open) return;
3297
3358
  const handleClick = (e) => {
3298
3359
  const target = e.target;
@@ -3303,8 +3364,8 @@ var TrackMenu = ({
3303
3364
  document.addEventListener("mousedown", handleClick);
3304
3365
  return () => document.removeEventListener("mousedown", handleClick);
3305
3366
  }, [open]);
3306
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(MenuContainer, { children: [
3307
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
3367
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(MenuContainer, { children: [
3368
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
3308
3369
  MenuButton,
3309
3370
  {
3310
3371
  ref: buttonRef,
@@ -3315,19 +3376,19 @@ var TrackMenu = ({
3315
3376
  onMouseDown: (e) => e.stopPropagation(),
3316
3377
  title: "Track menu",
3317
3378
  "aria-label": "Track menu",
3318
- children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(DotsIcon, { size: 16 })
3379
+ children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(DotsIcon, { size: 16 })
3319
3380
  }
3320
3381
  ),
3321
3382
  open && typeof document !== "undefined" && (0, import_react_dom.createPortal)(
3322
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
3383
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
3323
3384
  Dropdown,
3324
3385
  {
3325
3386
  ref: dropdownRef,
3326
3387
  $top: dropdownPos.top,
3327
3388
  $left: dropdownPos.left,
3328
3389
  onMouseDown: (e) => e.stopPropagation(),
3329
- children: items.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(import_react25.default.Fragment, { children: [
3330
- index > 0 && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(Divider, {}),
3390
+ children: items.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(import_react26.default.Fragment, { children: [
3391
+ index > 0 && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(Divider, {}),
3331
3392
  item.content
3332
3393
  ] }, item.id))
3333
3394
  }
@@ -3359,6 +3420,7 @@ var TrackMenu = ({
3359
3420
  ClipBoundary,
3360
3421
  ClipHeader,
3361
3422
  ClipHeaderPresentational,
3423
+ ClipViewportOriginProvider,
3362
3424
  CloseButton,
3363
3425
  Controls,
3364
3426
  DevicePixelRatioProvider,
@@ -3408,6 +3470,7 @@ var TrackMenu = ({
3408
3470
  samplesToSeconds,
3409
3471
  secondsToPixels,
3410
3472
  secondsToSamples,
3473
+ useClipViewportOrigin,
3411
3474
  useDevicePixelRatio,
3412
3475
  usePlaylistInfo,
3413
3476
  usePlayoutStatus,