@remotion/studio 4.0.520 → 4.0.522

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.
Files changed (117) hide show
  1. package/dist/api/shut-down-studio.d.ts +6 -0
  2. package/dist/api/shut-down-studio.js +23 -0
  3. package/dist/components/AudioWaveform.js +10 -6
  4. package/dist/components/Canvas.js +84 -12
  5. package/dist/components/CanvasIfSizeIsAvailable.js +7 -0
  6. package/dist/components/CaptionInspector.d.ts +1 -0
  7. package/dist/components/CaptionInspector.js +43 -2
  8. package/dist/components/CaptionTextEditor.js +1 -1
  9. package/dist/components/CompositionSelector.js +41 -30
  10. package/dist/components/CompositionSelectorItem.d.ts +7 -0
  11. package/dist/components/CompositionSelectorItem.js +343 -67
  12. package/dist/components/CopyButton.js +7 -14
  13. package/dist/components/Editor.js +6 -5
  14. package/dist/components/EditorContent.js +1 -1
  15. package/dist/components/ElementInstallConfirmation.js +77 -65
  16. package/dist/components/ElementLibraryModal.js +3 -1
  17. package/dist/components/FixComputedValueModal.js +20 -10
  18. package/dist/components/InlineCaptionInspector.js +37 -1
  19. package/dist/components/InspectorLocationCopy.js +10 -5
  20. package/dist/components/InspectorPanel/ElementLibraryButton.js +3 -18
  21. package/dist/components/InspectorSequenceSection.js +1 -4
  22. package/dist/components/Menu/MenuSubItem.js +1 -1
  23. package/dist/components/MenuBuildIndicator.js +7 -2
  24. package/dist/components/MenuCompositionName.js +62 -6
  25. package/dist/components/ModalContainer.d.ts +1 -0
  26. package/dist/components/ModalContainer.js +2 -2
  27. package/dist/components/ModalHeader.js +5 -1
  28. package/dist/components/QuickSwitcher/asset-search.d.ts +0 -1
  29. package/dist/components/QuickSwitcher/asset-search.js +1 -16
  30. package/dist/components/RenderButton.js +2 -2
  31. package/dist/components/RenderModal/CliCopyButton.js +13 -27
  32. package/dist/components/RenderModal/RenderStatusModal.js +7 -1
  33. package/dist/components/RenderModal/ServerRenderModal.js +1 -1
  34. package/dist/components/RenderModal/WebRenderModal.js +1 -1
  35. package/dist/components/RenderQueue/ClientRenderQueueProcessor.js +1 -1
  36. package/dist/components/RenderQueue/RenderQueueCopyToClipboard.js +7 -4
  37. package/dist/components/SettingsModal.js +8 -5
  38. package/dist/components/SidebarCollapserControls.js +11 -5
  39. package/dist/components/SkillsSettings.js +9 -5
  40. package/dist/components/Splitter/SplitterContainer.js +63 -10
  41. package/dist/components/Splitter/SplitterContext.d.ts +3 -0
  42. package/dist/components/Splitter/SplitterContext.js +5 -1
  43. package/dist/components/Splitter/SplitterElement.js +11 -2
  44. package/dist/components/Splitter/SplitterHandle.d.ts +1 -0
  45. package/dist/components/Splitter/SplitterHandle.js +55 -25
  46. package/dist/components/TimeValue.js +36 -7
  47. package/dist/components/Timeline/SubscribeToNodePaths.d.ts +2 -1
  48. package/dist/components/Timeline/SubscribeToNodePaths.js +2 -1
  49. package/dist/components/Timeline/Timeline.d.ts +1 -1
  50. package/dist/components/Timeline/Timeline.js +45 -22
  51. package/dist/components/Timeline/TimelineAssetField.d.ts +0 -1
  52. package/dist/components/Timeline/TimelineAssetField.js +16 -9
  53. package/dist/components/Timeline/TimelineCollapseToggle.js +1 -1
  54. package/dist/components/Timeline/TimelineDragHandler.js +9 -4
  55. package/dist/components/Timeline/TimelineFontWeightField.d.ts +11 -0
  56. package/dist/components/Timeline/TimelineFontWeightField.js +65 -0
  57. package/dist/components/Timeline/TimelineLayerChildren.d.ts +21 -0
  58. package/dist/components/Timeline/TimelineLayerChildren.js +129 -0
  59. package/dist/components/Timeline/TimelineNumberField.js +13 -5
  60. package/dist/components/Timeline/TimelinePinchZoom.js +6 -30
  61. package/dist/components/Timeline/TimelinePlayCursorSyncer.js +1 -7
  62. package/dist/components/Timeline/TimelinePrimitiveFieldValue.js +4 -0
  63. package/dist/components/Timeline/TimelineSequence.js +113 -42
  64. package/dist/components/Timeline/TimelineSequenceItem.js +14 -2
  65. package/dist/components/Timeline/TimelineSequenceRightEdgeDragHandle.d.ts +13 -3
  66. package/dist/components/Timeline/TimelineSequenceRightEdgeDragHandle.js +185 -126
  67. package/dist/components/Timeline/TimelineSlider.js +1 -4
  68. package/dist/components/Timeline/TimelineTickFormatProvider.d.ts +8 -0
  69. package/dist/components/Timeline/TimelineTickFormatProvider.js +16 -0
  70. package/dist/components/Timeline/TimelineTimeIndicators.js +7 -3
  71. package/dist/components/Timeline/TimelineVideoInfo.js +7 -3
  72. package/dist/components/Timeline/TimelineWidthProvider.js +2 -5
  73. package/dist/components/Timeline/TimelineZoomControls.js +20 -20
  74. package/dist/components/Timeline/get-sequence-split-menu-item.d.ts +11 -0
  75. package/dist/components/Timeline/get-sequence-split-menu-item.js +55 -0
  76. package/dist/components/Timeline/timeline-field-display-utils.js +1 -0
  77. package/dist/components/Timeline/use-sequence-props-subscription.d.ts +3 -2
  78. package/dist/components/Timeline/use-sequence-props-subscription.js +4 -10
  79. package/dist/components/TopPanel.js +3 -19
  80. package/dist/components/UpdateStatusContext.d.ts +5 -0
  81. package/dist/components/UpdateStatusContext.js +53 -2
  82. package/dist/components/UpdatesSettings.js +34 -9
  83. package/dist/components/WebMcp.js +26 -0
  84. package/dist/components/ZoomPersistor.js +6 -3
  85. package/dist/components/composition-menu-items.js +1 -1
  86. package/dist/components/composition-selector-drag-data.d.ts +27 -0
  87. package/dist/components/composition-selector-drag-data.js +80 -0
  88. package/dist/components/import-assets.d.ts +2 -1
  89. package/dist/components/import-assets.js +11 -1
  90. package/dist/components/parse-caption-file.d.ts +5 -0
  91. package/dist/components/parse-caption-file.js +63 -0
  92. package/dist/esm/{chunk-np6q13k5.js → chunk-3netyjd9.js} +7943 -6314
  93. package/dist/esm/index.mjs +23 -8
  94. package/dist/esm/internals.mjs +7965 -6336
  95. package/dist/esm/previewEntry.mjs +8007 -6378
  96. package/dist/esm/renderEntry.mjs +1 -1
  97. package/dist/helpers/copy-text.js +1 -2
  98. package/dist/helpers/get-preview-file-type.js +22 -3
  99. package/dist/helpers/inserted-element-selection.d.ts +2 -1
  100. package/dist/helpers/use-copy-feedback.d.ts +4 -0
  101. package/dist/helpers/use-copy-feedback.js +19 -0
  102. package/dist/helpers/use-menu-structure.js +2 -2
  103. package/dist/icons/copy.d.ts +5 -0
  104. package/dist/icons/copy.js +12 -0
  105. package/dist/index.d.ts +1 -0
  106. package/dist/index.js +3 -1
  107. package/dist/state/canvas-ref.d.ts +1 -0
  108. package/dist/state/canvas-ref.js +2 -1
  109. package/dist/state/modals.d.ts +0 -2
  110. package/dist/state/sidebar.d.ts +3 -0
  111. package/dist/state/sidebar.js +6 -1
  112. package/dist/state/timeline-zoom.d.ts +1 -1
  113. package/dist/state/timeline-zoom.js +3 -3
  114. package/dist/visual-controls/VisualControls.js +4 -0
  115. package/package.json +16 -16
  116. package/dist/helpers/use-max-media-duration.d.ts +0 -2
  117. package/dist/helpers/use-max-media-duration.js +0 -61
@@ -49,13 +49,28 @@ const editor_snapping_1 = require("../../state/editor-snapping");
49
49
  const ForceSpecificCursor_1 = require("../ForceSpecificCursor");
50
50
  const save_sequence_prop_1 = require("./save-sequence-prop");
51
51
  const TimelineSelection_1 = require("./TimelineSelection");
52
- const HANDLE_WIDTH = 6;
52
+ const HANDLE_INSET = 6;
53
+ const HANDLE_OUTSET = 8;
54
+ const timelineSequenceEdgeDragThresholdPx = 4;
53
55
  exports.timelineSequenceFromDragSnapThresholdPx = 10;
56
+ const getTimelineSequenceEdgeSelectionInteraction = ({ button, selected, shiftKey, metaKey, ctrlKey, }) => {
57
+ if (button !== 0 ||
58
+ !(0, TimelineSelection_1.shouldSelectTimelineRowOnPointerDown)({
59
+ selected,
60
+ shiftKey,
61
+ metaKey,
62
+ ctrlKey,
63
+ })) {
64
+ return null;
65
+ }
66
+ return { shiftKey, toggleKey: metaKey || ctrlKey };
67
+ };
54
68
  const baseStyle = {
55
69
  position: 'absolute',
56
70
  top: 0,
57
71
  bottom: 0,
58
- width: HANDLE_WIDTH,
72
+ // Keep the middle half of narrow layers available for moving.
73
+ width: `calc(${HANDLE_OUTSET}px + min(${HANDLE_INSET}px, 25%))`,
59
74
  cursor: 'ew-resize',
60
75
  zIndex: 1,
61
76
  touchAction: 'none',
@@ -231,7 +246,7 @@ const isFromDraggableSequence = (sequence) => {
231
246
  !sequence.isInsideSeries &&
232
247
  Boolean(sequence.controls));
233
248
  };
234
- const getTimelineSequenceDurationDragValue = ({ initialDuration, deltaFrames, minimumDuration = 1, }) => Math.max(minimumDuration, initialDuration + deltaFrames);
249
+ const getTimelineSequenceDurationDragValue = ({ initialDuration, deltaFrames, maximumDuration, minimumDuration = 1, }) => Math.min(maximumDuration, Math.max(minimumDuration, initialDuration + deltaFrames));
235
250
  exports.getTimelineSequenceDurationDragValue = getTimelineSequenceDurationDragValue;
236
251
  const getTimelineSequenceLeftEdgeDragDelta = ({ initialDuration, initialTrimBefore, deltaFrames, playbackRate, minimumDuration = 1, }) => {
237
252
  const minDeltaFrames = 0 - initialTrimBefore / playbackRate;
@@ -305,6 +320,7 @@ const getTimelineSequenceDurationDragChanges = ({ targets, deltaFrames, }) => {
305
320
  const nextValue = (0, exports.getTimelineSequenceDurationDragValue)({
306
321
  initialDuration: target.initialDuration,
307
322
  deltaFrames,
323
+ maximumDuration: target.maximumDuration,
308
324
  minimumDuration: target.minimumDuration,
309
325
  });
310
326
  if (nextValue === target.initialDuration) {
@@ -325,14 +341,11 @@ const getTimelineSequenceDurationDragChanges = ({ targets, deltaFrames, }) => {
325
341
  exports.getTimelineSequenceDurationDragChanges = getTimelineSequenceDurationDragChanges;
326
342
  const getTimelineSequenceFromDragValue = ({ initialFrom, deltaFrames, }) => initialFrom + deltaFrames;
327
343
  exports.getTimelineSequenceFromDragValue = getTimelineSequenceFromDragValue;
328
- const getTimelineSequenceFromDragDelta = ({ deltaFrames, pxPerFrame, snappingEnabled, targets, }) => {
344
+ const getTimelineSequenceFromDragDelta = ({ deltaFrames, timelineDurationInFrames, pxPerFrame, snappingEnabled, targets, }) => {
329
345
  var _a;
330
- if (!snappingEnabled) {
331
- return deltaFrames;
332
- }
333
346
  let closestSnap;
334
347
  for (const target of targets) {
335
- if (!target.canSnapToTimelineStart) {
348
+ if (!snappingEnabled || !target.canSnapToTimelineStart) {
336
349
  continue;
337
350
  }
338
351
  const nextFrom = (0, exports.getTimelineSequenceFromDragValue)({
@@ -349,7 +362,10 @@ const getTimelineSequenceFromDragDelta = ({ deltaFrames, pxPerFrame, snappingEna
349
362
  distancePx,
350
363
  };
351
364
  }
352
- return (_a = closestSnap === null || closestSnap === void 0 ? void 0 : closestSnap.deltaFrames) !== null && _a !== void 0 ? _a : deltaFrames;
365
+ const minimumDelta = Math.max(...targets.map((target) => target.minimumDeltaFrames));
366
+ const maximumDelta = Math.min(...targets.map((target) => timelineDurationInFrames - 1 - target.initialTimelineStart));
367
+ // Clamp the shared delta after snapping so every selected track retains a frame.
368
+ return Math.max(minimumDelta, Math.min(maximumDelta, (_a = closestSnap === null || closestSnap === void 0 ? void 0 : closestSnap.deltaFrames) !== null && _a !== void 0 ? _a : deltaFrames));
353
369
  };
354
370
  exports.getTimelineSequenceFromDragDelta = getTimelineSequenceFromDragDelta;
355
371
  const getTimelineSequenceFromDragChanges = ({ targets, deltaFrames, }) => {
@@ -414,7 +430,7 @@ const findSequenceTrack = ({ tracks, nodePathInfo, }) => {
414
430
  return ((0, studio_shared_1.stringifySequenceExpandedRowKey)(candidate.nodePathInfo.sequenceSubscriptionKey) === key && candidate.nodePathInfo.index === nodePathInfo.index);
415
431
  });
416
432
  };
417
- const getTimelineSequenceDurationDragTargets = ({ draggedNodePathInfo, selectedItems, sequences, overrideIdsToNodePaths, propStatuses, }) => {
433
+ const getTimelineSequenceDurationDragTargets = ({ draggedNodePathInfo, selectedItems, sequences, overrideIdsToNodePaths, propStatuses, timelineDurationInFrames, }) => {
418
434
  var _a;
419
435
  const draggedSelectionKey = (0, TimelineSelection_1.getTimelineSequenceSelectionKey)(draggedNodePathInfo);
420
436
  const selectedSequenceItems = selectedItems.filter((item) => item.type === 'sequence');
@@ -454,13 +470,13 @@ const getTimelineSequenceDurationDragTargets = ({ draggedNodePathInfo, selectedI
454
470
  }
455
471
  const key = (0, studio_shared_1.stringifySequenceSubscriptionKey)(nodePath);
456
472
  if (!targets.has(key)) {
473
+ const minimumDuration = Math.max(1 - originalSequence.from, getMinimumSequenceDuration({ sequence: originalSequence, sequences }));
457
474
  targets.set(key, {
458
475
  fileName: nodePath.absolutePath,
459
476
  initialDuration: originalSequence.duration,
460
- minimumDuration: getMinimumSequenceDuration({
461
- sequence: originalSequence,
462
- sequences,
463
- }),
477
+ maximumDuration: Math.max(minimumDuration, timelineDurationInFrames - track.cascadedStart),
478
+ // A negative start needs enough duration to retain one visible frame.
479
+ minimumDuration,
464
480
  nodePath,
465
481
  schema: controls.schema,
466
482
  });
@@ -556,6 +572,7 @@ const getTimelineSequenceFromDragTargets = ({ draggedNodePathInfo, selectedItems
556
572
  {
557
573
  nodePath: track.nodePathInfo.sequenceSubscriptionKey,
558
574
  originalSequence,
575
+ initialTimelineStart: track.cascadedStart,
559
576
  },
560
577
  ];
561
578
  });
@@ -577,7 +594,7 @@ const getTimelineSequenceFromDragTargets = ({ draggedNodePathInfo, selectedItems
577
594
  });
578
595
  const targets = new Map();
579
596
  const includedKeyframeNodePaths = new Set();
580
- for (const { nodePath, originalSequence, } of sequencesWithoutSelectedAncestors) {
597
+ for (const { nodePath, originalSequence, initialTimelineStart, } of sequencesWithoutSelectedAncestors) {
581
598
  if (!isFromDraggableSequence(originalSequence)) {
582
599
  return null;
583
600
  }
@@ -625,6 +642,8 @@ const getTimelineSequenceFromDragTargets = ({ draggedNodePathInfo, selectedItems
625
642
  const sequenceKeyframes = descendantKeyframes.flatMap((descendant) => descendant.sequenceKeyframes);
626
643
  targets.set(key, {
627
644
  canSnapToTimelineStart: originalSequence.parent === null,
645
+ minimumDeltaFrames: 1 - originalSequence.duration - originalSequence.from,
646
+ initialTimelineStart,
628
647
  effectKeyframes,
629
648
  fileName: nodePath.absolutePath,
630
649
  initialFrom: originalSequence.from,
@@ -663,14 +682,14 @@ const clearFromDragOverrides = ({ clearDragOverrides, clearEffectDragOverrides,
663
682
  }
664
683
  }
665
684
  };
666
- const TimelineSequenceLeftEdgeDragHandleInner = ({ nodePathInfo, windowWidth, timelineDurationInFrames, onDragEnd }) => {
685
+ const TimelineSequenceLeftEdgeDragHandleInner = ({ nodePathInfo, windowWidth, timelineDurationInFrames, onDragEnd, onSelect, selected, }) => {
667
686
  const { setPropStatuses, setDragOverrides, clearDragOverrides } = (0, react_1.useContext)(remotion_1.Internals.VisualModeSettersContext);
668
687
  const propStatusesRef = (0, react_1.useContext)(remotion_1.Internals.VisualModePropStatusesRefContext);
669
688
  const sequencesRef = (0, react_1.useContext)(remotion_1.Internals.SequenceManagerRefContext);
670
689
  const { overrideIdToNodePathMappings } = (0, react_1.useContext)(remotion_1.Internals.OverrideIdsToNodePathsGettersContext);
671
690
  const { previewServerState } = (0, react_1.useContext)(client_id_1.StudioServerConnectionCtx);
672
691
  const currentSelection = (0, TimelineSelection_1.useCurrentTimelineSelectionStateAsRef)();
673
- const [dragging, setDragging] = (0, react_1.useState)(false);
692
+ const stopPointerSessionRef = (0, react_1.useRef)(null);
674
693
  const dragStateRef = (0, react_1.useRef)(null);
675
694
  const latestRef = (0, react_1.useRef)({
676
695
  nodePathInfo,
@@ -680,6 +699,7 @@ const TimelineSequenceLeftEdgeDragHandleInner = ({ nodePathInfo, windowWidth, ti
680
699
  previewServerState,
681
700
  overrideIdToNodePathMappings,
682
701
  onDragEnd,
702
+ onSelect,
683
703
  });
684
704
  latestRef.current = {
685
705
  nodePathInfo,
@@ -689,6 +709,7 @@ const TimelineSequenceLeftEdgeDragHandleInner = ({ nodePathInfo, windowWidth, ti
689
709
  previewServerState,
690
710
  overrideIdToNodePathMappings,
691
711
  onDragEnd,
712
+ onSelect,
692
713
  };
693
714
  const finishDrag = (0, react_1.useCallback)((commit) => {
694
715
  const dragState = dragStateRef.current;
@@ -700,7 +721,11 @@ const TimelineSequenceLeftEdgeDragHandleInner = ({ nodePathInfo, windowWidth, ti
700
721
  document.body.style.userSelect = '';
701
722
  document.body.style.webkitUserSelect = '';
702
723
  (0, ForceSpecificCursor_1.stopForcingSpecificCursor)();
703
- setDragging(false);
724
+ if (commit &&
725
+ !dragState.didMove &&
726
+ dragState.selectionInteraction !== null) {
727
+ latestRef.current.onSelect(dragState.selectionInteraction);
728
+ }
704
729
  const { setPropStatuses: latestSetPropStatuses, clearDragOverrides: latestClear, previewServerState: latestServerState, } = latestRef.current;
705
730
  const changes = (0, exports.getTimelineSequenceLeftEdgeDragChanges)({
706
731
  targets: dragState.targets,
@@ -740,65 +765,61 @@ const TimelineSequenceLeftEdgeDragHandleInner = ({ nodePathInfo, windowWidth, ti
740
765
  });
741
766
  }, []);
742
767
  const onPointerDown = (0, react_1.useCallback)((e) => {
743
- var _a, _b;
768
+ var _a;
769
+ var _b;
744
770
  if (e.button !== 0) {
745
771
  return;
746
772
  }
773
+ e.stopPropagation();
774
+ e.preventDefault();
775
+ const selectionInteraction = getTimelineSequenceEdgeSelectionInteraction({
776
+ button: e.button,
777
+ selected,
778
+ shiftKey: e.shiftKey,
779
+ metaKey: e.metaKey,
780
+ ctrlKey: e.ctrlKey,
781
+ });
747
782
  const pxPerFrame = timelineDurationInFrames > 0
748
783
  ? (windowWidth - timeline_layout_1.TIMELINE_PADDING * 2) / timelineDurationInFrames
749
784
  : 0;
750
- if (pxPerFrame <= 0) {
751
- return;
752
- }
785
+ const canCalculateDelta = Number.isFinite(pxPerFrame) && pxPerFrame > 0;
753
786
  const { nodePathInfo: latestNodePathInfo, overrideIdToNodePathMappings: latestOverrideIdsToNodePaths, } = latestRef.current;
754
787
  const { selectedItems: latestSelectedItems } = currentSelection.current;
755
- const targets = (0, exports.getTimelineSequenceLeftEdgeDragTargets)({
756
- draggedNodePathInfo: latestNodePathInfo,
757
- selectedItems: latestSelectedItems,
758
- sequences: sequencesRef.current,
759
- overrideIdsToNodePaths: latestOverrideIdsToNodePaths,
760
- propStatuses: propStatusesRef.current,
761
- });
762
- if (targets === null || targets.length === 0) {
763
- return;
764
- }
765
- e.stopPropagation();
766
- e.preventDefault();
788
+ const targets = canCalculateDelta
789
+ ? ((_b = (0, exports.getTimelineSequenceLeftEdgeDragTargets)({
790
+ draggedNodePathInfo: latestNodePathInfo,
791
+ selectedItems: latestSelectedItems,
792
+ sequences: sequencesRef.current,
793
+ overrideIdsToNodePaths: latestOverrideIdsToNodePaths,
794
+ propStatuses: propStatusesRef.current,
795
+ })) !== null && _b !== void 0 ? _b : [])
796
+ : [];
797
+ (_a = stopPointerSessionRef.current) === null || _a === void 0 ? void 0 : _a.call(stopPointerSessionRef);
767
798
  dragStateRef.current = {
768
799
  initialClientX: e.clientX,
769
800
  latestDeltaFrames: 0,
770
801
  didMove: false,
771
- pxPerFrame,
802
+ pxPerFrame: canCalculateDelta ? pxPerFrame : 1,
772
803
  pointerId: e.pointerId,
773
- button: e.button,
774
- target: e.currentTarget,
804
+ selectionInteraction,
775
805
  targets,
776
806
  };
777
- (_b = (_a = e.currentTarget).setPointerCapture) === null || _b === void 0 ? void 0 : _b.call(_a, e.pointerId);
778
807
  document.body.style.userSelect = 'none';
779
808
  document.body.style.webkitUserSelect = 'none';
780
809
  (0, ForceSpecificCursor_1.forceSpecificCursor)('ew-resize');
781
- setDragging(true);
782
- }, [
783
- currentSelection,
784
- propStatusesRef,
785
- sequencesRef,
786
- timelineDurationInFrames,
787
- windowWidth,
788
- ]);
789
- (0, react_1.useEffect)(() => {
790
- if (!dragging) {
791
- return;
792
- }
793
- const onMove = (e) => {
810
+ const onMove = (pointerEvent) => {
794
811
  const dragState = dragStateRef.current;
795
- if (!dragState || e.pointerId !== dragState.pointerId) {
812
+ if (!dragState || pointerEvent.pointerId !== dragState.pointerId) {
796
813
  return;
797
814
  }
798
- const dx = e.clientX - dragState.initialClientX;
815
+ const dx = pointerEvent.clientX - dragState.initialClientX;
799
816
  const deltaFrames = Math.round(dx / dragState.pxPerFrame);
800
817
  dragState.latestDeltaFrames = deltaFrames;
801
- if (deltaFrames !== 0) {
818
+ if (Math.abs(dx) >= timelineSequenceEdgeDragThresholdPx ||
819
+ (0, exports.getTimelineSequenceLeftEdgeDragChanges)({
820
+ targets: dragState.targets,
821
+ deltaFrames,
822
+ }).length > 0) {
802
823
  dragState.didMove = true;
803
824
  }
804
825
  for (const target of dragState.targets) {
@@ -817,30 +838,31 @@ const TimelineSequenceLeftEdgeDragHandleInner = ({ nodePathInfo, windowWidth, ti
817
838
  latestRef.current.setDragOverrides(target.nodePath, 'trimBefore', remotion_1.Internals.makeStaticDragOverride(nextValues.trimBefore));
818
839
  }
819
840
  };
820
- const onUp = (e) => {
841
+ const onUp = (pointerEvent) => {
821
842
  const dragState = dragStateRef.current;
822
- if (!dragState || e.pointerId !== dragState.pointerId) {
843
+ if (!dragState || pointerEvent.pointerId !== dragState.pointerId) {
823
844
  return;
824
845
  }
846
+ // Include the release position even if the final pointermove was skipped.
847
+ onMove(pointerEvent);
825
848
  finishDrag(true);
826
849
  };
827
- const onCancel = (e) => {
850
+ const onCancel = (pointerEvent) => {
828
851
  const dragState = dragStateRef.current;
829
- if (!dragState || e.pointerId !== dragState.pointerId) {
852
+ if (!dragState || pointerEvent.pointerId !== dragState.pointerId) {
830
853
  return;
831
854
  }
832
855
  finishDrag(false);
833
856
  };
834
- const activeDragState = dragStateRef.current;
835
- if (!activeDragState) {
836
- return;
837
- }
838
- return (0, pointer_session_1.startCapturedPointerSession)({
839
- event: activeDragState,
840
- captureTarget: activeDragState.target,
857
+ stopPointerSessionRef.current = (0, pointer_session_1.startCapturedPointerSession)({
858
+ event: e.nativeEvent,
859
+ captureTarget: e.currentTarget,
841
860
  onMove,
842
861
  onEnd: (reason, endEvent) => {
843
- if ((reason === 'pointerup' || reason === 'buttons-released') &&
862
+ stopPointerSessionRef.current = null;
863
+ if ((reason === 'pointerup' ||
864
+ reason === 'buttons-released' ||
865
+ (reason === 'lostpointercapture' && (endEvent === null || endEvent === void 0 ? void 0 : endEvent.buttons) === 0)) &&
844
866
  endEvent) {
845
867
  onUp(endEvent);
846
868
  }
@@ -852,10 +874,25 @@ const TimelineSequenceLeftEdgeDragHandleInner = ({ nodePathInfo, windowWidth, ti
852
874
  }
853
875
  },
854
876
  });
855
- }, [dragging, finishDrag]);
877
+ }, [
878
+ currentSelection,
879
+ finishDrag,
880
+ propStatusesRef,
881
+ selected,
882
+ sequencesRef,
883
+ timelineDurationInFrames,
884
+ windowWidth,
885
+ ]);
886
+ (0, react_1.useEffect)(() => {
887
+ return () => {
888
+ var _a;
889
+ (_a = stopPointerSessionRef.current) === null || _a === void 0 ? void 0 : _a.call(stopPointerSessionRef);
890
+ stopPointerSessionRef.current = null;
891
+ };
892
+ }, []);
856
893
  const style = {
857
894
  ...baseStyle,
858
- left: 0,
895
+ left: -HANDLE_OUTSET,
859
896
  background: colors_1.TRANSPARENT,
860
897
  };
861
898
  return (jsx_runtime_1.jsx("div", { role: "separator", "aria-orientation": "vertical", title: "Drag to trim start", style: style, onPointerDown: onPointerDown }));
@@ -994,7 +1031,9 @@ const useTimelineSequenceFromDrag = ({ nodePathInfo, windowWidth, timelineDurati
994
1031
  // the global session listeners exist.
995
1032
  stopPointerSessionRef.current = (0, pointer_session_1.startCapturedPointerSession)({
996
1033
  event: e.nativeEvent,
997
- captureTarget: e.currentTarget,
1034
+ // The bar is removed when it leaves the visible timeline. Capture on
1035
+ // a stable element so the move continues until the pointer is released.
1036
+ captureTarget: e.currentTarget.ownerDocument.body,
998
1037
  onMove: (moveEvent) => {
999
1038
  const dragState = dragStateRef.current;
1000
1039
  if (!dragState) {
@@ -1002,6 +1041,7 @@ const useTimelineSequenceFromDrag = ({ nodePathInfo, windowWidth, timelineDurati
1002
1041
  }
1003
1042
  const dx = moveEvent.clientX - dragState.initialClientX;
1004
1043
  const deltaFrames = (0, exports.getTimelineSequenceFromDragDelta)({
1044
+ timelineDurationInFrames,
1005
1045
  deltaFrames: Math.round(dx / dragState.pxPerFrame),
1006
1046
  pxPerFrame: dragState.pxPerFrame,
1007
1047
  snappingEnabled: latestRef.current.editorSnapping,
@@ -1041,7 +1081,9 @@ const useTimelineSequenceFromDrag = ({ nodePathInfo, windowWidth, timelineDurati
1041
1081
  },
1042
1082
  onEnd: (reason, endEvent) => {
1043
1083
  stopPointerSessionRef.current = null;
1044
- finishDrag((reason === 'pointerup' || reason === 'buttons-released') &&
1084
+ finishDrag((reason === 'pointerup' ||
1085
+ reason === 'buttons-released' ||
1086
+ (reason === 'lostpointercapture' && (endEvent === null || endEvent === void 0 ? void 0 : endEvent.buttons) === 0)) &&
1045
1087
  endEvent !== null);
1046
1088
  },
1047
1089
  });
@@ -1065,14 +1107,14 @@ const useTimelineSequenceFromDrag = ({ nodePathInfo, windowWidth, timelineDurati
1065
1107
  };
1066
1108
  };
1067
1109
  exports.useTimelineSequenceFromDrag = useTimelineSequenceFromDrag;
1068
- const TimelineSequenceRightEdgeDragHandleInner = ({ nodePathInfo, windowWidth, timelineDurationInFrames, onDragEnd }) => {
1110
+ const TimelineSequenceRightEdgeDragHandleInner = ({ nodePathInfo, windowWidth, timelineDurationInFrames, onDragEnd, onSelect, selected, }) => {
1069
1111
  const { setPropStatuses, setDragOverrides, clearDragOverrides } = (0, react_1.useContext)(remotion_1.Internals.VisualModeSettersContext);
1070
1112
  const propStatusesRef = (0, react_1.useContext)(remotion_1.Internals.VisualModePropStatusesRefContext);
1071
1113
  const sequencesRef = (0, react_1.useContext)(remotion_1.Internals.SequenceManagerRefContext);
1072
1114
  const { overrideIdToNodePathMappings } = (0, react_1.useContext)(remotion_1.Internals.OverrideIdsToNodePathsGettersContext);
1073
1115
  const { previewServerState } = (0, react_1.useContext)(client_id_1.StudioServerConnectionCtx);
1074
1116
  const currentSelection = (0, TimelineSelection_1.useCurrentTimelineSelectionStateAsRef)();
1075
- const [dragging, setDragging] = (0, react_1.useState)(false);
1117
+ const stopPointerSessionRef = (0, react_1.useRef)(null);
1076
1118
  const dragStateRef = (0, react_1.useRef)(null);
1077
1119
  // Keep latest props/setters available to window listeners installed once at pointerdown.
1078
1120
  const latestRef = (0, react_1.useRef)({
@@ -1083,6 +1125,7 @@ const TimelineSequenceRightEdgeDragHandleInner = ({ nodePathInfo, windowWidth, t
1083
1125
  previewServerState,
1084
1126
  overrideIdToNodePathMappings,
1085
1127
  onDragEnd,
1128
+ onSelect,
1086
1129
  });
1087
1130
  latestRef.current = {
1088
1131
  nodePathInfo,
@@ -1092,6 +1135,7 @@ const TimelineSequenceRightEdgeDragHandleInner = ({ nodePathInfo, windowWidth, t
1092
1135
  previewServerState,
1093
1136
  overrideIdToNodePathMappings,
1094
1137
  onDragEnd,
1138
+ onSelect,
1095
1139
  };
1096
1140
  const finishDrag = (0, react_1.useCallback)((commit) => {
1097
1141
  const dragState = dragStateRef.current;
@@ -1103,7 +1147,11 @@ const TimelineSequenceRightEdgeDragHandleInner = ({ nodePathInfo, windowWidth, t
1103
1147
  document.body.style.userSelect = '';
1104
1148
  document.body.style.webkitUserSelect = '';
1105
1149
  (0, ForceSpecificCursor_1.stopForcingSpecificCursor)();
1106
- setDragging(false);
1150
+ if (commit &&
1151
+ !dragState.didMove &&
1152
+ dragState.selectionInteraction !== null) {
1153
+ latestRef.current.onSelect(dragState.selectionInteraction);
1154
+ }
1107
1155
  const { setPropStatuses: latestSetPropStatuses, clearDragOverrides: latestClear, previewServerState: latestServerState, } = latestRef.current;
1108
1156
  const changes = (0, exports.getTimelineSequenceDurationDragChanges)({
1109
1157
  targets: dragState.targets,
@@ -1141,102 +1189,98 @@ const TimelineSequenceRightEdgeDragHandleInner = ({ nodePathInfo, windowWidth, t
1141
1189
  });
1142
1190
  }, []);
1143
1191
  const onPointerDown = (0, react_1.useCallback)((e) => {
1144
- var _a, _b;
1192
+ var _a;
1193
+ var _b;
1145
1194
  if (e.button !== 0) {
1146
1195
  return;
1147
1196
  }
1197
+ e.stopPropagation();
1198
+ e.preventDefault();
1199
+ const selectionInteraction = getTimelineSequenceEdgeSelectionInteraction({
1200
+ button: e.button,
1201
+ selected,
1202
+ shiftKey: e.shiftKey,
1203
+ metaKey: e.metaKey,
1204
+ ctrlKey: e.ctrlKey,
1205
+ });
1148
1206
  const pxPerFrame = timelineDurationInFrames > 0
1149
1207
  ? (windowWidth - timeline_layout_1.TIMELINE_PADDING * 2) / timelineDurationInFrames
1150
1208
  : 0;
1151
- if (pxPerFrame <= 0) {
1152
- return;
1153
- }
1209
+ const canCalculateDelta = Number.isFinite(pxPerFrame) && pxPerFrame > 0;
1154
1210
  const { nodePathInfo: latestNodePathInfo, overrideIdToNodePathMappings: latestOverrideIdsToNodePaths, } = latestRef.current;
1155
1211
  const { selectedItems: latestSelectedItems } = currentSelection.current;
1156
- const targets = (0, exports.getTimelineSequenceDurationDragTargets)({
1157
- draggedNodePathInfo: latestNodePathInfo,
1158
- selectedItems: latestSelectedItems,
1159
- sequences: sequencesRef.current,
1160
- overrideIdsToNodePaths: latestOverrideIdsToNodePaths,
1161
- propStatuses: propStatusesRef.current,
1162
- });
1163
- if (targets === null || targets.length === 0) {
1164
- return;
1165
- }
1166
- e.stopPropagation();
1167
- e.preventDefault();
1212
+ const targets = canCalculateDelta
1213
+ ? ((_b = (0, exports.getTimelineSequenceDurationDragTargets)({
1214
+ draggedNodePathInfo: latestNodePathInfo,
1215
+ selectedItems: latestSelectedItems,
1216
+ sequences: sequencesRef.current,
1217
+ overrideIdsToNodePaths: latestOverrideIdsToNodePaths,
1218
+ propStatuses: propStatusesRef.current,
1219
+ timelineDurationInFrames,
1220
+ })) !== null && _b !== void 0 ? _b : [])
1221
+ : [];
1222
+ (_a = stopPointerSessionRef.current) === null || _a === void 0 ? void 0 : _a.call(stopPointerSessionRef);
1168
1223
  dragStateRef.current = {
1169
1224
  initialClientX: e.clientX,
1170
1225
  latestDeltaFrames: 0,
1171
1226
  didMove: false,
1172
- pxPerFrame,
1227
+ pxPerFrame: canCalculateDelta ? pxPerFrame : 1,
1173
1228
  pointerId: e.pointerId,
1174
- button: e.button,
1175
- target: e.currentTarget,
1229
+ selectionInteraction,
1176
1230
  targets,
1177
1231
  };
1178
- (_b = (_a = e.currentTarget).setPointerCapture) === null || _b === void 0 ? void 0 : _b.call(_a, e.pointerId);
1179
1232
  document.body.style.userSelect = 'none';
1180
1233
  document.body.style.webkitUserSelect = 'none';
1181
1234
  (0, ForceSpecificCursor_1.forceSpecificCursor)('ew-resize');
1182
- setDragging(true);
1183
- }, [
1184
- currentSelection,
1185
- propStatusesRef,
1186
- sequencesRef,
1187
- timelineDurationInFrames,
1188
- windowWidth,
1189
- ]);
1190
- // Install global pointer listeners while dragging. They survive parent re-renders
1191
- // and element remounts that would otherwise drop React's synthetic handlers or
1192
- // pointer capture.
1193
- (0, react_1.useEffect)(() => {
1194
- if (!dragging) {
1195
- return;
1196
- }
1197
- const onMove = (e) => {
1235
+ const onMove = (pointerEvent) => {
1198
1236
  const dragState = dragStateRef.current;
1199
- if (!dragState || e.pointerId !== dragState.pointerId) {
1237
+ if (!dragState || pointerEvent.pointerId !== dragState.pointerId) {
1200
1238
  return;
1201
1239
  }
1202
- const dx = e.clientX - dragState.initialClientX;
1240
+ const dx = pointerEvent.clientX - dragState.initialClientX;
1203
1241
  const deltaFrames = Math.round(dx / dragState.pxPerFrame);
1204
1242
  dragState.latestDeltaFrames = deltaFrames;
1205
- if (deltaFrames !== 0) {
1243
+ if (Math.abs(dx) >= timelineSequenceEdgeDragThresholdPx ||
1244
+ (0, exports.getTimelineSequenceDurationDragChanges)({
1245
+ targets: dragState.targets,
1246
+ deltaFrames,
1247
+ }).length > 0) {
1206
1248
  dragState.didMove = true;
1207
1249
  }
1208
1250
  for (const target of dragState.targets) {
1209
1251
  latestRef.current.setDragOverrides(target.nodePath, 'durationInFrames', remotion_1.Internals.makeStaticDragOverride((0, exports.getTimelineSequenceDurationDragValue)({
1210
1252
  initialDuration: target.initialDuration,
1211
1253
  deltaFrames,
1254
+ maximumDuration: target.maximumDuration,
1212
1255
  minimumDuration: target.minimumDuration,
1213
1256
  })));
1214
1257
  }
1215
1258
  };
1216
- const onUp = (e) => {
1259
+ const onUp = (pointerEvent) => {
1217
1260
  const dragState = dragStateRef.current;
1218
- if (!dragState || e.pointerId !== dragState.pointerId) {
1261
+ if (!dragState || pointerEvent.pointerId !== dragState.pointerId) {
1219
1262
  return;
1220
1263
  }
1264
+ // Include the release position even if the final pointermove was skipped.
1265
+ onMove(pointerEvent);
1221
1266
  finishDrag(true);
1222
1267
  };
1223
- const onCancel = (e) => {
1268
+ const onCancel = (pointerEvent) => {
1224
1269
  const dragState = dragStateRef.current;
1225
- if (!dragState || e.pointerId !== dragState.pointerId) {
1270
+ if (!dragState || pointerEvent.pointerId !== dragState.pointerId) {
1226
1271
  return;
1227
1272
  }
1228
1273
  finishDrag(false);
1229
1274
  };
1230
- const activeDragState = dragStateRef.current;
1231
- if (!activeDragState) {
1232
- return;
1233
- }
1234
- return (0, pointer_session_1.startCapturedPointerSession)({
1235
- event: activeDragState,
1236
- captureTarget: activeDragState.target,
1275
+ stopPointerSessionRef.current = (0, pointer_session_1.startCapturedPointerSession)({
1276
+ event: e.nativeEvent,
1277
+ captureTarget: e.currentTarget,
1237
1278
  onMove,
1238
1279
  onEnd: (reason, endEvent) => {
1239
- if ((reason === 'pointerup' || reason === 'buttons-released') &&
1280
+ stopPointerSessionRef.current = null;
1281
+ if ((reason === 'pointerup' ||
1282
+ reason === 'buttons-released' ||
1283
+ (reason === 'lostpointercapture' && (endEvent === null || endEvent === void 0 ? void 0 : endEvent.buttons) === 0)) &&
1240
1284
  endEvent) {
1241
1285
  onUp(endEvent);
1242
1286
  }
@@ -1248,10 +1292,25 @@ const TimelineSequenceRightEdgeDragHandleInner = ({ nodePathInfo, windowWidth, t
1248
1292
  }
1249
1293
  },
1250
1294
  });
1251
- }, [dragging, finishDrag]);
1295
+ }, [
1296
+ currentSelection,
1297
+ finishDrag,
1298
+ propStatusesRef,
1299
+ selected,
1300
+ sequencesRef,
1301
+ timelineDurationInFrames,
1302
+ windowWidth,
1303
+ ]);
1304
+ (0, react_1.useEffect)(() => {
1305
+ return () => {
1306
+ var _a;
1307
+ (_a = stopPointerSessionRef.current) === null || _a === void 0 ? void 0 : _a.call(stopPointerSessionRef);
1308
+ stopPointerSessionRef.current = null;
1309
+ };
1310
+ }, []);
1252
1311
  const style = {
1253
1312
  ...baseStyle,
1254
- right: 0,
1313
+ right: -HANDLE_OUTSET,
1255
1314
  background: colors_1.TRANSPARENT,
1256
1315
  };
1257
1316
  return (jsx_runtime_1.jsx("div", { role: "separator", "aria-orientation": "vertical", title: "Drag to change duration", style: style, onPointerDown: onPointerDown }));
@@ -55,13 +55,10 @@ const TimelineSliderInner = () => {
55
55
  const ref = (0, react_1.useRef)(null);
56
56
  const timelineWidth = (0, react_1.useContext)(TimelineWidthProvider_1.TimelineWidthContext);
57
57
  const { zoom: zoomMap } = (0, react_1.useContext)(timeline_zoom_1.TimelineZoomCtx);
58
- const { canvasContent } = (0, react_1.useContext)(remotion_1.Internals.CompositionManager);
59
58
  if (timelineWidth === null) {
60
59
  throw new Error('Unexpectedly did not have timeline width');
61
60
  }
62
- const zoomLevel = (canvasContent === null || canvasContent === void 0 ? void 0 : canvasContent.type) === 'composition'
63
- ? ((_a = zoomMap[canvasContent.compositionId]) !== null && _a !== void 0 ? _a : null)
64
- : null;
61
+ const zoomLevel = (_a = zoomMap[videoConfig.id]) !== null && _a !== void 0 ? _a : null;
65
62
  (0, react_1.useLayoutEffect)(() => {
66
63
  var _a;
67
64
  const el = ref.current;
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ export declare const TimelineTickFormatContext: React.Context<{
3
+ showFrames: boolean;
4
+ setShowFrames: React.Dispatch<React.SetStateAction<boolean>>;
5
+ }>;
6
+ export declare const TimelineTickFormatProvider: React.FC<{
7
+ readonly children: React.ReactNode;
8
+ }>;
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TimelineTickFormatProvider = exports.TimelineTickFormatContext = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("react");
6
+ const STORAGE_KEY = 'remotion.timelineShowFrames';
7
+ exports.TimelineTickFormatContext = (0, react_1.createContext)({ showFrames: false, setShowFrames: () => undefined });
8
+ const TimelineTickFormatProvider = ({ children }) => {
9
+ const [showFrames, setShowFrames] = (0, react_1.useState)(() => localStorage.getItem(STORAGE_KEY) === 'true');
10
+ (0, react_1.useEffect)(() => {
11
+ localStorage.setItem(STORAGE_KEY, String(showFrames));
12
+ }, [showFrames]);
13
+ const value = (0, react_1.useMemo)(() => ({ showFrames, setShowFrames }), [showFrames]);
14
+ return (jsx_runtime_1.jsx(exports.TimelineTickFormatContext.Provider, { value: value, children: children }));
15
+ };
16
+ exports.TimelineTickFormatProvider = TimelineTickFormatProvider;