@hyperframes/studio 0.6.32 → 0.6.33

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.
@@ -4,6 +4,7 @@ import { formatFrameTime, frameToSeconds, stepFrameTime, formatTime } from "../l
4
4
  import { shouldMutePreviewAudio } from "../lib/timelineIframeHelpers";
5
5
  import { usePlayerStore, liveTime } from "../store/playerStore";
6
6
  import { trackStudioEvent } from "../../utils/studioTelemetry";
7
+ import { Tooltip } from "../../components/ui";
7
8
 
8
9
  const SPEED_OPTIONS = [0.25, 0.5, 1, 1.5, 2] as const;
9
10
  const SEEK_EDGE_SNAP_PX = 8;
@@ -340,46 +341,51 @@ export const PlayerControls = memo(function PlayerControls({
340
341
  }}
341
342
  >
342
343
  {/* Play/Pause button */}
343
- <button
344
- type="button"
345
- aria-label={isPlaying ? "Pause" : "Play"}
346
- onClick={() => {
347
- trackStudioEvent("playback", { action: isPlaying ? "pause" : "play" });
348
- onTogglePlay();
349
- }}
350
- disabled={controlsDisabled}
351
- className="flex-shrink-0 w-8 h-8 flex items-center justify-center rounded-lg disabled:opacity-30 disabled:pointer-events-none transition-colors"
352
- style={{ background: "rgba(255,255,255,0.06)" }}
353
- >
354
- {isPlaying ? (
355
- <svg width="12" height="12" viewBox="0 0 24 24" fill="#FAFAFA" aria-hidden="true">
356
- <rect x="6" y="4" width="4" height="16" rx="1" />
357
- <rect x="14" y="4" width="4" height="16" rx="1" />
358
- </svg>
359
- ) : (
360
- <svg width="12" height="12" viewBox="0 0 24 24" fill="#FAFAFA" aria-hidden="true">
361
- <polygon points="6,3 20,12 6,21" />
362
- </svg>
363
- )}
364
- </button>
344
+ <Tooltip label={isPlaying ? "Pause" : "Play"}>
345
+ <button
346
+ type="button"
347
+ aria-label={isPlaying ? "Pause" : "Play"}
348
+ onClick={() => {
349
+ trackStudioEvent("playback", { action: isPlaying ? "pause" : "play" });
350
+ onTogglePlay();
351
+ }}
352
+ disabled={controlsDisabled}
353
+ className="flex-shrink-0 w-8 h-8 flex items-center justify-center rounded-lg disabled:opacity-30 disabled:pointer-events-none transition-colors"
354
+ style={{ background: "rgba(255,255,255,0.06)" }}
355
+ >
356
+ {isPlaying ? (
357
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="#FAFAFA" aria-hidden="true">
358
+ <rect x="6" y="4" width="4" height="16" rx="1" />
359
+ <rect x="14" y="4" width="4" height="16" rx="1" />
360
+ </svg>
361
+ ) : (
362
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="#FAFAFA" aria-hidden="true">
363
+ <polygon points="6,3 20,12 6,21" />
364
+ </svg>
365
+ )}
366
+ </button>
367
+ </Tooltip>
365
368
 
366
369
  {/* Time display — click to toggle time/frame mode */}
367
- <button
368
- type="button"
369
- onClick={() => setTimeDisplayMode((m) => (m === "time" ? "frame" : "time"))}
370
- disabled={disabled}
371
- title={timeDisplayMode === "time" ? "Switch to frame display" : "Switch to time display"}
372
- className="font-mono text-[11px] tabular-nums flex-shrink-0 w-[118px] text-left transition-colors disabled:pointer-events-none hover:opacity-80"
373
- style={{ color: "#A1A1AA", cursor: "pointer" }}
370
+ <Tooltip
371
+ label={timeDisplayMode === "time" ? "Switch to frame display" : "Switch to time display"}
374
372
  >
375
- <span ref={timeDisplayRef}>{formatTime(0)}</span>
376
- {timeDisplayMode === "time" ? (
377
- <>
378
- <span style={{ color: "#3F3F46", margin: "0 2px" }}>/</span>
379
- <span style={{ color: "#52525B" }}>{formatTime(duration)}</span>
380
- </>
381
- ) : null}
382
- </button>
373
+ <button
374
+ type="button"
375
+ onClick={() => setTimeDisplayMode((m) => (m === "time" ? "frame" : "time"))}
376
+ disabled={disabled}
377
+ className="font-mono text-[11px] tabular-nums flex-shrink-0 w-[118px] text-left transition-colors disabled:pointer-events-none hover:opacity-80"
378
+ style={{ color: "#A1A1AA", cursor: "pointer" }}
379
+ >
380
+ <span ref={timeDisplayRef}>{formatTime(0)}</span>
381
+ {timeDisplayMode === "time" ? (
382
+ <>
383
+ <span style={{ color: "#3F3F46", margin: "0 2px" }}>/</span>
384
+ <span style={{ color: "#52525B" }}>{formatTime(duration)}</span>
385
+ </>
386
+ ) : null}
387
+ </button>
388
+ </Tooltip>
383
389
 
384
390
  {/* Seek bar — teal progress fill */}
385
391
  <div
@@ -469,70 +475,73 @@ export const PlayerControls = memo(function PlayerControls({
469
475
  </div>
470
476
 
471
477
  {/* Mute toggle */}
472
- <button
473
- type="button"
474
- onClick={() => {
475
- if (!audioAutoMuted) {
476
- trackStudioEvent("playback", { action: "mute_toggle", muted: !audioMuted });
477
- setAudioMuted(!audioMuted);
478
- }
479
- }}
480
- disabled={controlsDisabled || audioAutoMuted}
481
- title={muteButtonLabel}
482
- aria-label={muteButtonLabel}
483
- aria-pressed={effectiveAudioMuted}
484
- className={`h-7 w-7 flex-shrink-0 flex items-center justify-center rounded-md border transition-colors disabled:pointer-events-none ${
485
- effectiveAudioMuted
486
- ? "text-studio-accent bg-studio-accent/10 border-studio-accent/30"
487
- : "border-neutral-700 text-neutral-400 hover:border-neutral-500 hover:bg-neutral-800"
488
- } ${audioAutoMuted ? "opacity-70" : ""}`}
489
- >
490
- {effectiveAudioMuted ? (
491
- <svg
492
- width="13"
493
- height="13"
494
- viewBox="0 0 24 24"
495
- fill="none"
496
- stroke="currentColor"
497
- strokeWidth="2"
498
- strokeLinecap="round"
499
- strokeLinejoin="round"
500
- aria-hidden="true"
501
- >
502
- <path d="M11 5 6 9H3v6h3l5 4V5Z" />
503
- <path d="m19 9-6 6" />
504
- <path d="m13 9 6 6" />
505
- </svg>
506
- ) : (
507
- <svg
508
- width="13"
509
- height="13"
510
- viewBox="0 0 24 24"
511
- fill="none"
512
- stroke="currentColor"
513
- strokeWidth="2"
514
- strokeLinecap="round"
515
- strokeLinejoin="round"
516
- aria-hidden="true"
517
- >
518
- <path d="M11 5 6 9H3v6h3l5 4V5Z" />
519
- <path d="M15.5 8.5a5 5 0 0 1 0 7" />
520
- <path d="M18.5 5.5a9 9 0 0 1 0 13" />
521
- </svg>
522
- )}
523
- </button>
524
-
525
- {/* Speed control */}
526
- <div ref={speedMenuContainerRef} className="relative flex-shrink-0">
478
+ <Tooltip label={muteButtonLabel}>
527
479
  <button
528
480
  type="button"
529
- onClick={() => setShowSpeedMenu((v) => !v)}
530
- disabled={disabled}
531
- className="w-10 px-2 py-1 rounded-md text-[10px] font-mono tabular-nums transition-colors"
532
- style={{ color: "#71717A", background: "rgba(255,255,255,0.04)" }}
481
+ onClick={() => {
482
+ if (!audioAutoMuted) {
483
+ trackStudioEvent("playback", { action: "mute_toggle", muted: !audioMuted });
484
+ setAudioMuted(!audioMuted);
485
+ }
486
+ }}
487
+ disabled={controlsDisabled || audioAutoMuted}
488
+ aria-label={muteButtonLabel}
489
+ aria-pressed={effectiveAudioMuted}
490
+ className={`h-7 w-7 flex-shrink-0 flex items-center justify-center rounded-md border transition-colors disabled:pointer-events-none ${
491
+ effectiveAudioMuted
492
+ ? "text-studio-accent bg-studio-accent/10 border-studio-accent/30"
493
+ : "border-neutral-700 text-neutral-400 hover:border-neutral-500 hover:bg-neutral-800"
494
+ } ${audioAutoMuted ? "opacity-70" : ""}`}
533
495
  >
534
- {playbackRate === 1 ? "1x" : `${playbackRate}x`}
496
+ {effectiveAudioMuted ? (
497
+ <svg
498
+ width="13"
499
+ height="13"
500
+ viewBox="0 0 24 24"
501
+ fill="none"
502
+ stroke="currentColor"
503
+ strokeWidth="2"
504
+ strokeLinecap="round"
505
+ strokeLinejoin="round"
506
+ aria-hidden="true"
507
+ >
508
+ <path d="M11 5 6 9H3v6h3l5 4V5Z" />
509
+ <path d="m19 9-6 6" />
510
+ <path d="m13 9 6 6" />
511
+ </svg>
512
+ ) : (
513
+ <svg
514
+ width="13"
515
+ height="13"
516
+ viewBox="0 0 24 24"
517
+ fill="none"
518
+ stroke="currentColor"
519
+ strokeWidth="2"
520
+ strokeLinecap="round"
521
+ strokeLinejoin="round"
522
+ aria-hidden="true"
523
+ >
524
+ <path d="M11 5 6 9H3v6h3l5 4V5Z" />
525
+ <path d="M15.5 8.5a5 5 0 0 1 0 7" />
526
+ <path d="M18.5 5.5a9 9 0 0 1 0 13" />
527
+ </svg>
528
+ )}
535
529
  </button>
530
+ </Tooltip>
531
+
532
+ {/* Speed control */}
533
+ <div ref={speedMenuContainerRef} className="relative flex-shrink-0">
534
+ <Tooltip label="Playback speed">
535
+ <button
536
+ type="button"
537
+ onClick={() => setShowSpeedMenu((v) => !v)}
538
+ disabled={disabled}
539
+ className="w-10 px-2 py-1 rounded-md text-[10px] font-mono tabular-nums transition-colors"
540
+ style={{ color: "#71717A", background: "rgba(255,255,255,0.04)" }}
541
+ >
542
+ {playbackRate === 1 ? "1x" : `${playbackRate}x`}
543
+ </button>
544
+ </Tooltip>
536
545
  {showSpeedMenu && (
537
546
  <div
538
547
  className="absolute bottom-full right-0 mb-1.5 rounded-lg shadow-xl z-50 min-w-[56px] overflow-hidden"
@@ -566,122 +575,126 @@ export const PlayerControls = memo(function PlayerControls({
566
575
  )}
567
576
  </div>
568
577
 
569
- <button
570
- type="button"
571
- onClick={() => {
572
- trackStudioEvent("playback", { action: "loop_toggle", enabled: !loopEnabled });
573
- setLoopEnabled(!loopEnabled);
574
- }}
575
- disabled={disabled}
576
- className={`h-7 w-7 flex items-center justify-center rounded-md border transition-colors ${
577
- loopEnabled
578
- ? "text-studio-accent bg-studio-accent/10 border-studio-accent/30"
579
- : "border-neutral-700 text-neutral-400 hover:border-neutral-500 hover:bg-neutral-800"
580
- }`}
581
- title="Loop playback"
582
- aria-label={loopEnabled ? "Disable loop playback" : "Enable loop playback"}
583
- aria-pressed={loopEnabled}
584
- >
585
- <svg
586
- width="13"
587
- height="13"
588
- viewBox="0 0 24 24"
589
- fill="none"
590
- stroke="currentColor"
591
- strokeWidth="2"
592
- strokeLinecap="round"
593
- strokeLinejoin="round"
594
- aria-hidden="true"
595
- >
596
- <path d="M17 2l4 4-4 4" />
597
- <path d="M3 11V9a4 4 0 0 1 4-4h14" />
598
- <path d="M7 22l-4-4 4-4" />
599
- <path d="M21 13v2a4 4 0 0 1-4 4H3" />
600
- </svg>
601
- </button>
602
-
603
- {/* Fullscreen toggle */}
604
- {onToggleFullscreen && (
578
+ <Tooltip label="Loop playback">
605
579
  <button
606
580
  type="button"
607
581
  onClick={() => {
608
- trackStudioEvent("playback", { action: "fullscreen_toggle", active: !isFullscreen });
609
- onToggleFullscreen();
582
+ trackStudioEvent("playback", { action: "loop_toggle", enabled: !loopEnabled });
583
+ setLoopEnabled(!loopEnabled);
610
584
  }}
611
- className={`h-7 w-7 flex-shrink-0 flex items-center justify-center rounded-md border transition-colors ${
612
- isFullscreen
585
+ disabled={disabled}
586
+ className={`h-7 w-7 flex items-center justify-center rounded-md border transition-colors ${
587
+ loopEnabled
613
588
  ? "text-studio-accent bg-studio-accent/10 border-studio-accent/30"
614
589
  : "border-neutral-700 text-neutral-400 hover:border-neutral-500 hover:bg-neutral-800"
615
590
  }`}
616
- title={isFullscreen ? "Exit fullscreen (F)" : "Enter fullscreen (F)"}
617
- aria-label={isFullscreen ? "Exit fullscreen" : "Enter fullscreen"}
618
- >
619
- {isFullscreen ? (
620
- <svg
621
- width="13"
622
- height="13"
623
- viewBox="0 0 24 24"
624
- fill="none"
625
- stroke="currentColor"
626
- strokeWidth="2"
627
- strokeLinecap="round"
628
- strokeLinejoin="round"
629
- aria-hidden="true"
630
- >
631
- <path d="M8 3v3a2 2 0 0 1-2 2H3" />
632
- <path d="M21 8h-3a2 2 0 0 1-2-2V3" />
633
- <path d="M3 16h3a2 2 0 0 1 2 2v3" />
634
- <path d="M16 21v-3a2 2 0 0 1 2-2h3" />
635
- </svg>
636
- ) : (
637
- <svg
638
- width="13"
639
- height="13"
640
- viewBox="0 0 24 24"
641
- fill="none"
642
- stroke="currentColor"
643
- strokeWidth="2"
644
- strokeLinecap="round"
645
- strokeLinejoin="round"
646
- aria-hidden="true"
647
- >
648
- <path d="M8 3H5a2 2 0 0 0-2 2v3" />
649
- <path d="M21 8V5a2 2 0 0 0-2-2h-3" />
650
- <path d="M3 16v3a2 2 0 0 0 2 2h3" />
651
- <path d="M16 21h3a2 2 0 0 0 2-2v-3" />
652
- </svg>
653
- )}
654
- </button>
655
- )}
656
-
657
- {/* Keyboard shortcuts + frame jump + work area — click to open panel */}
658
- <div ref={shortcutsPanelRef} className="relative flex-shrink-0">
659
- <button
660
- type="button"
661
- onClick={() => setShowShortcuts((v) => !v)}
662
- className={`w-6 h-6 flex items-center justify-center rounded border transition-colors ${
663
- showShortcuts
664
- ? "border-neutral-600 text-neutral-200 bg-neutral-800"
665
- : "border-neutral-800 text-neutral-600 hover:text-neutral-300 hover:border-neutral-600"
666
- }`}
667
- aria-label="Shortcuts and tools"
668
- aria-expanded={showShortcuts}
591
+ aria-label={loopEnabled ? "Disable loop playback" : "Enable loop playback"}
592
+ aria-pressed={loopEnabled}
669
593
  >
670
594
  <svg
671
- width="11"
672
- height="11"
595
+ width="13"
596
+ height="13"
673
597
  viewBox="0 0 24 24"
674
598
  fill="none"
675
599
  stroke="currentColor"
676
- strokeWidth="1.75"
600
+ strokeWidth="2"
677
601
  strokeLinecap="round"
678
602
  strokeLinejoin="round"
679
603
  aria-hidden="true"
680
604
  >
681
- <rect x="2" y="4" width="20" height="16" rx="2" />
682
- <path d="M6 8h.01M10 8h.01M14 8h.01M18 8h.01M6 12h.01M10 12h.01M14 12h.01M18 12h.01M8 16h8" />
605
+ <path d="M17 2l4 4-4 4" />
606
+ <path d="M3 11V9a4 4 0 0 1 4-4h14" />
607
+ <path d="M7 22l-4-4 4-4" />
608
+ <path d="M21 13v2a4 4 0 0 1-4 4H3" />
683
609
  </svg>
684
610
  </button>
611
+ </Tooltip>
612
+
613
+ {/* Fullscreen toggle */}
614
+ {onToggleFullscreen && (
615
+ <Tooltip label={isFullscreen ? "Exit fullscreen (F)" : "Enter fullscreen (F)"}>
616
+ <button
617
+ type="button"
618
+ onClick={() => {
619
+ trackStudioEvent("playback", { action: "fullscreen_toggle", active: !isFullscreen });
620
+ onToggleFullscreen();
621
+ }}
622
+ className={`h-7 w-7 flex-shrink-0 flex items-center justify-center rounded-md border transition-colors ${
623
+ isFullscreen
624
+ ? "text-studio-accent bg-studio-accent/10 border-studio-accent/30"
625
+ : "border-neutral-700 text-neutral-400 hover:border-neutral-500 hover:bg-neutral-800"
626
+ }`}
627
+ aria-label={isFullscreen ? "Exit fullscreen" : "Enter fullscreen"}
628
+ >
629
+ {isFullscreen ? (
630
+ <svg
631
+ width="13"
632
+ height="13"
633
+ viewBox="0 0 24 24"
634
+ fill="none"
635
+ stroke="currentColor"
636
+ strokeWidth="2"
637
+ strokeLinecap="round"
638
+ strokeLinejoin="round"
639
+ aria-hidden="true"
640
+ >
641
+ <path d="M8 3v3a2 2 0 0 1-2 2H3" />
642
+ <path d="M21 8h-3a2 2 0 0 1-2-2V3" />
643
+ <path d="M3 16h3a2 2 0 0 1 2 2v3" />
644
+ <path d="M16 21v-3a2 2 0 0 1 2-2h3" />
645
+ </svg>
646
+ ) : (
647
+ <svg
648
+ width="13"
649
+ height="13"
650
+ viewBox="0 0 24 24"
651
+ fill="none"
652
+ stroke="currentColor"
653
+ strokeWidth="2"
654
+ strokeLinecap="round"
655
+ strokeLinejoin="round"
656
+ aria-hidden="true"
657
+ >
658
+ <path d="M8 3H5a2 2 0 0 0-2 2v3" />
659
+ <path d="M21 8V5a2 2 0 0 0-2-2h-3" />
660
+ <path d="M3 16v3a2 2 0 0 0 2 2h3" />
661
+ <path d="M16 21h3a2 2 0 0 0 2-2v-3" />
662
+ </svg>
663
+ )}
664
+ </button>
665
+ </Tooltip>
666
+ )}
667
+
668
+ {/* Keyboard shortcuts + frame jump + work area — click to open panel */}
669
+ <div ref={shortcutsPanelRef} className="relative flex-shrink-0">
670
+ <Tooltip label="Shortcuts and tools">
671
+ <button
672
+ type="button"
673
+ onClick={() => setShowShortcuts((v) => !v)}
674
+ className={`w-6 h-6 flex items-center justify-center rounded border transition-colors ${
675
+ showShortcuts
676
+ ? "border-neutral-600 text-neutral-200 bg-neutral-800"
677
+ : "border-neutral-800 text-neutral-600 hover:text-neutral-300 hover:border-neutral-600"
678
+ }`}
679
+ aria-label="Shortcuts and tools"
680
+ aria-expanded={showShortcuts}
681
+ >
682
+ <svg
683
+ width="11"
684
+ height="11"
685
+ viewBox="0 0 24 24"
686
+ fill="none"
687
+ stroke="currentColor"
688
+ strokeWidth="1.75"
689
+ strokeLinecap="round"
690
+ strokeLinejoin="round"
691
+ aria-hidden="true"
692
+ >
693
+ <rect x="2" y="4" width="20" height="16" rx="2" />
694
+ <path d="M6 8h.01M10 8h.01M14 8h.01M18 8h.01M6 12h.01M10 12h.01M14 12h.01M18 12h.01M8 16h8" />
695
+ </svg>
696
+ </button>
697
+ </Tooltip>
685
698
  {showShortcuts && (
686
699
  <div
687
700
  className="absolute bottom-full right-0 mb-2 z-50 rounded-lg shadow-xl min-w-[220px] overflow-y-auto"
@@ -709,13 +722,15 @@ export const PlayerControls = memo(function PlayerControls({
709
722
  onKeyDown={handleJumpKeyDown}
710
723
  onBlur={commitJumpFrame}
711
724
  />
712
- <button
713
- type="submit"
714
- disabled={disabled}
715
- className="h-6 px-2 rounded border border-neutral-700 text-[10px] text-neutral-300 transition-colors hover:border-neutral-500 hover:bg-neutral-800 disabled:opacity-40"
716
- >
717
- Go
718
- </button>
725
+ <Tooltip label="Jump to frame">
726
+ <button
727
+ type="submit"
728
+ disabled={disabled}
729
+ className="h-6 px-2 rounded border border-neutral-700 text-[10px] text-neutral-300 transition-colors hover:border-neutral-500 hover:bg-neutral-800 disabled:opacity-40"
730
+ >
731
+ Go
732
+ </button>
733
+ </Tooltip>
719
734
  </form>
720
735
  </div>
721
736
  <div style={{ borderTop: "1px solid rgba(255,255,255,0.06)" }} />
@@ -741,23 +756,25 @@ export const PlayerControls = memo(function PlayerControls({
741
756
  <span className="font-mono text-[10px] text-neutral-300">
742
757
  {formatTime(inPoint)}
743
758
  </span>
744
- <button
745
- type="button"
746
- onClick={() => setInPoint(null)}
747
- className="w-4 h-4 flex items-center justify-center rounded text-neutral-500 hover:text-neutral-200 transition-colors"
748
- aria-label="Clear in-point"
749
- >
750
- <svg
751
- width="8"
752
- height="8"
753
- viewBox="0 0 24 24"
754
- fill="none"
755
- stroke="currentColor"
756
- strokeWidth="2.5"
759
+ <Tooltip label="Clear in-point">
760
+ <button
761
+ type="button"
762
+ onClick={() => setInPoint(null)}
763
+ className="w-4 h-4 flex items-center justify-center rounded text-neutral-500 hover:text-neutral-200 transition-colors"
764
+ aria-label="Clear in-point"
757
765
  >
758
- <path d="M18 6L6 18M6 6l12 12" />
759
- </svg>
760
- </button>
766
+ <svg
767
+ width="8"
768
+ height="8"
769
+ viewBox="0 0 24 24"
770
+ fill="none"
771
+ stroke="currentColor"
772
+ strokeWidth="2.5"
773
+ >
774
+ <path d="M18 6L6 18M6 6l12 12" />
775
+ </svg>
776
+ </button>
777
+ </Tooltip>
761
778
  </>
762
779
  ) : (
763
780
  <span className="text-[10px] text-neutral-600">—</span>
@@ -780,23 +797,25 @@ export const PlayerControls = memo(function PlayerControls({
780
797
  <span className="font-mono text-[10px] text-neutral-300">
781
798
  {formatTime(outPoint)}
782
799
  </span>
783
- <button
784
- type="button"
785
- onClick={() => setOutPoint(null)}
786
- className="w-4 h-4 flex items-center justify-center rounded text-neutral-500 hover:text-neutral-200 transition-colors"
787
- aria-label="Clear out-point"
788
- >
789
- <svg
790
- width="8"
791
- height="8"
792
- viewBox="0 0 24 24"
793
- fill="none"
794
- stroke="currentColor"
795
- strokeWidth="2.5"
800
+ <Tooltip label="Clear out-point">
801
+ <button
802
+ type="button"
803
+ onClick={() => setOutPoint(null)}
804
+ className="w-4 h-4 flex items-center justify-center rounded text-neutral-500 hover:text-neutral-200 transition-colors"
805
+ aria-label="Clear out-point"
796
806
  >
797
- <path d="M18 6L6 18M6 6l12 12" />
798
- </svg>
799
- </button>
807
+ <svg
808
+ width="8"
809
+ height="8"
810
+ viewBox="0 0 24 24"
811
+ fill="none"
812
+ stroke="currentColor"
813
+ strokeWidth="2.5"
814
+ >
815
+ <path d="M18 6L6 18M6 6l12 12" />
816
+ </svg>
817
+ </button>
818
+ </Tooltip>
800
819
  </>
801
820
  ) : (
802
821
  <span className="text-[10px] text-neutral-600">—</span>
@@ -18,13 +18,13 @@ describe("wrapTimeline seek keepPlaying option (#834)", () => {
18
18
  };
19
19
  }
20
20
 
21
- it("default seek pauses the GSAP timeline before seeking", () => {
21
+ it("default seek pauses the GSAP timeline before and after seeking", () => {
22
22
  const tl = mockTimeline();
23
23
  const adapter = wrapTimeline(tl);
24
24
 
25
25
  adapter.seek(5);
26
26
 
27
- expect(tl.pause).toHaveBeenCalledTimes(1);
27
+ expect(tl.pause).toHaveBeenCalledTimes(2);
28
28
  expect(tl.seek).toHaveBeenCalledWith(5);
29
29
  });
30
30
 
@@ -44,7 +44,7 @@ describe("wrapTimeline seek keepPlaying option (#834)", () => {
44
44
 
45
45
  adapter.seek(5, { keepPlaying: false });
46
46
 
47
- expect(tl.pause).toHaveBeenCalledTimes(1);
47
+ expect(tl.pause).toHaveBeenCalledTimes(2);
48
48
  expect(tl.seek).toHaveBeenCalledWith(5);
49
49
  });
50
50
  });
@@ -135,8 +135,10 @@ export function wrapTimeline(tl: TimelineLike): PlaybackAdapter {
135
135
  play: () => tl.play(),
136
136
  pause: () => tl.pause(),
137
137
  seek: (t, options) => {
138
- if (!options?.keepPlaying) tl.pause();
138
+ const shouldPause = !options?.keepPlaying;
139
+ if (shouldPause) tl.pause();
139
140
  tl.seek(t);
141
+ if (shouldPause) tl.pause();
140
142
  },
141
143
  getTime: () => tl.time(),
142
144
  getDuration: () => tl.duration(),
@@ -155,6 +155,7 @@ describe("insertTimelineAssetIntoSource", () => {
155
155
  '<img id="photo_asset" data-start="0" data-duration="3" />',
156
156
  );
157
157
 
158
- expect(html).toContain('<div id="root" data-composition-id="main"><img id="photo_asset"');
158
+ expect(html).toContain('data-composition-id="main">');
159
+ expect(html).toContain('<img id="photo_asset"');
159
160
  });
160
161
  });