@remotion/studio 4.0.483 → 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 (52) hide show
  1. package/dist/components/ColorPicker/ColorPicker.js +65 -3
  2. package/dist/components/ColorPicker/ColorPickerPopup.d.ts +1 -0
  3. package/dist/components/ColorPicker/ColorPickerPopup.js +3 -2
  4. package/dist/components/InspectorPanel/EasingInspector.js +184 -10
  5. package/dist/components/InspectorPanel/KeyframeEasingNavigator.d.ts +14 -0
  6. package/dist/components/InspectorPanel/KeyframeEasingNavigator.js +146 -0
  7. package/dist/components/InspectorPanel/KeyframeInspector.d.ts +1 -1
  8. package/dist/components/InspectorPanel/KeyframeInspector.js +228 -5
  9. package/dist/components/InspectorPanel/SequenceInspectorHeader.d.ts +17 -0
  10. package/dist/components/InspectorPanel/SequenceInspectorHeader.js +93 -0
  11. package/dist/components/InspectorPanel/SequenceSelectionInspector.js +4 -58
  12. package/dist/components/InspectorPanel/common.d.ts +12 -0
  13. package/dist/components/InspectorPanel/common.js +78 -1
  14. package/dist/components/InspectorPanel/easing-inspector-selection.d.ts +14 -0
  15. package/dist/components/InspectorPanel/easing-inspector-selection.js +18 -0
  16. package/dist/components/InspectorPanel/keyframe-easing-navigator-items.d.ts +24 -0
  17. package/dist/components/InspectorPanel/keyframe-easing-navigator-items.js +40 -0
  18. package/dist/components/InspectorPanel/keyframe-inspector-frame.d.ts +8 -0
  19. package/dist/components/InspectorPanel/keyframe-inspector-frame.js +14 -0
  20. package/dist/components/InspectorPanel/styles.d.ts +1 -0
  21. package/dist/components/InspectorPanel/styles.js +7 -1
  22. package/dist/components/InspectorSequenceSection.js +3 -9
  23. package/dist/components/Preview.js +1 -1
  24. package/dist/components/SelectedOutlineOverlay.d.ts +2 -0
  25. package/dist/components/SelectedOutlineOverlay.js +33 -12
  26. package/dist/components/Timeline/EasingEditorModal.js +9 -4
  27. package/dist/components/Timeline/TimelineDeleteKeybindings.js +11 -1
  28. package/dist/components/Timeline/TimelineEffectPropItem.js +82 -1
  29. package/dist/components/Timeline/TimelineKeyframeControls.js +37 -7
  30. package/dist/components/Timeline/TimelineKeyframeEasingLine.d.ts +4 -0
  31. package/dist/components/Timeline/TimelineKeyframeEasingLine.js +12 -6
  32. package/dist/components/Timeline/TimelineSequenceItem.js +48 -15
  33. package/dist/components/Timeline/TimelineSequenceName.d.ts +1 -0
  34. package/dist/components/Timeline/TimelineSequenceName.js +7 -6
  35. package/dist/components/Timeline/TimelineSequencePropItem.d.ts +1 -1
  36. package/dist/components/Timeline/TimelineSequencePropItem.js +80 -1
  37. package/dist/components/Timeline/TimelineSlider.js +17 -3
  38. package/dist/components/Timeline/delete-selected-timeline-item.d.ts +8 -2
  39. package/dist/components/Timeline/delete-selected-timeline-item.js +80 -1
  40. package/dist/components/Timeline/get-animation-item-selection-for-frame.d.ts +18 -0
  41. package/dist/components/Timeline/get-animation-item-selection-for-frame.js +49 -0
  42. package/dist/components/Timeline/get-easing-selection-after-keyframe-delete.d.ts +15 -0
  43. package/dist/components/Timeline/get-easing-selection-after-keyframe-delete.js +26 -0
  44. package/dist/components/Timeline/save-sequence-prop.js +2 -0
  45. package/dist/components/Timeline/update-selected-easing.d.ts +1 -0
  46. package/dist/components/Timeline/update-selected-easing.js +4 -3
  47. package/dist/esm/{chunk-fq0j774v.js → chunk-9erwh79m.js} +10745 -9242
  48. package/dist/esm/internals.mjs +10745 -9242
  49. package/dist/esm/previewEntry.mjs +10762 -9259
  50. package/dist/esm/renderEntry.mjs +1 -1
  51. package/dist/helpers/use-menu-structure.js +0 -14
  52. package/package.json +11 -11
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getNavigatorItemPlayheadFrame = exports.getKeyframeEasingNavigatorItems = void 0;
4
+ const getKeyframeEasingNavigatorItems = ({ includeEasings, keyframes, nodePathInfo, }) => {
5
+ return keyframes.flatMap((keyframe, index) => {
6
+ const keyframeItem = {
7
+ type: 'keyframe',
8
+ selection: {
9
+ type: 'keyframe',
10
+ nodePathInfo,
11
+ frame: keyframe.frame,
12
+ },
13
+ };
14
+ const nextKeyframe = keyframes[index + 1];
15
+ if (!includeEasings || !nextKeyframe) {
16
+ return [keyframeItem];
17
+ }
18
+ return [
19
+ keyframeItem,
20
+ {
21
+ type: 'easing',
22
+ selection: {
23
+ type: 'easing',
24
+ nodePathInfo,
25
+ fromFrame: keyframe.frame,
26
+ toFrame: nextKeyframe.frame,
27
+ segmentIndex: index,
28
+ },
29
+ },
30
+ ];
31
+ });
32
+ };
33
+ exports.getKeyframeEasingNavigatorItems = getKeyframeEasingNavigatorItems;
34
+ const getNavigatorItemPlayheadFrame = (item) => {
35
+ if (item.type === 'keyframe') {
36
+ return item.selection.frame;
37
+ }
38
+ return Math.round((item.selection.fromFrame + item.selection.toFrame) / 2);
39
+ };
40
+ exports.getNavigatorItemPlayheadFrame = getNavigatorItemPlayheadFrame;
@@ -0,0 +1,8 @@
1
+ export declare const clampInspectorKeyframeDisplayFrame: ({ durationInFrames, frame, }: {
2
+ readonly durationInFrames: number;
3
+ readonly frame: number;
4
+ }) => number;
5
+ export declare const getInspectorKeyframeSourceFrame: ({ displayFrame, keyframeDisplayOffset, }: {
6
+ readonly displayFrame: number;
7
+ readonly keyframeDisplayOffset: number;
8
+ }) => number;
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getInspectorKeyframeSourceFrame = exports.clampInspectorKeyframeDisplayFrame = void 0;
4
+ const clampInspectorKeyframeDisplayFrame = ({ durationInFrames, frame, }) => {
5
+ if (durationInFrames <= 0) {
6
+ return 0;
7
+ }
8
+ return Math.min(Math.max(Math.round(frame), 0), durationInFrames - 1);
9
+ };
10
+ exports.clampInspectorKeyframeDisplayFrame = clampInspectorKeyframeDisplayFrame;
11
+ const getInspectorKeyframeSourceFrame = ({ displayFrame, keyframeDisplayOffset, }) => {
12
+ return displayFrame - keyframeDisplayOffset;
13
+ };
14
+ exports.getInspectorKeyframeSourceFrame = getInspectorKeyframeSourceFrame;
@@ -5,6 +5,7 @@ export declare const defaultPropsSection: React.CSSProperties;
5
5
  export declare const visualControlsSection: React.CSSProperties;
6
6
  export declare const compositionSection: React.CSSProperties;
7
7
  export declare const inspectorSectionDivider: React.CSSProperties;
8
+ export declare const sequenceHeaderDivider: React.CSSProperties;
8
9
  export declare const sectionHeader: React.CSSProperties;
9
10
  export declare const sequenceHeader: React.CSSProperties;
10
11
  export declare const sequenceHeaderTitle: React.CSSProperties;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.keyframeEditorValue = exports.keyframeEditorLabel = exports.keyframeEditorRow = exports.detailValue = exports.detailLabel = exports.detailRow = exports.guideDetailsContainer = exports.detailsContainer = exports.centeredMessage = exports.selectedContainer = exports.resolveLinkStyle = exports.defaultPropsWarningMessages = exports.defaultPropsWarningContainer = exports.sectionHeaderEnd = exports.sectionHeaderStart = exports.sectionHeaderTitle = exports.sectionHeaderRow = exports.sequenceHeaderSubtitle = exports.sequenceHeaderTitle = exports.sequenceHeader = exports.sectionHeader = exports.inspectorSectionDivider = exports.compositionSection = exports.visualControlsSection = exports.defaultPropsSection = exports.scrollableContainer = exports.container = void 0;
3
+ exports.keyframeEditorValue = exports.keyframeEditorLabel = exports.keyframeEditorRow = exports.detailValue = exports.detailLabel = exports.detailRow = exports.guideDetailsContainer = exports.detailsContainer = exports.centeredMessage = exports.selectedContainer = exports.resolveLinkStyle = exports.defaultPropsWarningMessages = exports.defaultPropsWarningContainer = exports.sectionHeaderEnd = exports.sectionHeaderStart = exports.sectionHeaderTitle = exports.sectionHeaderRow = exports.sequenceHeaderSubtitle = exports.sequenceHeaderTitle = exports.sequenceHeader = exports.sectionHeader = exports.sequenceHeaderDivider = exports.inspectorSectionDivider = exports.compositionSection = exports.visualControlsSection = exports.defaultPropsSection = exports.scrollableContainer = exports.container = void 0;
4
4
  const colors_1 = require("../../helpers/colors");
5
5
  const InspectorPanelLayout_1 = require("../InspectorPanelLayout");
6
6
  exports.container = {
@@ -31,6 +31,12 @@ exports.compositionSection = {
31
31
  exports.inspectorSectionDivider = {
32
32
  borderBottom: `1px solid ${colors_1.LINE_COLOR}`,
33
33
  };
34
+ exports.sequenceHeaderDivider = {
35
+ backgroundColor: colors_1.LINE_COLOR,
36
+ flexShrink: 0,
37
+ height: 1,
38
+ margin: '4px 0',
39
+ };
34
40
  exports.sectionHeader = {
35
41
  color: colors_1.LIGHT_TEXT,
36
42
  fontSize: 12,
@@ -60,14 +60,8 @@ const emptyState = {
60
60
  lineHeight: 1.4,
61
61
  padding: '0 12px 8px',
62
62
  };
63
- const divider = {
64
- backgroundColor: colors_1.LINE_COLOR,
65
- flexShrink: 0,
66
- height: 1,
67
- margin: '4px 0',
68
- };
69
63
  const controlsEffectsDivider = {
70
- ...divider,
64
+ ...styles_1.sequenceHeaderDivider,
71
65
  margin: '8px 0 4px',
72
66
  };
73
67
  const effectsHeaderTitle = {
@@ -191,10 +185,10 @@ const InspectorSequenceSection = ({ sequence, validatedLocation, nodePathInfo, k
191
185
  };
192
186
  if (controlRows.length === 0 && !showEffectsSection) {
193
187
  return (jsx_runtime_1.jsxs("div", { style: container, children: [
194
- jsx_runtime_1.jsx("div", { style: divider }), jsx_runtime_1.jsx("div", { style: emptyState, children: "No schema" })
188
+ jsx_runtime_1.jsx("div", { style: styles_1.sequenceHeaderDivider }), jsx_runtime_1.jsx("div", { style: emptyState, children: "No schema" })
195
189
  ] }));
196
190
  }
197
191
  return (jsx_runtime_1.jsxs("div", { style: container, children: [
198
- jsx_runtime_1.jsx("div", { style: divider }), controlRows.length > 0 ? (jsx_runtime_1.jsx(TimelineSelection_1.TimelineSelectionOrderProvider, { items: controlSelectableItems, children: controlGroups.map((group, i) => (jsx_runtime_1.jsxs(react_1.default.Fragment, { children: [i === 0 ? null : jsx_runtime_1.jsx("div", { style: controlsEffectsDivider }), renderSectionHeader(group.label), group.rows.map(renderRow)] }, group.id))) })) : null, showEffectsSection ? (jsx_runtime_1.jsxs(jsx_runtime_1.Fragment, { children: [showControlsEffectsDivider ? (jsx_runtime_1.jsx("div", { style: controlsEffectsDivider })) : null, renderEffectsHeader(), effectRows.length === 0 ? (jsx_runtime_1.jsx("div", { style: emptyState, children: "None" })) : (jsx_runtime_1.jsx(TimelineSelection_1.TimelineSelectionOrderProvider, { items: effectSelectableItems, children: effectRows.map(renderRow) }))] })) : null] }));
192
+ jsx_runtime_1.jsx("div", { style: styles_1.sequenceHeaderDivider }), controlRows.length > 0 ? (jsx_runtime_1.jsx(TimelineSelection_1.TimelineSelectionOrderProvider, { items: controlSelectableItems, children: controlGroups.map((group, i) => (jsx_runtime_1.jsxs(react_1.default.Fragment, { children: [i === 0 ? null : jsx_runtime_1.jsx("div", { style: controlsEffectsDivider }), renderSectionHeader(group.label), group.rows.map(renderRow)] }, group.id))) })) : null, showEffectsSection ? (jsx_runtime_1.jsxs(jsx_runtime_1.Fragment, { children: [showControlsEffectsDivider ? (jsx_runtime_1.jsx("div", { style: controlsEffectsDivider })) : null, renderEffectsHeader(), effectRows.length === 0 ? (jsx_runtime_1.jsx("div", { style: emptyState, children: "None" })) : (jsx_runtime_1.jsx(TimelineSelection_1.TimelineSelectionOrderProvider, { items: effectSelectableItems, children: effectRows.map(renderRow) }))] })) : null] }));
199
193
  };
200
194
  exports.InspectorSequenceSection = InspectorSequenceSection;
@@ -122,7 +122,7 @@ const CompWhenItHasDimensions = ({ contentDimensions, canvasSize, canvasContent,
122
122
  return (jsx_runtime_1.jsx("div", { style: outer, children: jsx_runtime_1.jsx(RenderPreview_1.RenderPreview, { path: canvasContent.displayName, assetMetadata: assetMetadata, getBlob: canvasContent.getBlob }) }));
123
123
  }
124
124
  return (jsx_runtime_1.jsxs("div", { style: outer, children: [
125
- jsx_runtime_1.jsx(PortalContainer, { contentDimensions: contentDimensions, scale: scale, xCorrection: xCorrection, yCorrection: yCorrection }), jsx_runtime_1.jsx(SelectedOutlineOverlay_1.SelectedOutlineOverlay, { scale: scale })
125
+ jsx_runtime_1.jsx(PortalContainer, { contentDimensions: contentDimensions, scale: scale, xCorrection: xCorrection, yCorrection: yCorrection }), jsx_runtime_1.jsx(SelectedOutlineOverlay_1.SelectedOutlineOverlay, { scale: scale, translationX: previewSize.translation.x, translationY: previewSize.translation.y })
126
126
  ] }));
127
127
  };
128
128
  const PortalContainer = ({ scale, xCorrection, yCorrection, contentDimensions }) => {
@@ -11,4 +11,6 @@ export declare const orderOutlinesForRendering: ({ outlines, targetsByKey, }: {
11
11
  }) => readonly SelectedOutline[];
12
12
  export declare const SelectedOutlineOverlay: React.FC<{
13
13
  readonly scale: number;
14
+ readonly translationX: number;
15
+ readonly translationY: number;
14
16
  }>;
@@ -105,7 +105,7 @@ const orderOutlinesForRendering = ({ outlines, targetsByKey, }) => {
105
105
  });
106
106
  };
107
107
  exports.orderOutlinesForRendering = orderOutlinesForRendering;
108
- const SelectedOutlineOverlay = ({ scale }) => {
108
+ const SelectedOutlineOverlay = ({ scale, translationX, translationY }) => {
109
109
  const { selectedItems, selectItem } = (0, TimelineSelection_1.useTimelineSelection)();
110
110
  const { sequences } = (0, react_1.useContext)(remotion_1.Internals.SequenceManager);
111
111
  const { propStatuses } = (0, react_1.useContext)(remotion_1.Internals.VisualModePropStatusesContext);
@@ -613,28 +613,49 @@ const SelectedOutlineOverlay = ({ scale }) => {
613
613
  }
614
614
  };
615
615
  }, [keybindings, onArrowKeyDown, onArrowKeyUp, saveKeyboardNudgeSession]);
616
- (0, react_1.useEffect)(() => {
617
- if (outlineTargets.length === 0) {
616
+ const updateOutlines = (0, react_1.useCallback)(() => {
617
+ if (overlayRef.current === null || outlineTargets.length === 0) {
618
618
  setOutlines((prevOutlines) => prevOutlines.length === 0 ? prevOutlines : []);
619
619
  return;
620
620
  }
621
+ const nextOutlines = (0, selected_outline_measurement_1.measureOutlines)(overlayRef.current, outlineTargets);
622
+ setOutlines((prevOutlines) => (0, selected_outline_measurement_1.outlinesAreEqual)(prevOutlines, nextOutlines)
623
+ ? prevOutlines
624
+ : nextOutlines);
625
+ }, [outlineTargets]);
626
+ (0, react_1.useLayoutEffect)(() => {
627
+ updateOutlines();
628
+ }, [outlineTargets, scale, translationX, translationY, updateOutlines]);
629
+ (0, react_1.useLayoutEffect)(() => {
630
+ if (outlineTargets.length === 0 || typeof ResizeObserver === 'undefined') {
631
+ return;
632
+ }
621
633
  let animationFrame = null;
622
- const updateOutlines = () => {
623
- if (overlayRef.current) {
624
- const nextOutlines = (0, selected_outline_measurement_1.measureOutlines)(overlayRef.current, outlineTargets);
625
- setOutlines((prevOutlines) => (0, selected_outline_measurement_1.outlinesAreEqual)(prevOutlines, nextOutlines)
626
- ? prevOutlines
627
- : nextOutlines);
634
+ const scheduleUpdate = () => {
635
+ if (animationFrame !== null) {
636
+ return;
628
637
  }
629
- animationFrame = requestAnimationFrame(updateOutlines);
638
+ animationFrame = requestAnimationFrame(() => {
639
+ animationFrame = null;
640
+ updateOutlines();
641
+ });
630
642
  };
631
- updateOutlines();
643
+ const resizeObserver = new ResizeObserver(scheduleUpdate);
644
+ if (overlayRef.current !== null) {
645
+ resizeObserver.observe(overlayRef.current);
646
+ }
647
+ for (const target of outlineTargets) {
648
+ if (target.ref.current !== null) {
649
+ resizeObserver.observe(target.ref.current);
650
+ }
651
+ }
632
652
  return () => {
633
653
  if (animationFrame !== null) {
634
654
  cancelAnimationFrame(animationFrame);
635
655
  }
656
+ resizeObserver.disconnect();
636
657
  };
637
- }, [outlineTargets]);
658
+ }, [outlineTargets, updateOutlines]);
638
659
  if (outlineTargets.length === 0) {
639
660
  return null;
640
661
  }
@@ -80,8 +80,8 @@ const inlineContainer = {
80
80
  const segmentedControlWrapper = {
81
81
  display: 'flex',
82
82
  justifyContent: 'flex-start',
83
+ marginTop: 8,
83
84
  padding: `0 ${InspectorPanelLayout_1.INSPECTOR_PANEL_HORIZONTAL_PADDING}px`,
84
- marginBottom: 10,
85
85
  };
86
86
  const presetButtonsWrapper = {
87
87
  display: 'flex',
@@ -91,6 +91,10 @@ const presetButtonsWrapper = {
91
91
  marginBottom: 8,
92
92
  padding: `0 ${InspectorPanelLayout_1.INSPECTOR_PANEL_HORIZONTAL_PADDING}px`,
93
93
  };
94
+ const inspectorPresetButtonsWrapper = {
95
+ ...presetButtonsWrapper,
96
+ padding: `8px ${InspectorPanelLayout_1.INSPECTOR_PANEL_HORIZONTAL_PADDING}px 0`,
97
+ };
94
98
  const presetButtonBase = {
95
99
  alignItems: 'center',
96
100
  backgroundColor: colors_1.INPUT_BACKGROUND,
@@ -721,10 +725,11 @@ const EasingEditor = ({ state, renderHeader }) => {
721
725
  gridTemplateColumns: 'repeat(2, minmax(0, 1fr))',
722
726
  padding: '0 12px 12px',
723
727
  }), []);
724
- 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: [
725
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: [
726
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) })
727
- ] }), jsx_runtime_1.jsxs("div", { style: coordinatesGrid, children: [
732
+ ] }), modeSwitcher, jsx_runtime_1.jsxs("div", { style: coordinatesGrid, children: [
728
733
  jsx_runtime_1.jsxs("div", { style: coordinateRow, children: [
729
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 }) })
730
735
  ] }), jsx_runtime_1.jsxs("div", { style: coordinateRow, children: [
@@ -738,7 +743,7 @@ const EasingEditor = ({ state, renderHeader }) => {
738
743
  ] })) : (jsx_runtime_1.jsxs(jsx_runtime_1.Fragment, { children: [
739
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: [
740
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" })
741
- ] }), jsx_runtime_1.jsxs("div", { style: coordinatesGrid, children: [
746
+ ] }), modeSwitcher, jsx_runtime_1.jsxs("div", { style: coordinatesGrid, children: [
742
747
  jsx_runtime_1.jsxs("div", { style: coordinateRow, children: [
743
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 }) })
744
749
  ] }), jsx_runtime_1.jsxs("div", { style: coordinateRow, children: [
@@ -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
  };
@@ -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 }));
@@ -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);