@waveform-playlist/ui-components 5.0.0-alpha.5 → 5.0.0-alpha.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -359,6 +359,10 @@ var defaultTheme = {
359
359
  playheadColor: "#f00",
360
360
  selectionColor: "rgba(255, 105, 180, 0.7)",
361
361
  // hot pink - high contrast on light backgrounds
362
+ loopRegionColor: "rgba(59, 130, 246, 0.3)",
363
+ // Blue - distinct from pink selection
364
+ loopMarkerColor: "#3b82f6",
365
+ // Blue marker triangles
362
366
  clipHeaderBackgroundColor: "rgba(0, 0, 0, 0.1)",
363
367
  clipHeaderBorderColor: "rgba(0, 0, 0, 0.2)",
364
368
  clipHeaderTextColor: "#333",
@@ -430,6 +434,10 @@ var darkTheme = {
430
434
  // Darker Ampelmännchen green playhead
431
435
  selectionColor: "rgba(60, 140, 58, 0.6)",
432
436
  // Darker Ampelmännchen green selection - visible on dark backgrounds
437
+ loopRegionColor: "rgba(96, 165, 250, 0.35)",
438
+ // Light blue - distinct from green selection
439
+ loopMarkerColor: "#60a5fa",
440
+ // Light blue marker triangles
433
441
  clipHeaderBackgroundColor: "rgba(20, 16, 12, 0.85)",
434
442
  // Dark background for clip headers
435
443
  clipHeaderBorderColor: "rgba(200, 160, 120, 0.25)",
@@ -1206,7 +1214,8 @@ var TimescaleWrapper = styled16.div.attrs((props) => ({
1206
1214
  }))`
1207
1215
  background: ${(props) => props.$backgroundColor || "white"};
1208
1216
  width: 100%;
1209
- overflow: visible;
1217
+ position: relative;
1218
+ overflow: hidden; /* Constrain loop region to timescale area */
1210
1219
  `;
1211
1220
  var TracksContainer = styled16.div.attrs((props) => ({
1212
1221
  style: props.$width !== void 0 ? { minWidth: `${props.$width}px` } : {}
@@ -1222,7 +1231,8 @@ var ClickOverlay = styled16.div`
1222
1231
  right: 0;
1223
1232
  bottom: 0;
1224
1233
  cursor: crosshair;
1225
- z-index: 1; /* Low z-index - clip headers and boundaries have higher z-index */
1234
+ /* When selecting, raise z-index above clip boundaries (z-index: 105) to prevent interference */
1235
+ z-index: ${(props) => props.$isSelecting ? 110 : 1};
1226
1236
  `;
1227
1237
  var Playlist = ({
1228
1238
  children,
@@ -1237,7 +1247,8 @@ var Playlist = ({
1237
1247
  onTracksMouseDown,
1238
1248
  onTracksMouseMove,
1239
1249
  onTracksMouseUp,
1240
- scrollContainerRef
1250
+ scrollContainerRef,
1251
+ isSelecting
1241
1252
  }) => {
1242
1253
  return /* @__PURE__ */ jsx10(Wrapper2, { "data-scroll-container": "true", ref: scrollContainerRef, children: /* @__PURE__ */ jsxs5(
1243
1254
  ScrollContainer,
@@ -1252,6 +1263,7 @@ var Playlist = ({
1252
1263
  ClickOverlay,
1253
1264
  {
1254
1265
  $controlsWidth: controlsWidth,
1266
+ $isSelecting: isSelecting,
1255
1267
  onClick: onTracksClick,
1256
1268
  onMouseDown: onTracksMouseDown,
1257
1269
  onMouseMove: onTracksMouseMove,
@@ -1294,11 +1306,341 @@ var Selection = ({
1294
1306
  return /* @__PURE__ */ jsx11(SelectionOverlay, { $left: startPosition, $width: width, $color: color, "data-selection": true });
1295
1307
  };
1296
1308
 
1309
+ // src/components/LoopRegion.tsx
1310
+ import { useCallback as useCallback2, useRef as useRef3, useState } from "react";
1311
+ import styled18 from "styled-components";
1312
+ import { Fragment as Fragment2, jsx as jsx12, jsxs as jsxs6 } from "react/jsx-runtime";
1313
+ var LoopRegionOverlayDiv = styled18.div.attrs((props) => ({
1314
+ style: {
1315
+ left: `${props.$left}px`,
1316
+ width: `${props.$width}px`
1317
+ }
1318
+ }))`
1319
+ position: absolute;
1320
+ top: 0;
1321
+ background: ${(props) => props.$color};
1322
+ height: 100%;
1323
+ z-index: 55; /* Between clips (z-index: 50) and selection (z-index: 60) */
1324
+ pointer-events: none;
1325
+ `;
1326
+ var LoopMarker = styled18.div.attrs((props) => ({
1327
+ style: {
1328
+ left: `${props.$left}px`
1329
+ }
1330
+ }))`
1331
+ position: absolute;
1332
+ top: 0;
1333
+ width: 2px;
1334
+ height: 100%;
1335
+ background: ${(props) => props.$color};
1336
+ z-index: 90; /* Below playhead (z-index: 100) */
1337
+ pointer-events: none;
1338
+
1339
+ /* Triangle marker at top */
1340
+ &::before {
1341
+ content: '';
1342
+ position: absolute;
1343
+ top: 0;
1344
+ ${(props) => props.$isStart ? "left: 0" : "right: 0"};
1345
+ width: 0;
1346
+ height: 0;
1347
+ border-top: 8px solid ${(props) => props.$color};
1348
+ ${(props) => props.$isStart ? "border-right: 8px solid transparent;" : "border-left: 8px solid transparent;"}
1349
+ }
1350
+ `;
1351
+ var LoopRegion = ({
1352
+ startPosition,
1353
+ endPosition,
1354
+ regionColor = "rgba(59, 130, 246, 0.3)",
1355
+ markerColor = "#3b82f6"
1356
+ }) => {
1357
+ const width = Math.max(0, endPosition - startPosition);
1358
+ if (width <= 0) {
1359
+ return null;
1360
+ }
1361
+ return /* @__PURE__ */ jsxs6(Fragment2, { children: [
1362
+ /* @__PURE__ */ jsx12(
1363
+ LoopRegionOverlayDiv,
1364
+ {
1365
+ $left: startPosition,
1366
+ $width: width,
1367
+ $color: regionColor,
1368
+ "data-loop-region": true
1369
+ }
1370
+ ),
1371
+ /* @__PURE__ */ jsx12(
1372
+ LoopMarker,
1373
+ {
1374
+ $left: startPosition,
1375
+ $color: markerColor,
1376
+ $isStart: true,
1377
+ "data-loop-marker": "start"
1378
+ }
1379
+ ),
1380
+ /* @__PURE__ */ jsx12(
1381
+ LoopMarker,
1382
+ {
1383
+ $left: endPosition - 2,
1384
+ $color: markerColor,
1385
+ $isStart: false,
1386
+ "data-loop-marker": "end"
1387
+ }
1388
+ )
1389
+ ] });
1390
+ };
1391
+ var DraggableMarkerHandle = styled18.div.attrs((props) => ({
1392
+ style: {
1393
+ left: `${props.$left}px`
1394
+ }
1395
+ }))`
1396
+ position: absolute;
1397
+ top: 0;
1398
+ width: 12px;
1399
+ height: 100%;
1400
+ cursor: ew-resize;
1401
+ z-index: 100;
1402
+ /* Center the handle on the marker position */
1403
+ transform: translateX(-5px);
1404
+
1405
+ /* Visual marker line */
1406
+ &::before {
1407
+ content: '';
1408
+ position: absolute;
1409
+ top: 0;
1410
+ left: 5px;
1411
+ width: 2px;
1412
+ height: 100%;
1413
+ background: ${(props) => props.$color};
1414
+ opacity: ${(props) => props.$isDragging ? 1 : 0.8};
1415
+ }
1416
+
1417
+ /* Triangle marker at top */
1418
+ &::after {
1419
+ content: '';
1420
+ position: absolute;
1421
+ top: 0;
1422
+ ${(props) => props.$isStart ? "left: 5px" : "left: -1px"};
1423
+ width: 0;
1424
+ height: 0;
1425
+ border-top: 10px solid ${(props) => props.$color};
1426
+ ${(props) => props.$isStart ? "border-right: 10px solid transparent;" : "border-left: 10px solid transparent;"}
1427
+ }
1428
+
1429
+ &:hover::before {
1430
+ opacity: 1;
1431
+ }
1432
+ `;
1433
+ var TimescaleLoopShade = styled18.div.attrs((props) => ({
1434
+ style: {
1435
+ left: `${props.$left}px`,
1436
+ width: `${props.$width}px`
1437
+ }
1438
+ }))`
1439
+ position: absolute;
1440
+ top: 0;
1441
+ height: 100%;
1442
+ background: ${(props) => props.$color};
1443
+ z-index: 50;
1444
+ cursor: grab;
1445
+
1446
+ &:active {
1447
+ cursor: grabbing;
1448
+ }
1449
+ `;
1450
+ var LoopRegionMarkers = ({
1451
+ startPosition,
1452
+ endPosition,
1453
+ markerColor = "#3b82f6",
1454
+ regionColor = "rgba(59, 130, 246, 0.3)",
1455
+ onLoopStartChange,
1456
+ onLoopEndChange,
1457
+ onLoopRegionMove,
1458
+ minPosition = 0,
1459
+ maxPosition = Infinity
1460
+ }) => {
1461
+ const [draggingMarker, setDraggingMarker] = useState(null);
1462
+ const dragStartX = useRef3(0);
1463
+ const dragStartPosition = useRef3(0);
1464
+ const dragStartEnd = useRef3(0);
1465
+ const width = Math.max(0, endPosition - startPosition);
1466
+ const handleMarkerMouseDown = useCallback2((e, marker) => {
1467
+ e.preventDefault();
1468
+ e.stopPropagation();
1469
+ setDraggingMarker(marker);
1470
+ dragStartX.current = e.clientX;
1471
+ dragStartPosition.current = marker === "start" ? startPosition : endPosition;
1472
+ const handleMouseMove = (moveEvent) => {
1473
+ const delta = moveEvent.clientX - dragStartX.current;
1474
+ const newPosition = dragStartPosition.current + delta;
1475
+ if (marker === "start") {
1476
+ const clampedPosition = Math.max(minPosition, Math.min(endPosition - 10, newPosition));
1477
+ onLoopStartChange?.(clampedPosition);
1478
+ } else {
1479
+ const clampedPosition = Math.max(startPosition + 10, Math.min(maxPosition, newPosition));
1480
+ onLoopEndChange?.(clampedPosition);
1481
+ }
1482
+ };
1483
+ const handleMouseUp = () => {
1484
+ setDraggingMarker(null);
1485
+ document.removeEventListener("mousemove", handleMouseMove);
1486
+ document.removeEventListener("mouseup", handleMouseUp);
1487
+ };
1488
+ document.addEventListener("mousemove", handleMouseMove);
1489
+ document.addEventListener("mouseup", handleMouseUp);
1490
+ }, [startPosition, endPosition, minPosition, maxPosition, onLoopStartChange, onLoopEndChange]);
1491
+ const handleRegionMouseDown = useCallback2((e) => {
1492
+ e.preventDefault();
1493
+ e.stopPropagation();
1494
+ setDraggingMarker("region");
1495
+ dragStartX.current = e.clientX;
1496
+ dragStartPosition.current = startPosition;
1497
+ dragStartEnd.current = endPosition;
1498
+ const regionWidth = endPosition - startPosition;
1499
+ const handleMouseMove = (moveEvent) => {
1500
+ const delta = moveEvent.clientX - dragStartX.current;
1501
+ let newStart = dragStartPosition.current + delta;
1502
+ let newEnd = dragStartEnd.current + delta;
1503
+ if (newStart < minPosition) {
1504
+ newStart = minPosition;
1505
+ newEnd = minPosition + regionWidth;
1506
+ }
1507
+ if (newEnd > maxPosition) {
1508
+ newEnd = maxPosition;
1509
+ newStart = maxPosition - regionWidth;
1510
+ }
1511
+ onLoopRegionMove?.(newStart, newEnd);
1512
+ };
1513
+ const handleMouseUp = () => {
1514
+ setDraggingMarker(null);
1515
+ document.removeEventListener("mousemove", handleMouseMove);
1516
+ document.removeEventListener("mouseup", handleMouseUp);
1517
+ };
1518
+ document.addEventListener("mousemove", handleMouseMove);
1519
+ document.addEventListener("mouseup", handleMouseUp);
1520
+ }, [startPosition, endPosition, minPosition, maxPosition, onLoopRegionMove]);
1521
+ if (width <= 0) {
1522
+ return null;
1523
+ }
1524
+ return /* @__PURE__ */ jsxs6(Fragment2, { children: [
1525
+ /* @__PURE__ */ jsx12(
1526
+ TimescaleLoopShade,
1527
+ {
1528
+ $left: startPosition,
1529
+ $width: width,
1530
+ $color: regionColor,
1531
+ $isDragging: draggingMarker === "region",
1532
+ onMouseDown: handleRegionMouseDown,
1533
+ "data-loop-region-timescale": true
1534
+ }
1535
+ ),
1536
+ /* @__PURE__ */ jsx12(
1537
+ DraggableMarkerHandle,
1538
+ {
1539
+ $left: startPosition,
1540
+ $color: markerColor,
1541
+ $isStart: true,
1542
+ $isDragging: draggingMarker === "start",
1543
+ onMouseDown: (e) => handleMarkerMouseDown(e, "start"),
1544
+ "data-loop-marker-handle": "start"
1545
+ }
1546
+ ),
1547
+ /* @__PURE__ */ jsx12(
1548
+ DraggableMarkerHandle,
1549
+ {
1550
+ $left: endPosition,
1551
+ $color: markerColor,
1552
+ $isStart: false,
1553
+ $isDragging: draggingMarker === "end",
1554
+ onMouseDown: (e) => handleMarkerMouseDown(e, "end"),
1555
+ "data-loop-marker-handle": "end"
1556
+ }
1557
+ )
1558
+ ] });
1559
+ };
1560
+ var TimescaleLoopCreator = styled18.div.attrs((props) => ({
1561
+ style: {
1562
+ left: `${props.$leftOffset || 0}px`
1563
+ }
1564
+ }))`
1565
+ position: absolute;
1566
+ top: 0;
1567
+ right: 0;
1568
+ height: 100%; /* Stay within timescale bounds, don't extend into tracks */
1569
+ cursor: crosshair;
1570
+ z-index: 40; /* Below markers and shading */
1571
+ `;
1572
+ var TimescaleLoopRegion = ({
1573
+ startPosition,
1574
+ endPosition,
1575
+ markerColor = "#3b82f6",
1576
+ regionColor = "rgba(59, 130, 246, 0.3)",
1577
+ onLoopRegionChange,
1578
+ minPosition = 0,
1579
+ maxPosition = Infinity,
1580
+ controlsOffset = 0
1581
+ }) => {
1582
+ const [isCreating, setIsCreating] = useState(false);
1583
+ const createStartX = useRef3(0);
1584
+ const containerRef = useRef3(null);
1585
+ const hasLoopRegion = endPosition > startPosition;
1586
+ const handleBackgroundMouseDown = useCallback2((e) => {
1587
+ const target = e.target;
1588
+ if (target.closest("[data-loop-marker-handle]") || target.closest("[data-loop-region-timescale]")) {
1589
+ return;
1590
+ }
1591
+ e.preventDefault();
1592
+ setIsCreating(true);
1593
+ const rect = containerRef.current?.getBoundingClientRect();
1594
+ if (!rect) return;
1595
+ const clickX = e.clientX - rect.left;
1596
+ const clampedX = Math.max(minPosition, Math.min(maxPosition, clickX));
1597
+ createStartX.current = clampedX;
1598
+ onLoopRegionChange?.(clampedX, clampedX);
1599
+ const handleMouseMove = (moveEvent) => {
1600
+ const currentX = moveEvent.clientX - rect.left;
1601
+ const clampedCurrentX = Math.max(minPosition, Math.min(maxPosition, currentX));
1602
+ const newStart = Math.min(createStartX.current, clampedCurrentX);
1603
+ const newEnd = Math.max(createStartX.current, clampedCurrentX);
1604
+ onLoopRegionChange?.(newStart, newEnd);
1605
+ };
1606
+ const handleMouseUp = () => {
1607
+ setIsCreating(false);
1608
+ document.removeEventListener("mousemove", handleMouseMove);
1609
+ document.removeEventListener("mouseup", handleMouseUp);
1610
+ };
1611
+ document.addEventListener("mousemove", handleMouseMove);
1612
+ document.addEventListener("mouseup", handleMouseUp);
1613
+ }, [minPosition, maxPosition, onLoopRegionChange]);
1614
+ return /* @__PURE__ */ jsx12(
1615
+ TimescaleLoopCreator,
1616
+ {
1617
+ ref: containerRef,
1618
+ $leftOffset: controlsOffset,
1619
+ onMouseDown: handleBackgroundMouseDown,
1620
+ "data-timescale-loop-creator": true,
1621
+ children: hasLoopRegion && /* @__PURE__ */ jsx12(
1622
+ LoopRegionMarkers,
1623
+ {
1624
+ startPosition,
1625
+ endPosition,
1626
+ markerColor,
1627
+ regionColor,
1628
+ minPosition,
1629
+ maxPosition,
1630
+ onLoopStartChange: (newStart) => onLoopRegionChange?.(newStart, endPosition),
1631
+ onLoopEndChange: (newEnd) => onLoopRegionChange?.(startPosition, newEnd),
1632
+ onLoopRegionMove: (newStart, newEnd) => onLoopRegionChange?.(newStart, newEnd)
1633
+ }
1634
+ )
1635
+ }
1636
+ );
1637
+ };
1638
+
1297
1639
  // src/components/SelectionTimeInputs.tsx
1298
- import { useEffect as useEffect3, useState as useState2 } from "react";
1640
+ import { useEffect as useEffect3, useState as useState3 } from "react";
1299
1641
 
1300
1642
  // src/components/TimeInput.tsx
1301
- import { useEffect as useEffect2, useState } from "react";
1643
+ import { useEffect as useEffect2, useState as useState2 } from "react";
1302
1644
 
1303
1645
  // src/utils/timeFormat.ts
1304
1646
  function clockFormat(seconds, decimals) {
@@ -1348,7 +1690,7 @@ function parseTime(timeStr, format) {
1348
1690
  }
1349
1691
 
1350
1692
  // src/components/TimeInput.tsx
1351
- import { Fragment as Fragment2, jsx as jsx12, jsxs as jsxs6 } from "react/jsx-runtime";
1693
+ import { Fragment as Fragment3, jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
1352
1694
  var TimeInput = ({
1353
1695
  id,
1354
1696
  label,
@@ -1358,7 +1700,7 @@ var TimeInput = ({
1358
1700
  onChange,
1359
1701
  readOnly = false
1360
1702
  }) => {
1361
- const [displayValue, setDisplayValue] = useState("");
1703
+ const [displayValue, setDisplayValue] = useState2("");
1362
1704
  useEffect2(() => {
1363
1705
  const formatted = formatTime(value, format);
1364
1706
  setDisplayValue(formatted);
@@ -1379,9 +1721,9 @@ var TimeInput = ({
1379
1721
  e.currentTarget.blur();
1380
1722
  }
1381
1723
  };
1382
- return /* @__PURE__ */ jsxs6(Fragment2, { children: [
1383
- /* @__PURE__ */ jsx12(ScreenReaderOnly, { as: "label", htmlFor: id, children: label }),
1384
- /* @__PURE__ */ jsx12(
1724
+ return /* @__PURE__ */ jsxs7(Fragment3, { children: [
1725
+ /* @__PURE__ */ jsx13(ScreenReaderOnly, { as: "label", htmlFor: id, children: label }),
1726
+ /* @__PURE__ */ jsx13(
1385
1727
  BaseInput,
1386
1728
  {
1387
1729
  type: "text",
@@ -1398,14 +1740,14 @@ var TimeInput = ({
1398
1740
  };
1399
1741
 
1400
1742
  // src/components/SelectionTimeInputs.tsx
1401
- import { Fragment as Fragment3, jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
1743
+ import { Fragment as Fragment4, jsx as jsx14, jsxs as jsxs8 } from "react/jsx-runtime";
1402
1744
  var SelectionTimeInputs = ({
1403
1745
  selectionStart,
1404
1746
  selectionEnd,
1405
1747
  onSelectionChange,
1406
1748
  className
1407
1749
  }) => {
1408
- const [timeFormat, setTimeFormat] = useState2("hh:mm:ss.uuu");
1750
+ const [timeFormat, setTimeFormat] = useState3("hh:mm:ss.uuu");
1409
1751
  useEffect3(() => {
1410
1752
  const timeFormatSelect = document.querySelector(".time-format");
1411
1753
  const handleFormatChange = () => {
@@ -1431,8 +1773,8 @@ var SelectionTimeInputs = ({
1431
1773
  onSelectionChange(selectionStart, value);
1432
1774
  }
1433
1775
  };
1434
- return /* @__PURE__ */ jsxs7(Fragment3, { children: [
1435
- /* @__PURE__ */ jsx13(
1776
+ return /* @__PURE__ */ jsxs8(Fragment4, { children: [
1777
+ /* @__PURE__ */ jsx14(
1436
1778
  TimeInput,
1437
1779
  {
1438
1780
  id: "audio_start",
@@ -1443,7 +1785,7 @@ var SelectionTimeInputs = ({
1443
1785
  onChange: handleStartChange
1444
1786
  }
1445
1787
  ),
1446
- /* @__PURE__ */ jsx13(
1788
+ /* @__PURE__ */ jsx14(
1447
1789
  TimeInput,
1448
1790
  {
1449
1791
  id: "audio_end",
@@ -1458,14 +1800,14 @@ var SelectionTimeInputs = ({
1458
1800
  };
1459
1801
 
1460
1802
  // src/contexts/DevicePixelRatio.tsx
1461
- import { useState as useState3, createContext, useContext } from "react";
1462
- import { jsx as jsx14 } from "react/jsx-runtime";
1803
+ import { useState as useState4, createContext, useContext } from "react";
1804
+ import { jsx as jsx15 } from "react/jsx-runtime";
1463
1805
  function getScale() {
1464
1806
  return window.devicePixelRatio;
1465
1807
  }
1466
1808
  var DevicePixelRatioContext = createContext(getScale());
1467
1809
  var DevicePixelRatioProvider = ({ children }) => {
1468
- const [scale, setScale] = useState3(getScale());
1810
+ const [scale, setScale] = useState4(getScale());
1469
1811
  matchMedia(`(resolution: ${getScale()}dppx)`).addEventListener(
1470
1812
  "change",
1471
1813
  () => {
@@ -1473,7 +1815,7 @@ var DevicePixelRatioProvider = ({ children }) => {
1473
1815
  },
1474
1816
  { once: true }
1475
1817
  );
1476
- return /* @__PURE__ */ jsx14(DevicePixelRatioContext.Provider, { value: Math.ceil(scale), children });
1818
+ return /* @__PURE__ */ jsx15(DevicePixelRatioContext.Provider, { value: Math.ceil(scale), children });
1477
1819
  };
1478
1820
  var useDevicePixelRatio = () => useContext(DevicePixelRatioContext);
1479
1821
 
@@ -1501,18 +1843,18 @@ import { ThemeContext } from "styled-components";
1501
1843
  var useTheme2 = () => useContext3(ThemeContext);
1502
1844
 
1503
1845
  // src/contexts/TrackControls.tsx
1504
- import { createContext as createContext3, useContext as useContext4, Fragment as Fragment4 } from "react";
1505
- import { jsx as jsx15 } from "react/jsx-runtime";
1506
- var TrackControlsContext = createContext3(/* @__PURE__ */ jsx15(Fragment4, {}));
1846
+ import { createContext as createContext3, useContext as useContext4, Fragment as Fragment5 } from "react";
1847
+ import { jsx as jsx16 } from "react/jsx-runtime";
1848
+ var TrackControlsContext = createContext3(/* @__PURE__ */ jsx16(Fragment5, {}));
1507
1849
  var useTrackControls = () => useContext4(TrackControlsContext);
1508
1850
 
1509
1851
  // src/contexts/Playout.tsx
1510
1852
  import {
1511
- useState as useState4,
1853
+ useState as useState5,
1512
1854
  createContext as createContext4,
1513
1855
  useContext as useContext5
1514
1856
  } from "react";
1515
- import { jsx as jsx16 } from "react/jsx-runtime";
1857
+ import { jsx as jsx17 } from "react/jsx-runtime";
1516
1858
  var defaultProgress = 0;
1517
1859
  var defaultIsPlaying = false;
1518
1860
  var defaultSelectionStart = 0;
@@ -1533,21 +1875,21 @@ var PlayoutStatusUpdateContext = createContext4({
1533
1875
  }
1534
1876
  });
1535
1877
  var PlayoutProvider = ({ children }) => {
1536
- const [isPlaying, setIsPlaying] = useState4(defaultIsPlaying);
1537
- const [progress, setProgress] = useState4(defaultProgress);
1538
- const [selectionStart, setSelectionStart] = useState4(defaultSelectionStart);
1539
- const [selectionEnd, setSelectionEnd] = useState4(defaultSelectionEnd);
1878
+ const [isPlaying, setIsPlaying] = useState5(defaultIsPlaying);
1879
+ const [progress, setProgress] = useState5(defaultProgress);
1880
+ const [selectionStart, setSelectionStart] = useState5(defaultSelectionStart);
1881
+ const [selectionEnd, setSelectionEnd] = useState5(defaultSelectionEnd);
1540
1882
  const setSelection = (start, end) => {
1541
1883
  setSelectionStart(start);
1542
1884
  setSelectionEnd(end);
1543
1885
  };
1544
- return /* @__PURE__ */ jsx16(PlayoutStatusUpdateContext.Provider, { value: { setIsPlaying, setProgress, setSelection }, children: /* @__PURE__ */ jsx16(PlayoutStatusContext.Provider, { value: { isPlaying, progress, selectionStart, selectionEnd }, children }) });
1886
+ return /* @__PURE__ */ jsx17(PlayoutStatusUpdateContext.Provider, { value: { setIsPlaying, setProgress, setSelection }, children: /* @__PURE__ */ jsx17(PlayoutStatusContext.Provider, { value: { isPlaying, progress, selectionStart, selectionEnd }, children }) });
1545
1887
  };
1546
1888
  var usePlayoutStatus = () => useContext5(PlayoutStatusContext);
1547
1889
  var usePlayoutStatusUpdate = () => useContext5(PlayoutStatusUpdateContext);
1548
1890
 
1549
1891
  // src/components/SmartChannel.tsx
1550
- import { jsx as jsx17 } from "react/jsx-runtime";
1892
+ import { jsx as jsx18 } from "react/jsx-runtime";
1551
1893
  var SmartChannel = ({ isSelected, transparentBackground, ...props }) => {
1552
1894
  const theme = useTheme2();
1553
1895
  const { waveHeight, barWidth, barGap } = usePlaylistInfo();
@@ -1555,7 +1897,7 @@ var SmartChannel = ({ isSelected, transparentBackground, ...props }) => {
1555
1897
  const waveOutlineColor = isSelected && theme ? theme.selectedWaveOutlineColor : theme?.waveOutlineColor;
1556
1898
  const waveFillColor = isSelected && theme ? theme.selectedWaveFillColor : theme?.waveFillColor;
1557
1899
  const drawMode = theme?.waveformDrawMode || "inverted";
1558
- return /* @__PURE__ */ jsx17(
1900
+ return /* @__PURE__ */ jsx18(
1559
1901
  Channel,
1560
1902
  {
1561
1903
  ...props,
@@ -1576,8 +1918,8 @@ var SmartChannel = ({ isSelected, transparentBackground, ...props }) => {
1576
1918
  import { useContext as useContext7 } from "react";
1577
1919
 
1578
1920
  // src/components/TimeScale.tsx
1579
- import React9, { useRef as useRef3, useEffect as useEffect4, useContext as useContext6 } from "react";
1580
- import styled18, { withTheme as withTheme2 } from "styled-components";
1921
+ import React10, { useRef as useRef4, useEffect as useEffect4, useContext as useContext6 } from "react";
1922
+ import styled19, { withTheme as withTheme2 } from "styled-components";
1581
1923
 
1582
1924
  // src/utils/conversions.ts
1583
1925
  function samplesToSeconds(samples, sampleRate) {
@@ -1600,14 +1942,14 @@ function secondsToPixels(seconds, samplesPerPixel, sampleRate) {
1600
1942
  }
1601
1943
 
1602
1944
  // src/components/TimeScale.tsx
1603
- import { jsx as jsx18, jsxs as jsxs8 } from "react/jsx-runtime";
1945
+ import { jsx as jsx19, jsxs as jsxs9 } from "react/jsx-runtime";
1604
1946
  function formatTime2(milliseconds) {
1605
1947
  const seconds = Math.floor(milliseconds / 1e3);
1606
1948
  const s = seconds % 60;
1607
1949
  const m = (seconds - s) / 60;
1608
1950
  return `${m}:${String(s).padStart(2, "0")}`;
1609
1951
  }
1610
- var PlaylistTimeScaleScroll = styled18.div.attrs((props) => ({
1952
+ var PlaylistTimeScaleScroll = styled19.div.attrs((props) => ({
1611
1953
  style: {
1612
1954
  width: `${props.$cssWidth}px`,
1613
1955
  marginLeft: `${props.$controlWidth}px`,
@@ -1619,7 +1961,7 @@ var PlaylistTimeScaleScroll = styled18.div.attrs((props) => ({
1619
1961
  border-bottom: 1px solid ${(props) => props.theme.timeColor};
1620
1962
  box-sizing: border-box;
1621
1963
  `;
1622
- var TimeTicks = styled18.canvas.attrs((props) => ({
1964
+ var TimeTicks = styled19.canvas.attrs((props) => ({
1623
1965
  style: {
1624
1966
  width: `${props.$cssWidth}px`,
1625
1967
  height: `${props.$timeScaleHeight}px`
@@ -1630,7 +1972,7 @@ var TimeTicks = styled18.canvas.attrs((props) => ({
1630
1972
  right: 0;
1631
1973
  bottom: 0;
1632
1974
  `;
1633
- var TimeStamp = styled18.div.attrs((props) => ({
1975
+ var TimeStamp = styled19.div.attrs((props) => ({
1634
1976
  style: {
1635
1977
  left: `${props.$left + 4}px`
1636
1978
  // Offset 4px to the right of the tick
@@ -1652,7 +1994,7 @@ var TimeScale = (props) => {
1652
1994
  } = props;
1653
1995
  const canvasInfo = /* @__PURE__ */ new Map();
1654
1996
  const timeMarkers = [];
1655
- const canvasRef = useRef3(null);
1997
+ const canvasRef = useRef4(null);
1656
1998
  const {
1657
1999
  sampleRate,
1658
2000
  samplesPerPixel,
@@ -1694,7 +2036,7 @@ var TimeScale = (props) => {
1694
2036
  if (counter % marker === 0) {
1695
2037
  const timeMs = counter;
1696
2038
  const timestamp = formatTime2(timeMs);
1697
- const timestampContent = renderTimestamp ? /* @__PURE__ */ jsx18(React9.Fragment, { children: renderTimestamp(timeMs, pix) }, `timestamp-${counter}`) : /* @__PURE__ */ jsx18(TimeStamp, { $left: pix, children: timestamp }, timestamp);
2039
+ const timestampContent = renderTimestamp ? /* @__PURE__ */ jsx19(React10.Fragment, { children: renderTimestamp(timeMs, pix) }, `timestamp-${counter}`) : /* @__PURE__ */ jsx19(TimeStamp, { $left: pix, children: timestamp }, timestamp);
1698
2040
  timeMarkers.push(timestampContent);
1699
2041
  canvasInfo.set(pix, timeScaleHeight);
1700
2042
  } else if (counter % bigStep === 0) {
@@ -1704,7 +2046,7 @@ var TimeScale = (props) => {
1704
2046
  }
1705
2047
  counter += secondStep;
1706
2048
  }
1707
- return /* @__PURE__ */ jsxs8(
2049
+ return /* @__PURE__ */ jsxs9(
1708
2050
  PlaylistTimeScaleScroll,
1709
2051
  {
1710
2052
  $cssWidth: widthX,
@@ -1712,7 +2054,7 @@ var TimeScale = (props) => {
1712
2054
  $timeScaleHeight: timeScaleHeight,
1713
2055
  children: [
1714
2056
  timeMarkers,
1715
- /* @__PURE__ */ jsx18(
2057
+ /* @__PURE__ */ jsx19(
1716
2058
  TimeTicks,
1717
2059
  {
1718
2060
  $cssWidth: widthX,
@@ -1729,7 +2071,7 @@ var TimeScale = (props) => {
1729
2071
  var StyledTimeScale = withTheme2(TimeScale);
1730
2072
 
1731
2073
  // src/components/SmartScale.tsx
1732
- import { jsx as jsx19 } from "react/jsx-runtime";
2074
+ import { jsx as jsx20 } from "react/jsx-runtime";
1733
2075
  var timeinfo = /* @__PURE__ */ new Map([
1734
2076
  [
1735
2077
  700,
@@ -1805,7 +2147,7 @@ function getScaleInfo(samplesPerPixel) {
1805
2147
  var SmartScale = () => {
1806
2148
  const { samplesPerPixel, duration } = useContext7(PlaylistInfoContext);
1807
2149
  let config = getScaleInfo(samplesPerPixel);
1808
- return /* @__PURE__ */ jsx19(
2150
+ return /* @__PURE__ */ jsx20(
1809
2151
  StyledTimeScale,
1810
2152
  {
1811
2153
  marker: config.marker,
@@ -1817,9 +2159,9 @@ var SmartScale = () => {
1817
2159
  };
1818
2160
 
1819
2161
  // src/components/TimeFormatSelect.tsx
1820
- import styled19 from "styled-components";
1821
- import { jsx as jsx20 } from "react/jsx-runtime";
1822
- var SelectWrapper = styled19.div`
2162
+ import styled20 from "styled-components";
2163
+ import { jsx as jsx21 } from "react/jsx-runtime";
2164
+ var SelectWrapper = styled20.div`
1823
2165
  display: inline-flex;
1824
2166
  align-items: center;
1825
2167
  gap: 0.5rem;
@@ -1841,7 +2183,7 @@ var TimeFormatSelect = ({
1841
2183
  const handleChange = (e) => {
1842
2184
  onChange(e.target.value);
1843
2185
  };
1844
- return /* @__PURE__ */ jsx20(SelectWrapper, { className, children: /* @__PURE__ */ jsx20(
2186
+ return /* @__PURE__ */ jsx21(SelectWrapper, { className, children: /* @__PURE__ */ jsx21(
1845
2187
  BaseSelect,
1846
2188
  {
1847
2189
  className: "time-format",
@@ -1849,15 +2191,15 @@ var TimeFormatSelect = ({
1849
2191
  onChange: handleChange,
1850
2192
  disabled,
1851
2193
  "aria-label": "Time format selection",
1852
- children: TIME_FORMAT_OPTIONS.map((option) => /* @__PURE__ */ jsx20("option", { value: option.value, children: option.label }, option.value))
2194
+ children: TIME_FORMAT_OPTIONS.map((option) => /* @__PURE__ */ jsx21("option", { value: option.value, children: option.label }, option.value))
1853
2195
  }
1854
2196
  ) });
1855
2197
  };
1856
2198
 
1857
2199
  // src/components/Track.tsx
1858
- import styled20 from "styled-components";
1859
- import { jsx as jsx21, jsxs as jsxs9 } from "react/jsx-runtime";
1860
- var Container = styled20.div.attrs((props) => ({
2200
+ import styled21 from "styled-components";
2201
+ import { jsx as jsx22, jsxs as jsxs10 } from "react/jsx-runtime";
2202
+ var Container = styled21.div.attrs((props) => ({
1861
2203
  style: {
1862
2204
  height: `${props.$waveHeight * props.$numChannels + (props.$hasClipHeaders ? CLIP_HEADER_HEIGHT : 0)}px`
1863
2205
  }
@@ -1866,7 +2208,7 @@ var Container = styled20.div.attrs((props) => ({
1866
2208
  display: flex;
1867
2209
  ${(props) => props.$width !== void 0 && `width: ${props.$width}px;`}
1868
2210
  `;
1869
- var ChannelContainer = styled20.div.attrs((props) => ({
2211
+ var ChannelContainer = styled21.div.attrs((props) => ({
1870
2212
  style: {
1871
2213
  paddingLeft: `${props.$offset || 0}px`
1872
2214
  }
@@ -1875,7 +2217,7 @@ var ChannelContainer = styled20.div.attrs((props) => ({
1875
2217
  background: ${(props) => props.$backgroundColor || "transparent"};
1876
2218
  flex: 1;
1877
2219
  `;
1878
- var ControlsWrapper = styled20.div.attrs((props) => ({
2220
+ var ControlsWrapper = styled21.div.attrs((props) => ({
1879
2221
  style: {
1880
2222
  width: `${props.$controlWidth}px`
1881
2223
  }
@@ -1911,7 +2253,7 @@ var Track = ({
1911
2253
  controls: { show, width: controlWidth }
1912
2254
  } = usePlaylistInfo();
1913
2255
  const controls = useTrackControls();
1914
- return /* @__PURE__ */ jsxs9(
2256
+ return /* @__PURE__ */ jsxs10(
1915
2257
  Container,
1916
2258
  {
1917
2259
  $numChannels: numChannels,
@@ -1922,7 +2264,7 @@ var Track = ({
1922
2264
  $hasClipHeaders: hasClipHeaders,
1923
2265
  $isSelected: isSelected,
1924
2266
  children: [
1925
- /* @__PURE__ */ jsx21(
2267
+ /* @__PURE__ */ jsx22(
1926
2268
  ControlsWrapper,
1927
2269
  {
1928
2270
  $controlWidth: show ? controlWidth : 0,
@@ -1930,7 +2272,7 @@ var Track = ({
1930
2272
  children: controls
1931
2273
  }
1932
2274
  ),
1933
- /* @__PURE__ */ jsx21(
2275
+ /* @__PURE__ */ jsx22(
1934
2276
  ChannelContainer,
1935
2277
  {
1936
2278
  $controlWidth: show ? controlWidth : 0,
@@ -1947,8 +2289,8 @@ var Track = ({
1947
2289
  };
1948
2290
 
1949
2291
  // src/components/TrackControls/Button.tsx
1950
- import styled21 from "styled-components";
1951
- var Button = styled21.button.attrs({
2292
+ import styled22 from "styled-components";
2293
+ var Button = styled22.button.attrs({
1952
2294
  type: "button"
1953
2295
  })`
1954
2296
  display: inline-block;
@@ -2020,8 +2362,8 @@ var Button = styled21.button.attrs({
2020
2362
  `;
2021
2363
 
2022
2364
  // src/components/TrackControls/ButtonGroup.tsx
2023
- import styled22 from "styled-components";
2024
- var ButtonGroup = styled22.div`
2365
+ import styled23 from "styled-components";
2366
+ var ButtonGroup = styled23.div`
2025
2367
  margin-bottom: 0.3rem;
2026
2368
 
2027
2369
  button:not(:first-child) {
@@ -2036,8 +2378,8 @@ var ButtonGroup = styled22.div`
2036
2378
  `;
2037
2379
 
2038
2380
  // src/components/TrackControls/Controls.tsx
2039
- import styled23 from "styled-components";
2040
- var Controls = styled23.div`
2381
+ import styled24 from "styled-components";
2382
+ var Controls = styled24.div`
2041
2383
  background: transparent;
2042
2384
  width: 100%;
2043
2385
  height: 100%;
@@ -2053,8 +2395,8 @@ var Controls = styled23.div`
2053
2395
  `;
2054
2396
 
2055
2397
  // src/components/TrackControls/Header.tsx
2056
- import styled24 from "styled-components";
2057
- var Header = styled24.header`
2398
+ import styled25 from "styled-components";
2399
+ var Header = styled25.header`
2058
2400
  overflow: hidden;
2059
2401
  height: 26px;
2060
2402
  width: 100%;
@@ -2069,22 +2411,22 @@ var Header = styled24.header`
2069
2411
 
2070
2412
  // src/components/TrackControls/VolumeDownIcon.tsx
2071
2413
  import { SpeakerLowIcon } from "@phosphor-icons/react";
2072
- import { jsx as jsx22 } from "react/jsx-runtime";
2073
- var VolumeDownIcon = (props) => /* @__PURE__ */ jsx22(SpeakerLowIcon, { weight: "light", ...props });
2414
+ import { jsx as jsx23 } from "react/jsx-runtime";
2415
+ var VolumeDownIcon = (props) => /* @__PURE__ */ jsx23(SpeakerLowIcon, { weight: "light", ...props });
2074
2416
 
2075
2417
  // src/components/TrackControls/VolumeUpIcon.tsx
2076
2418
  import { SpeakerHighIcon } from "@phosphor-icons/react";
2077
- import { jsx as jsx23 } from "react/jsx-runtime";
2078
- var VolumeUpIcon = (props) => /* @__PURE__ */ jsx23(SpeakerHighIcon, { weight: "light", ...props });
2419
+ import { jsx as jsx24 } from "react/jsx-runtime";
2420
+ var VolumeUpIcon = (props) => /* @__PURE__ */ jsx24(SpeakerHighIcon, { weight: "light", ...props });
2079
2421
 
2080
2422
  // src/components/TrackControls/TrashIcon.tsx
2081
2423
  import { TrashIcon as PhosphorTrashIcon } from "@phosphor-icons/react";
2082
- import { jsx as jsx24 } from "react/jsx-runtime";
2083
- var TrashIcon = (props) => /* @__PURE__ */ jsx24(PhosphorTrashIcon, { weight: "light", ...props });
2424
+ import { jsx as jsx25 } from "react/jsx-runtime";
2425
+ var TrashIcon = (props) => /* @__PURE__ */ jsx25(PhosphorTrashIcon, { weight: "light", ...props });
2084
2426
 
2085
2427
  // src/components/TrackControls/Slider.tsx
2086
- import styled25 from "styled-components";
2087
- var Slider = styled25(BaseSlider)`
2428
+ import styled26 from "styled-components";
2429
+ var Slider = styled26(BaseSlider)`
2088
2430
  width: 75%;
2089
2431
  height: 5px;
2090
2432
  background: ${(props) => props.theme.sliderTrackColor};
@@ -2136,8 +2478,8 @@ var Slider = styled25(BaseSlider)`
2136
2478
  `;
2137
2479
 
2138
2480
  // src/components/TrackControls/SliderWrapper.tsx
2139
- import styled26 from "styled-components";
2140
- var SliderWrapper = styled26.label`
2481
+ import styled27 from "styled-components";
2482
+ var SliderWrapper = styled27.label`
2141
2483
  width: 100%;
2142
2484
  display: flex;
2143
2485
  justify-content: space-between;
@@ -2148,15 +2490,15 @@ var SliderWrapper = styled26.label`
2148
2490
  `;
2149
2491
 
2150
2492
  // src/components/TrackControlsWithDelete.tsx
2151
- import styled27 from "styled-components";
2152
- import { jsx as jsx25, jsxs as jsxs10 } from "react/jsx-runtime";
2153
- var HeaderContainer2 = styled27.div`
2493
+ import styled28 from "styled-components";
2494
+ import { jsx as jsx26, jsxs as jsxs11 } from "react/jsx-runtime";
2495
+ var HeaderContainer2 = styled28.div`
2154
2496
  display: flex;
2155
2497
  align-items: center;
2156
2498
  gap: 0.25rem;
2157
2499
  padding: 0.5rem 0.5rem 0.25rem 0.5rem;
2158
2500
  `;
2159
- var TrackNameSpan = styled27.span`
2501
+ var TrackNameSpan = styled28.span`
2160
2502
  flex: 1;
2161
2503
  font-weight: 600;
2162
2504
  font-size: 0.875rem;
@@ -2165,7 +2507,7 @@ var TrackNameSpan = styled27.span`
2165
2507
  white-space: nowrap;
2166
2508
  margin: 0 0.25rem;
2167
2509
  `;
2168
- var DeleteIconButton = styled27.button`
2510
+ var DeleteIconButton = styled28.button`
2169
2511
  display: flex;
2170
2512
  align-items: center;
2171
2513
  justify-content: center;
@@ -2203,13 +2545,13 @@ var TrackControlsWithDelete = ({
2203
2545
  onPanChange,
2204
2546
  onDelete
2205
2547
  }) => {
2206
- return /* @__PURE__ */ jsxs10(Controls, { children: [
2207
- /* @__PURE__ */ jsxs10(HeaderContainer2, { children: [
2208
- /* @__PURE__ */ jsx25(DeleteIconButton, { onClick: onDelete, title: "Delete track", children: /* @__PURE__ */ jsx25(TrashIcon, {}) }),
2209
- /* @__PURE__ */ jsx25(TrackNameSpan, { children: trackName })
2548
+ return /* @__PURE__ */ jsxs11(Controls, { children: [
2549
+ /* @__PURE__ */ jsxs11(HeaderContainer2, { children: [
2550
+ /* @__PURE__ */ jsx26(DeleteIconButton, { onClick: onDelete, title: "Delete track", children: /* @__PURE__ */ jsx26(TrashIcon, {}) }),
2551
+ /* @__PURE__ */ jsx26(TrackNameSpan, { children: trackName })
2210
2552
  ] }),
2211
- /* @__PURE__ */ jsxs10(ButtonGroup, { children: [
2212
- /* @__PURE__ */ jsx25(
2553
+ /* @__PURE__ */ jsxs11(ButtonGroup, { children: [
2554
+ /* @__PURE__ */ jsx26(
2213
2555
  Button,
2214
2556
  {
2215
2557
  $variant: muted ? "danger" : "outline",
@@ -2217,7 +2559,7 @@ var TrackControlsWithDelete = ({
2217
2559
  children: "Mute"
2218
2560
  }
2219
2561
  ),
2220
- /* @__PURE__ */ jsx25(
2562
+ /* @__PURE__ */ jsx26(
2221
2563
  Button,
2222
2564
  {
2223
2565
  $variant: soloed ? "info" : "outline",
@@ -2226,9 +2568,9 @@ var TrackControlsWithDelete = ({
2226
2568
  }
2227
2569
  )
2228
2570
  ] }),
2229
- /* @__PURE__ */ jsxs10(SliderWrapper, { children: [
2230
- /* @__PURE__ */ jsx25(VolumeDownIcon, {}),
2231
- /* @__PURE__ */ jsx25(
2571
+ /* @__PURE__ */ jsxs11(SliderWrapper, { children: [
2572
+ /* @__PURE__ */ jsx26(VolumeDownIcon, {}),
2573
+ /* @__PURE__ */ jsx26(
2232
2574
  Slider,
2233
2575
  {
2234
2576
  min: "0",
@@ -2238,11 +2580,11 @@ var TrackControlsWithDelete = ({
2238
2580
  onChange: (e) => onVolumeChange(parseFloat(e.target.value))
2239
2581
  }
2240
2582
  ),
2241
- /* @__PURE__ */ jsx25(VolumeUpIcon, {})
2583
+ /* @__PURE__ */ jsx26(VolumeUpIcon, {})
2242
2584
  ] }),
2243
- /* @__PURE__ */ jsxs10(SliderWrapper, { children: [
2244
- /* @__PURE__ */ jsx25("span", { children: "L" }),
2245
- /* @__PURE__ */ jsx25(
2585
+ /* @__PURE__ */ jsxs11(SliderWrapper, { children: [
2586
+ /* @__PURE__ */ jsx26("span", { children: "L" }),
2587
+ /* @__PURE__ */ jsx26(
2246
2588
  Slider,
2247
2589
  {
2248
2590
  min: "-1",
@@ -2252,7 +2594,7 @@ var TrackControlsWithDelete = ({
2252
2594
  onChange: (e) => onPanChange(parseFloat(e.target.value))
2253
2595
  }
2254
2596
  ),
2255
- /* @__PURE__ */ jsx25("span", { children: "R" })
2597
+ /* @__PURE__ */ jsx26("span", { children: "R" })
2256
2598
  ] })
2257
2599
  ] });
2258
2600
  };
@@ -2283,6 +2625,8 @@ export {
2283
2625
  FadeOverlay,
2284
2626
  Header,
2285
2627
  InlineLabel,
2628
+ LoopRegion,
2629
+ LoopRegionMarkers,
2286
2630
  MasterVolumeControl,
2287
2631
  Playhead,
2288
2632
  PlayheadWithMarker,
@@ -2301,6 +2645,7 @@ export {
2301
2645
  TimeFormatSelect,
2302
2646
  TimeInput,
2303
2647
  TimeScale,
2648
+ TimescaleLoopRegion,
2304
2649
  Track,
2305
2650
  TrackControlsContext,
2306
2651
  TrackControlsWithDelete,