@remotion/studio 4.0.482 → 4.0.484

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 (77) hide show
  1. package/dist/components/Canvas.js +19 -0
  2. package/dist/components/ColorPicker/ColorPicker.js +65 -3
  3. package/dist/components/ColorPicker/ColorPickerPopup.d.ts +1 -0
  4. package/dist/components/ColorPicker/ColorPickerPopup.js +3 -2
  5. package/dist/components/InspectorPanel/EasingInspector.js +184 -10
  6. package/dist/components/InspectorPanel/KeyframeEasingNavigator.d.ts +14 -0
  7. package/dist/components/InspectorPanel/KeyframeEasingNavigator.js +146 -0
  8. package/dist/components/InspectorPanel/KeyframeInspector.d.ts +1 -1
  9. package/dist/components/InspectorPanel/KeyframeInspector.js +228 -5
  10. package/dist/components/InspectorPanel/SequenceInspectorHeader.d.ts +17 -0
  11. package/dist/components/InspectorPanel/SequenceInspectorHeader.js +93 -0
  12. package/dist/components/InspectorPanel/SequenceSelectionInspector.js +4 -58
  13. package/dist/components/InspectorPanel/common.d.ts +12 -0
  14. package/dist/components/InspectorPanel/common.js +78 -1
  15. package/dist/components/InspectorPanel/easing-inspector-selection.d.ts +14 -0
  16. package/dist/components/InspectorPanel/easing-inspector-selection.js +18 -0
  17. package/dist/components/InspectorPanel/keyframe-easing-navigator-items.d.ts +24 -0
  18. package/dist/components/InspectorPanel/keyframe-easing-navigator-items.js +40 -0
  19. package/dist/components/InspectorPanel/keyframe-inspector-frame.d.ts +8 -0
  20. package/dist/components/InspectorPanel/keyframe-inspector-frame.js +14 -0
  21. package/dist/components/InspectorPanel/styles.d.ts +1 -0
  22. package/dist/components/InspectorPanel/styles.js +9 -1
  23. package/dist/components/InspectorSequenceSection.js +47 -10
  24. package/dist/components/Preview.js +1 -1
  25. package/dist/components/SelectedOutlineOverlay.d.ts +2 -0
  26. package/dist/components/SelectedOutlineOverlay.js +33 -12
  27. package/dist/components/SelectedOutlineUvControls.js +461 -2
  28. package/dist/components/Timeline/EasingEditorModal.js +48 -5
  29. package/dist/components/Timeline/Timeline.js +2 -1
  30. package/dist/components/Timeline/TimelineDeleteKeybindings.js +11 -1
  31. package/dist/components/Timeline/TimelineEffectItem.js +8 -5
  32. package/dist/components/Timeline/TimelineEffectPropItem.js +82 -1
  33. package/dist/components/Timeline/TimelineExpandedKeyframeRow.js +2 -2
  34. package/dist/components/Timeline/TimelineKeyframeControls.js +37 -7
  35. package/dist/components/Timeline/TimelineKeyframeEasingLine.d.ts +4 -0
  36. package/dist/components/Timeline/TimelineKeyframeEasingLine.js +12 -6
  37. package/dist/components/Timeline/TimelineKeyframedValue.js +1 -0
  38. package/dist/components/Timeline/TimelineRowChrome.js +6 -4
  39. package/dist/components/Timeline/TimelineSelection.d.ts +7 -1
  40. package/dist/components/Timeline/TimelineSelection.js +19 -3
  41. package/dist/components/Timeline/TimelineSequence.js +20 -8
  42. package/dist/components/Timeline/TimelineSequenceItem.js +64 -28
  43. package/dist/components/Timeline/TimelineSequenceName.d.ts +1 -0
  44. package/dist/components/Timeline/TimelineSequenceName.js +7 -6
  45. package/dist/components/Timeline/TimelineSequencePropItem.d.ts +1 -1
  46. package/dist/components/Timeline/TimelineSequencePropItem.js +80 -1
  47. package/dist/components/Timeline/TimelineSequenceRightEdgeDragHandle.d.ts +40 -1
  48. package/dist/components/Timeline/TimelineSequenceRightEdgeDragHandle.js +308 -1
  49. package/dist/components/Timeline/TimelineSlider.js +17 -3
  50. package/dist/components/Timeline/TimelineTrack.js +2 -4
  51. package/dist/components/Timeline/delete-selected-timeline-item.d.ts +8 -2
  52. package/dist/components/Timeline/delete-selected-timeline-item.js +80 -1
  53. package/dist/components/Timeline/get-animation-item-selection-for-frame.d.ts +18 -0
  54. package/dist/components/Timeline/get-animation-item-selection-for-frame.js +49 -0
  55. package/dist/components/Timeline/get-easing-selection-after-keyframe-delete.d.ts +15 -0
  56. package/dist/components/Timeline/get-easing-selection-after-keyframe-delete.js +26 -0
  57. package/dist/components/Timeline/save-sequence-prop.js +2 -0
  58. package/dist/components/Timeline/should-subscribe-to-sequence-props.d.ts +4 -0
  59. package/dist/components/Timeline/should-subscribe-to-sequence-props.js +10 -0
  60. package/dist/components/Timeline/update-selected-easing.d.ts +1 -0
  61. package/dist/components/Timeline/update-selected-easing.js +4 -3
  62. package/dist/components/Timeline/use-sequence-freeze-frame-menu-item.d.ts +2 -1
  63. package/dist/components/Timeline/use-sequence-freeze-frame-menu-item.js +19 -13
  64. package/dist/components/element-drag-and-drop.d.ts +3 -0
  65. package/dist/components/element-drag-and-drop.js +30 -0
  66. package/dist/components/import-assets.d.ts +11 -0
  67. package/dist/components/import-assets.js +23 -1
  68. package/dist/components/selected-outline-uv.d.ts +71 -1
  69. package/dist/components/selected-outline-uv.js +332 -10
  70. package/dist/esm/{chunk-nkqfa5bw.js → chunk-9erwh79m.js} +11896 -9019
  71. package/dist/esm/internals.mjs +11896 -9019
  72. package/dist/esm/previewEntry.mjs +11923 -9046
  73. package/dist/esm/renderEntry.mjs +1 -1
  74. package/dist/helpers/timeline-layout.d.ts +3 -3
  75. package/dist/helpers/timeline-layout.js +3 -1
  76. package/dist/helpers/use-menu-structure.js +0 -14
  77. package/package.json +11 -11
@@ -37,9 +37,12 @@ const PRESET_PREVIEW_HEIGHT = 30;
37
37
  const PRESET_PREVIEW_PADDING = 5;
38
38
  const PRESET_PREVIEW_Y_MIN = -0.35;
39
39
  const PRESET_PREVIEW_Y_MAX = 1.45;
40
+ const DEFAULT_DURATION_REST_THRESHOLD = 0.02;
40
41
  const DEFAULT_SPRING_EASING = {
41
42
  type: 'spring',
43
+ allowTail: true,
42
44
  damping: 10,
45
+ durationRestThreshold: DEFAULT_DURATION_REST_THRESHOLD,
43
46
  mass: 1,
44
47
  overshootClamping: false,
45
48
  stiffness: 100,
@@ -54,14 +57,22 @@ const EDITOR_EASING_PRESETS = [
54
57
  ];
55
58
  const SPRING_LIMITS = {
56
59
  damping: { min: 1, max: 200, step: 1 },
60
+ durationRestThreshold: { min: 0.001, max: 0.5, step: 0.001 },
57
61
  mass: { min: 0.1, max: 20, step: 0.1 },
58
62
  stiffness: { min: 1, max: 1000, step: 1 },
59
63
  };
60
64
  const SPRING_DECIMAL_PLACES = {
61
65
  damping: 0,
66
+ durationRestThreshold: 3,
62
67
  mass: 2,
63
68
  stiffness: 0,
64
69
  };
70
+ const SPRING_FALLBACKS = {
71
+ damping: DEFAULT_SPRING_EASING.damping,
72
+ durationRestThreshold: DEFAULT_DURATION_REST_THRESHOLD,
73
+ mass: DEFAULT_SPRING_EASING.mass,
74
+ stiffness: DEFAULT_SPRING_EASING.stiffness,
75
+ };
65
76
  const inlineContainer = {
66
77
  width: '100%',
67
78
  minWidth: 0,
@@ -69,8 +80,8 @@ const inlineContainer = {
69
80
  const segmentedControlWrapper = {
70
81
  display: 'flex',
71
82
  justifyContent: 'flex-start',
83
+ marginTop: 8,
72
84
  padding: `0 ${InspectorPanelLayout_1.INSPECTOR_PANEL_HORIZONTAL_PADDING}px`,
73
- marginBottom: 10,
74
85
  };
75
86
  const presetButtonsWrapper = {
76
87
  display: 'flex',
@@ -80,6 +91,10 @@ const presetButtonsWrapper = {
80
91
  marginBottom: 8,
81
92
  padding: `0 ${InspectorPanelLayout_1.INSPECTOR_PANEL_HORIZONTAL_PADDING}px`,
82
93
  };
94
+ const inspectorPresetButtonsWrapper = {
95
+ ...presetButtonsWrapper,
96
+ padding: `8px ${InspectorPanelLayout_1.INSPECTOR_PANEL_HORIZONTAL_PADDING}px 0`,
97
+ };
83
98
  const presetButtonBase = {
84
99
  alignItems: 'center',
85
100
  backgroundColor: colors_1.INPUT_BACKGROUND,
@@ -186,7 +201,11 @@ const sanitizeSpringValue = (value, key, fallback) => {
186
201
  };
187
202
  const sanitizeSpring = (spring) => ({
188
203
  type: 'spring',
204
+ allowTail: spring.allowTail,
189
205
  damping: sanitizeSpringValue(spring.damping, 'damping', DEFAULT_SPRING_EASING.damping),
206
+ durationRestThreshold: spring.durationRestThreshold === null
207
+ ? null
208
+ : sanitizeSpringValue(spring.durationRestThreshold, 'durationRestThreshold', DEFAULT_DURATION_REST_THRESHOLD),
190
209
  mass: sanitizeSpringValue(spring.mass, 'mass', DEFAULT_SPRING_EASING.mass),
191
210
  overshootClamping: spring.overshootClamping,
192
211
  stiffness: sanitizeSpringValue(spring.stiffness, 'stiffness', DEFAULT_SPRING_EASING.stiffness),
@@ -210,6 +229,7 @@ const formatNumberWithDecimalPlaces = (decimalPlaces) => (value) => {
210
229
  };
211
230
  const springFormatters = {
212
231
  damping: formatNumberWithDecimalPlaces(SPRING_DECIMAL_PLACES.damping),
232
+ durationRestThreshold: formatNumberWithDecimalPlaces(SPRING_DECIMAL_PLACES.durationRestThreshold),
213
233
  mass: formatNumberWithDecimalPlaces(SPRING_DECIMAL_PLACES.mass),
214
234
  stiffness: formatNumberWithDecimalPlaces(SPRING_DECIMAL_PLACES.stiffness),
215
235
  };
@@ -225,7 +245,9 @@ const areEasingsEqual = (first, second) => {
225
245
  return true;
226
246
  case 'spring':
227
247
  return (second.type === 'spring' &&
248
+ first.allowTail === second.allowTail &&
228
249
  first.damping === second.damping &&
250
+ first.durationRestThreshold === second.durationRestThreshold &&
229
251
  first.mass === second.mass &&
230
252
  first.overshootClamping === second.overshootClamping &&
231
253
  first.stiffness === second.stiffness);
@@ -295,6 +317,7 @@ const presetPreviewYToSvg = (value) => PRESET_PREVIEW_PADDING +
295
317
  (PRESET_PREVIEW_Y_MAX - PRESET_PREVIEW_Y_MIN)) *
296
318
  (PRESET_PREVIEW_HEIGHT - PRESET_PREVIEW_PADDING * 2);
297
319
  const getEasingFunction = (easing) => {
320
+ var _a, _b;
298
321
  switch (easing.type) {
299
322
  case 'linear':
300
323
  return remotion_1.Easing.linear;
@@ -302,7 +325,9 @@ const getEasingFunction = (easing) => {
302
325
  return remotion_1.Easing.bezier(easing.x1, easing.y1, easing.x2, easing.y2);
303
326
  case 'spring':
304
327
  return remotion_1.Easing.spring({
328
+ allowTail: (_a = easing.allowTail) !== null && _a !== void 0 ? _a : undefined,
305
329
  damping: easing.damping,
330
+ durationRestThreshold: (_b = easing.durationRestThreshold) !== null && _b !== void 0 ? _b : undefined,
306
331
  mass: easing.mass,
307
332
  overshootClamping: easing.overshootClamping,
308
333
  stiffness: easing.stiffness,
@@ -374,6 +399,7 @@ const EasingPresetButton = ({ currentEasing, disabled, onClick, preset }) => {
374
399
  return (jsx_runtime_1.jsx("button", { type: "button", style: style, title: preset.label, "aria-label": `Apply ${preset.label} easing`, disabled: disabled, onClick: handleClick, children: jsx_runtime_1.jsx("svg", { width: PRESET_PREVIEW_WIDTH, height: PRESET_PREVIEW_HEIGHT, viewBox: `0 0 ${PRESET_PREVIEW_WIDTH} ${PRESET_PREVIEW_HEIGHT}`, style: presetPreviewSvgStyle, "aria-hidden": "true", focusable: false, children: jsx_runtime_1.jsx("path", { d: path, fill: "none", stroke: "white", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round" }) }) }));
375
400
  };
376
401
  const EasingEditor = ({ state, renderHeader }) => {
402
+ var _a, _b;
377
403
  const { previewServerState } = (0, react_1.useContext)(client_id_1.StudioServerConnectionCtx);
378
404
  const sequencesRef = (0, react_1.useContext)(remotion_1.Internals.SequenceManagerRefContext);
379
405
  const propStatusesRef = (0, react_1.useContext)(remotion_1.Internals.VisualModePropStatusesRefContext);
@@ -532,7 +558,7 @@ const EasingEditor = ({ state, renderHeader }) => {
532
558
  const setSpringNumber = (0, react_1.useCallback)((key, value, commit) => {
533
559
  const next = {
534
560
  ...springRef.current,
535
- [key]: sanitizeSpringValue(value, key, DEFAULT_SPRING_EASING[key]),
561
+ [key]: sanitizeSpringValue(value, key, SPRING_FALLBACKS[key]),
536
562
  };
537
563
  const version = setSpringAndPreview(next);
538
564
  if (commit) {
@@ -547,6 +573,15 @@ const EasingEditor = ({ state, renderHeader }) => {
547
573
  const version = setSpringAndPreview(next);
548
574
  commitEasing(serializeSpring(next), version);
549
575
  }, [commitEasing, setSpringAndPreview]);
576
+ const setAllowTail = (0, react_1.useCallback)(() => {
577
+ var _a;
578
+ const next = {
579
+ ...springRef.current,
580
+ allowTail: !((_a = springRef.current.allowTail) !== null && _a !== void 0 ? _a : false),
581
+ };
582
+ const version = setSpringAndPreview(next);
583
+ commitEasing(serializeSpring(next), version);
584
+ }, [commitEasing, setSpringAndPreview]);
550
585
  const switchMode = (0, react_1.useCallback)((nextMode) => {
551
586
  setMode(nextMode);
552
587
  if (mode === nextMode || previewServerState.type !== 'connected') {
@@ -647,8 +682,11 @@ const EasingEditor = ({ state, renderHeader }) => {
647
682
  return `M ${startPoint.x} ${startPoint.y} C ${firstHandle.x} ${firstHandle.y}, ${secondHandle.x} ${secondHandle.y}, ${endPoint.x} ${endPoint.y}`;
648
683
  }, [endPoint, firstHandle, secondHandle, startPoint]);
649
684
  const springPath = (0, react_1.useMemo)(() => {
685
+ var _a, _b;
650
686
  const easing = remotion_1.Easing.spring({
687
+ allowTail: (_a = spring.allowTail) !== null && _a !== void 0 ? _a : undefined,
651
688
  damping: spring.damping,
689
+ durationRestThreshold: (_b = spring.durationRestThreshold) !== null && _b !== void 0 ? _b : undefined,
652
690
  mass: spring.mass,
653
691
  overshootClamping: spring.overshootClamping,
654
692
  stiffness: spring.stiffness,
@@ -687,10 +725,11 @@ const EasingEditor = ({ state, renderHeader }) => {
687
725
  gridTemplateColumns: 'repeat(2, minmax(0, 1fr))',
688
726
  padding: '0 12px 12px',
689
727
  }), []);
690
- return (jsx_runtime_1.jsxs("div", { style: inlineContainer, children: [renderHeader ? (renderHeader(modeItems)) : (jsx_runtime_1.jsx("div", { style: segmentedControlWrapper, children: jsx_runtime_1.jsx(SegmentedControl_1.SegmentedControl, { items: modeItems, needsWrapping: false, size: "compact" }) })), jsx_runtime_1.jsx("div", { style: presetButtonsWrapper, children: EDITOR_EASING_PRESETS.map((preset) => (jsx_runtime_1.jsx(EasingPresetButton, { currentEasing: currentEasing, disabled: disabled, onClick: applyPreset, preset: preset }, preset.id))) }), mode === 'bezier' ? (jsx_runtime_1.jsxs(jsx_runtime_1.Fragment, { children: [
728
+ const modeSwitcher = (jsx_runtime_1.jsx("div", { style: segmentedControlWrapper, children: jsx_runtime_1.jsx(SegmentedControl_1.SegmentedControl, { items: modeItems, needsWrapping: false }) }));
729
+ return (jsx_runtime_1.jsxs("div", { style: inlineContainer, children: [renderHeader ? renderHeader(modeItems) : null, jsx_runtime_1.jsx("div", { style: renderHeader ? inspectorPresetButtonsWrapper : presetButtonsWrapper, children: EDITOR_EASING_PRESETS.map((preset) => (jsx_runtime_1.jsx(EasingPresetButton, { currentEasing: currentEasing, disabled: disabled, onClick: applyPreset, preset: preset }, preset.id))) }), mode === 'bezier' ? (jsx_runtime_1.jsxs(jsx_runtime_1.Fragment, { children: [
691
730
  jsx_runtime_1.jsxs("svg", { ref: svgRef, width: SVG_WIDTH, height: SVG_HEIGHT, viewBox: `0 0 ${SVG_WIDTH} ${SVG_HEIGHT}`, style: svgStyle, "aria-label": "Bezier curve editor", children: [
692
731
  jsx_runtime_1.jsx(EasingGraphScaffold, { labels: graphLabels }), jsx_runtime_1.jsx("line", { x1: startPoint.x, y1: startPoint.y, x2: firstHandle.x, y2: firstHandle.y, stroke: "rgba(255, 255, 255, 0.35)", strokeWidth: 1 }), jsx_runtime_1.jsx("line", { x1: endPoint.x, y1: endPoint.y, x2: secondHandle.x, y2: secondHandle.y, stroke: "rgba(255, 255, 255, 0.35)", strokeWidth: 1 }), jsx_runtime_1.jsx("path", { d: bezierPath, fill: "none", stroke: colors_1.BLUE, strokeWidth: 3 }), jsx_runtime_1.jsx("circle", { cx: startPoint.x, cy: startPoint.y, r: 4, fill: "white" }), jsx_runtime_1.jsx("circle", { cx: endPoint.x, cy: endPoint.y, r: 4, fill: "white" }), jsx_runtime_1.jsx("circle", { cx: firstHandle.x, cy: firstHandle.y, r: 6, fill: "white", stroke: colors_1.BLUE, strokeWidth: 2, vectorEffect: "non-scaling-stroke", pointerEvents: disabled ? 'none' : 'all', cursor: activeHandle === 0 ? 'grabbing' : 'default', onPointerDown: (event) => onHandlePointerDown(0, event) }), jsx_runtime_1.jsx("circle", { cx: secondHandle.x, cy: secondHandle.y, r: 6, fill: "white", stroke: colors_1.BLUE, strokeWidth: 2, vectorEffect: "non-scaling-stroke", pointerEvents: disabled ? 'none' : 'all', cursor: activeHandle === 1 ? 'grabbing' : 'default', onPointerDown: (event) => onHandlePointerDown(1, event) })
693
- ] }), jsx_runtime_1.jsxs("div", { style: coordinatesGrid, children: [
732
+ ] }), modeSwitcher, jsx_runtime_1.jsxs("div", { style: coordinatesGrid, children: [
694
733
  jsx_runtime_1.jsxs("div", { style: coordinateRow, children: [
695
734
  jsx_runtime_1.jsx("div", { style: coordinateLabel, children: "X1" }), jsx_runtime_1.jsx("div", { style: coordinateInputWrapper, children: jsx_runtime_1.jsx(InputDragger_1.InputDragger, { type: "number", value: bezier[0], status: "ok", onValueChange: (value) => setCoordinate(0, 'x', value, false), onValueChangeEnd: (value) => setCoordinate(0, 'x', value, true), onTextChange: () => undefined, min: 0, max: 1, step: 0.01, formatter: formatNumber, rightAlign: false, style: numberInputStyle, snapToStep: false, disabled: disabled }) })
696
735
  ] }), jsx_runtime_1.jsxs("div", { style: coordinateRow, children: [
@@ -704,15 +743,19 @@ const EasingEditor = ({ state, renderHeader }) => {
704
743
  ] })) : (jsx_runtime_1.jsxs(jsx_runtime_1.Fragment, { children: [
705
744
  jsx_runtime_1.jsxs("svg", { width: SVG_WIDTH, height: SVG_HEIGHT, viewBox: `0 0 ${SVG_WIDTH} ${SVG_HEIGHT}`, style: svgStyle, "aria-label": "Spring easing curve", children: [
706
745
  jsx_runtime_1.jsx(EasingGraphScaffold, { labels: graphLabels }), jsx_runtime_1.jsx("path", { d: springPath, fill: "none", stroke: colors_1.BLUE, strokeWidth: 3 }), jsx_runtime_1.jsx("circle", { cx: xToSvg(0), cy: yToSvg(0), r: 4, fill: "white" }), jsx_runtime_1.jsx("circle", { cx: xToSvg(1), cy: yToSvg(1), r: 4, fill: "white" })
707
- ] }), jsx_runtime_1.jsxs("div", { style: coordinatesGrid, children: [
746
+ ] }), modeSwitcher, jsx_runtime_1.jsxs("div", { style: coordinatesGrid, children: [
708
747
  jsx_runtime_1.jsxs("div", { style: coordinateRow, children: [
709
748
  jsx_runtime_1.jsx("div", { style: coordinateLabel, children: "Damping" }), jsx_runtime_1.jsx("div", { style: coordinateInputWrapper, children: jsx_runtime_1.jsx(InputDragger_1.InputDragger, { type: "number", value: spring.damping, status: "ok", onValueChange: (value) => setSpringNumber('damping', value, false), onValueChangeEnd: (value) => setSpringNumber('damping', value, true), onTextChange: () => undefined, min: SPRING_LIMITS.damping.min, max: SPRING_LIMITS.damping.max, step: SPRING_LIMITS.damping.step, formatter: springFormatters.damping, rightAlign: false, style: numberInputStyle, snapToStep: false, dragDecimalPlaces: SPRING_DECIMAL_PLACES.damping, disabled: disabled }) })
710
749
  ] }), jsx_runtime_1.jsxs("div", { style: coordinateRow, children: [
711
750
  jsx_runtime_1.jsx("div", { style: coordinateLabel, children: "Mass" }), jsx_runtime_1.jsx("div", { style: coordinateInputWrapper, children: jsx_runtime_1.jsx(InputDragger_1.InputDragger, { type: "number", value: spring.mass, status: "ok", onValueChange: (value) => setSpringNumber('mass', value, false), onValueChangeEnd: (value) => setSpringNumber('mass', value, true), onTextChange: () => undefined, min: SPRING_LIMITS.mass.min, max: SPRING_LIMITS.mass.max, step: SPRING_LIMITS.mass.step, formatter: springFormatters.mass, rightAlign: false, style: numberInputStyle, snapToStep: false, dragDecimalPlaces: SPRING_DECIMAL_PLACES.mass, disabled: disabled }) })
712
751
  ] }), jsx_runtime_1.jsxs("div", { style: coordinateRow, children: [
713
752
  jsx_runtime_1.jsx("div", { style: coordinateLabel, children: "Stiffness" }), jsx_runtime_1.jsx("div", { style: coordinateInputWrapper, children: jsx_runtime_1.jsx(InputDragger_1.InputDragger, { type: "number", value: spring.stiffness, status: "ok", onValueChange: (value) => setSpringNumber('stiffness', value, false), onValueChangeEnd: (value) => setSpringNumber('stiffness', value, true), onTextChange: () => undefined, min: SPRING_LIMITS.stiffness.min, max: SPRING_LIMITS.stiffness.max, step: SPRING_LIMITS.stiffness.step, formatter: springFormatters.stiffness, rightAlign: false, style: numberInputStyle, snapToStep: false, dragDecimalPlaces: SPRING_DECIMAL_PLACES.stiffness, disabled: disabled }) })
753
+ ] }), jsx_runtime_1.jsxs("div", { style: coordinateRow, children: [
754
+ jsx_runtime_1.jsx("div", { style: coordinateLabel, children: "Rest threshold" }), jsx_runtime_1.jsx("div", { style: coordinateInputWrapper, children: jsx_runtime_1.jsx(InputDragger_1.InputDragger, { type: "number", value: (_a = spring.durationRestThreshold) !== null && _a !== void 0 ? _a : DEFAULT_DURATION_REST_THRESHOLD, status: "ok", onValueChange: (value) => setSpringNumber('durationRestThreshold', value, false), onValueChangeEnd: (value) => setSpringNumber('durationRestThreshold', value, true), onTextChange: () => undefined, min: SPRING_LIMITS.durationRestThreshold.min, max: SPRING_LIMITS.durationRestThreshold.max, step: SPRING_LIMITS.durationRestThreshold.step, formatter: springFormatters.durationRestThreshold, rightAlign: false, style: numberInputStyle, snapToStep: false, dragDecimalPlaces: SPRING_DECIMAL_PLACES.durationRestThreshold, disabled: disabled }) })
714
755
  ] }), jsx_runtime_1.jsxs("div", { style: coordinateRow, children: [
715
756
  jsx_runtime_1.jsx("div", { style: coordinateLabel, children: "Clamp overshoot" }), jsx_runtime_1.jsx("div", { style: checkboxWrapper, children: jsx_runtime_1.jsx(Checkbox_1.Checkbox, { checked: spring.overshootClamping, onChange: setOvershootClamping, name: "spring-overshoot-clamping", disabled: disabled, variant: "small" }) })
757
+ ] }), jsx_runtime_1.jsxs("div", { style: coordinateRow, children: [
758
+ jsx_runtime_1.jsx("div", { style: coordinateLabel, children: "Allow tail" }), jsx_runtime_1.jsx("div", { style: checkboxWrapper, children: jsx_runtime_1.jsx(Checkbox_1.Checkbox, { checked: (_b = spring.allowTail) !== null && _b !== void 0 ? _b : false, onChange: setAllowTail, name: "spring-allow-tail", disabled: disabled, variant: "small" }) })
716
759
  ] })
717
760
  ] })
718
761
  ] }))] }));
@@ -53,6 +53,7 @@ const SplitterHandle_1 = require("../Splitter/SplitterHandle");
53
53
  const MaxTimelineTracks_1 = require("./MaxTimelineTracks");
54
54
  const SequencePropsObserver_1 = require("./SequencePropsObserver");
55
55
  const should_show_track_in_timeline_1 = require("./should-show-track-in-timeline");
56
+ const should_subscribe_to_sequence_props_1 = require("./should-subscribe-to-sequence-props");
56
57
  const SubscribeToNodePaths_1 = require("./SubscribeToNodePaths");
57
58
  const timeline_refs_1 = require("./timeline-refs");
58
59
  const TimelineDragHandler_1 = require("./TimelineDragHandler");
@@ -227,7 +228,7 @@ const TimelineInner = () => {
227
228
  }, [filtered]);
228
229
  const hasBeenCut = filtered.length > shown.length;
229
230
  return (jsx_runtime_1.jsxs(TimelineContextMenuArea, { children: [sequences.map((sequence) => {
230
- if (!sequence.controls || !previewConnected || !sequence.getStack()) {
231
+ if (!(0, should_subscribe_to_sequence_props_1.shouldSubscribeToSequenceProps)(sequence, previewConnected)) {
231
232
  return null;
232
233
  }
233
234
  return (jsx_runtime_1.jsx(SubscribeToNodePaths_1.SubscribeToNodePaths, { overrideId: sequence.controls.overrideId, componentIdentity: sequence.controls.componentIdentity, schema: sequence.controls.schema, getStack: sequence.getStack, effects: sequence.effects }, sequence.id));
@@ -24,6 +24,9 @@ const TimelineDeleteKeybindings = () => {
24
24
  const { canSelect } = (0, TimelineSelection_1.useTimelineSelection)();
25
25
  const currentSelection = (0, TimelineSelection_1.useCurrentTimelineSelectionStateAsRef)();
26
26
  const confirm = (0, ConfirmationDialog_1.useConfirmationDialog)();
27
+ const timelinePosition = remotion_1.Internals.Timeline.useTimelinePosition();
28
+ const timelinePositionRef = (0, react_1.useRef)(timelinePosition);
29
+ timelinePositionRef.current = timelinePosition;
27
30
  (0, react_1.useEffect)(() => {
28
31
  if (!canSelect || previewServerState.type !== 'connected') {
29
32
  return;
@@ -58,7 +61,13 @@ const TimelineDeleteKeybindings = () => {
58
61
  deletePromise
59
62
  .then((deleted) => {
60
63
  if (deleted) {
61
- const nextSelection = (0, delete_selected_timeline_item_1.getTimelineSelectionAfterDeletingItems)(selectedItems);
64
+ const nextSelection = (0, delete_selected_timeline_item_1.getTimelineSelectionAfterDeletingItems)({
65
+ selections: selectedItems,
66
+ sequences,
67
+ overrideIdsToNodePaths: overrideIdToNodePathMappings,
68
+ propStatuses,
69
+ timelinePosition: timelinePositionRef.current,
70
+ });
62
71
  if (nextSelection.length === 0) {
63
72
  clearSelection();
64
73
  }
@@ -154,6 +163,7 @@ const TimelineDeleteKeybindings = () => {
154
163
  sequencesRef,
155
164
  setGuidesList,
156
165
  setPropStatuses,
166
+ timelinePositionRef,
157
167
  ]);
158
168
  return null;
159
169
  };
@@ -71,6 +71,7 @@ const TimelineEffectItem = ({ label, nodePathInfo, effectIndex, effectSchema, do
71
71
  const { propStatuses } = (0, react_1.useContext)(remotion_1.Internals.VisualModePropStatusesContext);
72
72
  const { setPropStatuses } = (0, react_1.useContext)(remotion_1.Internals.VisualModeSettersContext);
73
73
  const selection = (0, TimelineSelection_1.useTimelineRowSelection)(nodePathInfo);
74
+ const containsSelection = (0, TimelineSelection_1.useTimelineRowContainsSelection)(nodePathInfo);
74
75
  const [dropIndicator, setDropIndicator] = (0, react_1.useState)(null);
75
76
  const effectStatus = (0, react_1.useMemo)(() => remotion_1.Internals.getEffectPropStatusesCtx({
76
77
  propStatuses,
@@ -205,12 +206,14 @@ const TimelineEffectItem = ({ label, nodePathInfo, effectIndex, effectSchema, do
205
206
  alignSelf: 'stretch',
206
207
  alignItems: 'center',
207
208
  color: (0, TimelineSelection_1.getTimelineColor)(selection.selected, true),
208
- display: 'flex',
209
- flex: 1,
209
+ display: 'inline-flex',
210
+ marginRight: timeline_layout_1.EXPANDED_SECTION_PADDING_RIGHT,
210
211
  minWidth: 0,
211
- paddingRight: timeline_layout_1.EXPANDED_SECTION_PADDING_RIGHT,
212
+ boxShadow: containsSelection && !selection.selected
213
+ ? `inset 0 0 0 2px ${TimelineSelection_1.TIMELINE_SELECTED_LABEL_BACKGROUND}`
214
+ : undefined,
212
215
  };
213
- }, [selection.selected]);
216
+ }, [containsSelection, selection.selected]);
214
217
  const getDropTarget = (0, react_1.useCallback)((e) => {
215
218
  const dragData = getEffectReorderDragData(e.dataTransfer);
216
219
  if (!dragData || dragData.nodePathKey !== nodePathKey) {
@@ -316,7 +319,7 @@ const TimelineEffectItem = ({ label, nodePathInfo, effectIndex, effectSchema, do
316
319
  ...(dropIndicator === 'before' ? { top: -1 } : { bottom: -1 }),
317
320
  };
318
321
  }, [dropIndicator]);
319
- const row = (jsx_runtime_1.jsx(TimelineRowChrome_1.TimelineRowChrome, { depth: rowDepth, eye: canToggle ? (jsx_runtime_1.jsx(TimelineLayerEye_1.TimelineLayerEye, { type: "effect", hidden: isDisabled, onInvoked: onToggle })) : (jsx_runtime_1.jsx(TimelineLayerEye_1.TimelineLayerEyeSpacer, {})), arrow: jsx_runtime_1.jsx(TimelineExpandArrowButton_1.TimelineExpandArrowButton, { isExpanded: isExpanded, onClick: () => toggleTrack(nodePathInfo), label: `${label} section`, disabled: false }), style: rowStyle, selected: selection.selected, selectable: selection.selectable, selectionItem: selection.selectionItem, onSelect: selection.onSelect, showSelectedBackground: true, containsSelection: false, outerHeight: null, children: jsx_runtime_1.jsx("span", { title: label, style: labelStyle, children: label }) }));
322
+ const row = (jsx_runtime_1.jsx(TimelineRowChrome_1.TimelineRowChrome, { depth: rowDepth, eye: canToggle ? (jsx_runtime_1.jsx(TimelineLayerEye_1.TimelineLayerEye, { type: "effect", hidden: isDisabled, onInvoked: onToggle })) : (jsx_runtime_1.jsx(TimelineLayerEye_1.TimelineLayerEyeSpacer, {})), arrow: jsx_runtime_1.jsx(TimelineExpandArrowButton_1.TimelineExpandArrowButton, { isExpanded: isExpanded, onClick: () => toggleTrack(nodePathInfo), label: `${label} section`, disabled: false }), style: rowStyle, selected: selection.selected, selectable: selection.selectable, selectionItem: selection.selectionItem, onSelect: selection.onSelect, showSelectedBackground: true, containsSelection: containsSelection, outerHeight: null, children: jsx_runtime_1.jsx("span", { title: label, style: labelStyle, children: label }) }));
320
323
  const draggableRow = canReorder ? (jsx_runtime_1.jsxs("div", { draggable: true, onDragStart: onDragStart, onDragEnd: onDragEnd, onDragOver: onDragOver, onDragLeave: onDragLeave, onDrop: onDrop, style: reorderWrapper, children: [reorderLineStyle ? jsx_runtime_1.jsx("div", { style: reorderLineStyle }) : null, row] })) : (row);
321
324
  return previewConnected ? (jsx_runtime_1.jsx(ContextMenu_1.ContextMenu, { values: contextMenuValues, onOpen: selection.selectable ? selection.onSelect : null, children: draggableRow })) : (draggableRow);
322
325
  };
@@ -10,6 +10,7 @@ const modals_1 = require("../../state/modals");
10
10
  const call_api_1 = require("../call-api");
11
11
  const ContextMenu_1 = require("../ContextMenu");
12
12
  const call_add_keyframe_1 = require("./call-add-keyframe");
13
+ const get_animation_item_selection_for_frame_1 = require("./get-animation-item-selection-for-frame");
13
14
  const get_timeline_keyframes_1 = require("./get-timeline-keyframes");
14
15
  const save_effect_prop_1 = require("./save-effect-prop");
15
16
  const save_prop_queue_1 = require("./save-prop-queue");
@@ -22,6 +23,7 @@ const TimelineLayerEye_1 = require("./TimelineLayerEye");
22
23
  const TimelineRowChrome_1 = require("./TimelineRowChrome");
23
24
  const TimelineSchemaField_1 = require("./TimelineSchemaField");
24
25
  const TimelineSelection_1 = require("./TimelineSelection");
26
+ const update_selected_easing_1 = require("./update-selected-easing");
25
27
  const fieldRowBase = {};
26
28
  const isKeyframedStatus = (status) => {
27
29
  return status.status === 'keyframed';
@@ -219,6 +221,9 @@ const TimelineEffectPropItem = ({ field, validatedLocation, rowDepth, nodePath,
219
221
  const { propStatuses } = (0, react_1.useContext)(remotion_1.Internals.VisualModePropStatusesContext);
220
222
  const { getEffectDragOverrides } = (0, react_1.useContext)(remotion_1.Internals.VisualModeDragOverridesContext);
221
223
  const selection = (0, TimelineSelection_1.useTimelineRowSelection)(nodePathInfo);
224
+ const { selectItems } = (0, TimelineSelection_1.useTimelineSelection)();
225
+ const setFrame = remotion_1.Internals.useTimelineSetFrame();
226
+ const videoConfig = (0, remotion_1.useVideoConfig)();
222
227
  const timelinePosition = remotion_1.Internals.Timeline.useTimelinePosition();
223
228
  const sourceFrame = timelinePosition - keyframeDisplayOffset;
224
229
  const style = (0, react_1.useMemo)(() => {
@@ -359,7 +364,83 @@ const TimelineEffectPropItem = ({ field, validatedLocation, rowDepth, nodePath,
359
364
  previewServerState,
360
365
  propStatus,
361
366
  ]);
362
- const row = (jsx_runtime_1.jsxs(TimelineRowChrome_1.TimelineRowChrome, { depth: rowDepth, eye: jsx_runtime_1.jsx(TimelineLayerEye_1.TimelineLayerEyeSpacer, {}), keyframeControls: keyframeControls, arrow: jsx_runtime_1.jsx(TimelineExpandArrowButton_1.TimelineExpandArrowSpacer, {}), style: style, selected: selection.selected, selectable: selection.selectable, selectionItem: selection.selectionItem, onSelect: selection.onSelect, showSelectedBackground: true, containsSelection: false, outerHeight: null, children: [
367
+ const seekToDisplayFrame = (0, react_1.useCallback)((frame) => {
368
+ setFrame((current) => {
369
+ const next = { ...current, [videoConfig.id]: frame };
370
+ remotion_1.Internals.persistCurrentFrame(next);
371
+ return next;
372
+ });
373
+ }, [setFrame, videoConfig.id]);
374
+ const onPropertyDoubleClick = (0, react_1.useCallback)((event) => {
375
+ if (propStatus === null || propStatus.status === 'computed') {
376
+ return;
377
+ }
378
+ const keyframeSelection = {
379
+ type: 'keyframe',
380
+ nodePathInfo,
381
+ frame: sourceFrame + keyframeDisplayOffset,
382
+ };
383
+ if (propStatus.status === 'static') {
384
+ if (!keyframable || previewServerState.type !== 'connected') {
385
+ return;
386
+ }
387
+ const value = remotion_1.Internals.getEffectiveVisualModeValue({
388
+ propStatus,
389
+ dragOverrideValue,
390
+ frame: sourceFrame,
391
+ defaultValue: field.fieldSchema.default,
392
+ shouldResortToDefaultValueIfUndefined: true,
393
+ });
394
+ event.stopPropagation();
395
+ (0, call_add_keyframe_1.callAddEffectKeyframe)({
396
+ fileName: validatedLocation.source,
397
+ nodePath,
398
+ effectIndex: field.effectIndex,
399
+ fieldKey: field.key,
400
+ sourceFrame,
401
+ value,
402
+ schema: field.effectSchema,
403
+ setPropStatuses,
404
+ clientId: previewServerState.clientId,
405
+ }).catch(() => undefined);
406
+ selectItems([keyframeSelection], { reveal: true });
407
+ seekToDisplayFrame(keyframeSelection.frame);
408
+ return;
409
+ }
410
+ const targetSelection = (0, get_animation_item_selection_for_frame_1.getAnimationItemSelectionForSourceFrame)({
411
+ includeEasings: (0, update_selected_easing_1.canEditEasingForInterpolationFunction)(propStatus.interpolationFunction),
412
+ keyframeDisplayOffset,
413
+ keyframes: propStatus.keyframes,
414
+ nodePathInfo,
415
+ sourceFrame,
416
+ });
417
+ if (targetSelection === null) {
418
+ return;
419
+ }
420
+ event.stopPropagation();
421
+ selectItems([targetSelection], { reveal: true });
422
+ if (targetSelection.type === 'keyframe') {
423
+ seekToDisplayFrame(targetSelection.frame);
424
+ }
425
+ }, [
426
+ dragOverrideValue,
427
+ field.effectIndex,
428
+ field.effectSchema,
429
+ field.fieldSchema.default,
430
+ field.key,
431
+ keyframeDisplayOffset,
432
+ keyframable,
433
+ nodePath,
434
+ nodePathInfo,
435
+ previewServerState,
436
+ propStatus,
437
+ seekToDisplayFrame,
438
+ selectItems,
439
+ setPropStatuses,
440
+ sourceFrame,
441
+ validatedLocation.source,
442
+ ]);
443
+ const row = (jsx_runtime_1.jsxs(TimelineRowChrome_1.TimelineRowChrome, { depth: rowDepth, eye: jsx_runtime_1.jsx(TimelineLayerEye_1.TimelineLayerEyeSpacer, {}), keyframeControls: keyframeControls, arrow: jsx_runtime_1.jsx(TimelineExpandArrowButton_1.TimelineExpandArrowSpacer, {}), style: style, selected: selection.selected, selectable: selection.selectable, selectionItem: selection.selectionItem, onSelect: selection.onSelect, onDoubleClick: onPropertyDoubleClick, showSelectedBackground: true, containsSelection: false, outerHeight: null, children: [
363
444
  jsx_runtime_1.jsx(TimelineFieldLabel_1.TimelineFieldLabel, { rowDepth: rowDepth, selected: selection.selected, label: (_c = field.description) !== null && _c !== void 0 ? _c : field.key }), jsx_runtime_1.jsx("div", { style: timeline_field_row_layout_1.timelineFieldValueColumnStyle, children: jsx_runtime_1.jsx(exports.TimelineEffectPropValue, { field: field, nodePath: nodePath, validatedLocation: validatedLocation, sourceFrame: sourceFrame }) })
364
445
  ] }));
365
446
  return (jsx_runtime_1.jsx(ContextMenu_1.ContextMenu, { values: contextMenuValues, onOpen: selection.selectable ? selection.onSelect : null, children: row }));
@@ -50,11 +50,11 @@ const rowSeparator = {
50
50
  };
51
51
  const TimelineExpandedKeyframeRowUnmemoized = ({ height, keyframes, canEditEasing, nodePathInfo, showSeparator }) => {
52
52
  const timelineWidth = (0, react_1.useContext)(TimelineWidthProvider_1.TimelineWidthContext);
53
- const { selected: rowSelected } = (0, TimelineSelection_1.useTimelineRowSelection)(nodePathInfo);
53
+ const rowHighlightBackground = (0, TimelineSelection_1.useTimelineRowHighlightBackground)(nodePathInfo);
54
54
  const easingSegments = canEditEasing
55
55
  ? (0, get_timeline_easing_segments_1.getTimelineEasingSegments)(keyframes)
56
56
  : [];
57
- return (jsx_runtime_1.jsxs(jsx_runtime_1.Fragment, { children: [showSeparator ? jsx_runtime_1.jsx("div", { style: rowSeparator }) : null, jsx_runtime_1.jsxs("div", { style: { ...row, height }, children: [rowSelected && timelineWidth !== null ? (jsx_runtime_1.jsx("div", { style: (0, TimelineSelection_1.getTimelineSelectedTrackHighlightStyle)(timelineWidth) })) : null, easingSegments.map((segment) => (jsx_runtime_1.jsx(TimelineKeyframeEasingLine_1.TimelineKeyframeEasingLine, { fromFrame: segment.fromFrame, toFrame: segment.toFrame, rowHeight: height, nodePathInfo: nodePathInfo, segmentIndex: segment.segmentIndex }, `${segment.segmentIndex}-${segment.fromFrame}-${segment.toFrame}`))), keyframes.map((keyframe) => (jsx_runtime_1.jsx(TimelineKeyframeDiamond_1.TimelineKeyframeDiamond, { frame: keyframe.frame, rowHeight: height, nodePathInfo: nodePathInfo }, keyframe.frame)))] })
57
+ return (jsx_runtime_1.jsxs(jsx_runtime_1.Fragment, { children: [showSeparator ? jsx_runtime_1.jsx("div", { style: rowSeparator }) : null, jsx_runtime_1.jsxs("div", { style: { ...row, height }, children: [rowHighlightBackground && timelineWidth !== null ? (jsx_runtime_1.jsx("div", { style: (0, TimelineSelection_1.getTimelineSelectedTrackHighlightStyle)(timelineWidth, rowHighlightBackground) })) : null, easingSegments.map((segment) => (jsx_runtime_1.jsx(TimelineKeyframeEasingLine_1.TimelineKeyframeEasingLine, { fromFrame: segment.fromFrame, toFrame: segment.toFrame, rowHeight: height, nodePathInfo: nodePathInfo, segmentIndex: segment.segmentIndex }, `${segment.segmentIndex}-${segment.fromFrame}-${segment.toFrame}`))), keyframes.map((keyframe) => (jsx_runtime_1.jsx(TimelineKeyframeDiamond_1.TimelineKeyframeDiamond, { frame: keyframe.frame, rowHeight: height, nodePathInfo: nodePathInfo }, keyframe.frame)))] })
58
58
  ] }));
59
59
  };
60
60
  exports.TimelineExpandedKeyframeRow = react_1.default.memo(TimelineExpandedKeyframeRowUnmemoized);
@@ -11,11 +11,13 @@ const timeline_layout_1 = require("../../helpers/timeline-layout");
11
11
  const timeline_node_path_key_1 = require("../../helpers/timeline-node-path-key");
12
12
  const call_add_keyframe_1 = require("./call-add-keyframe");
13
13
  const call_delete_keyframe_1 = require("./call-delete-keyframe");
14
+ const get_easing_selection_after_keyframe_delete_1 = require("./get-easing-selection-after-keyframe-delete");
14
15
  const get_keyframe_navigation_1 = require("./get-keyframe-navigation");
15
16
  const get_timeline_keyframes_1 = require("./get-timeline-keyframes");
16
17
  const TimelineKeyframeDiamondIcon_1 = require("./TimelineKeyframeDiamondIcon");
17
18
  const TimelineKeyframeTracksContext_1 = require("./TimelineKeyframeTracksContext");
18
19
  const TimelineSelection_1 = require("./TimelineSelection");
20
+ const update_selected_easing_1 = require("./update-selected-easing");
19
21
  const controlsContainerStyle = {
20
22
  alignItems: 'center',
21
23
  display: 'flex',
@@ -262,7 +264,7 @@ const shouldShowTimelineKeyframeNavigation = ({ propStatus, selected, }) => {
262
264
  return isKeyframedStatus(propStatus);
263
265
  };
264
266
  exports.shouldShowTimelineKeyframeNavigation = shouldShowTimelineKeyframeNavigation;
265
- const TimelineKeyframeControls = ({ fieldKey, propStatus, nodePath, fileName, keyframeDisplayOffset, defaultValue, dragOverrideValue, schema, effectIndex, nodePathInfo, }) => {
267
+ const TimelineKeyframeControls = ({ fieldKey, propStatus, nodePath, fileName, keyframeDisplayOffset, defaultValue, dragOverrideValue, schema, effectIndex, nodePathInfo, mode = 'timeline', }) => {
266
268
  const videoConfig = (0, remotion_1.useVideoConfig)();
267
269
  const timelinePosition = remotion_1.Internals.Timeline.useTimelinePosition();
268
270
  const setFrame = remotion_1.Internals.useTimelineSetFrame();
@@ -270,7 +272,7 @@ const TimelineKeyframeControls = ({ fieldKey, propStatus, nodePath, fileName, ke
270
272
  const { propStatuses } = (0, react_1.useContext)(remotion_1.Internals.VisualModePropStatusesContext);
271
273
  const { getDragOverrides, getEffectDragOverrides } = (0, react_1.useContext)(remotion_1.Internals.VisualModeDragOverridesContext);
272
274
  const { previewServerState } = (0, react_1.useContext)(client_id_1.StudioServerConnectionCtx);
273
- const { selectedItems } = (0, TimelineSelection_1.useTimelineSelection)();
275
+ const { selectedItems, selectItems } = (0, TimelineSelection_1.useTimelineSelection)();
274
276
  const tracks = (0, TimelineKeyframeTracksContext_1.useTimelineKeyframeTracks)();
275
277
  const clientId = previewServerState.type === 'connected'
276
278
  ? previewServerState.clientId
@@ -385,37 +387,65 @@ const TimelineKeyframeControls = ({ fieldKey, propStatus, nodePath, fileName, ke
385
387
  return;
386
388
  }
387
389
  if (hasKeyframeAtCurrentFrame) {
388
- const deleteChanges = keyframeToggleTargets.flatMap((target) => {
390
+ const deleteTargets = keyframeToggleTargets.flatMap((target) => {
389
391
  const change = getDeleteChange(target);
390
- return change === null ? [] : [change];
392
+ return change === null ? [] : [{ target, change }];
391
393
  });
394
+ const singleDeleteTarget = deleteTargets[0];
395
+ const easingSelection = deleteTargets.length === 1 &&
396
+ singleDeleteTarget &&
397
+ isKeyframedStatus(singleDeleteTarget.target.propStatus) &&
398
+ (0, update_selected_easing_1.canEditEasingForInterpolationFunction)(singleDeleteTarget.target.propStatus.interpolationFunction)
399
+ ? (0, get_easing_selection_after_keyframe_delete_1.getEasingSelectionAfterKeyframeDelete)({
400
+ deletedSourceFrames: [singleDeleteTarget.target.sourceFrame],
401
+ keyframeDisplayOffset: singleDeleteTarget.target.keyframeDisplayOffset,
402
+ nodePathInfo: singleDeleteTarget.target.nodePathInfo,
403
+ propStatus: singleDeleteTarget.target.propStatus,
404
+ timelinePosition,
405
+ })
406
+ : null;
407
+ const deleteChanges = deleteTargets.map(({ change }) => change);
392
408
  await (0, call_delete_keyframe_1.callDeleteKeyframes)({
393
409
  sequenceKeyframes: deleteChanges.filter((change) => !hasEffectIndex(change)),
394
410
  effectKeyframes: deleteChanges.filter((change) => hasEffectIndex(change)),
395
411
  setPropStatuses,
396
412
  clientId,
397
413
  });
414
+ if (mode === 'timeline' && easingSelection !== null) {
415
+ selectItems([easingSelection], { reveal: true });
416
+ }
398
417
  return;
399
418
  }
400
419
  const addChanges = keyframeToggleTargets.flatMap((target) => {
401
420
  const change = getAddChange(target);
402
- return change === null ? [] : [change];
421
+ return change === null ? [] : [{ target, change }];
403
422
  });
404
423
  if (addChanges.length === 0) {
405
424
  return;
406
425
  }
426
+ const addChangeValues = addChanges.map(({ change }) => change);
407
427
  await (0, call_add_keyframe_1.callAddKeyframes)({
408
- sequenceKeyframes: addChanges.filter((change) => !hasEffectIndex(change)),
409
- effectKeyframes: addChanges.filter((change) => hasEffectIndex(change)),
428
+ sequenceKeyframes: addChangeValues.filter((change) => !hasEffectIndex(change)),
429
+ effectKeyframes: addChangeValues.filter((change) => hasEffectIndex(change)),
410
430
  setPropStatuses,
411
431
  clientId,
412
432
  });
433
+ if (mode === 'timeline') {
434
+ selectItems(addChanges.map(({ target }) => ({
435
+ type: 'keyframe',
436
+ nodePathInfo: target.nodePathInfo,
437
+ frame: target.sourceFrame + target.keyframeDisplayOffset,
438
+ })), { reveal: true });
439
+ }
413
440
  }, [
414
441
  canToggleKeyframe,
415
442
  clientId,
416
443
  hasKeyframeAtCurrentFrame,
417
444
  keyframeToggleTargets,
445
+ mode,
446
+ selectItems,
418
447
  setPropStatuses,
448
+ timelinePosition,
419
449
  ]);
420
450
  const previousDisabled = previousDisplayFrame === null;
421
451
  const nextDisabled = nextDisplayFrame === null;
@@ -1,5 +1,9 @@
1
1
  import React from 'react';
2
2
  import type { SequenceNodePathInfo } from '../../helpers/get-timeline-sequence-sort-key';
3
+ export declare const getTimelineKeyframeEasingLineStyle: (selected: boolean) => React.CSSProperties;
4
+ export declare const TimelineKeyframeEasingLineVisual: React.FC<{
5
+ readonly selected: boolean;
6
+ }>;
3
7
  export declare const TimelineKeyframeEasingLine: React.NamedExoticComponent<{
4
8
  readonly fromFrame: number;
5
9
  readonly toFrame: number;
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.TimelineKeyframeEasingLine = void 0;
36
+ exports.TimelineKeyframeEasingLine = exports.TimelineKeyframeEasingLineVisual = exports.getTimelineKeyframeEasingLineStyle = void 0;
37
37
  const jsx_runtime_1 = require("react/jsx-runtime");
38
38
  const studio_shared_1 = require("@remotion/studio-shared");
39
39
  const react_1 = __importStar(require("react"));
@@ -67,6 +67,16 @@ const easingLine = {
67
67
  top: '50%',
68
68
  transform: 'translateY(-50%)',
69
69
  };
70
+ const getTimelineKeyframeEasingLineStyle = (selected) => ({
71
+ ...easingLine,
72
+ outline: selected ? `1px solid ${colors_1.BLUE}` : undefined,
73
+ });
74
+ exports.getTimelineKeyframeEasingLineStyle = getTimelineKeyframeEasingLineStyle;
75
+ const TimelineKeyframeEasingLineVisual = ({ selected }) => {
76
+ const lineStyle = (0, react_1.useMemo)(() => (0, exports.getTimelineKeyframeEasingLineStyle)(selected), [selected]);
77
+ return jsx_runtime_1.jsx("div", { style: lineStyle });
78
+ };
79
+ exports.TimelineKeyframeEasingLineVisual = TimelineKeyframeEasingLineVisual;
70
80
  const TimelineKeyframeEasingLineUnmemoized = ({ fromFrame, toFrame, rowHeight, nodePathInfo, segmentIndex }) => {
71
81
  const buttonRef = (0, react_1.useRef)(null);
72
82
  const videoConfig = (0, remotion_1.useVideoConfig)();
@@ -176,10 +186,6 @@ const TimelineKeyframeEasingLineUnmemoized = ({ fromFrame, toFrame, rowHeight, n
176
186
  toFrame,
177
187
  videoConfig.durationInFrames,
178
188
  ]);
179
- const lineStyle = (0, react_1.useMemo)(() => ({
180
- ...easingLine,
181
- outline: selected ? `1px solid ${colors_1.BLUE}` : undefined,
182
- }), [selected]);
183
189
  const onPointerDown = (0, use_timeline_keyframe_drag_1.useTimelineEasingKeyframeDrag)({
184
190
  onSelect,
185
191
  selectable,
@@ -190,7 +196,7 @@ const TimelineKeyframeEasingLineUnmemoized = ({ fromFrame, toFrame, rowHeight, n
190
196
  return null;
191
197
  }
192
198
  return (jsx_runtime_1.jsxs(jsx_runtime_1.Fragment, { children: [
193
- jsx_runtime_1.jsx("button", { ref: buttonRef, [TimelineSelection_1.TIMELINE_MARQUEE_ITEM_ATTR]: true, type: "button", style: style, title: `Easing from frame ${fromFrame} to ${toFrame}`, "aria-label": `Select easing from frame ${fromFrame} to ${toFrame}`, onPointerDown: selectable ? onPointerDown : undefined, children: jsx_runtime_1.jsx("div", { style: lineStyle }) }), jsx_runtime_1.jsx(ContextMenu_1.ContextMenuForTarget, { triggerRef: buttonRef, values: contextMenuValues, onOpen: onOpenContextMenu })
199
+ jsx_runtime_1.jsx("button", { ref: buttonRef, [TimelineSelection_1.TIMELINE_MARQUEE_ITEM_ATTR]: true, type: "button", style: style, title: `Easing from frame ${fromFrame} to ${toFrame}`, "aria-label": `Select easing from frame ${fromFrame} to ${toFrame}`, onPointerDown: selectable ? onPointerDown : undefined, children: jsx_runtime_1.jsx(exports.TimelineKeyframeEasingLineVisual, { selected: selected }) }), jsx_runtime_1.jsx(ContextMenu_1.ContextMenuForTarget, { triggerRef: buttonRef, values: contextMenuValues, onOpen: onOpenContextMenu })
194
200
  ] }));
195
201
  };
196
202
  exports.TimelineKeyframeEasingLine = react_1.default.memo(TimelineKeyframeEasingLineUnmemoized);
@@ -11,6 +11,7 @@ const valuesEqual = (left, right) => {
11
11
  const TimelineKeyframedValue = ({ field, propStatus, sourceFrame, dragOverrideValue, onSave, onDragValueChange, onDragEnd, scaleLockNodePath, }) => {
12
12
  const computedValue = (0, react_1.useMemo)(() => {
13
13
  const raw = remotion_1.Internals.interpolateKeyframedStatus({
14
+ forceSpringAllowTail: false,
14
15
  frame: sourceFrame,
15
16
  status: propStatus,
16
17
  });
@@ -51,13 +51,15 @@ const TimelineRowChrome = ({ depth, eye, keyframeControls, arrow, children, styl
51
51
  e.stopPropagation();
52
52
  onSelect();
53
53
  }, [onSelect]);
54
- const highlightBackground = showSelectedBackground && (selected || containsSelection)
55
- ? TimelineSelection_1.TIMELINE_SELECTED_BACKGROUND
56
- : undefined;
54
+ const highlightBackground = (0, TimelineSelection_1.getTimelineRowHighlightBackground)({
55
+ showSelectedBackground,
56
+ selected,
57
+ containsSelection,
58
+ });
57
59
  const innerRowStyle = (0, react_1.useMemo)(() => ({
58
60
  ...rowBase,
59
61
  ...style,
60
- backgroundColor: outerHeight === undefined ? highlightBackground : undefined,
62
+ backgroundColor: outerHeight === null ? highlightBackground : undefined,
61
63
  }), [style, outerHeight, highlightBackground]);
62
64
  const outerStyle = (0, react_1.useMemo)(() => {
63
65
  if (outerHeight === null) {
@@ -8,7 +8,12 @@ export declare const TIMELINE_SELECTED_LABEL_TEXT = "black";
8
8
  export declare const TIMELINE_SELECTED_LABEL_HORIZONTAL_PADDING = 2;
9
9
  export declare const getTimelineSelectedLabelStyle: (selected: boolean, subcategory: boolean) => CSSProperties;
10
10
  export declare const getTimelineColor: (selected: boolean, subcategory: boolean) => "black" | "rgba(255, 255, 255, 0.8)";
11
- export declare const getTimelineSelectedTrackHighlightStyle: (timelineWidth: number) => CSSProperties;
11
+ export declare const getTimelineSelectedTrackHighlightStyle: (timelineWidth: number, backgroundColor?: string) => CSSProperties;
12
+ export declare const getTimelineRowHighlightBackground: ({ showSelectedBackground, selected, containsSelection, }: {
13
+ readonly showSelectedBackground: boolean;
14
+ readonly selected: boolean;
15
+ readonly containsSelection: boolean;
16
+ }) => string | undefined;
12
17
  export declare const TIMELINE_BACKGROUND = "#0F1113";
13
18
  export declare const TIMELINE_TICKS_BACKGROUND = "rgb(31,36,40)";
14
19
  export type TimelineSelection = {
@@ -204,4 +209,5 @@ export declare const useTimelineGuideSelection: (guideId: string) => {
204
209
  selectionItem: TimelineSelection;
205
210
  };
206
211
  export declare const useTimelineRowContainsSelection: (nodePathInfo: SequenceNodePathInfo | null) => boolean;
212
+ export declare const useTimelineRowHighlightBackground: (nodePathInfo: SequenceNodePathInfo | null) => string | undefined;
207
213
  export {};