@waveform-playlist/annotations 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,1043 @@
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.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ Annotation: () => Annotation,
34
+ AnnotationBox: () => AnnotationBox,
35
+ AnnotationBoxesWrapper: () => AnnotationBoxesWrapper,
36
+ AnnotationText: () => AnnotationText2,
37
+ AnnotationsTrack: () => AnnotationsTrack,
38
+ ContinuousPlayCheckbox: () => ContinuousPlayCheckbox,
39
+ DownloadAnnotationsButton: () => DownloadAnnotationsButton,
40
+ EditableCheckbox: () => EditableCheckbox,
41
+ LinkEndpointsCheckbox: () => LinkEndpointsCheckbox,
42
+ parseAeneas: () => parseAeneas,
43
+ serializeAeneas: () => serializeAeneas,
44
+ useAnnotationControls: () => useAnnotationControls
45
+ });
46
+ module.exports = __toCommonJS(index_exports);
47
+
48
+ // src/parsers/aeneas.ts
49
+ function parseAeneas(data) {
50
+ return {
51
+ id: data.id,
52
+ start: parseFloat(data.begin),
53
+ end: parseFloat(data.end),
54
+ lines: data.lines,
55
+ lang: data.language
56
+ };
57
+ }
58
+ function serializeAeneas(annotation) {
59
+ return {
60
+ id: annotation.id,
61
+ begin: annotation.start.toFixed(3),
62
+ end: annotation.end.toFixed(3),
63
+ lines: annotation.lines,
64
+ language: annotation.lang || "en"
65
+ };
66
+ }
67
+
68
+ // src/components/Annotation.tsx
69
+ var import_react = require("react");
70
+ var import_styled_components = __toESM(require("styled-components"));
71
+ var import_jsx_runtime = require("react/jsx-runtime");
72
+ var AnnotationOverlay = import_styled_components.default.div.attrs((props) => ({
73
+ style: {
74
+ left: `${props.$left}px`,
75
+ width: `${props.$width}px`
76
+ }
77
+ }))`
78
+ position: absolute;
79
+ top: 0;
80
+ background: ${(props) => props.$color};
81
+ height: 100%;
82
+ z-index: 10;
83
+ pointer-events: auto;
84
+ opacity: 0.3;
85
+ border: 2px solid ${(props) => props.$color};
86
+ border-radius: 4px;
87
+ cursor: pointer;
88
+
89
+ &:hover {
90
+ opacity: 0.5;
91
+ border-color: ${(props) => props.$color};
92
+ }
93
+ `;
94
+ var AnnotationText = import_styled_components.default.div`
95
+ position: absolute;
96
+ bottom: 0;
97
+ left: 0;
98
+ right: 0;
99
+ background: rgba(0, 0, 0, 0.7);
100
+ color: white;
101
+ padding: 4px 8px;
102
+ font-size: 12px;
103
+ line-height: 1.3;
104
+ max-height: 60%;
105
+ overflow: hidden;
106
+ text-overflow: ellipsis;
107
+ pointer-events: none;
108
+ white-space: pre-wrap;
109
+ word-break: break-word;
110
+ `;
111
+ var EditableText = import_styled_components.default.textarea`
112
+ position: absolute;
113
+ bottom: 0;
114
+ left: 0;
115
+ right: 0;
116
+ background: rgba(0, 0, 0, 0.9);
117
+ color: white;
118
+ padding: 4px 8px;
119
+ font-size: 12px;
120
+ line-height: 1.3;
121
+ max-height: 60%;
122
+ overflow: auto;
123
+ border: 1px solid #fff;
124
+ resize: none;
125
+ font-family: inherit;
126
+
127
+ &:focus {
128
+ outline: none;
129
+ border-color: #4CAF50;
130
+ }
131
+ `;
132
+ var ControlsBar = import_styled_components.default.div`
133
+ position: absolute;
134
+ top: 0;
135
+ left: 0;
136
+ right: 0;
137
+ background: rgba(0, 0, 0, 0.8);
138
+ display: flex;
139
+ gap: 4px;
140
+ padding: 4px;
141
+ justify-content: flex-start;
142
+ align-items: center;
143
+ `;
144
+ var ControlButton = import_styled_components.default.button`
145
+ background: transparent;
146
+ border: 1px solid rgba(255, 255, 255, 0.5);
147
+ color: white;
148
+ padding: 4px 8px;
149
+ font-size: 10px;
150
+ cursor: pointer;
151
+ border-radius: 3px;
152
+ display: flex;
153
+ align-items: center;
154
+ justify-content: center;
155
+ min-width: 24px;
156
+ height: 24px;
157
+
158
+ &:hover {
159
+ background: rgba(255, 255, 255, 0.2);
160
+ border-color: white;
161
+ }
162
+
163
+ &:active {
164
+ background: rgba(255, 255, 255, 0.3);
165
+ }
166
+ `;
167
+ var Annotation = ({
168
+ annotation,
169
+ index,
170
+ allAnnotations,
171
+ startPosition,
172
+ endPosition,
173
+ color = "#ff9800",
174
+ editable = false,
175
+ controls = [],
176
+ onAnnotationUpdate,
177
+ annotationListConfig,
178
+ onClick
179
+ }) => {
180
+ const [isEditing, setIsEditing] = (0, import_react.useState)(false);
181
+ const [editedText, setEditedText] = (0, import_react.useState)(annotation.lines.join("\n"));
182
+ const width = Math.max(0, endPosition - startPosition);
183
+ if (width <= 0) {
184
+ return null;
185
+ }
186
+ const handleClick = () => {
187
+ if (onClick) {
188
+ onClick(annotation);
189
+ }
190
+ };
191
+ const handleDoubleClick = () => {
192
+ if (editable) {
193
+ setIsEditing(true);
194
+ }
195
+ };
196
+ const handleTextChange = (e) => {
197
+ setEditedText(e.target.value);
198
+ };
199
+ const handleTextBlur = () => {
200
+ setIsEditing(false);
201
+ const newLines = editedText.split("\n");
202
+ if (newLines.join("\n") !== annotation.lines.join("\n")) {
203
+ const updatedAnnotations = [...allAnnotations];
204
+ updatedAnnotations[index] = { ...annotation, lines: newLines };
205
+ if (onAnnotationUpdate) {
206
+ onAnnotationUpdate(updatedAnnotations);
207
+ }
208
+ }
209
+ };
210
+ const handleControlClick = (control) => {
211
+ const annotationsCopy = [...allAnnotations];
212
+ control.action(annotationsCopy[index], index, annotationsCopy, annotationListConfig || {});
213
+ if (onAnnotationUpdate) {
214
+ onAnnotationUpdate(annotationsCopy);
215
+ }
216
+ };
217
+ const getIconClass = (classString) => {
218
+ return classString.replace(/\./g, " ");
219
+ };
220
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
221
+ AnnotationOverlay,
222
+ {
223
+ $left: startPosition,
224
+ $width: width,
225
+ $color: color,
226
+ onClick: handleClick,
227
+ onDoubleClick: handleDoubleClick,
228
+ children: [
229
+ controls.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ControlsBar, { children: controls.map((control, idx) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
230
+ ControlButton,
231
+ {
232
+ title: control.title,
233
+ onClick: (e) => {
234
+ e.stopPropagation();
235
+ handleControlClick(control);
236
+ },
237
+ children: control.text ? control.text : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("i", { className: getIconClass(control.class || "") })
238
+ },
239
+ idx
240
+ )) }),
241
+ isEditing ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
242
+ EditableText,
243
+ {
244
+ value: editedText,
245
+ onChange: handleTextChange,
246
+ onBlur: handleTextBlur,
247
+ autoFocus: true,
248
+ onClick: (e) => e.stopPropagation(),
249
+ onDoubleClick: (e) => e.stopPropagation()
250
+ }
251
+ ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AnnotationText, { children: annotation.lines.join("\n") })
252
+ ]
253
+ }
254
+ );
255
+ };
256
+
257
+ // src/components/AnnotationBox.tsx
258
+ var import_styled_components2 = __toESM(require("styled-components"));
259
+ var import_core = require("@dnd-kit/core");
260
+ var import_jsx_runtime2 = require("react/jsx-runtime");
261
+ var Wrapper = import_styled_components2.default.div.attrs((props) => ({
262
+ style: {
263
+ left: `${props.$left}px`,
264
+ width: `${props.$width}px`
265
+ }
266
+ }))`
267
+ position: absolute;
268
+ top: 0;
269
+ height: 100%;
270
+ pointer-events: none; /* Let events pass through to children */
271
+ `;
272
+ var Box = import_styled_components2.default.div`
273
+ position: absolute;
274
+ top: 0;
275
+ left: 0;
276
+ right: 0;
277
+ height: 100%;
278
+ background: ${(props) => props.$isActive ? props.theme?.annotationBoxActiveBackground || "rgba(255, 200, 100, 0.95)" : props.theme?.annotationBoxBackground || "rgba(255, 255, 255, 0.85)"};
279
+ border: ${(props) => props.$isActive ? "3px" : "2px"} solid ${(props) => props.$isActive ? props.theme?.annotationBoxActiveBorder || "#ff9800" : props.$color};
280
+ border-radius: 4px;
281
+ cursor: pointer;
282
+ pointer-events: auto;
283
+ display: flex;
284
+ align-items: center;
285
+ justify-content: center;
286
+ overflow: hidden;
287
+ transition: all 0.2s ease;
288
+ box-shadow: ${(props) => props.$isActive ? "0 2px 8px rgba(255, 152, 0, 0.4), inset 0 0 0 1px rgba(255, 152, 0, 0.2)" : "0 1px 3px rgba(0, 0, 0, 0.1)"};
289
+
290
+ &:hover {
291
+ background: ${(props) => props.theme?.annotationBoxHoverBackground || "rgba(255, 255, 255, 0.98)"};
292
+ border-color: ${(props) => props.theme?.annotationBoxActiveBorder || "#ff9800"};
293
+ border-width: 3px;
294
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
295
+ }
296
+ `;
297
+ var Label = import_styled_components2.default.span`
298
+ font-size: 12px;
299
+ font-weight: 600;
300
+ color: ${(props) => props.theme?.annotationLabelColor || "#2a2a2a"};
301
+ white-space: nowrap;
302
+ overflow: hidden;
303
+ text-overflow: ellipsis;
304
+ padding: 0 6px;
305
+ letter-spacing: 0.3px;
306
+ user-select: none;
307
+ `;
308
+ var ResizeHandle = import_styled_components2.default.div`
309
+ position: absolute;
310
+ top: 0;
311
+ ${(props) => props.$position === "left" ? "left: -8px" : "right: -8px"};
312
+ width: 16px;
313
+ height: 100%;
314
+ cursor: ew-resize;
315
+ z-index: 120; /* Above ClickOverlay (z-index: 100) and AnnotationBoxesWrapper (z-index: 110) */
316
+ background: ${(props) => props.$isDragging ? props.theme?.annotationResizeHandleColor || "rgba(0, 0, 0, 0.2)" : "transparent"};
317
+ border-radius: 4px;
318
+ touch-action: none; /* Important for @dnd-kit on touch devices */
319
+ pointer-events: auto;
320
+
321
+ &::before {
322
+ content: '';
323
+ position: absolute;
324
+ top: 50%;
325
+ left: 50%;
326
+ transform: translate(-50%, -50%);
327
+ width: 4px;
328
+ height: 60%;
329
+ background: ${(props) => props.$isDragging ? props.theme?.annotationResizeHandleActiveColor || "rgba(0, 0, 0, 0.8)" : props.theme?.annotationResizeHandleColor || "rgba(0, 0, 0, 0.4)"};
330
+ border-radius: 2px;
331
+ opacity: ${(props) => props.$isDragging ? 1 : 0.6};
332
+ transition: opacity 0.2s, background 0.2s;
333
+ }
334
+
335
+ &:hover {
336
+ background: ${(props) => props.theme?.annotationResizeHandleColor || "rgba(0, 0, 0, 0.1)"};
337
+ }
338
+
339
+ &:hover::before {
340
+ opacity: 1;
341
+ background: ${(props) => props.theme?.annotationResizeHandleActiveColor || "rgba(0, 0, 0, 0.7)"};
342
+ }
343
+ `;
344
+ var AnnotationBox = ({
345
+ annotationId,
346
+ annotationIndex,
347
+ startPosition,
348
+ endPosition,
349
+ label,
350
+ color = "#ff9800",
351
+ isActive = false,
352
+ onClick,
353
+ editable = true
354
+ }) => {
355
+ const width = Math.max(0, endPosition - startPosition);
356
+ const leftBoundaryId = `annotation-boundary-start-${annotationIndex}`;
357
+ const {
358
+ attributes: leftAttributes,
359
+ listeners: leftListeners,
360
+ setActivatorNodeRef: setLeftActivatorRef,
361
+ isDragging: isLeftDragging
362
+ } = (0, import_core.useDraggable)({
363
+ id: leftBoundaryId,
364
+ data: { annotationId, annotationIndex, edge: "start" },
365
+ disabled: !editable
366
+ });
367
+ const rightBoundaryId = `annotation-boundary-end-${annotationIndex}`;
368
+ const {
369
+ attributes: rightAttributes,
370
+ listeners: rightListeners,
371
+ setActivatorNodeRef: setRightActivatorRef,
372
+ isDragging: isRightDragging
373
+ } = (0, import_core.useDraggable)({
374
+ id: rightBoundaryId,
375
+ data: { annotationId, annotationIndex, edge: "end" },
376
+ disabled: !editable
377
+ });
378
+ if (width <= 0) {
379
+ return null;
380
+ }
381
+ const createPointerDownHandler = (dndKitHandler) => {
382
+ return (e) => {
383
+ e.stopPropagation();
384
+ dndKitHandler?.(e);
385
+ };
386
+ };
387
+ const handleHandleClick = (e) => {
388
+ e.stopPropagation();
389
+ };
390
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Wrapper, { $left: startPosition, $width: width, children: [
391
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
392
+ Box,
393
+ {
394
+ $color: color,
395
+ $isActive: isActive,
396
+ onClick,
397
+ children: label && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Label, { children: label })
398
+ }
399
+ ),
400
+ editable && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
401
+ ResizeHandle,
402
+ {
403
+ ref: setLeftActivatorRef,
404
+ $position: "left",
405
+ $isDragging: isLeftDragging,
406
+ onClick: handleHandleClick,
407
+ ...leftListeners,
408
+ onPointerDown: createPointerDownHandler(leftListeners?.onPointerDown),
409
+ ...leftAttributes
410
+ }
411
+ ),
412
+ editable && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
413
+ ResizeHandle,
414
+ {
415
+ ref: setRightActivatorRef,
416
+ $position: "right",
417
+ $isDragging: isRightDragging,
418
+ onClick: handleHandleClick,
419
+ ...rightListeners,
420
+ onPointerDown: createPointerDownHandler(rightListeners?.onPointerDown),
421
+ ...rightAttributes
422
+ }
423
+ )
424
+ ] });
425
+ };
426
+
427
+ // src/components/AnnotationBoxesWrapper.tsx
428
+ var import_styled_components3 = __toESM(require("styled-components"));
429
+ var import_ui_components = require("@waveform-playlist/ui-components");
430
+ var import_jsx_runtime3 = require("react/jsx-runtime");
431
+ var Container = import_styled_components3.default.div.attrs((props) => ({
432
+ style: {
433
+ height: `${props.$height}px`
434
+ }
435
+ }))`
436
+ position: relative;
437
+ display: flex;
438
+ ${(props) => props.$width !== void 0 && `width: ${props.$width}px;`}
439
+ background: transparent;
440
+ z-index: 110;
441
+ `;
442
+ var ControlsPlaceholder = import_styled_components3.default.div`
443
+ position: sticky;
444
+ z-index: 200;
445
+ left: 0;
446
+ height: 100%;
447
+ width: ${(props) => props.$controlWidth}px;
448
+ flex-shrink: 0;
449
+ background: transparent;
450
+ `;
451
+ var BoxesContainer = import_styled_components3.default.div`
452
+ position: relative;
453
+ flex: 1;
454
+ padding-left: ${(props) => props.$offset || 0}px;
455
+ `;
456
+ var AnnotationBoxesWrapper = ({
457
+ children,
458
+ className,
459
+ height = 30,
460
+ offset = 0,
461
+ width
462
+ }) => {
463
+ const {
464
+ controls: { show, width: controlWidth }
465
+ } = (0, import_ui_components.usePlaylistInfo)();
466
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
467
+ Container,
468
+ {
469
+ className,
470
+ $height: height,
471
+ $controlWidth: show ? controlWidth : 0,
472
+ $width: width,
473
+ children: [
474
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ControlsPlaceholder, { $controlWidth: show ? controlWidth : 0 }),
475
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(BoxesContainer, { $offset: offset, children })
476
+ ]
477
+ }
478
+ );
479
+ };
480
+
481
+ // src/components/AnnotationsTrack.tsx
482
+ var import_styled_components4 = __toESM(require("styled-components"));
483
+ var import_ui_components2 = require("@waveform-playlist/ui-components");
484
+ var import_jsx_runtime4 = require("react/jsx-runtime");
485
+ var Container2 = import_styled_components4.default.div.attrs((props) => ({
486
+ style: {
487
+ height: `${props.$height}px`
488
+ }
489
+ }))`
490
+ position: relative;
491
+ display: flex;
492
+ ${(props) => props.$width !== void 0 && `width: ${props.$width}px;`}
493
+ background: transparent;
494
+ `;
495
+ var ControlsPlaceholder2 = import_styled_components4.default.div`
496
+ position: sticky;
497
+ z-index: 200;
498
+ left: 0;
499
+ height: 100%;
500
+ width: ${(props) => props.$controlWidth}px;
501
+ flex-shrink: 0;
502
+ background: transparent;
503
+ display: flex;
504
+ align-items: center;
505
+ justify-content: center;
506
+ font-size: 12px;
507
+ color: ${(props) => props.theme?.textColorMuted || "#666"};
508
+ font-weight: bold;
509
+ `;
510
+ var AnnotationsContainer = import_styled_components4.default.div`
511
+ position: relative;
512
+ flex: 1;
513
+ padding-left: ${(props) => props.$offset || 0}px;
514
+ `;
515
+ var AnnotationsTrack = ({
516
+ children,
517
+ className,
518
+ height = 100,
519
+ offset = 0,
520
+ width
521
+ }) => {
522
+ const {
523
+ controls: { show, width: controlWidth }
524
+ } = (0, import_ui_components2.usePlaylistInfo)();
525
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
526
+ Container2,
527
+ {
528
+ className,
529
+ $height: height,
530
+ $controlWidth: show ? controlWidth : 0,
531
+ $width: width,
532
+ children: [
533
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ControlsPlaceholder2, { $controlWidth: show ? controlWidth : 0, children: "Annotations" }),
534
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(AnnotationsContainer, { $offset: offset, children })
535
+ ]
536
+ }
537
+ );
538
+ };
539
+
540
+ // src/components/AnnotationText.tsx
541
+ var import_react2 = __toESM(require("react"));
542
+ var import_styled_components5 = __toESM(require("styled-components"));
543
+ var import_jsx_runtime5 = require("react/jsx-runtime");
544
+ var Container3 = import_styled_components5.default.div`
545
+ background: ${(props) => props.theme?.backgroundColor || "#fff"};
546
+ ${(props) => props.$height ? `height: ${props.$height}px;` : "max-height: 200px;"}
547
+ overflow-y: auto;
548
+ padding: 8px;
549
+ `;
550
+ var AnnotationItem = import_styled_components5.default.div`
551
+ padding: 12px;
552
+ margin-bottom: 6px;
553
+ border-left: 4px solid ${(props) => props.$isActive ? "#ff9800" : "transparent"};
554
+ background: ${(props) => props.$isActive ? "rgba(255, 152, 0, 0.15)" : "transparent"};
555
+ border-radius: 4px;
556
+ transition: all 0.2s;
557
+ cursor: pointer;
558
+ box-shadow: ${(props) => props.$isActive ? "0 2px 8px rgba(255, 152, 0, 0.25), inset 0 0 0 1px rgba(255, 152, 0, 0.3)" : "none"};
559
+
560
+ &:hover {
561
+ background: ${(props) => props.$isActive ? "rgba(255, 152, 0, 0.2)" : props.theme?.annotationTextItemHoverBackground || "rgba(0, 0, 0, 0.05)"};
562
+ border-left-color: ${(props) => props.$isActive ? "#ff9800" : props.theme?.borderColor || "#ddd"};
563
+ }
564
+
565
+ &:focus-visible {
566
+ outline: 2px solid #ff9800;
567
+ outline-offset: 2px;
568
+ }
569
+ `;
570
+ var AnnotationHeader = import_styled_components5.default.div`
571
+ display: flex;
572
+ justify-content: space-between;
573
+ align-items: center;
574
+ margin-bottom: 6px;
575
+ `;
576
+ var AnnotationInfo = import_styled_components5.default.div`
577
+ display: flex;
578
+ align-items: center;
579
+ gap: 8px;
580
+ `;
581
+ var AnnotationIdLabel = import_styled_components5.default.span`
582
+ font-size: 11px;
583
+ font-weight: 600;
584
+ color: ${(props) => props.theme?.textColorMuted || "#666"};
585
+ background: transparent;
586
+ padding: 2px 6px;
587
+ border-radius: 3px;
588
+ min-width: 20px;
589
+ outline: ${(props) => props.$isEditable ? `1px dashed ${props.theme?.borderColor || "#ddd"}` : "none"};
590
+
591
+ &[contenteditable='true']:focus {
592
+ outline: 2px solid #ff9800;
593
+ background: rgba(255, 152, 0, 0.1);
594
+ }
595
+ `;
596
+ var TimeRange = import_styled_components5.default.span`
597
+ font-size: 12px;
598
+ font-weight: 500;
599
+ color: ${(props) => props.theme?.textColorMuted || "#555"};
600
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
601
+ letter-spacing: 0.5px;
602
+ `;
603
+ var AnnotationControls = import_styled_components5.default.div`
604
+ display: flex;
605
+ gap: 6px;
606
+ `;
607
+ var ControlButton2 = import_styled_components5.default.button`
608
+ background: ${(props) => props.theme?.surfaceColor || "#f5f5f5"};
609
+ border: 1px solid ${(props) => props.theme?.borderColor || "#ccc"};
610
+ color: ${(props) => props.theme?.textColor || "#333"};
611
+ padding: 4px 8px;
612
+ font-size: 14px;
613
+ cursor: pointer;
614
+ border-radius: 4px;
615
+ transition: all 0.15s ease;
616
+
617
+ &:hover {
618
+ background: ${(props) => props.theme?.inputBackground || "#3d3d3d"};
619
+ border-color: ${(props) => props.theme?.textColorMuted || "#999"};
620
+ transform: scale(1.05);
621
+ }
622
+
623
+ &:active {
624
+ transform: scale(0.95);
625
+ }
626
+ `;
627
+ var AnnotationTextContent = import_styled_components5.default.div`
628
+ font-size: 14px;
629
+ line-height: 1.6;
630
+ color: ${(props) => props.theme?.textColor || "#2a2a2a"};
631
+ white-space: pre-wrap;
632
+ word-break: break-word;
633
+ outline: ${(props) => props.$isEditable ? `1px dashed ${props.theme?.borderColor || "#ddd"}` : "none"};
634
+ padding: ${(props) => props.$isEditable ? "6px" : "0"};
635
+ border-radius: 3px;
636
+ min-height: 20px;
637
+
638
+ &[contenteditable='true']:focus {
639
+ outline: 2px solid #ff9800;
640
+ background: rgba(255, 152, 0, 0.1);
641
+ }
642
+ `;
643
+ var AnnotationTextComponent = ({
644
+ annotations,
645
+ activeAnnotationId,
646
+ shouldScrollToActive = false,
647
+ editable = false,
648
+ controls = [],
649
+ annotationListConfig,
650
+ height,
651
+ onAnnotationClick,
652
+ onAnnotationUpdate
653
+ }) => {
654
+ const activeAnnotationRef = (0, import_react2.useRef)(null);
655
+ const containerRef = (0, import_react2.useRef)(null);
656
+ const prevActiveIdRef = (0, import_react2.useRef)(void 0);
657
+ (0, import_react2.useEffect)(() => {
658
+ });
659
+ (0, import_react2.useEffect)(() => {
660
+ const container = containerRef.current;
661
+ if (!container) return;
662
+ const handleScroll = () => {
663
+ };
664
+ container.addEventListener("scroll", handleScroll);
665
+ return () => container.removeEventListener("scroll", handleScroll);
666
+ }, []);
667
+ (0, import_react2.useEffect)(() => {
668
+ if (activeAnnotationId && activeAnnotationRef.current && shouldScrollToActive) {
669
+ activeAnnotationRef.current.scrollIntoView({
670
+ behavior: "smooth",
671
+ block: "nearest"
672
+ });
673
+ }
674
+ prevActiveIdRef.current = activeAnnotationId;
675
+ }, [activeAnnotationId, shouldScrollToActive]);
676
+ const formatTime = (seconds) => {
677
+ if (isNaN(seconds) || !isFinite(seconds)) {
678
+ return "0:00.000";
679
+ }
680
+ const mins = Math.floor(seconds / 60);
681
+ const secs = (seconds % 60).toFixed(3);
682
+ return `${mins}:${secs.padStart(6, "0")}`;
683
+ };
684
+ const handleTextEdit = (index, newText) => {
685
+ if (!editable || !onAnnotationUpdate) return;
686
+ const updatedAnnotations = [...annotations];
687
+ updatedAnnotations[index] = {
688
+ ...updatedAnnotations[index],
689
+ lines: newText.split("\n")
690
+ };
691
+ onAnnotationUpdate(updatedAnnotations);
692
+ };
693
+ const handleIdEdit = (index, newId) => {
694
+ if (!editable || !onAnnotationUpdate) return;
695
+ const trimmedId = newId.trim();
696
+ if (!trimmedId) return;
697
+ const updatedAnnotations = [...annotations];
698
+ updatedAnnotations[index] = {
699
+ ...updatedAnnotations[index],
700
+ id: trimmedId
701
+ };
702
+ onAnnotationUpdate(updatedAnnotations);
703
+ };
704
+ const handleControlClick = (control, annotation, index) => {
705
+ if (!onAnnotationUpdate) return;
706
+ const annotationsCopy = [...annotations];
707
+ control.action(annotationsCopy[index], index, annotationsCopy, annotationListConfig || {});
708
+ onAnnotationUpdate(annotationsCopy);
709
+ };
710
+ const getIconClass = (classString) => {
711
+ return classString.replace(/\./g, " ");
712
+ };
713
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Container3, { ref: containerRef, $height: height, children: annotations.map((annotation, index) => {
714
+ const isActive = annotation.id === activeAnnotationId;
715
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
716
+ AnnotationItem,
717
+ {
718
+ ref: isActive ? activeAnnotationRef : null,
719
+ $isActive: isActive,
720
+ onClick: () => onAnnotationClick?.(annotation),
721
+ children: [
722
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(AnnotationHeader, { children: [
723
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(AnnotationInfo, { children: [
724
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
725
+ AnnotationIdLabel,
726
+ {
727
+ $isEditable: editable,
728
+ contentEditable: editable,
729
+ suppressContentEditableWarning: true,
730
+ onBlur: (e) => handleIdEdit(index, e.currentTarget.textContent || ""),
731
+ children: annotation.id
732
+ }
733
+ ),
734
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(TimeRange, { children: [
735
+ formatTime(annotation.start),
736
+ " - ",
737
+ formatTime(annotation.end)
738
+ ] })
739
+ ] }),
740
+ controls.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(AnnotationControls, { onClick: (e) => e.stopPropagation(), children: controls.map((control, idx) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
741
+ ControlButton2,
742
+ {
743
+ title: control.title,
744
+ onClick: () => handleControlClick(control, annotation, index),
745
+ children: control.text ? control.text : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("i", { className: getIconClass(control.class || "") })
746
+ },
747
+ idx
748
+ )) })
749
+ ] }),
750
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
751
+ AnnotationTextContent,
752
+ {
753
+ $isEditable: editable,
754
+ contentEditable: editable,
755
+ suppressContentEditableWarning: true,
756
+ onBlur: (e) => handleTextEdit(index, e.currentTarget.textContent || ""),
757
+ children: annotation.lines.join("\n")
758
+ }
759
+ )
760
+ ]
761
+ },
762
+ annotation.id
763
+ );
764
+ }) });
765
+ };
766
+ var AnnotationText2 = import_react2.default.memo(AnnotationTextComponent);
767
+
768
+ // src/components/ContinuousPlayCheckbox.tsx
769
+ var import_ui_components3 = require("@waveform-playlist/ui-components");
770
+ var import_jsx_runtime6 = require("react/jsx-runtime");
771
+ var ContinuousPlayCheckbox = ({
772
+ checked,
773
+ onChange,
774
+ disabled = false,
775
+ className
776
+ }) => {
777
+ const handleChange = (e) => {
778
+ onChange(e.target.checked);
779
+ };
780
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_ui_components3.BaseCheckboxWrapper, { className, children: [
781
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
782
+ import_ui_components3.BaseCheckbox,
783
+ {
784
+ type: "checkbox",
785
+ id: "continuous-play",
786
+ className: "continuous-play",
787
+ checked,
788
+ onChange: handleChange,
789
+ disabled
790
+ }
791
+ ),
792
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_ui_components3.BaseCheckboxLabel, { htmlFor: "continuous-play", children: "Continuous Play" })
793
+ ] });
794
+ };
795
+
796
+ // src/components/LinkEndpointsCheckbox.tsx
797
+ var import_ui_components4 = require("@waveform-playlist/ui-components");
798
+ var import_jsx_runtime7 = require("react/jsx-runtime");
799
+ var LinkEndpointsCheckbox = ({
800
+ checked,
801
+ onChange,
802
+ disabled = false,
803
+ className
804
+ }) => {
805
+ const handleChange = (e) => {
806
+ onChange(e.target.checked);
807
+ };
808
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ui_components4.BaseCheckboxWrapper, { className, children: [
809
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
810
+ import_ui_components4.BaseCheckbox,
811
+ {
812
+ type: "checkbox",
813
+ id: "link-endpoints",
814
+ className: "link-endpoints",
815
+ checked,
816
+ onChange: handleChange,
817
+ disabled
818
+ }
819
+ ),
820
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ui_components4.BaseCheckboxLabel, { htmlFor: "link-endpoints", children: "Link Endpoints" })
821
+ ] });
822
+ };
823
+
824
+ // src/components/EditableCheckbox.tsx
825
+ var import_ui_components5 = require("@waveform-playlist/ui-components");
826
+ var import_jsx_runtime8 = require("react/jsx-runtime");
827
+ var EditableCheckbox = ({
828
+ checked,
829
+ onChange,
830
+ className
831
+ }) => {
832
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_ui_components5.BaseCheckboxWrapper, { className, children: [
833
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
834
+ import_ui_components5.BaseCheckbox,
835
+ {
836
+ type: "checkbox",
837
+ id: "editable-annotations",
838
+ checked,
839
+ onChange: (e) => onChange(e.target.checked)
840
+ }
841
+ ),
842
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ui_components5.BaseCheckboxLabel, { htmlFor: "editable-annotations", children: "Editable Annotations" })
843
+ ] });
844
+ };
845
+
846
+ // src/components/DownloadAnnotationsButton.tsx
847
+ var import_styled_components6 = __toESM(require("styled-components"));
848
+ var import_jsx_runtime9 = require("react/jsx-runtime");
849
+ var StyledButton = import_styled_components6.default.button`
850
+ padding: 0.5rem 1rem;
851
+ background: ${(props) => props.theme?.surfaceColor || "#f5f5f5"};
852
+ color: ${(props) => props.theme?.textColor || "#333"};
853
+ border: 1px solid ${(props) => props.theme?.borderColor || "#ccc"};
854
+ border-radius: ${(props) => props.theme?.borderRadius || "4px"};
855
+ cursor: pointer;
856
+ font-family: ${(props) => props.theme?.fontFamily || "inherit"};
857
+ font-size: ${(props) => props.theme?.fontSize || "14px"};
858
+ font-weight: 500;
859
+ transition: all 0.15s ease;
860
+
861
+ &:hover:not(:disabled) {
862
+ background: ${(props) => props.theme?.inputBackground || "#3d3d3d"};
863
+ border-color: ${(props) => props.theme?.textColorMuted || "#999"};
864
+ }
865
+
866
+ &:focus {
867
+ outline: none;
868
+ box-shadow: 0 0 0 2px ${(props) => props.theme?.inputFocusBorder || "#007bff"}44;
869
+ }
870
+
871
+ &:disabled {
872
+ opacity: 0.6;
873
+ cursor: not-allowed;
874
+ }
875
+ `;
876
+ var DownloadAnnotationsButton = ({
877
+ annotations,
878
+ filename = "annotations.json",
879
+ disabled = false,
880
+ className,
881
+ children = "Download JSON"
882
+ }) => {
883
+ const handleDownload = () => {
884
+ if (annotations.length === 0) {
885
+ return;
886
+ }
887
+ const jsonData = annotations.map((annotation) => serializeAeneas(annotation));
888
+ const jsonString = JSON.stringify(jsonData, null, 2);
889
+ const blob = new Blob([jsonString], { type: "application/json" });
890
+ const url = URL.createObjectURL(blob);
891
+ const link = document.createElement("a");
892
+ link.href = url;
893
+ link.download = filename;
894
+ document.body.appendChild(link);
895
+ link.click();
896
+ document.body.removeChild(link);
897
+ URL.revokeObjectURL(url);
898
+ };
899
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
900
+ StyledButton,
901
+ {
902
+ onClick: handleDownload,
903
+ disabled: disabled || annotations.length === 0,
904
+ className,
905
+ title: annotations.length === 0 ? "No annotations to download" : "Download the annotations as JSON",
906
+ children
907
+ }
908
+ );
909
+ };
910
+
911
+ // src/hooks/useAnnotationControls.ts
912
+ var import_react3 = require("react");
913
+ var LINK_THRESHOLD = 0.01;
914
+ var useAnnotationControls = (options = {}) => {
915
+ const {
916
+ initialContinuousPlay = false,
917
+ initialLinkEndpoints = true
918
+ } = options;
919
+ const [continuousPlay, setContinuousPlay] = (0, import_react3.useState)(initialContinuousPlay);
920
+ const [linkEndpoints, setLinkEndpoints] = (0, import_react3.useState)(initialLinkEndpoints);
921
+ const updateAnnotationBoundaries = (0, import_react3.useCallback)(
922
+ ({
923
+ annotationIndex,
924
+ newTime,
925
+ isDraggingStart,
926
+ annotations,
927
+ duration,
928
+ linkEndpoints: shouldLinkEndpoints
929
+ }) => {
930
+ const updatedAnnotations = [...annotations];
931
+ const annotation = annotations[annotationIndex];
932
+ if (isDraggingStart) {
933
+ const constrainedStart = Math.min(annotation.end - 0.1, Math.max(0, newTime));
934
+ const delta = constrainedStart - annotation.start;
935
+ updatedAnnotations[annotationIndex] = {
936
+ ...annotation,
937
+ start: constrainedStart
938
+ };
939
+ if (shouldLinkEndpoints && annotationIndex > 0) {
940
+ const prevAnnotation = updatedAnnotations[annotationIndex - 1];
941
+ if (Math.abs(prevAnnotation.end - annotation.start) < LINK_THRESHOLD) {
942
+ updatedAnnotations[annotationIndex - 1] = {
943
+ ...prevAnnotation,
944
+ end: Math.max(prevAnnotation.start + 0.1, prevAnnotation.end + delta)
945
+ };
946
+ } else if (constrainedStart <= prevAnnotation.end) {
947
+ updatedAnnotations[annotationIndex] = {
948
+ ...updatedAnnotations[annotationIndex],
949
+ start: prevAnnotation.end
950
+ };
951
+ }
952
+ } else if (!shouldLinkEndpoints && annotationIndex > 0 && constrainedStart < updatedAnnotations[annotationIndex - 1].end) {
953
+ updatedAnnotations[annotationIndex - 1] = {
954
+ ...updatedAnnotations[annotationIndex - 1],
955
+ end: constrainedStart
956
+ };
957
+ }
958
+ } else {
959
+ const constrainedEnd = Math.max(annotation.start + 0.1, Math.min(newTime, duration));
960
+ const delta = constrainedEnd - annotation.end;
961
+ updatedAnnotations[annotationIndex] = {
962
+ ...annotation,
963
+ end: constrainedEnd
964
+ };
965
+ if (shouldLinkEndpoints && annotationIndex < updatedAnnotations.length - 1) {
966
+ const nextAnnotation = updatedAnnotations[annotationIndex + 1];
967
+ if (Math.abs(nextAnnotation.start - annotation.end) < LINK_THRESHOLD) {
968
+ const newStart = nextAnnotation.start + delta;
969
+ updatedAnnotations[annotationIndex + 1] = {
970
+ ...nextAnnotation,
971
+ start: Math.min(nextAnnotation.end - 0.1, newStart)
972
+ };
973
+ let currentIndex = annotationIndex + 1;
974
+ while (currentIndex < updatedAnnotations.length - 1) {
975
+ const current = updatedAnnotations[currentIndex];
976
+ const next = updatedAnnotations[currentIndex + 1];
977
+ if (Math.abs(next.start - current.end) < LINK_THRESHOLD) {
978
+ const nextDelta = current.end - annotations[currentIndex].end;
979
+ updatedAnnotations[currentIndex + 1] = {
980
+ ...next,
981
+ start: Math.min(next.end - 0.1, next.start + nextDelta)
982
+ };
983
+ currentIndex++;
984
+ } else {
985
+ break;
986
+ }
987
+ }
988
+ } else if (constrainedEnd >= nextAnnotation.start) {
989
+ updatedAnnotations[annotationIndex] = {
990
+ ...updatedAnnotations[annotationIndex],
991
+ end: nextAnnotation.start
992
+ };
993
+ }
994
+ } else if (!shouldLinkEndpoints && annotationIndex < updatedAnnotations.length - 1 && constrainedEnd > updatedAnnotations[annotationIndex + 1].start) {
995
+ const nextAnnotation = updatedAnnotations[annotationIndex + 1];
996
+ updatedAnnotations[annotationIndex + 1] = {
997
+ ...nextAnnotation,
998
+ start: constrainedEnd
999
+ };
1000
+ let currentIndex = annotationIndex + 1;
1001
+ while (currentIndex < updatedAnnotations.length - 1) {
1002
+ const current = updatedAnnotations[currentIndex];
1003
+ const next = updatedAnnotations[currentIndex + 1];
1004
+ if (current.end > next.start) {
1005
+ updatedAnnotations[currentIndex + 1] = {
1006
+ ...next,
1007
+ start: current.end
1008
+ };
1009
+ currentIndex++;
1010
+ } else {
1011
+ break;
1012
+ }
1013
+ }
1014
+ }
1015
+ }
1016
+ return updatedAnnotations;
1017
+ },
1018
+ []
1019
+ );
1020
+ return {
1021
+ continuousPlay,
1022
+ linkEndpoints,
1023
+ setContinuousPlay,
1024
+ setLinkEndpoints,
1025
+ updateAnnotationBoundaries
1026
+ };
1027
+ };
1028
+ // Annotate the CommonJS export names for ESM import in node:
1029
+ 0 && (module.exports = {
1030
+ Annotation,
1031
+ AnnotationBox,
1032
+ AnnotationBoxesWrapper,
1033
+ AnnotationText,
1034
+ AnnotationsTrack,
1035
+ ContinuousPlayCheckbox,
1036
+ DownloadAnnotationsButton,
1037
+ EditableCheckbox,
1038
+ LinkEndpointsCheckbox,
1039
+ parseAeneas,
1040
+ serializeAeneas,
1041
+ useAnnotationControls
1042
+ });
1043
+ //# sourceMappingURL=index.js.map