@waveform-playlist/ui-components 5.0.0-alpha.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 ADDED
@@ -0,0 +1,2419 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.tsx
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ AudioPosition: () => AudioPosition,
34
+ AutomaticScrollCheckbox: () => AutomaticScrollCheckbox,
35
+ BaseButton: () => BaseButton,
36
+ BaseCheckbox: () => BaseCheckbox,
37
+ BaseCheckboxLabel: () => BaseCheckboxLabel,
38
+ BaseCheckboxWrapper: () => BaseCheckboxWrapper,
39
+ BaseControlButton: () => BaseControlButton,
40
+ BaseInput: () => BaseInput,
41
+ BaseLabel: () => BaseLabel,
42
+ BaseSelect: () => BaseSelect,
43
+ BaseSlider: () => BaseSlider,
44
+ Button: () => Button,
45
+ ButtonGroup: () => ButtonGroup,
46
+ CLIP_BOUNDARY_WIDTH: () => CLIP_BOUNDARY_WIDTH,
47
+ CLIP_HEADER_HEIGHT: () => CLIP_HEADER_HEIGHT,
48
+ Channel: () => Channel,
49
+ Clip: () => Clip,
50
+ ClipBoundary: () => ClipBoundary,
51
+ ClipHeader: () => ClipHeader,
52
+ ClipHeaderPresentational: () => ClipHeaderPresentational,
53
+ Controls: () => Controls,
54
+ DevicePixelRatioProvider: () => DevicePixelRatioProvider,
55
+ FadeOverlay: () => FadeOverlay,
56
+ Header: () => Header,
57
+ InlineLabel: () => InlineLabel,
58
+ MasterVolumeControl: () => MasterVolumeControl,
59
+ Playhead: () => Playhead,
60
+ PlayheadWithMarker: () => PlayheadWithMarker,
61
+ Playlist: () => Playlist,
62
+ PlaylistInfoContext: () => PlaylistInfoContext,
63
+ PlayoutProvider: () => PlayoutProvider,
64
+ ScreenReaderOnly: () => ScreenReaderOnly,
65
+ Selection: () => Selection,
66
+ SelectionTimeInputs: () => SelectionTimeInputs,
67
+ Slider: () => Slider,
68
+ SliderWrapper: () => SliderWrapper,
69
+ SmartChannel: () => SmartChannel,
70
+ SmartScale: () => SmartScale,
71
+ StyledPlaylist: () => StyledPlaylist,
72
+ StyledTimeScale: () => StyledTimeScale,
73
+ TimeFormatSelect: () => TimeFormatSelect,
74
+ TimeInput: () => TimeInput,
75
+ TimeScale: () => TimeScale,
76
+ Track: () => Track,
77
+ TrackControlsContext: () => TrackControlsContext,
78
+ TrackControlsWithDelete: () => TrackControlsWithDelete,
79
+ TrashIcon: () => TrashIcon,
80
+ VolumeDownIcon: () => VolumeDownIcon,
81
+ VolumeUpIcon: () => VolumeUpIcon,
82
+ darkTheme: () => darkTheme,
83
+ defaultTheme: () => defaultTheme,
84
+ formatTime: () => formatTime,
85
+ isWaveformGradient: () => isWaveformGradient,
86
+ parseTime: () => parseTime,
87
+ pixelsToSamples: () => pixelsToSamples,
88
+ pixelsToSeconds: () => pixelsToSeconds,
89
+ samplesToPixels: () => samplesToPixels,
90
+ samplesToSeconds: () => samplesToSeconds,
91
+ secondsToPixels: () => secondsToPixels,
92
+ secondsToSamples: () => secondsToSamples,
93
+ useDevicePixelRatio: () => useDevicePixelRatio,
94
+ usePlaylistInfo: () => usePlaylistInfo,
95
+ usePlayoutStatus: () => usePlayoutStatus,
96
+ usePlayoutStatusUpdate: () => usePlayoutStatusUpdate,
97
+ useTheme: () => useTheme2,
98
+ useTrackControls: () => useTrackControls,
99
+ waveformColorToCss: () => waveformColorToCss
100
+ });
101
+ module.exports = __toCommonJS(index_exports);
102
+
103
+ // src/components/AudioPosition.tsx
104
+ var import_styled_components = __toESM(require("styled-components"));
105
+ var import_jsx_runtime = require("react/jsx-runtime");
106
+ var PositionDisplay = import_styled_components.default.span`
107
+ font-family: 'Courier New', Monaco, monospace;
108
+ font-size: 1rem;
109
+ font-weight: 600;
110
+ color: ${(props) => props.theme?.textColor || "#333"};
111
+ user-select: none;
112
+ `;
113
+ var AudioPosition = ({
114
+ formattedTime,
115
+ className
116
+ }) => {
117
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PositionDisplay, { className, "aria-label": "Audio position", children: formattedTime });
118
+ };
119
+
120
+ // src/styled/BaseButton.tsx
121
+ var import_styled_components2 = __toESM(require("styled-components"));
122
+ var BaseButton = import_styled_components2.default.button`
123
+ display: inline-flex;
124
+ align-items: center;
125
+ justify-content: center;
126
+ padding: 0.5rem 1rem;
127
+ font-family: ${(props) => props.theme.fontFamily};
128
+ font-size: ${(props) => props.theme.fontSize};
129
+ font-weight: 500;
130
+ color: ${(props) => props.theme.buttonText};
131
+ background-color: ${(props) => props.theme.buttonBackground};
132
+ border: 1px solid ${(props) => props.theme.buttonBorder};
133
+ border-radius: ${(props) => props.theme.borderRadius};
134
+ cursor: pointer;
135
+ outline: none;
136
+ transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out,
137
+ box-shadow 0.15s ease-in-out;
138
+
139
+ &:hover:not(:disabled) {
140
+ background-color: ${(props) => props.theme.buttonHoverBackground};
141
+ }
142
+
143
+ &:focus {
144
+ box-shadow: 0 0 0 2px ${(props) => props.theme.inputFocusBorder}33;
145
+ }
146
+
147
+ &:disabled {
148
+ opacity: 0.6;
149
+ cursor: not-allowed;
150
+ }
151
+ `;
152
+ var BaseButtonSmall = (0, import_styled_components2.default)(BaseButton)`
153
+ padding: 0.25rem 0.5rem;
154
+ font-size: ${(props) => props.theme.fontSizeSmall};
155
+ `;
156
+ var IconButton = (0, import_styled_components2.default)(BaseButton)`
157
+ padding: 0.5rem;
158
+ min-width: 2.25rem;
159
+ min-height: 2.25rem;
160
+ `;
161
+ var IconButtonSmall = (0, import_styled_components2.default)(BaseButton)`
162
+ padding: 0.25rem;
163
+ min-width: 1.75rem;
164
+ min-height: 1.75rem;
165
+ font-size: ${(props) => props.theme.fontSizeSmall};
166
+ `;
167
+
168
+ // src/styled/BaseCheckbox.tsx
169
+ var import_styled_components3 = __toESM(require("styled-components"));
170
+ var BaseCheckboxWrapper = import_styled_components3.default.div`
171
+ display: inline-flex;
172
+ align-items: center;
173
+ gap: 0.5rem;
174
+ `;
175
+ var BaseCheckbox = import_styled_components3.default.input`
176
+ cursor: pointer;
177
+ accent-color: ${(props) => props.theme.inputFocusBorder};
178
+
179
+ &:disabled {
180
+ cursor: not-allowed;
181
+ }
182
+ `;
183
+ var BaseCheckboxLabel = import_styled_components3.default.label`
184
+ margin: 0;
185
+ cursor: pointer;
186
+ user-select: none;
187
+ font-family: ${(props) => props.theme.fontFamily};
188
+ font-size: ${(props) => props.theme.fontSize};
189
+ color: ${(props) => props.theme.textColor};
190
+ `;
191
+
192
+ // src/styled/BaseControlButton.tsx
193
+ var import_styled_components4 = __toESM(require("styled-components"));
194
+ var BaseControlButton = import_styled_components4.default.button`
195
+ padding: 0.5rem 1rem;
196
+ background: ${(props) => props.theme.buttonBackground || "#007bff"};
197
+ color: ${(props) => props.theme.buttonText || "white"};
198
+ border: none;
199
+ border-radius: ${(props) => props.theme.borderRadius};
200
+ cursor: pointer;
201
+ font-family: ${(props) => props.theme.fontFamily};
202
+ font-size: ${(props) => props.theme.fontSize};
203
+ font-weight: 500;
204
+ transition: background-color 0.15s ease-in-out;
205
+
206
+ &:hover:not(:disabled) {
207
+ background: ${(props) => props.theme.buttonHoverBackground || "#0056b3"};
208
+ }
209
+
210
+ &:focus {
211
+ outline: none;
212
+ box-shadow: 0 0 0 2px ${(props) => props.theme.buttonBackground || "#007bff"}66;
213
+ }
214
+
215
+ &:disabled {
216
+ background: #6c757d;
217
+ cursor: not-allowed;
218
+ opacity: 0.6;
219
+ }
220
+ `;
221
+
222
+ // src/styled/BaseInput.tsx
223
+ var import_styled_components5 = __toESM(require("styled-components"));
224
+ var BaseInput = import_styled_components5.default.input`
225
+ padding: 0.5rem 0.75rem;
226
+ font-family: ${(props) => props.theme.fontFamily};
227
+ font-size: ${(props) => props.theme.fontSize};
228
+ color: ${(props) => props.theme.inputText};
229
+ background-color: ${(props) => props.theme.inputBackground};
230
+ border: 1px solid ${(props) => props.theme.inputBorder};
231
+ border-radius: ${(props) => props.theme.borderRadius};
232
+ outline: none;
233
+ transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
234
+
235
+ &::placeholder {
236
+ color: ${(props) => props.theme.inputPlaceholder};
237
+ }
238
+
239
+ &:focus {
240
+ border-color: ${(props) => props.theme.inputFocusBorder};
241
+ box-shadow: 0 0 0 2px ${(props) => props.theme.inputFocusBorder}33;
242
+ }
243
+
244
+ &:disabled {
245
+ opacity: 0.6;
246
+ cursor: not-allowed;
247
+ }
248
+ `;
249
+ var BaseInputSmall = (0, import_styled_components5.default)(BaseInput)`
250
+ padding: 0.25rem 0.5rem;
251
+ font-size: ${(props) => props.theme.fontSizeSmall};
252
+ `;
253
+
254
+ // src/styled/BaseLabel.tsx
255
+ var import_styled_components6 = __toESM(require("styled-components"));
256
+ var BaseLabel = import_styled_components6.default.label`
257
+ font-family: ${(props) => props.theme.fontFamily};
258
+ font-size: ${(props) => props.theme.fontSizeSmall};
259
+ font-weight: 500;
260
+ color: ${(props) => props.theme.textColorMuted};
261
+ margin-bottom: 0.25rem;
262
+ display: block;
263
+ `;
264
+ var InlineLabel = import_styled_components6.default.label`
265
+ font-family: ${(props) => props.theme.fontFamily};
266
+ font-size: ${(props) => props.theme.fontSize};
267
+ color: ${(props) => props.theme.textColor};
268
+ display: inline-flex;
269
+ align-items: center;
270
+ gap: 0.5rem;
271
+ cursor: pointer;
272
+ `;
273
+ var ScreenReaderOnly = import_styled_components6.default.span`
274
+ position: absolute;
275
+ width: 1px;
276
+ height: 1px;
277
+ padding: 0;
278
+ margin: -1px;
279
+ overflow: hidden;
280
+ clip: rect(0, 0, 0, 0);
281
+ white-space: nowrap;
282
+ border: 0;
283
+ `;
284
+
285
+ // src/styled/BaseSelect.tsx
286
+ var import_styled_components7 = __toESM(require("styled-components"));
287
+ var BaseSelect = import_styled_components7.default.select`
288
+ padding: 0.5rem 2rem 0.5rem 0.75rem;
289
+ font-family: ${(props) => props.theme.fontFamily};
290
+ font-size: ${(props) => props.theme.fontSize};
291
+ color: ${(props) => props.theme.inputText};
292
+ background-color: ${(props) => props.theme.inputBackground};
293
+ border: 1px solid ${(props) => props.theme.inputBorder};
294
+ border-radius: ${(props) => props.theme.borderRadius};
295
+ outline: none;
296
+ cursor: pointer;
297
+ appearance: none;
298
+ 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");
299
+ background-repeat: no-repeat;
300
+ background-position: right 0.75rem center;
301
+ transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
302
+
303
+ &:focus {
304
+ border-color: ${(props) => props.theme.inputFocusBorder};
305
+ box-shadow: 0 0 0 2px ${(props) => props.theme.inputFocusBorder}33;
306
+ }
307
+
308
+ &:disabled {
309
+ opacity: 0.6;
310
+ cursor: not-allowed;
311
+ }
312
+
313
+ /* Style native option elements for dark mode support */
314
+ option {
315
+ color: ${(props) => props.theme.inputText};
316
+ background-color: ${(props) => props.theme.inputBackground};
317
+ }
318
+ `;
319
+ var BaseSelectSmall = (0, import_styled_components7.default)(BaseSelect)`
320
+ padding: 0.25rem 1.75rem 0.25rem 0.5rem;
321
+ font-size: ${(props) => props.theme.fontSizeSmall};
322
+ `;
323
+
324
+ // src/styled/BaseSlider.tsx
325
+ var import_styled_components8 = __toESM(require("styled-components"));
326
+ var BaseSlider = import_styled_components8.default.input.attrs({ type: "range" })`
327
+ -webkit-appearance: none;
328
+ appearance: none;
329
+ width: 100%;
330
+ height: 6px;
331
+ background: ${(props) => props.theme.sliderTrackColor};
332
+ border-radius: 3px;
333
+ cursor: pointer;
334
+ outline: none;
335
+
336
+ /* WebKit (Chrome, Safari) */
337
+ &::-webkit-slider-thumb {
338
+ -webkit-appearance: none;
339
+ appearance: none;
340
+ width: 16px;
341
+ height: 16px;
342
+ background: ${(props) => props.theme.sliderThumbColor};
343
+ border: 2px solid ${(props) => props.theme.inputBackground};
344
+ border-radius: 50%;
345
+ cursor: pointer;
346
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
347
+ transition: transform 0.15s ease, box-shadow 0.15s ease;
348
+ }
349
+
350
+ &::-webkit-slider-thumb:hover {
351
+ transform: scale(1.1);
352
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
353
+ }
354
+
355
+ /* Firefox */
356
+ &::-moz-range-thumb {
357
+ width: 16px;
358
+ height: 16px;
359
+ background: ${(props) => props.theme.sliderThumbColor};
360
+ border: 2px solid ${(props) => props.theme.inputBackground};
361
+ border-radius: 50%;
362
+ cursor: pointer;
363
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
364
+ transition: transform 0.15s ease, box-shadow 0.15s ease;
365
+ }
366
+
367
+ &::-moz-range-thumb:hover {
368
+ transform: scale(1.1);
369
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
370
+ }
371
+
372
+ &::-moz-range-track {
373
+ background: ${(props) => props.theme.sliderTrackColor};
374
+ border-radius: 3px;
375
+ height: 6px;
376
+ }
377
+
378
+ &:focus {
379
+ outline: none;
380
+ }
381
+
382
+ &:focus::-webkit-slider-thumb {
383
+ box-shadow: 0 0 0 3px ${(props) => props.theme.inputFocusBorder}33;
384
+ }
385
+
386
+ &:focus::-moz-range-thumb {
387
+ box-shadow: 0 0 0 3px ${(props) => props.theme.inputFocusBorder}33;
388
+ }
389
+
390
+ &:disabled {
391
+ cursor: not-allowed;
392
+ opacity: 0.5;
393
+ }
394
+
395
+ &:disabled::-webkit-slider-thumb {
396
+ cursor: not-allowed;
397
+ }
398
+
399
+ &:disabled::-moz-range-thumb {
400
+ cursor: not-allowed;
401
+ }
402
+ `;
403
+
404
+ // src/components/AutomaticScrollCheckbox.tsx
405
+ var import_jsx_runtime2 = require("react/jsx-runtime");
406
+ var AutomaticScrollCheckbox = ({
407
+ checked,
408
+ onChange,
409
+ disabled = false,
410
+ className
411
+ }) => {
412
+ const handleChange = (e) => {
413
+ onChange(e.target.checked);
414
+ };
415
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(BaseCheckboxWrapper, { className, children: [
416
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
417
+ BaseCheckbox,
418
+ {
419
+ type: "checkbox",
420
+ id: "automatic-scroll",
421
+ className: "automatic-scroll",
422
+ checked,
423
+ onChange: handleChange,
424
+ disabled
425
+ }
426
+ ),
427
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(BaseCheckboxLabel, { htmlFor: "automatic-scroll", children: "Automatic Scroll" })
428
+ ] });
429
+ };
430
+
431
+ // src/components/Channel.tsx
432
+ var import_react = require("react");
433
+ var import_styled_components9 = __toESM(require("styled-components"));
434
+
435
+ // src/wfpl-theme.ts
436
+ function isWaveformGradient(color) {
437
+ return typeof color === "object" && color !== null && "type" in color;
438
+ }
439
+ function waveformColorToCss(color) {
440
+ if (!isWaveformGradient(color)) {
441
+ return color;
442
+ }
443
+ const direction = color.direction === "vertical" ? "to bottom" : "to right";
444
+ const stops = color.stops.map((stop) => `${stop.color} ${stop.offset * 100}%`).join(", ");
445
+ return `linear-gradient(${direction}, ${stops})`;
446
+ }
447
+ var defaultTheme = {
448
+ waveformDrawMode: "inverted",
449
+ waveOutlineColor: "#ffffff",
450
+ waveFillColor: "#1a7f8e",
451
+ // White background for crisp look
452
+ waveProgressColor: "rgba(0, 0, 0, 0.10)",
453
+ // Subtle dark overlay for light mode
454
+ selectedWaveOutlineColor: "#ffffff",
455
+ selectedWaveFillColor: "#00b4d8",
456
+ // Selected: brighter cyan
457
+ selectedTrackControlsBackground: "#d9e9ff",
458
+ // Light blue background for selected track controls
459
+ timeColor: "#000",
460
+ timescaleBackgroundColor: "#fff",
461
+ playheadColor: "#f00",
462
+ selectionColor: "rgba(255, 105, 180, 0.7)",
463
+ // hot pink - high contrast on light backgrounds
464
+ clipHeaderBackgroundColor: "rgba(0, 0, 0, 0.1)",
465
+ clipHeaderBorderColor: "rgba(0, 0, 0, 0.2)",
466
+ clipHeaderTextColor: "#333",
467
+ clipHeaderFontFamily: "inherit",
468
+ selectedClipHeaderBackgroundColor: "#b3d9ff",
469
+ // Brighter blue for selected track clip headers
470
+ // Fade overlay colors
471
+ fadeOverlayColor: "rgba(0, 0, 0, 0.4)",
472
+ // Semi-transparent overlay for fade regions
473
+ // UI component colors
474
+ backgroundColor: "#ffffff",
475
+ surfaceColor: "#f5f5f5",
476
+ borderColor: "#ddd",
477
+ textColor: "#333",
478
+ textColorMuted: "#666",
479
+ // Interactive element colors
480
+ inputBackground: "#ffffff",
481
+ inputBorder: "#ccc",
482
+ inputText: "#333",
483
+ inputPlaceholder: "#999",
484
+ inputFocusBorder: "#0066cc",
485
+ // Button colors - blue to match common UI patterns
486
+ buttonBackground: "#0091ff",
487
+ buttonText: "#ffffff",
488
+ buttonBorder: "#0081e6",
489
+ buttonHoverBackground: "#0081e6",
490
+ // Slider colors
491
+ sliderTrackColor: "#ddd",
492
+ sliderThumbColor: "#daa520",
493
+ // goldenrod
494
+ // Annotation colors
495
+ annotationBoxBackground: "rgba(255, 255, 255, 0.85)",
496
+ annotationBoxActiveBackground: "rgba(255, 255, 255, 0.95)",
497
+ annotationBoxHoverBackground: "rgba(255, 255, 255, 0.98)",
498
+ annotationBoxBorder: "#ff9800",
499
+ annotationBoxActiveBorder: "#d67600",
500
+ annotationLabelColor: "#2a2a2a",
501
+ annotationResizeHandleColor: "rgba(0, 0, 0, 0.4)",
502
+ annotationResizeHandleActiveColor: "rgba(0, 0, 0, 0.8)",
503
+ annotationTextItemHoverBackground: "rgba(0, 0, 0, 0.03)",
504
+ // Spacing and sizing
505
+ borderRadius: "4px",
506
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, sans-serif',
507
+ fontSize: "14px",
508
+ fontSizeSmall: "12px"
509
+ };
510
+ var darkTheme = {
511
+ // Normal mode: waveOutlineColor = bars, waveFillColor = background
512
+ waveformDrawMode: "inverted",
513
+ // Dark bars on warm amber background
514
+ waveOutlineColor: "#c49a6c",
515
+ // Solid warm amber for background
516
+ waveFillColor: "#1a1612",
517
+ // Very dark warm brown for bars
518
+ waveProgressColor: "rgba(100, 70, 40, 0.6)",
519
+ // Warm brown progress overlay
520
+ // Selected: slightly lighter bars on brighter amber background
521
+ selectedWaveFillColor: "#241c14",
522
+ // Slightly lighter warm brown bars when selected
523
+ selectedWaveOutlineColor: "#e8c090",
524
+ // Brighter amber background when selected
525
+ selectedTrackControlsBackground: "#2a2218",
526
+ // Dark warm brown for selected track controls
527
+ timeColor: "#d8c0a8",
528
+ // Warm amber for timescale text
529
+ timescaleBackgroundColor: "#1a1612",
530
+ // Dark warm brown background
531
+ playheadColor: "#3a8838",
532
+ // Darker Ampelmännchen green playhead
533
+ selectionColor: "rgba(224, 160, 100, 0.5)",
534
+ // Warm amber selection
535
+ clipHeaderBackgroundColor: "rgba(20, 16, 12, 0.85)",
536
+ // Dark background for clip headers
537
+ clipHeaderBorderColor: "rgba(200, 160, 120, 0.25)",
538
+ clipHeaderTextColor: "#d8c0a8",
539
+ // Warm amber text
540
+ clipHeaderFontFamily: "inherit",
541
+ selectedClipHeaderBackgroundColor: "#3a2c20",
542
+ // Darker warm brown for selected clip headers
543
+ // Fade overlay colors
544
+ fadeOverlayColor: "rgba(200, 100, 80, 0.5)",
545
+ // Warm red-orange overlay visible on dark backgrounds
546
+ // UI component colors
547
+ backgroundColor: "#1e1e1e",
548
+ surfaceColor: "#2d2d2d",
549
+ borderColor: "#444",
550
+ textColor: "#e0e0e0",
551
+ textColorMuted: "#999",
552
+ // Interactive element colors
553
+ inputBackground: "#2d2d2d",
554
+ inputBorder: "#555",
555
+ inputText: "#e0e0e0",
556
+ inputPlaceholder: "#777",
557
+ inputFocusBorder: "#4A9EFF",
558
+ // Button colors - Ampelmännchen green (#63C75F) with black text
559
+ buttonBackground: "#63C75F",
560
+ buttonText: "#0a0a0f",
561
+ buttonBorder: "#52b84e",
562
+ buttonHoverBackground: "#78d074",
563
+ // Slider colors
564
+ sliderTrackColor: "#555",
565
+ sliderThumbColor: "#f0c040",
566
+ // brighter goldenrod for dark mode
567
+ // Annotation colors (dark mode - warm amber theme)
568
+ annotationBoxBackground: "rgba(40, 32, 24, 0.9)",
569
+ annotationBoxActiveBackground: "rgba(50, 40, 30, 0.95)",
570
+ annotationBoxHoverBackground: "rgba(60, 48, 36, 0.98)",
571
+ annotationBoxBorder: "#c49a6c",
572
+ annotationBoxActiveBorder: "#d4a87c",
573
+ annotationLabelColor: "#d8c0a8",
574
+ annotationResizeHandleColor: "rgba(200, 160, 120, 0.5)",
575
+ annotationResizeHandleActiveColor: "rgba(220, 180, 140, 0.8)",
576
+ annotationTextItemHoverBackground: "rgba(200, 160, 120, 0.08)",
577
+ // Spacing and sizing
578
+ borderRadius: "4px",
579
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, sans-serif',
580
+ fontSize: "14px",
581
+ fontSizeSmall: "12px"
582
+ };
583
+
584
+ // src/components/Channel.tsx
585
+ var import_jsx_runtime3 = require("react/jsx-runtime");
586
+ var MAX_CANVAS_WIDTH = 1e3;
587
+ function createCanvasFillStyle(ctx, color, width, height) {
588
+ if (!isWaveformGradient(color)) {
589
+ return color;
590
+ }
591
+ let gradient;
592
+ if (color.direction === "vertical") {
593
+ gradient = ctx.createLinearGradient(0, 0, 0, height);
594
+ } else {
595
+ gradient = ctx.createLinearGradient(0, 0, width, 0);
596
+ }
597
+ for (const stop of color.stops) {
598
+ gradient.addColorStop(stop.offset, stop.color);
599
+ }
600
+ return gradient;
601
+ }
602
+ var Waveform = import_styled_components9.default.canvas.attrs((props) => ({
603
+ style: {
604
+ width: `${props.$cssWidth}px`,
605
+ height: `${props.$waveHeight}px`
606
+ }
607
+ }))`
608
+ float: left;
609
+ position: relative;
610
+ /* Promote to own compositing layer for smoother scrolling */
611
+ will-change: transform;
612
+ /* Disable image rendering interpolation */
613
+ image-rendering: pixelated;
614
+ image-rendering: crisp-edges;
615
+ `;
616
+ var Wrapper = import_styled_components9.default.div.attrs((props) => ({
617
+ style: {
618
+ top: `${props.$waveHeight * props.$index}px`,
619
+ width: `${props.$cssWidth}px`,
620
+ height: `${props.$waveHeight}px`
621
+ }
622
+ }))`
623
+ position: absolute;
624
+ background: ${(props) => props.$waveFillColor};
625
+ /* Force GPU compositing layer to reduce scroll flickering */
626
+ transform: translateZ(0);
627
+ backface-visibility: hidden;
628
+ `;
629
+ var Channel = (props) => {
630
+ const {
631
+ data,
632
+ bits,
633
+ length,
634
+ index,
635
+ className,
636
+ devicePixelRatio = 1,
637
+ waveHeight = 80,
638
+ waveOutlineColor = "#E0EFF1",
639
+ waveFillColor = "grey",
640
+ barWidth = 1,
641
+ barGap = 0,
642
+ transparentBackground = false,
643
+ drawMode = "inverted"
644
+ } = props;
645
+ const canvasesRef = (0, import_react.useRef)([]);
646
+ const canvasRef = (0, import_react.useCallback)(
647
+ (canvas) => {
648
+ if (canvas !== null) {
649
+ const index2 = parseInt(canvas.dataset.index, 10);
650
+ canvasesRef.current[index2] = canvas;
651
+ }
652
+ },
653
+ []
654
+ );
655
+ (0, import_react.useLayoutEffect)(() => {
656
+ const canvases = canvasesRef.current;
657
+ const step = barWidth + barGap;
658
+ let globalPixelOffset = 0;
659
+ for (let i = 0; i < canvases.length; i++) {
660
+ const canvas = canvases[i];
661
+ const ctx = canvas.getContext("2d");
662
+ const h2 = Math.floor(waveHeight / 2);
663
+ const maxValue = 2 ** (bits - 1);
664
+ if (ctx) {
665
+ ctx.resetTransform();
666
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
667
+ ctx.imageSmoothingEnabled = false;
668
+ ctx.scale(devicePixelRatio, devicePixelRatio);
669
+ const canvasWidth = canvas.width / devicePixelRatio;
670
+ let fillColor;
671
+ if (drawMode === "normal") {
672
+ fillColor = waveFillColor;
673
+ } else {
674
+ fillColor = waveOutlineColor;
675
+ }
676
+ ctx.fillStyle = createCanvasFillStyle(
677
+ ctx,
678
+ fillColor,
679
+ canvasWidth,
680
+ waveHeight
681
+ );
682
+ const canvasStartGlobal = globalPixelOffset;
683
+ const canvasEndGlobal = globalPixelOffset + canvasWidth;
684
+ const firstBarGlobal = Math.floor((canvasStartGlobal - barWidth + step) / step) * step;
685
+ for (let barGlobal = Math.max(0, firstBarGlobal); barGlobal < canvasEndGlobal; barGlobal += step) {
686
+ const x = barGlobal - canvasStartGlobal;
687
+ if (x + barWidth <= 0) continue;
688
+ const peakIndex = barGlobal;
689
+ if (peakIndex * 2 + 1 < data.length) {
690
+ const minPeak = data[peakIndex * 2] / maxValue;
691
+ const maxPeak = data[peakIndex * 2 + 1] / maxValue;
692
+ const min = Math.abs(minPeak * h2);
693
+ const max = Math.abs(maxPeak * h2);
694
+ if (drawMode === "normal") {
695
+ ctx.fillRect(x, h2 - max, barWidth, max + min);
696
+ } else {
697
+ ctx.fillRect(x, 0, barWidth, h2 - max);
698
+ ctx.fillRect(x, h2 + min, barWidth, h2 - min);
699
+ }
700
+ }
701
+ }
702
+ }
703
+ globalPixelOffset += canvas.width / devicePixelRatio;
704
+ }
705
+ }, [
706
+ data,
707
+ bits,
708
+ waveHeight,
709
+ waveOutlineColor,
710
+ waveFillColor,
711
+ devicePixelRatio,
712
+ length,
713
+ barWidth,
714
+ barGap,
715
+ drawMode
716
+ ]);
717
+ let totalWidth = length;
718
+ let waveformCount = 0;
719
+ const waveforms = [];
720
+ while (totalWidth > 0) {
721
+ const currentWidth = Math.min(totalWidth, MAX_CANVAS_WIDTH);
722
+ const waveform = /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
723
+ Waveform,
724
+ {
725
+ $cssWidth: currentWidth,
726
+ width: currentWidth * devicePixelRatio,
727
+ height: waveHeight * devicePixelRatio,
728
+ $waveHeight: waveHeight,
729
+ "data-index": waveformCount,
730
+ ref: canvasRef
731
+ },
732
+ `${length}-${waveformCount}`
733
+ );
734
+ waveforms.push(waveform);
735
+ totalWidth -= currentWidth;
736
+ waveformCount += 1;
737
+ }
738
+ const bgColor = waveFillColor;
739
+ const backgroundCss = transparentBackground ? "transparent" : waveformColorToCss(bgColor);
740
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
741
+ Wrapper,
742
+ {
743
+ $index: index,
744
+ $cssWidth: length,
745
+ className,
746
+ $waveHeight: waveHeight,
747
+ $waveFillColor: backgroundCss,
748
+ children: waveforms
749
+ }
750
+ );
751
+ };
752
+
753
+ // src/components/Clip.tsx
754
+ var import_styled_components13 = __toESM(require("styled-components"));
755
+ var import_core = require("@dnd-kit/core");
756
+ var import_utilities = require("@dnd-kit/utilities");
757
+
758
+ // src/components/ClipHeader.tsx
759
+ var import_styled_components10 = __toESM(require("styled-components"));
760
+ var import_jsx_runtime4 = require("react/jsx-runtime");
761
+ var CLIP_HEADER_HEIGHT = 22;
762
+ var HeaderContainer = import_styled_components10.default.div`
763
+ position: relative;
764
+ height: ${CLIP_HEADER_HEIGHT}px;
765
+ background: ${(props) => props.$isSelected ? props.theme.selectedClipHeaderBackgroundColor : props.theme.clipHeaderBackgroundColor};
766
+ border-bottom: 1px solid ${(props) => props.theme.clipHeaderBorderColor};
767
+ display: flex;
768
+ align-items: center;
769
+ padding: 0 8px;
770
+ cursor: ${(props) => props.$interactive ? props.$isDragging ? "grabbing" : "grab" : "default"};
771
+ user-select: none;
772
+ z-index: 110;
773
+ flex-shrink: 0;
774
+ pointer-events: auto; /* Re-enable pointer events (parent ClipContainer has pointer-events: none) */
775
+
776
+ ${(props) => props.$interactive && `
777
+ &:hover {
778
+ background: ${props.theme.clipHeaderBackgroundColor}dd;
779
+ }
780
+
781
+ &:active {
782
+ cursor: grabbing;
783
+ }
784
+ `}
785
+ `;
786
+ var TrackName = import_styled_components10.default.span`
787
+ font-size: 11px;
788
+ font-weight: 600;
789
+ font-family: ${(props) => props.theme.clipHeaderFontFamily};
790
+ color: ${(props) => props.theme.clipHeaderTextColor};
791
+ white-space: nowrap;
792
+ overflow: hidden;
793
+ text-overflow: ellipsis;
794
+ `;
795
+ var ClipHeaderPresentational = ({
796
+ trackName,
797
+ isSelected = false
798
+ }) => {
799
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
800
+ HeaderContainer,
801
+ {
802
+ $isDragging: false,
803
+ $interactive: false,
804
+ $isSelected: isSelected,
805
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(TrackName, { children: trackName })
806
+ }
807
+ );
808
+ };
809
+ var ClipHeader = ({
810
+ clipId,
811
+ trackIndex,
812
+ clipIndex,
813
+ trackName,
814
+ isSelected = false,
815
+ disableDrag = false,
816
+ dragHandleProps
817
+ }) => {
818
+ if (disableDrag || !dragHandleProps) {
819
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
820
+ ClipHeaderPresentational,
821
+ {
822
+ trackName,
823
+ isSelected
824
+ }
825
+ );
826
+ }
827
+ const { attributes, listeners, setActivatorNodeRef } = dragHandleProps;
828
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
829
+ HeaderContainer,
830
+ {
831
+ ref: setActivatorNodeRef,
832
+ "data-clip-id": clipId,
833
+ $interactive: true,
834
+ $isSelected: isSelected,
835
+ ...listeners,
836
+ ...attributes,
837
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(TrackName, { children: trackName })
838
+ }
839
+ );
840
+ };
841
+
842
+ // src/components/ClipBoundary.tsx
843
+ var import_react2 = __toESM(require("react"));
844
+ var import_styled_components11 = __toESM(require("styled-components"));
845
+ var import_jsx_runtime5 = require("react/jsx-runtime");
846
+ var CLIP_BOUNDARY_WIDTH = 8;
847
+ var BoundaryContainer = import_styled_components11.default.div`
848
+ position: absolute;
849
+ ${(props) => props.$edge === "left" ? "left: 0;" : "right: 0;"}
850
+ top: 0;
851
+ bottom: 0;
852
+ width: ${CLIP_BOUNDARY_WIDTH}px;
853
+ cursor: col-resize;
854
+ user-select: none;
855
+ z-index: 105; /* Above waveform, below header */
856
+ pointer-events: auto; /* Re-enable pointer events (parent ClipContainer has pointer-events: none) */
857
+
858
+ /* Invisible by default, visible on hover */
859
+ background: ${(props) => props.$isDragging ? "rgba(255, 255, 255, 0.4)" : props.$isHovered ? "rgba(255, 255, 255, 0.2)" : "transparent"};
860
+
861
+ ${(props) => props.$edge === "left" ? `border-left: 2px solid ${props.$isDragging ? "rgba(255, 255, 255, 0.8)" : props.$isHovered ? "rgba(255, 255, 255, 0.5)" : "transparent"};` : `border-right: 2px solid ${props.$isDragging ? "rgba(255, 255, 255, 0.8)" : props.$isHovered ? "rgba(255, 255, 255, 0.5)" : "transparent"};`}
862
+
863
+ transition: background 0.15s ease, border-color 0.15s ease;
864
+
865
+ &:hover {
866
+ background: rgba(255, 255, 255, 0.2);
867
+ ${(props) => props.$edge === "left" ? "border-left: 2px solid rgba(255, 255, 255, 0.5);" : "border-right: 2px solid rgba(255, 255, 255, 0.5);"}
868
+ }
869
+
870
+ &:active {
871
+ background: rgba(255, 255, 255, 0.4);
872
+ ${(props) => props.$edge === "left" ? "border-left: 2px solid rgba(255, 255, 255, 0.8);" : "border-right: 2px solid rgba(255, 255, 255, 0.8);"}
873
+ }
874
+ `;
875
+ var ClipBoundary = ({
876
+ clipId,
877
+ trackIndex,
878
+ clipIndex,
879
+ edge,
880
+ dragHandleProps
881
+ }) => {
882
+ const [isHovered, setIsHovered] = import_react2.default.useState(false);
883
+ if (!dragHandleProps) {
884
+ return null;
885
+ }
886
+ const { attributes, listeners, setActivatorNodeRef, isDragging } = dragHandleProps;
887
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
888
+ BoundaryContainer,
889
+ {
890
+ ref: setActivatorNodeRef,
891
+ "data-clip-id": clipId,
892
+ "data-boundary-edge": edge,
893
+ $edge: edge,
894
+ $isDragging: isDragging,
895
+ $isHovered: isHovered,
896
+ onMouseEnter: () => setIsHovered(true),
897
+ onMouseLeave: () => setIsHovered(false),
898
+ ...listeners,
899
+ ...attributes
900
+ }
901
+ );
902
+ };
903
+
904
+ // src/components/FadeOverlay.tsx
905
+ var import_styled_components12 = __toESM(require("styled-components"));
906
+ var import_jsx_runtime6 = require("react/jsx-runtime");
907
+ var FadeContainer = import_styled_components12.default.div.attrs((props) => ({
908
+ style: {
909
+ left: `${props.$left}px`,
910
+ width: `${props.$width}px`
911
+ }
912
+ }))`
913
+ position: absolute;
914
+ top: 0;
915
+ bottom: 0;
916
+ pointer-events: none;
917
+ z-index: 50;
918
+ `;
919
+ var FadeSvg = import_styled_components12.default.svg`
920
+ width: 100%;
921
+ height: 100%;
922
+ display: block;
923
+ /* Flip horizontally for fadeOut - makes it mirror of fadeIn */
924
+ transform: ${(props) => props.$type === "fadeOut" ? "scaleX(-1)" : "none"};
925
+ `;
926
+ function generateFadePath(width, height, curveType = "logarithmic") {
927
+ const points = [];
928
+ const numPoints = Math.max(20, Math.min(width, 100));
929
+ for (let i = 0; i <= numPoints; i++) {
930
+ const x = i / numPoints * width;
931
+ const progress = i / numPoints;
932
+ let curvedProgress;
933
+ switch (curveType) {
934
+ case "linear":
935
+ curvedProgress = progress;
936
+ break;
937
+ case "exponential":
938
+ curvedProgress = progress * progress;
939
+ break;
940
+ case "sCurve":
941
+ curvedProgress = (1 - Math.cos(progress * Math.PI)) / 2;
942
+ break;
943
+ case "logarithmic":
944
+ default:
945
+ curvedProgress = Math.log10(1 + progress * 9) / Math.log10(10);
946
+ break;
947
+ }
948
+ const y = (1 - curvedProgress) * height;
949
+ points.push(`${x},${y}`);
950
+ }
951
+ return `M 0,${height} L ${points.join(" L ")} L ${width},0 L 0,0 Z`;
952
+ }
953
+ var FadeOverlay = ({
954
+ left,
955
+ width,
956
+ type,
957
+ curveType = "logarithmic",
958
+ color
959
+ }) => {
960
+ const theme = (0, import_styled_components12.useTheme)();
961
+ if (width < 1) return null;
962
+ const fillColor = color || theme?.fadeOverlayColor || "rgba(0, 0, 0, 0.4)";
963
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(FadeContainer, { $left: left, $width: width, $type: type, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(FadeSvg, { $type: type, viewBox: `0 0 ${width} 100`, preserveAspectRatio: "none", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
964
+ "path",
965
+ {
966
+ d: generateFadePath(width, 100, curveType),
967
+ fill: fillColor
968
+ }
969
+ ) }) });
970
+ };
971
+
972
+ // src/components/Clip.tsx
973
+ var import_jsx_runtime7 = require("react/jsx-runtime");
974
+ var ClipContainer = import_styled_components13.default.div.attrs((props) => ({
975
+ style: props.$isOverlay ? {} : {
976
+ left: `${props.$left}px`,
977
+ width: `${props.$width}px`
978
+ }
979
+ }))`
980
+ position: ${(props) => props.$isOverlay ? "relative" : "absolute"};
981
+ top: 0;
982
+ height: ${(props) => props.$isOverlay ? "auto" : "100%"};
983
+ width: ${(props) => props.$isOverlay ? `${props.$width}px` : "auto"};
984
+ display: flex;
985
+ flex-direction: column;
986
+ background: rgba(255, 255, 255, 0.05);
987
+ z-index: 10; /* Above progress overlay (z-index: 2) but below controls/playhead */
988
+ pointer-events: none; /* Let clicks pass through to ClickOverlay for playhead positioning */
989
+
990
+ &:hover {
991
+ background: rgba(255, 255, 255, 0.08);
992
+ }
993
+ `;
994
+ var ChannelsWrapper = import_styled_components13.default.div`
995
+ flex: 1;
996
+ position: relative;
997
+ overflow: ${(props) => props.$isOverlay ? "visible" : "hidden"};
998
+ `;
999
+ var Clip = ({
1000
+ children,
1001
+ className,
1002
+ clipId,
1003
+ trackIndex,
1004
+ clipIndex,
1005
+ trackName,
1006
+ startSample,
1007
+ durationSamples,
1008
+ samplesPerPixel,
1009
+ showHeader = false,
1010
+ disableHeaderDrag = false,
1011
+ isOverlay = false,
1012
+ isSelected = false,
1013
+ onMouseDown,
1014
+ trackId,
1015
+ fadeIn,
1016
+ fadeOut,
1017
+ sampleRate = 44100,
1018
+ showFades = false
1019
+ }) => {
1020
+ const left = Math.floor(startSample / samplesPerPixel);
1021
+ const endPixel = Math.floor((startSample + durationSamples) / samplesPerPixel);
1022
+ const width = endPixel - left;
1023
+ const enableDrag = showHeader && !disableHeaderDrag && !isOverlay;
1024
+ const draggableId = `clip-${trackIndex}-${clipIndex}`;
1025
+ const { attributes, listeners, setNodeRef, setActivatorNodeRef, transform, isDragging } = (0, import_core.useDraggable)({
1026
+ id: draggableId,
1027
+ data: { clipId, trackIndex, clipIndex },
1028
+ disabled: !enableDrag
1029
+ });
1030
+ const leftBoundaryId = `clip-boundary-left-${trackIndex}-${clipIndex}`;
1031
+ const {
1032
+ attributes: leftBoundaryAttributes,
1033
+ listeners: leftBoundaryListeners,
1034
+ setActivatorNodeRef: setLeftBoundaryActivatorRef,
1035
+ isDragging: isLeftBoundaryDragging
1036
+ } = (0, import_core.useDraggable)({
1037
+ id: leftBoundaryId,
1038
+ data: { clipId, trackIndex, clipIndex, boundary: "left" },
1039
+ disabled: !enableDrag
1040
+ });
1041
+ const rightBoundaryId = `clip-boundary-right-${trackIndex}-${clipIndex}`;
1042
+ const {
1043
+ attributes: rightBoundaryAttributes,
1044
+ listeners: rightBoundaryListeners,
1045
+ setActivatorNodeRef: setRightBoundaryActivatorRef,
1046
+ isDragging: isRightBoundaryDragging
1047
+ } = (0, import_core.useDraggable)({
1048
+ id: rightBoundaryId,
1049
+ data: { clipId, trackIndex, clipIndex, boundary: "right" },
1050
+ disabled: !enableDrag
1051
+ });
1052
+ const style = transform ? {
1053
+ transform: import_utilities.CSS.Translate.toString(transform),
1054
+ zIndex: isDragging ? 100 : void 0
1055
+ // Below controls (z-index: 999) but above other clips
1056
+ } : void 0;
1057
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1058
+ ClipContainer,
1059
+ {
1060
+ ref: setNodeRef,
1061
+ style,
1062
+ className,
1063
+ $left: left,
1064
+ $width: width,
1065
+ $isOverlay: isOverlay,
1066
+ "data-clip-container": "true",
1067
+ "data-track-id": trackId,
1068
+ onMouseDown,
1069
+ children: [
1070
+ showHeader && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1071
+ ClipHeader,
1072
+ {
1073
+ clipId,
1074
+ trackIndex,
1075
+ clipIndex,
1076
+ trackName,
1077
+ isSelected,
1078
+ disableDrag: disableHeaderDrag,
1079
+ dragHandleProps: enableDrag ? { attributes, listeners, setActivatorNodeRef } : void 0
1080
+ }
1081
+ ),
1082
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(ChannelsWrapper, { $isOverlay: isOverlay, children: [
1083
+ children,
1084
+ showFades && fadeIn && fadeIn.duration > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1085
+ FadeOverlay,
1086
+ {
1087
+ left: 0,
1088
+ width: Math.floor(fadeIn.duration * sampleRate / samplesPerPixel),
1089
+ type: "fadeIn",
1090
+ curveType: fadeIn.type
1091
+ }
1092
+ ),
1093
+ showFades && fadeOut && fadeOut.duration > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1094
+ FadeOverlay,
1095
+ {
1096
+ left: width - Math.floor(fadeOut.duration * sampleRate / samplesPerPixel),
1097
+ width: Math.floor(fadeOut.duration * sampleRate / samplesPerPixel),
1098
+ type: "fadeOut",
1099
+ curveType: fadeOut.type
1100
+ }
1101
+ )
1102
+ ] }),
1103
+ showHeader && !disableHeaderDrag && !isOverlay && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
1104
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1105
+ ClipBoundary,
1106
+ {
1107
+ clipId,
1108
+ trackIndex,
1109
+ clipIndex,
1110
+ edge: "left",
1111
+ dragHandleProps: {
1112
+ attributes: leftBoundaryAttributes,
1113
+ listeners: leftBoundaryListeners,
1114
+ setActivatorNodeRef: setLeftBoundaryActivatorRef,
1115
+ isDragging: isLeftBoundaryDragging
1116
+ }
1117
+ }
1118
+ ),
1119
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1120
+ ClipBoundary,
1121
+ {
1122
+ clipId,
1123
+ trackIndex,
1124
+ clipIndex,
1125
+ edge: "right",
1126
+ dragHandleProps: {
1127
+ attributes: rightBoundaryAttributes,
1128
+ listeners: rightBoundaryListeners,
1129
+ setActivatorNodeRef: setRightBoundaryActivatorRef,
1130
+ isDragging: isRightBoundaryDragging
1131
+ }
1132
+ }
1133
+ )
1134
+ ] })
1135
+ ]
1136
+ }
1137
+ );
1138
+ };
1139
+
1140
+ // src/components/MasterVolumeControl.tsx
1141
+ var import_styled_components14 = __toESM(require("styled-components"));
1142
+ var import_jsx_runtime8 = require("react/jsx-runtime");
1143
+ var VolumeContainer = import_styled_components14.default.div`
1144
+ display: inline-flex;
1145
+ align-items: center;
1146
+ gap: 0.5rem;
1147
+ `;
1148
+ var VolumeLabel = (0, import_styled_components14.default)(BaseLabel)`
1149
+ margin: 0;
1150
+ white-space: nowrap;
1151
+ `;
1152
+ var VolumeSlider = (0, import_styled_components14.default)(BaseSlider)`
1153
+ width: 120px;
1154
+ `;
1155
+ var MasterVolumeControl = ({
1156
+ volume,
1157
+ onChange,
1158
+ disabled = false,
1159
+ className
1160
+ }) => {
1161
+ const handleChange = (e) => {
1162
+ onChange(parseFloat(e.target.value) / 100);
1163
+ };
1164
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(VolumeContainer, { className, children: [
1165
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(VolumeLabel, { htmlFor: "master-gain", children: "Master Volume" }),
1166
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1167
+ VolumeSlider,
1168
+ {
1169
+ min: "0",
1170
+ max: "100",
1171
+ value: volume * 100,
1172
+ onChange: handleChange,
1173
+ disabled,
1174
+ id: "master-gain"
1175
+ }
1176
+ )
1177
+ ] });
1178
+ };
1179
+
1180
+ // src/components/Playhead.tsx
1181
+ var import_react3 = require("react");
1182
+ var import_styled_components15 = __toESM(require("styled-components"));
1183
+ var import_jsx_runtime9 = require("react/jsx-runtime");
1184
+ var PlayheadLine = import_styled_components15.default.div.attrs((props) => ({
1185
+ style: {
1186
+ transform: `translate3d(${props.$position}px, 0, 0)`
1187
+ }
1188
+ }))`
1189
+ position: absolute;
1190
+ top: 0;
1191
+ left: 0;
1192
+ width: 2px;
1193
+ background: ${(props) => props.$color};
1194
+ height: 100%;
1195
+ z-index: 100; /* Below sticky controls (z-index: 101) so playhead is hidden when scrolled behind controls */
1196
+ pointer-events: none;
1197
+ will-change: transform;
1198
+ `;
1199
+ var Playhead = ({ position, color = "#ff0000" }) => {
1200
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(PlayheadLine, { $position: position, $color: color });
1201
+ };
1202
+ var PlayheadWithMarkerContainer = import_styled_components15.default.div`
1203
+ position: absolute;
1204
+ top: 0;
1205
+ left: 0;
1206
+ height: 100%;
1207
+ z-index: 100; /* Below sticky controls (z-index: 101) so playhead is hidden when scrolled behind controls */
1208
+ pointer-events: none;
1209
+ will-change: transform;
1210
+ `;
1211
+ var MarkerTriangle = import_styled_components15.default.div`
1212
+ position: absolute;
1213
+ top: -10px;
1214
+ left: -6px;
1215
+ width: 0;
1216
+ height: 0;
1217
+ border-left: 7px solid transparent;
1218
+ border-right: 7px solid transparent;
1219
+ border-top: 10px solid ${(props) => props.$color};
1220
+ `;
1221
+ var MarkerLine = import_styled_components15.default.div`
1222
+ position: absolute;
1223
+ top: 0;
1224
+ left: 0;
1225
+ width: 2px;
1226
+ height: 100%;
1227
+ background: ${(props) => props.$color};
1228
+ `;
1229
+ var PlayheadWithMarker = ({
1230
+ color = "#ff0000",
1231
+ isPlaying,
1232
+ currentTimeRef,
1233
+ playbackStartTimeRef,
1234
+ audioStartPositionRef,
1235
+ samplesPerPixel,
1236
+ sampleRate,
1237
+ controlsOffset,
1238
+ getAudioContextTime
1239
+ }) => {
1240
+ const containerRef = (0, import_react3.useRef)(null);
1241
+ const animationFrameRef = (0, import_react3.useRef)(null);
1242
+ (0, import_react3.useEffect)(() => {
1243
+ const updatePosition = () => {
1244
+ if (containerRef.current) {
1245
+ let time;
1246
+ if (isPlaying && getAudioContextTime) {
1247
+ const elapsed = getAudioContextTime() - (playbackStartTimeRef.current ?? 0);
1248
+ time = (audioStartPositionRef.current ?? 0) + elapsed;
1249
+ } else {
1250
+ time = currentTimeRef.current ?? 0;
1251
+ }
1252
+ const pos = time * sampleRate / samplesPerPixel + controlsOffset;
1253
+ containerRef.current.style.transform = `translate3d(${pos}px, 0, 0)`;
1254
+ }
1255
+ if (isPlaying) {
1256
+ animationFrameRef.current = requestAnimationFrame(updatePosition);
1257
+ }
1258
+ };
1259
+ if (isPlaying) {
1260
+ animationFrameRef.current = requestAnimationFrame(updatePosition);
1261
+ } else {
1262
+ updatePosition();
1263
+ }
1264
+ return () => {
1265
+ if (animationFrameRef.current) {
1266
+ cancelAnimationFrame(animationFrameRef.current);
1267
+ animationFrameRef.current = null;
1268
+ }
1269
+ };
1270
+ }, [isPlaying, sampleRate, samplesPerPixel, controlsOffset, currentTimeRef, playbackStartTimeRef, audioStartPositionRef, getAudioContextTime]);
1271
+ (0, import_react3.useEffect)(() => {
1272
+ if (!isPlaying && containerRef.current) {
1273
+ const time = currentTimeRef.current ?? 0;
1274
+ const pos = time * sampleRate / samplesPerPixel + controlsOffset;
1275
+ containerRef.current.style.transform = `translate3d(${pos}px, 0, 0)`;
1276
+ }
1277
+ });
1278
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(PlayheadWithMarkerContainer, { ref: containerRef, $color: color, children: [
1279
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(MarkerTriangle, { $color: color }),
1280
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(MarkerLine, { $color: color })
1281
+ ] });
1282
+ };
1283
+
1284
+ // src/components/Playlist.tsx
1285
+ var import_styled_components16 = __toESM(require("styled-components"));
1286
+ var import_jsx_runtime10 = require("react/jsx-runtime");
1287
+ var Wrapper2 = import_styled_components16.default.div`
1288
+ overflow-y: hidden;
1289
+ overflow-x: auto;
1290
+ position: relative;
1291
+ `;
1292
+ var ScrollContainer = import_styled_components16.default.div.attrs((props) => ({
1293
+ style: props.$width !== void 0 ? { width: `${props.$width}px` } : {}
1294
+ }))`
1295
+ position: relative;
1296
+ background: ${(props) => props.$backgroundColor || "transparent"};
1297
+ `;
1298
+ var TimescaleWrapper = import_styled_components16.default.div.attrs((props) => ({
1299
+ style: props.$width ? { minWidth: `${props.$width}px` } : {}
1300
+ }))`
1301
+ background: ${(props) => props.$backgroundColor || "white"};
1302
+ width: 100%;
1303
+ overflow: visible;
1304
+ `;
1305
+ var TracksContainer = import_styled_components16.default.div.attrs((props) => ({
1306
+ style: props.$width !== void 0 ? { minWidth: `${props.$width}px` } : {}
1307
+ }))`
1308
+ position: relative;
1309
+ background: ${(props) => props.$backgroundColor || "transparent"};
1310
+ width: 100%;
1311
+ `;
1312
+ var ClickOverlay = import_styled_components16.default.div`
1313
+ position: absolute;
1314
+ top: 0;
1315
+ left: 0;
1316
+ right: 0;
1317
+ bottom: 0;
1318
+ cursor: crosshair;
1319
+ z-index: 1; /* Low z-index - clip headers and boundaries have higher z-index */
1320
+ `;
1321
+ var Playlist = ({
1322
+ children,
1323
+ backgroundColor,
1324
+ timescaleBackgroundColor,
1325
+ timescale,
1326
+ timescaleWidth,
1327
+ tracksWidth,
1328
+ scrollContainerWidth,
1329
+ controlsWidth,
1330
+ onTracksClick,
1331
+ onTracksMouseDown,
1332
+ onTracksMouseMove,
1333
+ onTracksMouseUp,
1334
+ scrollContainerRef
1335
+ }) => {
1336
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Wrapper2, { "data-scroll-container": "true", ref: scrollContainerRef, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1337
+ ScrollContainer,
1338
+ {
1339
+ $backgroundColor: backgroundColor,
1340
+ $width: scrollContainerWidth,
1341
+ children: [
1342
+ timescale && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(TimescaleWrapper, { $width: timescaleWidth, $backgroundColor: timescaleBackgroundColor, children: timescale }),
1343
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(TracksContainer, { $width: tracksWidth, $backgroundColor: backgroundColor, children: [
1344
+ children,
1345
+ (onTracksClick || onTracksMouseDown) && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1346
+ ClickOverlay,
1347
+ {
1348
+ $controlsWidth: controlsWidth,
1349
+ onClick: onTracksClick,
1350
+ onMouseDown: onTracksMouseDown,
1351
+ onMouseMove: onTracksMouseMove,
1352
+ onMouseUp: onTracksMouseUp
1353
+ }
1354
+ )
1355
+ ] })
1356
+ ]
1357
+ }
1358
+ ) });
1359
+ };
1360
+ var StyledPlaylist = (0, import_styled_components16.withTheme)(Playlist);
1361
+
1362
+ // src/components/Selection.tsx
1363
+ var import_styled_components17 = __toESM(require("styled-components"));
1364
+ var import_jsx_runtime11 = require("react/jsx-runtime");
1365
+ var SelectionOverlay = import_styled_components17.default.div.attrs((props) => ({
1366
+ style: {
1367
+ left: `${props.$left}px`,
1368
+ width: `${props.$width}px`
1369
+ }
1370
+ }))`
1371
+ position: absolute;
1372
+ top: 0;
1373
+ background: ${(props) => props.$color};
1374
+ height: 100%;
1375
+ z-index: 5;
1376
+ pointer-events: none;
1377
+ opacity: 0.3;
1378
+ `;
1379
+ var Selection = ({
1380
+ startPosition,
1381
+ endPosition,
1382
+ color = "#00ff00"
1383
+ }) => {
1384
+ const width = Math.max(0, endPosition - startPosition);
1385
+ if (width <= 0) {
1386
+ return null;
1387
+ }
1388
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(SelectionOverlay, { $left: startPosition, $width: width, $color: color });
1389
+ };
1390
+
1391
+ // src/components/SelectionTimeInputs.tsx
1392
+ var import_react5 = require("react");
1393
+
1394
+ // src/components/TimeInput.tsx
1395
+ var import_react4 = require("react");
1396
+
1397
+ // src/utils/timeFormat.ts
1398
+ function clockFormat(seconds, decimals) {
1399
+ const hours = Math.floor(seconds / 3600) % 24;
1400
+ const minutes = Math.floor(seconds / 60) % 60;
1401
+ const secs = (seconds % 60).toFixed(decimals);
1402
+ return String(hours).padStart(2, "0") + ":" + String(minutes).padStart(2, "0") + ":" + secs.padStart(decimals + 3, "0");
1403
+ }
1404
+ function formatTime(seconds, format) {
1405
+ switch (format) {
1406
+ case "seconds":
1407
+ return seconds.toFixed(0);
1408
+ case "thousandths":
1409
+ return seconds.toFixed(3);
1410
+ case "hh:mm:ss":
1411
+ return clockFormat(seconds, 0);
1412
+ case "hh:mm:ss.u":
1413
+ return clockFormat(seconds, 1);
1414
+ case "hh:mm:ss.uu":
1415
+ return clockFormat(seconds, 2);
1416
+ case "hh:mm:ss.uuu":
1417
+ return clockFormat(seconds, 3);
1418
+ default:
1419
+ return clockFormat(seconds, 3);
1420
+ }
1421
+ }
1422
+ function parseTime(timeStr, format) {
1423
+ if (!timeStr) return 0;
1424
+ switch (format) {
1425
+ case "seconds":
1426
+ case "thousandths":
1427
+ return parseFloat(timeStr) || 0;
1428
+ case "hh:mm:ss":
1429
+ case "hh:mm:ss.u":
1430
+ case "hh:mm:ss.uu":
1431
+ case "hh:mm:ss.uuu": {
1432
+ const parts = timeStr.split(":");
1433
+ if (parts.length !== 3) return 0;
1434
+ const hours = parseInt(parts[0], 10) || 0;
1435
+ const minutes = parseInt(parts[1], 10) || 0;
1436
+ const seconds = parseFloat(parts[2]) || 0;
1437
+ return hours * 3600 + minutes * 60 + seconds;
1438
+ }
1439
+ default:
1440
+ return 0;
1441
+ }
1442
+ }
1443
+
1444
+ // src/components/TimeInput.tsx
1445
+ var import_jsx_runtime12 = require("react/jsx-runtime");
1446
+ var TimeInput = ({
1447
+ id,
1448
+ label,
1449
+ value,
1450
+ format,
1451
+ className,
1452
+ onChange,
1453
+ readOnly = false
1454
+ }) => {
1455
+ const [displayValue, setDisplayValue] = (0, import_react4.useState)("");
1456
+ (0, import_react4.useEffect)(() => {
1457
+ const formatted = formatTime(value, format);
1458
+ setDisplayValue(formatted);
1459
+ }, [value, format, id]);
1460
+ const handleChange = (e) => {
1461
+ const newDisplayValue = e.target.value;
1462
+ setDisplayValue(newDisplayValue);
1463
+ };
1464
+ const handleBlur = () => {
1465
+ if (onChange) {
1466
+ const parsedValue = parseTime(displayValue, format);
1467
+ onChange(parsedValue);
1468
+ }
1469
+ setDisplayValue(formatTime(value, format));
1470
+ };
1471
+ const handleKeyDown = (e) => {
1472
+ if (e.key === "Enter") {
1473
+ e.currentTarget.blur();
1474
+ }
1475
+ };
1476
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
1477
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ScreenReaderOnly, { as: "label", htmlFor: id, children: label }),
1478
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1479
+ BaseInput,
1480
+ {
1481
+ type: "text",
1482
+ className,
1483
+ id,
1484
+ value: displayValue,
1485
+ onChange: handleChange,
1486
+ onBlur: handleBlur,
1487
+ onKeyDown: handleKeyDown,
1488
+ readOnly
1489
+ }
1490
+ )
1491
+ ] });
1492
+ };
1493
+
1494
+ // src/components/SelectionTimeInputs.tsx
1495
+ var import_jsx_runtime13 = require("react/jsx-runtime");
1496
+ var SelectionTimeInputs = ({
1497
+ selectionStart,
1498
+ selectionEnd,
1499
+ onSelectionChange,
1500
+ className
1501
+ }) => {
1502
+ const [timeFormat, setTimeFormat] = (0, import_react5.useState)("hh:mm:ss.uuu");
1503
+ (0, import_react5.useEffect)(() => {
1504
+ const timeFormatSelect = document.querySelector(".time-format");
1505
+ const handleFormatChange = () => {
1506
+ if (timeFormatSelect) {
1507
+ setTimeFormat(timeFormatSelect.value);
1508
+ }
1509
+ };
1510
+ if (timeFormatSelect) {
1511
+ setTimeFormat(timeFormatSelect.value);
1512
+ timeFormatSelect.addEventListener("change", handleFormatChange);
1513
+ }
1514
+ return () => {
1515
+ timeFormatSelect?.removeEventListener("change", handleFormatChange);
1516
+ };
1517
+ }, []);
1518
+ const handleStartChange = (value) => {
1519
+ if (onSelectionChange) {
1520
+ onSelectionChange(value, selectionEnd);
1521
+ }
1522
+ };
1523
+ const handleEndChange = (value) => {
1524
+ if (onSelectionChange) {
1525
+ onSelectionChange(selectionStart, value);
1526
+ }
1527
+ };
1528
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
1529
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1530
+ TimeInput,
1531
+ {
1532
+ id: "audio_start",
1533
+ label: "Start of audio selection",
1534
+ value: selectionStart,
1535
+ format: timeFormat,
1536
+ className: "audio-start form-control mr-sm-2",
1537
+ onChange: handleStartChange
1538
+ }
1539
+ ),
1540
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1541
+ TimeInput,
1542
+ {
1543
+ id: "audio_end",
1544
+ label: "End of audio selection",
1545
+ value: selectionEnd,
1546
+ format: timeFormat,
1547
+ className: "audio-end form-control mr-sm-2",
1548
+ onChange: handleEndChange
1549
+ }
1550
+ )
1551
+ ] });
1552
+ };
1553
+
1554
+ // src/contexts/DevicePixelRatio.tsx
1555
+ var import_react6 = require("react");
1556
+ var import_jsx_runtime14 = require("react/jsx-runtime");
1557
+ function getScale() {
1558
+ return window.devicePixelRatio;
1559
+ }
1560
+ var DevicePixelRatioContext = (0, import_react6.createContext)(getScale());
1561
+ var DevicePixelRatioProvider = ({ children }) => {
1562
+ const [scale, setScale] = (0, import_react6.useState)(getScale());
1563
+ matchMedia(`(resolution: ${getScale()}dppx)`).addEventListener(
1564
+ "change",
1565
+ () => {
1566
+ setScale(getScale());
1567
+ },
1568
+ { once: true }
1569
+ );
1570
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DevicePixelRatioContext.Provider, { value: Math.ceil(scale), children });
1571
+ };
1572
+ var useDevicePixelRatio = () => (0, import_react6.useContext)(DevicePixelRatioContext);
1573
+
1574
+ // src/contexts/PlaylistInfo.tsx
1575
+ var import_react7 = require("react");
1576
+ var PlaylistInfoContext = (0, import_react7.createContext)({
1577
+ sampleRate: 48e3,
1578
+ samplesPerPixel: 1e3,
1579
+ zoomLevels: [1e3, 1500, 2e3, 2500],
1580
+ waveHeight: 80,
1581
+ timeScaleHeight: 15,
1582
+ controls: {
1583
+ show: false,
1584
+ width: 150
1585
+ },
1586
+ duration: 3e4,
1587
+ barWidth: 1,
1588
+ barGap: 0
1589
+ });
1590
+ var usePlaylistInfo = () => (0, import_react7.useContext)(PlaylistInfoContext);
1591
+
1592
+ // src/contexts/Theme.tsx
1593
+ var import_react8 = require("react");
1594
+ var import_styled_components18 = require("styled-components");
1595
+ var useTheme2 = () => (0, import_react8.useContext)(import_styled_components18.ThemeContext);
1596
+
1597
+ // src/contexts/TrackControls.tsx
1598
+ var import_react9 = require("react");
1599
+ var import_jsx_runtime15 = require("react/jsx-runtime");
1600
+ var TrackControlsContext = (0, import_react9.createContext)(/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react9.Fragment, {}));
1601
+ var useTrackControls = () => (0, import_react9.useContext)(TrackControlsContext);
1602
+
1603
+ // src/contexts/Playout.tsx
1604
+ var import_react10 = require("react");
1605
+ var import_jsx_runtime16 = require("react/jsx-runtime");
1606
+ var defaultProgress = 0;
1607
+ var defaultIsPlaying = false;
1608
+ var defaultSelectionStart = 0;
1609
+ var defaultSelectionEnd = 0;
1610
+ var defaultPlayout = {
1611
+ progress: defaultProgress,
1612
+ isPlaying: defaultIsPlaying,
1613
+ selectionStart: defaultSelectionStart,
1614
+ selectionEnd: defaultSelectionEnd
1615
+ };
1616
+ var PlayoutStatusContext = (0, import_react10.createContext)(defaultPlayout);
1617
+ var PlayoutStatusUpdateContext = (0, import_react10.createContext)({
1618
+ setIsPlaying: () => {
1619
+ },
1620
+ setProgress: () => {
1621
+ },
1622
+ setSelection: () => {
1623
+ }
1624
+ });
1625
+ var PlayoutProvider = ({ children }) => {
1626
+ const [isPlaying, setIsPlaying] = (0, import_react10.useState)(defaultIsPlaying);
1627
+ const [progress, setProgress] = (0, import_react10.useState)(defaultProgress);
1628
+ const [selectionStart, setSelectionStart] = (0, import_react10.useState)(defaultSelectionStart);
1629
+ const [selectionEnd, setSelectionEnd] = (0, import_react10.useState)(defaultSelectionEnd);
1630
+ const setSelection = (start, end) => {
1631
+ setSelectionStart(start);
1632
+ setSelectionEnd(end);
1633
+ };
1634
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(PlayoutStatusUpdateContext.Provider, { value: { setIsPlaying, setProgress, setSelection }, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(PlayoutStatusContext.Provider, { value: { isPlaying, progress, selectionStart, selectionEnd }, children }) });
1635
+ };
1636
+ var usePlayoutStatus = () => (0, import_react10.useContext)(PlayoutStatusContext);
1637
+ var usePlayoutStatusUpdate = () => (0, import_react10.useContext)(PlayoutStatusUpdateContext);
1638
+
1639
+ // src/components/SmartChannel.tsx
1640
+ var import_jsx_runtime17 = require("react/jsx-runtime");
1641
+ var SmartChannel = ({ isSelected, transparentBackground, ...props }) => {
1642
+ const theme = useTheme2();
1643
+ const { waveHeight, barWidth, barGap } = usePlaylistInfo();
1644
+ const devicePixelRatio = useDevicePixelRatio();
1645
+ const waveOutlineColor = isSelected && theme ? theme.selectedWaveOutlineColor : theme?.waveOutlineColor;
1646
+ const waveFillColor = isSelected && theme ? theme.selectedWaveFillColor : theme?.waveFillColor;
1647
+ const drawMode = theme?.waveformDrawMode || "inverted";
1648
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1649
+ Channel,
1650
+ {
1651
+ ...props,
1652
+ ...theme,
1653
+ waveOutlineColor,
1654
+ waveFillColor,
1655
+ waveHeight,
1656
+ devicePixelRatio,
1657
+ barWidth,
1658
+ barGap,
1659
+ transparentBackground,
1660
+ drawMode
1661
+ }
1662
+ );
1663
+ };
1664
+
1665
+ // src/components/SmartScale.tsx
1666
+ var import_react12 = require("react");
1667
+
1668
+ // src/components/TimeScale.tsx
1669
+ var import_react11 = __toESM(require("react"));
1670
+ var import_styled_components19 = __toESM(require("styled-components"));
1671
+
1672
+ // src/utils/conversions.ts
1673
+ function samplesToSeconds(samples, sampleRate) {
1674
+ return samples / sampleRate;
1675
+ }
1676
+ function secondsToSamples(seconds, sampleRate) {
1677
+ return Math.ceil(seconds * sampleRate);
1678
+ }
1679
+ function samplesToPixels(samples, samplesPerPixel) {
1680
+ return Math.floor(samples / samplesPerPixel);
1681
+ }
1682
+ function pixelsToSamples(pixels, samplesPerPixel) {
1683
+ return Math.floor(pixels * samplesPerPixel);
1684
+ }
1685
+ function pixelsToSeconds(pixels, samplesPerPixel, sampleRate) {
1686
+ return pixels * samplesPerPixel / sampleRate;
1687
+ }
1688
+ function secondsToPixels(seconds, samplesPerPixel, sampleRate) {
1689
+ return Math.ceil(seconds * sampleRate / samplesPerPixel);
1690
+ }
1691
+
1692
+ // src/components/TimeScale.tsx
1693
+ var import_jsx_runtime18 = require("react/jsx-runtime");
1694
+ function formatTime2(milliseconds) {
1695
+ const seconds = Math.floor(milliseconds / 1e3);
1696
+ const s = seconds % 60;
1697
+ const m = (seconds - s) / 60;
1698
+ return `${m}:${String(s).padStart(2, "0")}`;
1699
+ }
1700
+ var PlaylistTimeScaleScroll = import_styled_components19.default.div.attrs((props) => ({
1701
+ style: {
1702
+ width: `${props.$cssWidth}px`,
1703
+ marginLeft: `${props.$controlWidth}px`,
1704
+ height: `${props.$timeScaleHeight}px`
1705
+ }
1706
+ }))`
1707
+ position: relative;
1708
+ overflow: visible; /* Allow time labels to render above the container */
1709
+ border-bottom: 1px solid ${(props) => props.theme.timeColor};
1710
+ box-sizing: border-box;
1711
+ `;
1712
+ var TimeTicks = import_styled_components19.default.canvas.attrs((props) => ({
1713
+ style: {
1714
+ width: `${props.$cssWidth}px`,
1715
+ height: `${props.$timeScaleHeight}px`
1716
+ }
1717
+ }))`
1718
+ position: absolute;
1719
+ left: 0;
1720
+ right: 0;
1721
+ bottom: 0;
1722
+ `;
1723
+ var TimeStamp = import_styled_components19.default.div.attrs((props) => ({
1724
+ style: {
1725
+ left: `${props.$left + 4}px`
1726
+ // Offset 4px to the right of the tick
1727
+ }
1728
+ }))`
1729
+ position: absolute;
1730
+ font-size: 0.75rem; /* Smaller font to prevent overflow */
1731
+ white-space: nowrap; /* Prevent text wrapping */
1732
+ color: ${(props) => props.theme.timeColor}; /* Use theme color instead of inheriting */
1733
+ `;
1734
+ var TimeScale = (props) => {
1735
+ const {
1736
+ theme: { timeColor },
1737
+ duration,
1738
+ marker,
1739
+ bigStep,
1740
+ secondStep,
1741
+ renderTimestamp
1742
+ } = props;
1743
+ const canvasInfo = /* @__PURE__ */ new Map();
1744
+ const timeMarkers = [];
1745
+ const canvasRef = (0, import_react11.useRef)(null);
1746
+ const {
1747
+ sampleRate,
1748
+ samplesPerPixel,
1749
+ timeScaleHeight,
1750
+ controls: { show: showControls, width: controlWidth }
1751
+ } = (0, import_react11.useContext)(PlaylistInfoContext);
1752
+ const devicePixelRatio = useDevicePixelRatio();
1753
+ (0, import_react11.useEffect)(() => {
1754
+ if (canvasRef.current !== null) {
1755
+ const canvas = canvasRef.current;
1756
+ const ctx = canvas.getContext("2d");
1757
+ if (ctx) {
1758
+ ctx.resetTransform();
1759
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
1760
+ ctx.imageSmoothingEnabled = false;
1761
+ ctx.fillStyle = timeColor;
1762
+ ctx.scale(devicePixelRatio, devicePixelRatio);
1763
+ for (const [pixLeft, scaleHeight] of canvasInfo.entries()) {
1764
+ const scaleY = timeScaleHeight - scaleHeight;
1765
+ ctx.fillRect(pixLeft, scaleY, 1, scaleHeight);
1766
+ }
1767
+ }
1768
+ }
1769
+ }, [
1770
+ duration,
1771
+ devicePixelRatio,
1772
+ timeColor,
1773
+ timeScaleHeight,
1774
+ bigStep,
1775
+ secondStep,
1776
+ marker,
1777
+ canvasInfo
1778
+ ]);
1779
+ const widthX = secondsToPixels(duration / 1e3, samplesPerPixel, sampleRate);
1780
+ const pixPerSec = sampleRate / samplesPerPixel;
1781
+ let counter = 0;
1782
+ for (let i = 0; i < widthX; i += pixPerSec * secondStep / 1e3) {
1783
+ const pix = Math.floor(i);
1784
+ if (counter % marker === 0) {
1785
+ const timeMs = counter;
1786
+ const timestamp = formatTime2(timeMs);
1787
+ const timestampContent = renderTimestamp ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react11.default.Fragment, { children: renderTimestamp(timeMs, pix) }, `timestamp-${counter}`) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(TimeStamp, { $left: pix, children: timestamp }, timestamp);
1788
+ timeMarkers.push(timestampContent);
1789
+ canvasInfo.set(pix, timeScaleHeight);
1790
+ } else if (counter % bigStep === 0) {
1791
+ canvasInfo.set(pix, Math.floor(timeScaleHeight / 2));
1792
+ } else if (counter % secondStep === 0) {
1793
+ canvasInfo.set(pix, Math.floor(timeScaleHeight / 5));
1794
+ }
1795
+ counter += secondStep;
1796
+ }
1797
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
1798
+ PlaylistTimeScaleScroll,
1799
+ {
1800
+ $cssWidth: widthX,
1801
+ $controlWidth: showControls ? controlWidth : 0,
1802
+ $timeScaleHeight: timeScaleHeight,
1803
+ children: [
1804
+ timeMarkers,
1805
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
1806
+ TimeTicks,
1807
+ {
1808
+ $cssWidth: widthX,
1809
+ $timeScaleHeight: timeScaleHeight,
1810
+ width: widthX * devicePixelRatio,
1811
+ height: timeScaleHeight * devicePixelRatio,
1812
+ ref: canvasRef
1813
+ }
1814
+ )
1815
+ ]
1816
+ }
1817
+ );
1818
+ };
1819
+ var StyledTimeScale = (0, import_styled_components19.withTheme)(TimeScale);
1820
+
1821
+ // src/components/SmartScale.tsx
1822
+ var import_jsx_runtime19 = require("react/jsx-runtime");
1823
+ var timeinfo = /* @__PURE__ */ new Map([
1824
+ [
1825
+ 700,
1826
+ {
1827
+ marker: 1e3,
1828
+ bigStep: 500,
1829
+ smallStep: 100
1830
+ }
1831
+ ],
1832
+ [
1833
+ 1500,
1834
+ {
1835
+ marker: 2e3,
1836
+ bigStep: 1e3,
1837
+ smallStep: 200
1838
+ }
1839
+ ],
1840
+ [
1841
+ 2500,
1842
+ {
1843
+ marker: 2e3,
1844
+ bigStep: 1e3,
1845
+ smallStep: 500
1846
+ }
1847
+ ],
1848
+ [
1849
+ 5e3,
1850
+ {
1851
+ marker: 5e3,
1852
+ bigStep: 1e3,
1853
+ smallStep: 500
1854
+ }
1855
+ ],
1856
+ [
1857
+ 1e4,
1858
+ {
1859
+ marker: 1e4,
1860
+ bigStep: 5e3,
1861
+ smallStep: 1e3
1862
+ }
1863
+ ],
1864
+ [
1865
+ 12e3,
1866
+ {
1867
+ marker: 15e3,
1868
+ bigStep: 5e3,
1869
+ smallStep: 1e3
1870
+ }
1871
+ ],
1872
+ [
1873
+ Infinity,
1874
+ {
1875
+ marker: 3e4,
1876
+ bigStep: 1e4,
1877
+ smallStep: 5e3
1878
+ }
1879
+ ]
1880
+ ]);
1881
+ function getScaleInfo(samplesPerPixel) {
1882
+ const keys = timeinfo.keys();
1883
+ let config;
1884
+ for (const resolution of keys) {
1885
+ if (samplesPerPixel < resolution) {
1886
+ config = timeinfo.get(resolution);
1887
+ break;
1888
+ }
1889
+ }
1890
+ if (config === void 0) {
1891
+ config = { marker: 3e4, bigStep: 1e4, smallStep: 5e3 };
1892
+ }
1893
+ return config;
1894
+ }
1895
+ var SmartScale = () => {
1896
+ const { samplesPerPixel, duration } = (0, import_react12.useContext)(PlaylistInfoContext);
1897
+ let config = getScaleInfo(samplesPerPixel);
1898
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
1899
+ StyledTimeScale,
1900
+ {
1901
+ marker: config.marker,
1902
+ bigStep: config.bigStep,
1903
+ secondStep: config.smallStep,
1904
+ duration
1905
+ }
1906
+ );
1907
+ };
1908
+
1909
+ // src/components/TimeFormatSelect.tsx
1910
+ var import_styled_components20 = __toESM(require("styled-components"));
1911
+ var import_jsx_runtime20 = require("react/jsx-runtime");
1912
+ var SelectWrapper = import_styled_components20.default.div`
1913
+ display: inline-flex;
1914
+ align-items: center;
1915
+ gap: 0.5rem;
1916
+ `;
1917
+ var TIME_FORMAT_OPTIONS = [
1918
+ { value: "seconds", label: "seconds" },
1919
+ { value: "thousandths", label: "thousandths" },
1920
+ { value: "hh:mm:ss", label: "hh:mm:ss" },
1921
+ { value: "hh:mm:ss.u", label: "hh:mm:ss + tenths" },
1922
+ { value: "hh:mm:ss.uu", label: "hh:mm:ss + hundredths" },
1923
+ { value: "hh:mm:ss.uuu", label: "hh:mm:ss + milliseconds" }
1924
+ ];
1925
+ var TimeFormatSelect = ({
1926
+ value,
1927
+ onChange,
1928
+ disabled = false,
1929
+ className
1930
+ }) => {
1931
+ const handleChange = (e) => {
1932
+ onChange(e.target.value);
1933
+ };
1934
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(SelectWrapper, { className, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
1935
+ BaseSelect,
1936
+ {
1937
+ className: "time-format",
1938
+ value,
1939
+ onChange: handleChange,
1940
+ disabled,
1941
+ "aria-label": "Time format selection",
1942
+ children: TIME_FORMAT_OPTIONS.map((option) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("option", { value: option.value, children: option.label }, option.value))
1943
+ }
1944
+ ) });
1945
+ };
1946
+
1947
+ // src/components/Track.tsx
1948
+ var import_styled_components21 = __toESM(require("styled-components"));
1949
+ var import_jsx_runtime21 = require("react/jsx-runtime");
1950
+ var Container = import_styled_components21.default.div.attrs((props) => ({
1951
+ style: {
1952
+ height: `${props.$waveHeight * props.$numChannels + (props.$hasClipHeaders ? CLIP_HEADER_HEIGHT : 0)}px`
1953
+ }
1954
+ }))`
1955
+ position: relative;
1956
+ display: flex;
1957
+ ${(props) => props.$width !== void 0 && `width: ${props.$width}px;`}
1958
+ `;
1959
+ var ChannelContainer = import_styled_components21.default.div.attrs((props) => ({
1960
+ style: {
1961
+ paddingLeft: `${props.$offset || 0}px`
1962
+ }
1963
+ }))`
1964
+ position: relative;
1965
+ background: ${(props) => props.$backgroundColor || "transparent"};
1966
+ flex: 1;
1967
+ `;
1968
+ var ControlsWrapper = import_styled_components21.default.div.attrs((props) => ({
1969
+ style: {
1970
+ width: `${props.$controlWidth}px`
1971
+ }
1972
+ }))`
1973
+ position: sticky;
1974
+ z-index: 101; /* Above waveform content, below Docusaurus navbar (z-index: 200) */
1975
+ left: 0;
1976
+ height: 100%;
1977
+ flex-shrink: 0;
1978
+ pointer-events: auto;
1979
+ background: ${(props) => props.theme.surfaceColor};
1980
+ transition: background 0.15s ease-in-out;
1981
+
1982
+ /* Selected track: highlighted background */
1983
+ ${(props) => props.$isSelected && `
1984
+ background: ${props.theme.selectedTrackControlsBackground};
1985
+ `}
1986
+ `;
1987
+ var Track = ({
1988
+ numChannels,
1989
+ children,
1990
+ className,
1991
+ backgroundColor,
1992
+ offset = 0,
1993
+ width,
1994
+ hasClipHeaders = false,
1995
+ onClick,
1996
+ trackId,
1997
+ isSelected = false
1998
+ }) => {
1999
+ const {
2000
+ waveHeight,
2001
+ controls: { show, width: controlWidth }
2002
+ } = usePlaylistInfo();
2003
+ const controls = useTrackControls();
2004
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
2005
+ Container,
2006
+ {
2007
+ $numChannels: numChannels,
2008
+ className,
2009
+ $waveHeight: waveHeight,
2010
+ $controlWidth: show ? controlWidth : 0,
2011
+ $width: width,
2012
+ $hasClipHeaders: hasClipHeaders,
2013
+ $isSelected: isSelected,
2014
+ children: [
2015
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2016
+ ControlsWrapper,
2017
+ {
2018
+ $controlWidth: show ? controlWidth : 0,
2019
+ $isSelected: isSelected,
2020
+ children: controls
2021
+ }
2022
+ ),
2023
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2024
+ ChannelContainer,
2025
+ {
2026
+ $controlWidth: show ? controlWidth : 0,
2027
+ $backgroundColor: backgroundColor,
2028
+ $offset: offset,
2029
+ onClick,
2030
+ "data-track-id": trackId,
2031
+ children
2032
+ }
2033
+ )
2034
+ ]
2035
+ }
2036
+ );
2037
+ };
2038
+
2039
+ // src/components/TrackControls/Button.tsx
2040
+ var import_styled_components22 = __toESM(require("styled-components"));
2041
+ var Button = import_styled_components22.default.button.attrs({
2042
+ type: "button"
2043
+ })`
2044
+ display: inline-block;
2045
+ font-family: ${(props) => props.theme.fontFamily};
2046
+ font-weight: 500;
2047
+ text-align: center;
2048
+ vertical-align: middle;
2049
+ user-select: none;
2050
+ padding: 0.25rem 0.4rem;
2051
+ font-size: ${(props) => props.theme.fontSizeSmall};
2052
+ line-height: 1;
2053
+ border-radius: ${(props) => props.theme.borderRadius};
2054
+ transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out,
2055
+ border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
2056
+ cursor: pointer;
2057
+
2058
+ ${(props) => {
2059
+ if (props.$variant === "danger") {
2060
+ return `
2061
+ color: #fff;
2062
+ background-color: #dc3545;
2063
+ border: 1px solid #dc3545;
2064
+
2065
+ &:hover {
2066
+ background-color: #c82333;
2067
+ border-color: #bd2130;
2068
+ }
2069
+
2070
+ &:focus {
2071
+ outline: none;
2072
+ box-shadow: 0 0 0 0.2rem rgba(225, 83, 97, 0.5);
2073
+ }
2074
+ `;
2075
+ } else if (props.$variant === "info") {
2076
+ return `
2077
+ color: #fff;
2078
+ background-color: #17a2b8;
2079
+ border: 1px solid #17a2b8;
2080
+
2081
+ &:hover {
2082
+ background-color: #138496;
2083
+ border-color: #117a8b;
2084
+ }
2085
+
2086
+ &:focus {
2087
+ outline: none;
2088
+ box-shadow: 0 0 0 0.2rem rgba(58, 176, 195, 0.5);
2089
+ }
2090
+ `;
2091
+ } else {
2092
+ return `
2093
+ color: ${props.theme.textColor};
2094
+ background-color: transparent;
2095
+ border: 1px solid ${props.theme.borderColor};
2096
+
2097
+ &:hover {
2098
+ color: #fff;
2099
+ background-color: ${props.theme.textColor};
2100
+ border-color: ${props.theme.textColor};
2101
+ }
2102
+
2103
+ &:focus {
2104
+ outline: none;
2105
+ box-shadow: 0 0 0 0.2rem ${props.theme.inputFocusBorder}33;
2106
+ }
2107
+ `;
2108
+ }
2109
+ }}
2110
+ `;
2111
+
2112
+ // src/components/TrackControls/ButtonGroup.tsx
2113
+ var import_styled_components23 = __toESM(require("styled-components"));
2114
+ var ButtonGroup = import_styled_components23.default.div`
2115
+ margin-bottom: 0.3rem;
2116
+
2117
+ button:not(:first-child) {
2118
+ border-top-left-radius: 0;
2119
+ border-bottom-left-radius: 0;
2120
+ }
2121
+
2122
+ button:not(:last-child) {
2123
+ border-top-right-radius: 0;
2124
+ border-bottom-right-radius: 0;
2125
+ }
2126
+ `;
2127
+
2128
+ // src/components/TrackControls/Controls.tsx
2129
+ var import_styled_components24 = __toESM(require("styled-components"));
2130
+ var Controls = import_styled_components24.default.div`
2131
+ background: transparent;
2132
+ width: 100%;
2133
+ height: 100%;
2134
+ display: flex;
2135
+ flex-direction: column;
2136
+ align-items: center;
2137
+ justify-content: flex-start;
2138
+ overflow: hidden;
2139
+ box-sizing: border-box;
2140
+ text-align: center;
2141
+ border: 1px solid ${(props) => props.theme.borderColor};
2142
+ border-radius: ${(props) => props.theme.borderRadius};
2143
+ `;
2144
+
2145
+ // src/components/TrackControls/Header.tsx
2146
+ var import_styled_components25 = __toESM(require("styled-components"));
2147
+ var Header = import_styled_components25.default.header`
2148
+ overflow: hidden;
2149
+ height: 26px;
2150
+ width: 100%;
2151
+ display: flex;
2152
+ align-items: center;
2153
+ justify-content: space-between;
2154
+ padding: 0 0.2rem;
2155
+ font-size: ${(props) => props.theme.fontSizeSmall};
2156
+ color: ${(props) => props.theme.textColor};
2157
+ background-color: transparent;
2158
+ `;
2159
+
2160
+ // src/components/TrackControls/VolumeDownIcon.tsx
2161
+ var import_react13 = require("@phosphor-icons/react");
2162
+ var import_jsx_runtime22 = require("react/jsx-runtime");
2163
+ var VolumeDownIcon = (props) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react13.SpeakerLowIcon, { weight: "light", ...props });
2164
+
2165
+ // src/components/TrackControls/VolumeUpIcon.tsx
2166
+ var import_react14 = require("@phosphor-icons/react");
2167
+ var import_jsx_runtime23 = require("react/jsx-runtime");
2168
+ var VolumeUpIcon = (props) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_react14.SpeakerHighIcon, { weight: "light", ...props });
2169
+
2170
+ // src/components/TrackControls/TrashIcon.tsx
2171
+ var import_react15 = require("@phosphor-icons/react");
2172
+ var import_jsx_runtime24 = require("react/jsx-runtime");
2173
+ var TrashIcon = (props) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_react15.TrashIcon, { weight: "light", ...props });
2174
+
2175
+ // src/components/TrackControls/Slider.tsx
2176
+ var import_styled_components26 = __toESM(require("styled-components"));
2177
+ var Slider = (0, import_styled_components26.default)(BaseSlider)`
2178
+ width: 75%;
2179
+ height: 5px;
2180
+ background: ${(props) => props.theme.sliderTrackColor};
2181
+
2182
+ &::-webkit-slider-thumb {
2183
+ width: 12px;
2184
+ height: 12px;
2185
+ background: ${(props) => props.theme.sliderThumbColor};
2186
+ border: none;
2187
+ margin-top: -4px;
2188
+ cursor: ew-resize;
2189
+ }
2190
+
2191
+ &::-moz-range-thumb {
2192
+ width: 12px;
2193
+ height: 12px;
2194
+ background: ${(props) => props.theme.sliderThumbColor};
2195
+ border: none;
2196
+ cursor: ew-resize;
2197
+ }
2198
+
2199
+ &::-webkit-slider-runnable-track {
2200
+ height: 5px;
2201
+ background: ${(props) => props.theme.sliderTrackColor};
2202
+ border-radius: 3px;
2203
+ }
2204
+
2205
+ &::-moz-range-track {
2206
+ height: 5px;
2207
+ background: ${(props) => props.theme.sliderTrackColor};
2208
+ border-radius: 3px;
2209
+ }
2210
+
2211
+ &:focus::-webkit-slider-runnable-track {
2212
+ background: ${(props) => props.theme.inputBorder};
2213
+ }
2214
+
2215
+ &:focus::-moz-range-track {
2216
+ background: ${(props) => props.theme.inputBorder};
2217
+ }
2218
+
2219
+ &:focus::-webkit-slider-thumb {
2220
+ border: 2px solid ${(props) => props.theme.textColor};
2221
+ }
2222
+
2223
+ &:focus::-moz-range-thumb {
2224
+ border: 2px solid ${(props) => props.theme.textColor};
2225
+ }
2226
+ `;
2227
+
2228
+ // src/components/TrackControls/SliderWrapper.tsx
2229
+ var import_styled_components27 = __toESM(require("styled-components"));
2230
+ var SliderWrapper = import_styled_components27.default.label`
2231
+ width: 100%;
2232
+ display: flex;
2233
+ justify-content: space-between;
2234
+ align-items: center;
2235
+ padding: 0 1rem;
2236
+ margin-bottom: 0.2rem;
2237
+ font-size: 14px;
2238
+ `;
2239
+
2240
+ // src/components/TrackControlsWithDelete.tsx
2241
+ var import_styled_components28 = __toESM(require("styled-components"));
2242
+ var import_jsx_runtime25 = require("react/jsx-runtime");
2243
+ var HeaderContainer2 = import_styled_components28.default.div`
2244
+ display: flex;
2245
+ align-items: center;
2246
+ gap: 0.25rem;
2247
+ padding: 0.5rem 0.5rem 0.25rem 0.5rem;
2248
+ `;
2249
+ var TrackNameSpan = import_styled_components28.default.span`
2250
+ flex: 1;
2251
+ font-weight: 600;
2252
+ font-size: 0.875rem;
2253
+ overflow: hidden;
2254
+ text-overflow: ellipsis;
2255
+ white-space: nowrap;
2256
+ margin: 0 0.25rem;
2257
+ `;
2258
+ var DeleteIconButton = import_styled_components28.default.button`
2259
+ display: flex;
2260
+ align-items: center;
2261
+ justify-content: center;
2262
+ width: 20px;
2263
+ height: 20px;
2264
+ padding: 0;
2265
+ border: none;
2266
+ background: transparent;
2267
+ color: #999;
2268
+ cursor: pointer;
2269
+ font-size: 16px;
2270
+ line-height: 1;
2271
+ border-radius: 3px;
2272
+ transition: all 0.2s ease-in-out;
2273
+ flex-shrink: 0;
2274
+
2275
+ &:hover {
2276
+ background: #dc3545;
2277
+ color: white;
2278
+ }
2279
+
2280
+ &:active {
2281
+ transform: scale(0.9);
2282
+ }
2283
+ `;
2284
+ var TrackControlsWithDelete = ({
2285
+ trackName,
2286
+ muted,
2287
+ soloed,
2288
+ volume,
2289
+ pan,
2290
+ onMuteChange,
2291
+ onSoloChange,
2292
+ onVolumeChange,
2293
+ onPanChange,
2294
+ onDelete
2295
+ }) => {
2296
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(Controls, { children: [
2297
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(HeaderContainer2, { children: [
2298
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(DeleteIconButton, { onClick: onDelete, title: "Delete track", children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(TrashIcon, {}) }),
2299
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(TrackNameSpan, { children: trackName })
2300
+ ] }),
2301
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(ButtonGroup, { children: [
2302
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
2303
+ Button,
2304
+ {
2305
+ $variant: muted ? "danger" : "outline",
2306
+ onClick: () => onMuteChange(!muted),
2307
+ children: "Mute"
2308
+ }
2309
+ ),
2310
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
2311
+ Button,
2312
+ {
2313
+ $variant: soloed ? "info" : "outline",
2314
+ onClick: () => onSoloChange(!soloed),
2315
+ children: "Solo"
2316
+ }
2317
+ )
2318
+ ] }),
2319
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(SliderWrapper, { children: [
2320
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(VolumeDownIcon, {}),
2321
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
2322
+ Slider,
2323
+ {
2324
+ min: "0",
2325
+ max: "1",
2326
+ step: "0.01",
2327
+ value: volume,
2328
+ onChange: (e) => onVolumeChange(parseFloat(e.target.value))
2329
+ }
2330
+ ),
2331
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(VolumeUpIcon, {})
2332
+ ] }),
2333
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(SliderWrapper, { children: [
2334
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { children: "L" }),
2335
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
2336
+ Slider,
2337
+ {
2338
+ min: "-1",
2339
+ max: "1",
2340
+ step: "0.01",
2341
+ value: pan,
2342
+ onChange: (e) => onPanChange(parseFloat(e.target.value))
2343
+ }
2344
+ ),
2345
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { children: "R" })
2346
+ ] })
2347
+ ] });
2348
+ };
2349
+ // Annotate the CommonJS export names for ESM import in node:
2350
+ 0 && (module.exports = {
2351
+ AudioPosition,
2352
+ AutomaticScrollCheckbox,
2353
+ BaseButton,
2354
+ BaseCheckbox,
2355
+ BaseCheckboxLabel,
2356
+ BaseCheckboxWrapper,
2357
+ BaseControlButton,
2358
+ BaseInput,
2359
+ BaseLabel,
2360
+ BaseSelect,
2361
+ BaseSlider,
2362
+ Button,
2363
+ ButtonGroup,
2364
+ CLIP_BOUNDARY_WIDTH,
2365
+ CLIP_HEADER_HEIGHT,
2366
+ Channel,
2367
+ Clip,
2368
+ ClipBoundary,
2369
+ ClipHeader,
2370
+ ClipHeaderPresentational,
2371
+ Controls,
2372
+ DevicePixelRatioProvider,
2373
+ FadeOverlay,
2374
+ Header,
2375
+ InlineLabel,
2376
+ MasterVolumeControl,
2377
+ Playhead,
2378
+ PlayheadWithMarker,
2379
+ Playlist,
2380
+ PlaylistInfoContext,
2381
+ PlayoutProvider,
2382
+ ScreenReaderOnly,
2383
+ Selection,
2384
+ SelectionTimeInputs,
2385
+ Slider,
2386
+ SliderWrapper,
2387
+ SmartChannel,
2388
+ SmartScale,
2389
+ StyledPlaylist,
2390
+ StyledTimeScale,
2391
+ TimeFormatSelect,
2392
+ TimeInput,
2393
+ TimeScale,
2394
+ Track,
2395
+ TrackControlsContext,
2396
+ TrackControlsWithDelete,
2397
+ TrashIcon,
2398
+ VolumeDownIcon,
2399
+ VolumeUpIcon,
2400
+ darkTheme,
2401
+ defaultTheme,
2402
+ formatTime,
2403
+ isWaveformGradient,
2404
+ parseTime,
2405
+ pixelsToSamples,
2406
+ pixelsToSeconds,
2407
+ samplesToPixels,
2408
+ samplesToSeconds,
2409
+ secondsToPixels,
2410
+ secondsToSamples,
2411
+ useDevicePixelRatio,
2412
+ usePlaylistInfo,
2413
+ usePlayoutStatus,
2414
+ usePlayoutStatusUpdate,
2415
+ useTheme,
2416
+ useTrackControls,
2417
+ waveformColorToCss
2418
+ });
2419
+ //# sourceMappingURL=index.js.map