@remotion/studio 4.0.477 → 4.0.479

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 (191) hide show
  1. package/dist/components/AssetSelector.js +8 -14
  2. package/dist/components/Button.d.ts +1 -0
  3. package/dist/components/Button.js +8 -5
  4. package/dist/components/CanvasIfSizeIsAvailable.js +2 -6
  5. package/dist/components/CompactExplanation.d.ts +12 -0
  6. package/dist/components/CompactExplanation.js +52 -0
  7. package/dist/components/CompositionSelector.js +2 -4
  8. package/dist/components/CurrentAsset.d.ts +3 -1
  9. package/dist/components/CurrentAsset.js +42 -34
  10. package/dist/components/CurrentComposition.d.ts +1 -1
  11. package/dist/components/CurrentComposition.js +38 -31
  12. package/dist/components/DefaultPropsEditor.d.ts +12 -2
  13. package/dist/components/DefaultPropsEditor.js +2 -2
  14. package/dist/components/EditorGuides/Guide.js +37 -9
  15. package/dist/components/EditorRuler/Ruler.js +4 -14
  16. package/dist/components/EditorRuler/index.js +9 -4
  17. package/dist/components/EffectPickerModal.d.ts +5 -0
  18. package/dist/components/EffectPickerModal.js +179 -0
  19. package/dist/components/InlineAction.js +1 -0
  20. package/dist/components/InspectorInfoHeader.d.ts +11 -0
  21. package/dist/components/InspectorInfoHeader.js +55 -0
  22. package/dist/components/InspectorPanel/DefaultInspector.d.ts +8 -0
  23. package/dist/components/InspectorPanel/DefaultInspector.js +93 -0
  24. package/dist/components/InspectorPanel/EasingInspector.d.ts +5 -0
  25. package/dist/components/InspectorPanel/EasingInspector.js +41 -0
  26. package/dist/components/InspectorPanel/GuideInspector.d.ts +7 -0
  27. package/dist/components/InspectorPanel/GuideInspector.js +55 -0
  28. package/dist/components/InspectorPanel/KeyframeInspector.d.ts +7 -0
  29. package/dist/components/InspectorPanel/KeyframeInspector.js +109 -0
  30. package/dist/components/InspectorPanel/SelectedInspector.d.ts +5 -0
  31. package/dist/components/InspectorPanel/SelectedInspector.js +26 -0
  32. package/dist/components/InspectorPanel/SequenceSelectionInspector.d.ts +5 -0
  33. package/dist/components/InspectorPanel/SequenceSelectionInspector.js +116 -0
  34. package/dist/components/InspectorPanel/common.d.ts +15 -0
  35. package/dist/components/InspectorPanel/common.js +18 -0
  36. package/dist/components/InspectorPanel/inspector-selection.d.ts +10 -0
  37. package/dist/components/InspectorPanel/inspector-selection.js +34 -0
  38. package/dist/components/InspectorPanel/styles.d.ts +27 -0
  39. package/dist/components/InspectorPanel/styles.js +178 -0
  40. package/dist/components/InspectorPanel/use-track-for-selection.d.ts +2 -0
  41. package/dist/components/InspectorPanel/use-track-for-selection.js +22 -0
  42. package/dist/components/InspectorPanel.d.ts +8 -0
  43. package/dist/components/InspectorPanel.js +26 -0
  44. package/dist/components/InspectorPanelLayout.d.ts +1 -0
  45. package/dist/components/InspectorPanelLayout.js +4 -0
  46. package/dist/components/InspectorSequenceSection.d.ts +16 -0
  47. package/dist/components/InspectorSequenceSection.js +147 -0
  48. package/dist/components/InspectorSourceLocation.d.ts +7 -0
  49. package/dist/components/InspectorSourceLocation.js +71 -0
  50. package/dist/components/MenuToolbar.d.ts +1 -0
  51. package/dist/components/MenuToolbar.js +4 -1
  52. package/dist/components/Modals.js +3 -3
  53. package/dist/components/NewComposition/InputDragger.js +1 -1
  54. package/dist/components/NewComposition/RemInput.d.ts +1 -0
  55. package/dist/components/NewComposition/RemInput.js +8 -2
  56. package/dist/components/NewComposition/RemTextarea.d.ts +1 -0
  57. package/dist/components/NewComposition/RemTextarea.js +8 -2
  58. package/dist/components/NewComposition/ValidationMessage.d.ts +3 -0
  59. package/dist/components/NewComposition/ValidationMessage.js +16 -5
  60. package/dist/components/OptionsPanel.d.ts +1 -1
  61. package/dist/components/OptionsPanel.js +8 -17
  62. package/dist/components/QuickSwitcher/QuickSwitcherContent.js +2 -7
  63. package/dist/components/QuickSwitcher/QuickSwitcherResult.js +3 -10
  64. package/dist/components/QuickSwitcher/shared.d.ts +4 -0
  65. package/dist/components/QuickSwitcher/shared.js +24 -0
  66. package/dist/components/RenderModal/DataEditor.d.ts +29 -2
  67. package/dist/components/RenderModal/DataEditor.js +107 -56
  68. package/dist/components/RenderModal/RenderModalJSONPropsEditor.d.ts +1 -0
  69. package/dist/components/RenderModal/RenderModalJSONPropsEditor.js +25 -7
  70. package/dist/components/RenderModal/SchemaEditor/Fieldset.d.ts +2 -1
  71. package/dist/components/RenderModal/SchemaEditor/Fieldset.js +10 -5
  72. package/dist/components/RenderModal/SchemaEditor/SchemaEditor.d.ts +4 -0
  73. package/dist/components/RenderModal/SchemaEditor/SchemaEditor.js +27 -4
  74. package/dist/components/RenderModal/SchemaEditor/SchemaErrorMessages.d.ts +12 -4
  75. package/dist/components/RenderModal/SchemaEditor/SchemaErrorMessages.js +23 -9
  76. package/dist/components/RenderModal/SchemaEditor/SchemaLabel.js +3 -3
  77. package/dist/components/RenderModal/SchemaEditor/SchemaSeparationLine.js +8 -1
  78. package/dist/components/RenderModal/SchemaEditor/ZodColorEditor.js +1 -1
  79. package/dist/components/RenderModal/SchemaEditor/ZodDateEditor.js +1 -1
  80. package/dist/components/RenderModal/SchemaEditor/ZodDiscriminatedUnionEditor.js +1 -1
  81. package/dist/components/RenderModal/SchemaEditor/ZodEnumEditor.js +1 -1
  82. package/dist/components/RenderModal/SchemaEditor/ZodErrorMessages.d.ts +1 -0
  83. package/dist/components/RenderModal/SchemaEditor/ZodErrorMessages.js +23 -6
  84. package/dist/components/RenderModal/SchemaEditor/ZodFieldValidation.js +2 -2
  85. package/dist/components/RenderModal/SchemaEditor/ZodNonEditableValue.js +2 -1
  86. package/dist/components/RenderModal/SchemaEditor/ZodNumberEditor.js +1 -1
  87. package/dist/components/RenderModal/SchemaEditor/ZodObjectEditor.js +9 -11
  88. package/dist/components/RenderModal/SchemaEditor/ZodOrNullishEditor.js +1 -1
  89. package/dist/components/RenderModal/SchemaEditor/ZodStaticFileEditor.js +1 -1
  90. package/dist/components/RenderModal/SchemaEditor/ZodStringEditor.js +1 -1
  91. package/dist/components/RenderModal/SchemaEditor/ZodTextareaEditor.js +1 -1
  92. package/dist/components/RenderModal/WarningIndicatorButton.d.ts +1 -0
  93. package/dist/components/RenderModal/WarningIndicatorButton.js +17 -4
  94. package/dist/components/RenderModal/get-render-modal-warnings.d.ts +2 -1
  95. package/dist/components/RenderModal/get-render-modal-warnings.js +6 -3
  96. package/dist/components/RendersTab.js +1 -1
  97. package/dist/components/SegmentedControl.d.ts +3 -0
  98. package/dist/components/SegmentedControl.js +11 -5
  99. package/dist/components/SelectedOutlineElement.js +135 -31
  100. package/dist/components/SelectedOutlineOverlay.d.ts +1 -1
  101. package/dist/components/SelectedOutlineOverlay.js +45 -29
  102. package/dist/components/SelectedOutlineUvControls.d.ts +9 -0
  103. package/dist/components/SelectedOutlineUvControls.js +64 -10
  104. package/dist/components/Tabs/index.js +4 -4
  105. package/dist/components/Timeline/EasingEditorModal.d.ts +5 -4
  106. package/dist/components/Timeline/EasingEditorModal.js +597 -124
  107. package/dist/components/Timeline/KeyframeSettingsModal.d.ts +2 -2
  108. package/dist/components/Timeline/SubscribeToNodePaths.d.ts +2 -2
  109. package/dist/components/Timeline/TimelineClipboardKeybindings.d.ts +15 -3
  110. package/dist/components/Timeline/TimelineClipboardKeybindings.js +85 -1
  111. package/dist/components/Timeline/TimelineDeleteKeybindings.js +10 -3
  112. package/dist/components/Timeline/TimelineEffectItem.d.ts +2 -2
  113. package/dist/components/Timeline/TimelineEffectPropItem.d.ts +8 -0
  114. package/dist/components/Timeline/TimelineEffectPropItem.js +24 -20
  115. package/dist/components/Timeline/TimelineExpandedRow.d.ts +5 -2
  116. package/dist/components/Timeline/TimelineExpandedRow.js +4 -4
  117. package/dist/components/Timeline/TimelineExpandedSection.d.ts +1 -1
  118. package/dist/components/Timeline/TimelineExpandedSection.js +5 -19
  119. package/dist/components/Timeline/TimelineKeyframeControls.d.ts +8 -2
  120. package/dist/components/Timeline/TimelineKeyframeControls.js +24 -3
  121. package/dist/components/Timeline/TimelineKeyframeEasingLine.js +2 -47
  122. package/dist/components/Timeline/TimelineKeyframedValue.d.ts +1 -1
  123. package/dist/components/Timeline/TimelineKeyframedValue.js +8 -10
  124. package/dist/components/Timeline/TimelineNumberField.js +5 -11
  125. package/dist/components/Timeline/TimelineRotationField.js +5 -6
  126. package/dist/components/Timeline/TimelineScaleField.js +4 -8
  127. package/dist/components/Timeline/TimelineSelection.d.ts +6 -0
  128. package/dist/components/Timeline/TimelineSelection.js +109 -14
  129. package/dist/components/Timeline/TimelineSequence.js +22 -14
  130. package/dist/components/Timeline/TimelineSequenceItem.js +12 -67
  131. package/dist/components/Timeline/TimelineSequencePropItem.d.ts +12 -2
  132. package/dist/components/Timeline/TimelineSequencePropItem.js +56 -52
  133. package/dist/components/Timeline/TimelineTransformOriginField.js +4 -5
  134. package/dist/components/Timeline/TimelineTranslateField.js +4 -5
  135. package/dist/components/Timeline/TimelineUvCoordinateField.js +4 -4
  136. package/dist/components/Timeline/TimelineVideoInfo.d.ts +1 -0
  137. package/dist/components/Timeline/TimelineVideoInfo.js +93 -8
  138. package/dist/components/Timeline/call-add-keyframe.d.ts +4 -4
  139. package/dist/components/Timeline/call-delete-keyframe.d.ts +4 -4
  140. package/dist/components/Timeline/call-move-keyframe.d.ts +2 -2
  141. package/dist/components/Timeline/call-update-keyframe-settings.d.ts +3 -3
  142. package/dist/components/Timeline/delete-selected-timeline-item.d.ts +2 -1
  143. package/dist/components/Timeline/delete-selected-timeline-item.js +27 -1
  144. package/dist/components/Timeline/duplicate-selected-timeline-item.d.ts +7 -0
  145. package/dist/components/Timeline/duplicate-selected-timeline-item.js +32 -3
  146. package/dist/components/Timeline/parse-keyframe-field-from-node-path.js +2 -2
  147. package/dist/components/Timeline/reset-selected-timeline-props.d.ts +3 -3
  148. package/dist/components/Timeline/reset-selected-timeline-props.js +19 -5
  149. package/dist/components/Timeline/save-effect-prop.d.ts +2 -2
  150. package/dist/components/Timeline/save-sequence-prop.d.ts +2 -2
  151. package/dist/components/Timeline/sequence-props-subscription-store.d.ts +3 -3
  152. package/dist/components/Timeline/timeline-field-display-utils.d.ts +5 -0
  153. package/dist/components/Timeline/timeline-field-display-utils.js +244 -0
  154. package/dist/components/Timeline/timeline-video-filmstrip-times.d.ts +17 -0
  155. package/dist/components/Timeline/timeline-video-filmstrip-times.js +22 -0
  156. package/dist/components/Timeline/update-selected-easing.d.ts +47 -4
  157. package/dist/components/Timeline/update-selected-easing.js +40 -9
  158. package/dist/components/Timeline/use-sequence-freeze-frame-menu-item.d.ts +13 -0
  159. package/dist/components/Timeline/use-sequence-freeze-frame-menu-item.js +73 -0
  160. package/dist/components/Timeline/use-sequence-props-subscription.d.ts +3 -3
  161. package/dist/components/Timeline/use-timeline-expanded-tree.d.ts +11 -0
  162. package/dist/components/Timeline/use-timeline-expanded-tree.js +33 -0
  163. package/dist/components/VisualControls/VisualControlsContent.d.ts +1 -1
  164. package/dist/components/VisualControls/VisualControlsContent.js +4 -5
  165. package/dist/components/effect-drag-and-drop.d.ts +10 -0
  166. package/dist/components/effect-drag-and-drop.js +17 -8
  167. package/dist/components/effect-picker-search.d.ts +5 -0
  168. package/dist/components/effect-picker-search.js +77 -0
  169. package/dist/components/import-assets.d.ts +11 -2
  170. package/dist/components/import-assets.js +61 -6
  171. package/dist/components/selected-outline-drag.d.ts +44 -2
  172. package/dist/components/selected-outline-drag.js +74 -1
  173. package/dist/components/selected-outline-types.d.ts +7 -7
  174. package/dist/components/selected-outline-uv.d.ts +4 -3
  175. package/dist/components/selected-outline-uv.js +6 -2
  176. package/dist/error-overlay/remotion-overlay/Overlay.js +3 -0
  177. package/dist/esm/{chunk-t8fjnw2d.js → chunk-fge2mq5p.js} +17004 -13432
  178. package/dist/esm/internals.mjs +17004 -13432
  179. package/dist/esm/previewEntry.mjs +26617 -23041
  180. package/dist/esm/renderEntry.mjs +1 -1
  181. package/dist/helpers/editor-guide-selection.js +1 -1
  182. package/dist/helpers/get-preview-file-type.js +1 -1
  183. package/dist/helpers/render-codec-label.d.ts +2 -0
  184. package/dist/helpers/render-codec-label.js +49 -0
  185. package/dist/helpers/ruler-canvas-size.d.ts +5 -0
  186. package/dist/helpers/ruler-canvas-size.js +17 -0
  187. package/dist/helpers/timeline-layout.d.ts +4 -4
  188. package/dist/helpers/use-media-metadata.d.ts +8 -2
  189. package/dist/helpers/use-media-metadata.js +17 -4
  190. package/dist/state/modals.d.ts +9 -4
  191. package/package.json +12 -12
@@ -1,38 +1,105 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.EasingEditorModal = void 0;
3
+ exports.EasingEditor = void 0;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const studio_shared_1 = require("@remotion/studio-shared");
5
6
  const react_1 = require("react");
6
7
  const remotion_1 = require("remotion");
7
8
  const client_id_1 = require("../../helpers/client-id");
8
9
  const colors_1 = require("../../helpers/colors");
9
- const modals_1 = require("../../state/modals");
10
- const Button_1 = require("../Button");
11
- const layout_1 = require("../layout");
12
- const ModalButton_1 = require("../ModalButton");
13
- const ModalFooter_1 = require("../ModalFooter");
14
- const ModalHeader_1 = require("../ModalHeader");
15
- const DismissableModal_1 = require("../NewComposition/DismissableModal");
10
+ const Checkbox_1 = require("../Checkbox");
11
+ const InspectorPanelLayout_1 = require("../InspectorPanelLayout");
16
12
  const InputDragger_1 = require("../NewComposition/InputDragger");
13
+ const SegmentedControl_1 = require("../SegmentedControl");
14
+ const timeline_field_display_utils_1 = require("./timeline-field-display-utils");
17
15
  const update_selected_easing_1 = require("./update-selected-easing");
18
16
  const SVG_WIDTH = 560;
19
17
  const SVG_HEIGHT = 320;
20
18
  const PLOT_LEFT = 42;
21
- const PLOT_TOP = 8;
19
+ const PLOT_TOP = 0;
22
20
  const PLOT_WIDTH = 500;
23
- const PLOT_HEIGHT = 304;
21
+ const PLOT_HEIGHT = SVG_HEIGHT;
24
22
  const Y_MIN = -2;
25
23
  const Y_MAX = 3;
24
+ const LINEAR_EASING = { type: 'linear' };
26
25
  const LINEAR_BEZIER = [0.25, 0.25, 0.75, 0.75];
27
- const container = {
28
- width: 600,
26
+ const DEFAULT_EASING_GRAPH_LABELS = {
27
+ start: '0',
28
+ end: '1',
29
29
  };
30
- const coordinatesGrid = {
30
+ const EASING_GRAPH_GUIDE_COLOR = 'rgba(255, 255, 255, 0.12)';
31
+ const EASING_GRAPH_LABEL_FONT_SIZE = 13;
32
+ const EASING_GRAPH_LABEL_HEIGHT = 20;
33
+ const EASING_GRAPH_LABEL_HORIZONTAL_PADDING = 4;
34
+ const EASING_GRAPH_LABEL_MAX_WIDTH = PLOT_WIDTH / 2;
35
+ const PRESET_PREVIEW_WIDTH = 48;
36
+ const PRESET_PREVIEW_HEIGHT = 30;
37
+ const PRESET_PREVIEW_PADDING = 5;
38
+ const PRESET_PREVIEW_Y_MIN = -0.35;
39
+ const PRESET_PREVIEW_Y_MAX = 1.45;
40
+ const DEFAULT_SPRING_EASING = {
41
+ type: 'spring',
42
+ damping: 10,
43
+ mass: 1,
44
+ overshootClamping: false,
45
+ stiffness: 100,
46
+ };
47
+ const EDITOR_EASING_PRESETS = [
48
+ {
49
+ id: 'linear',
50
+ label: 'Linear',
51
+ easing: studio_shared_1.LINEAR_KEYFRAME_EASING,
52
+ },
53
+ ...studio_shared_1.KEYFRAME_EASING_PRESETS,
54
+ ];
55
+ const SPRING_LIMITS = {
56
+ damping: { min: 1, max: 200, step: 1 },
57
+ mass: { min: 0.1, max: 20, step: 0.1 },
58
+ stiffness: { min: 1, max: 1000, step: 1 },
59
+ };
60
+ const SPRING_DECIMAL_PLACES = {
61
+ damping: 0,
62
+ mass: 2,
63
+ stiffness: 0,
64
+ };
65
+ const inlineContainer = {
66
+ width: '100%',
67
+ minWidth: 0,
68
+ };
69
+ const segmentedControlWrapper = {
70
+ display: 'flex',
71
+ justifyContent: 'flex-start',
72
+ padding: `0 ${InspectorPanelLayout_1.INSPECTOR_PANEL_HORIZONTAL_PADDING}px`,
73
+ marginBottom: 10,
74
+ };
75
+ const presetButtonsWrapper = {
76
+ display: 'flex',
77
+ flexWrap: 'wrap',
78
+ gap: 6,
79
+ justifyContent: 'flex-start',
80
+ marginBottom: 8,
81
+ padding: `0 ${InspectorPanelLayout_1.INSPECTOR_PANEL_HORIZONTAL_PADDING}px`,
82
+ };
83
+ const presetButtonBase = {
84
+ alignItems: 'center',
85
+ backgroundColor: colors_1.INPUT_BACKGROUND,
86
+ border: `1px solid ${colors_1.INPUT_BORDER_COLOR_HOVERED}`,
87
+ borderRadius: 4,
88
+ display: 'inline-flex',
89
+ height: 34,
90
+ justifyContent: 'center',
91
+ padding: 0,
92
+ width: 52,
93
+ };
94
+ const presetPreviewSvgStyle = {
95
+ display: 'block',
96
+ height: PRESET_PREVIEW_HEIGHT,
97
+ width: PRESET_PREVIEW_WIDTH,
98
+ };
99
+ const coordinatesGridBase = {
31
100
  display: 'grid',
32
- gridTemplateColumns: 'repeat(4, minmax(0, 1fr))',
33
101
  gap: 10,
34
102
  marginTop: 12,
35
- padding: '0 16px 16px',
36
103
  };
37
104
  const coordinateRow = {
38
105
  gap: 4,
@@ -47,18 +114,22 @@ const coordinateInputWrapper = {
47
114
  display: 'flex',
48
115
  alignItems: 'center',
49
116
  };
117
+ const checkboxWrapper = {
118
+ ...coordinateInputWrapper,
119
+ paddingLeft: 6,
120
+ };
50
121
  const numberInputStyle = {
51
122
  backgroundColor: colors_1.INPUT_BACKGROUND,
52
123
  borderRadius: 4,
53
124
  width: '100%',
54
125
  };
55
126
  const svgStyle = {
127
+ aspectRatio: `${SVG_WIDTH} / ${SVG_HEIGHT}`,
56
128
  display: 'block',
129
+ height: 'auto',
130
+ overflow: 'visible',
57
131
  width: '100%',
58
132
  };
59
- const hiddenSubmit = {
60
- display: 'none',
61
- };
62
133
  const clamp = (value, min, max) => {
63
134
  return Math.min(max, Math.max(min, value));
64
135
  };
@@ -68,16 +139,60 @@ const sanitizeBezier = (bezier) => [
68
139
  clamp(bezier[2], 0, 1),
69
140
  clamp(bezier[3], Y_MIN, Y_MAX),
70
141
  ];
142
+ const isSpringEasing = (easing) => {
143
+ return easing.type === 'spring';
144
+ };
145
+ const isBezierEasing = (easing) => {
146
+ return easing.type === 'bezier';
147
+ };
71
148
  const easingToBezier = (easing) => {
72
- return easing === 'linear' ? LINEAR_BEZIER : sanitizeBezier(easing);
149
+ return isBezierEasing(easing)
150
+ ? sanitizeBezier([easing.x1, easing.y1, easing.x2, easing.y2])
151
+ : LINEAR_BEZIER;
152
+ };
153
+ const easingToSpring = (easing) => {
154
+ return isSpringEasing(easing)
155
+ ? sanitizeSpring(easing)
156
+ : DEFAULT_SPRING_EASING;
157
+ };
158
+ const easingToMode = (easing) => {
159
+ return isSpringEasing(easing) ? 'spring' : 'bezier';
73
160
  };
74
- const roundCoordinate = (value) => Math.round(value * 10000) / 10000;
161
+ const roundToDecimalPlaces = (value, decimalPlaces) => {
162
+ const factor = 10 ** decimalPlaces;
163
+ const rounded = Math.round(value * factor) / factor;
164
+ return Object.is(rounded, -0) ? 0 : rounded;
165
+ };
166
+ const roundCoordinate = (value) => roundToDecimalPlaces(value, 4);
75
167
  const serializeBezier = (bezier) => {
76
168
  const rounded = sanitizeBezier(bezier).map(roundCoordinate);
77
169
  if (rounded[0] === rounded[1] && rounded[2] === rounded[3]) {
78
- return 'linear';
170
+ return LINEAR_EASING;
171
+ }
172
+ return {
173
+ type: 'bezier',
174
+ x1: rounded[0],
175
+ y1: rounded[1],
176
+ x2: rounded[2],
177
+ y2: rounded[3],
178
+ };
179
+ };
180
+ const sanitizeSpringValue = (value, key, fallback) => {
181
+ const limits = SPRING_LIMITS[key];
182
+ if (!Number.isFinite(value)) {
183
+ return fallback;
79
184
  }
80
- return rounded;
185
+ return roundToDecimalPlaces(clamp(value, limits.min, limits.max), SPRING_DECIMAL_PLACES[key]);
186
+ };
187
+ const sanitizeSpring = (spring) => ({
188
+ type: 'spring',
189
+ damping: sanitizeSpringValue(spring.damping, 'damping', DEFAULT_SPRING_EASING.damping),
190
+ mass: sanitizeSpringValue(spring.mass, 'mass', DEFAULT_SPRING_EASING.mass),
191
+ overshootClamping: spring.overshootClamping,
192
+ stiffness: sanitizeSpringValue(spring.stiffness, 'stiffness', DEFAULT_SPRING_EASING.stiffness),
193
+ });
194
+ const serializeSpring = (spring) => {
195
+ return sanitizeSpring(spring);
81
196
  };
82
197
  const formatNumber = (value) => {
83
198
  const numericValue = Number(value);
@@ -86,44 +201,384 @@ const formatNumber = (value) => {
86
201
  }
87
202
  return String(roundCoordinate(numericValue));
88
203
  };
204
+ const formatNumberWithDecimalPlaces = (decimalPlaces) => (value) => {
205
+ const numericValue = Number(value);
206
+ if (!Number.isFinite(numericValue)) {
207
+ return String(value);
208
+ }
209
+ return String(roundToDecimalPlaces(numericValue, decimalPlaces));
210
+ };
211
+ const springFormatters = {
212
+ damping: formatNumberWithDecimalPlaces(SPRING_DECIMAL_PLACES.damping),
213
+ mass: formatNumberWithDecimalPlaces(SPRING_DECIMAL_PLACES.mass),
214
+ stiffness: formatNumberWithDecimalPlaces(SPRING_DECIMAL_PLACES.stiffness),
215
+ };
216
+ const areEasingsEqual = (first, second) => {
217
+ if (first === second) {
218
+ return true;
219
+ }
220
+ if (first.type !== second.type) {
221
+ return false;
222
+ }
223
+ switch (first.type) {
224
+ case 'linear':
225
+ return true;
226
+ case 'spring':
227
+ return (second.type === 'spring' &&
228
+ first.damping === second.damping &&
229
+ first.mass === second.mass &&
230
+ first.overshootClamping === second.overshootClamping &&
231
+ first.stiffness === second.stiffness);
232
+ case 'bezier':
233
+ return (second.type === 'bezier' &&
234
+ first.x1 === second.x1 &&
235
+ first.y1 === second.y1 &&
236
+ first.x2 === second.x2 &&
237
+ first.y2 === second.y2);
238
+ default:
239
+ throw new Error(`Unsupported easing: ${JSON.stringify(first)}`);
240
+ }
241
+ };
242
+ const getEasingUpdateTargetKey = (update) => {
243
+ const nodePathKey = remotion_1.Internals.makeSequencePropsSubscriptionKey(update.nodePath);
244
+ if (update.type === 'sequence') {
245
+ return `sequence:${nodePathKey}:${update.fieldKey}:${update.segmentIndex}`;
246
+ }
247
+ return `effect:${nodePathKey}:${update.effectIndex}:${update.fieldKey}:${update.segmentIndex}`;
248
+ };
249
+ const getBuiltInVisualStyleFieldSchema = (fieldKey) => {
250
+ const transformSchema = remotion_1.Internals.transformSchema;
251
+ const directField = transformSchema[fieldKey];
252
+ if (directField) {
253
+ return directField;
254
+ }
255
+ return transformSchema[fieldKey.startsWith('style.') ? fieldKey : `style.${fieldKey}`];
256
+ };
257
+ const getEasingGraphFieldSchema = (update) => {
258
+ var _a;
259
+ return ((_a = update.schema[update.fieldKey]) !== null && _a !== void 0 ? _a : getBuiltInVisualStyleFieldSchema(update.fieldKey));
260
+ };
261
+ const formatEasingGraphLabel = (value, update) => (0, timeline_field_display_utils_1.formatTimelineFieldValueForDisplay)({
262
+ fieldSchema: getEasingGraphFieldSchema(update),
263
+ value,
264
+ });
265
+ const getEasingGraphLabelsFromUpdate = (update) => {
266
+ const startKeyframe = update.propStatus.keyframes[update.segmentIndex];
267
+ const endKeyframe = update.propStatus.keyframes[update.segmentIndex + 1];
268
+ if (!startKeyframe || !endKeyframe) {
269
+ return null;
270
+ }
271
+ return {
272
+ start: formatEasingGraphLabel(startKeyframe.value, update),
273
+ end: formatEasingGraphLabel(endKeyframe.value, update),
274
+ };
275
+ };
276
+ const areEasingGraphLabelsEqual = (first, second) => first.start === second.start && first.end === second.end;
277
+ const getEasingGraphLabels = (updates) => {
278
+ const firstLabels = updates.length === 0 ? null : getEasingGraphLabelsFromUpdate(updates[0]);
279
+ if (firstLabels === null) {
280
+ return DEFAULT_EASING_GRAPH_LABELS;
281
+ }
282
+ return updates.every((update) => {
283
+ const labels = getEasingGraphLabelsFromUpdate(update);
284
+ return labels !== null && areEasingGraphLabelsEqual(labels, firstLabels);
285
+ })
286
+ ? firstLabels
287
+ : DEFAULT_EASING_GRAPH_LABELS;
288
+ };
89
289
  const xToSvg = (value) => PLOT_LEFT + value * PLOT_WIDTH;
90
290
  const yToSvg = (value) => PLOT_TOP + ((Y_MAX - value) / (Y_MAX - Y_MIN)) * PLOT_HEIGHT;
291
+ const presetPreviewXToSvg = (value) => PRESET_PREVIEW_PADDING +
292
+ value * (PRESET_PREVIEW_WIDTH - PRESET_PREVIEW_PADDING * 2);
293
+ const presetPreviewYToSvg = (value) => PRESET_PREVIEW_PADDING +
294
+ ((PRESET_PREVIEW_Y_MAX - value) /
295
+ (PRESET_PREVIEW_Y_MAX - PRESET_PREVIEW_Y_MIN)) *
296
+ (PRESET_PREVIEW_HEIGHT - PRESET_PREVIEW_PADDING * 2);
297
+ const getEasingFunction = (easing) => {
298
+ switch (easing.type) {
299
+ case 'linear':
300
+ return remotion_1.Easing.linear;
301
+ case 'bezier':
302
+ return remotion_1.Easing.bezier(easing.x1, easing.y1, easing.x2, easing.y2);
303
+ case 'spring':
304
+ return remotion_1.Easing.spring({
305
+ damping: easing.damping,
306
+ mass: easing.mass,
307
+ overshootClamping: easing.overshootClamping,
308
+ stiffness: easing.stiffness,
309
+ });
310
+ default:
311
+ throw new Error(`Unsupported easing: ${JSON.stringify(easing)}`);
312
+ }
313
+ };
314
+ const getPresetPreviewPath = (easing) => {
315
+ const easingFunction = getEasingFunction(easing);
316
+ const samples = 36;
317
+ const points = [];
318
+ for (let i = 0; i <= samples; i++) {
319
+ const progress = i / samples;
320
+ const x = presetPreviewXToSvg(progress);
321
+ const y = presetPreviewYToSvg(clamp(easingFunction(progress), PRESET_PREVIEW_Y_MIN, PRESET_PREVIEW_Y_MAX));
322
+ points.push(`${i === 0 ? 'M' : 'L'} ${x} ${y}`);
323
+ }
324
+ return points.join(' ');
325
+ };
91
326
  const pointFromBezier = (bezier, handle) => {
92
327
  const x = handle === 0 ? bezier[0] : bezier[2];
93
328
  const y = handle === 0 ? bezier[1] : bezier[3];
94
329
  return { x: xToSvg(x), y: yToSvg(y) };
95
330
  };
96
- const startPoint = { x: xToSvg(0), y: yToSvg(0) };
97
- const endPoint = { x: xToSvg(1), y: yToSvg(1) };
98
- const EasingEditorModal = ({ state }) => {
99
- const { setSelectedModal } = (0, react_1.useContext)(modals_1.ModalsContext);
331
+ const getEasingGraphLabelWidth = (label) => {
332
+ return clamp(label.length * 7.8 + EASING_GRAPH_LABEL_HORIZONTAL_PADDING * 2, 24, EASING_GRAPH_LABEL_MAX_WIDTH);
333
+ };
334
+ const getEasingGraphLabelStyle = (textAlign) => ({
335
+ alignItems: 'center',
336
+ backgroundColor: colors_1.BACKGROUND,
337
+ color: colors_1.LIGHT_TEXT,
338
+ display: 'flex',
339
+ fontSize: EASING_GRAPH_LABEL_FONT_SIZE,
340
+ height: '100%',
341
+ justifyContent: textAlign === 'left' ? 'flex-start' : 'flex-end',
342
+ lineHeight: `${EASING_GRAPH_LABEL_HEIGHT}px`,
343
+ overflow: 'hidden',
344
+ padding: `0 ${EASING_GRAPH_LABEL_HORIZONTAL_PADDING}px`,
345
+ textAlign,
346
+ textOverflow: 'ellipsis',
347
+ whiteSpace: 'nowrap',
348
+ });
349
+ const EasingGraphScaffold = ({ labels }) => {
350
+ const yZero = yToSvg(0);
351
+ const yOne = yToSvg(1);
352
+ const bottomLabelWidth = getEasingGraphLabelWidth(labels.start);
353
+ const topLabelWidth = getEasingGraphLabelWidth(labels.end);
354
+ return (jsx_runtime_1.jsxs(jsx_runtime_1.Fragment, { children: [
355
+ jsx_runtime_1.jsx("line", { x1: PLOT_LEFT, y1: yZero, x2: PLOT_LEFT + PLOT_WIDTH, y2: yZero, stroke: EASING_GRAPH_GUIDE_COLOR, strokeWidth: 1 }), jsx_runtime_1.jsx("line", { x1: PLOT_LEFT, y1: yOne, x2: PLOT_LEFT + PLOT_WIDTH, y2: yOne, stroke: EASING_GRAPH_GUIDE_COLOR, strokeWidth: 1 }), jsx_runtime_1.jsx("foreignObject", { x: PLOT_LEFT - EASING_GRAPH_LABEL_HORIZONTAL_PADDING, y: yOne - EASING_GRAPH_LABEL_HEIGHT / 2, width: topLabelWidth, height: EASING_GRAPH_LABEL_HEIGHT, children: jsx_runtime_1.jsx("div", { style: getEasingGraphLabelStyle('left'), title: labels.end, children: labels.end }) }), jsx_runtime_1.jsx("foreignObject", { x: PLOT_LEFT +
356
+ PLOT_WIDTH -
357
+ bottomLabelWidth +
358
+ EASING_GRAPH_LABEL_HORIZONTAL_PADDING, y: yZero - EASING_GRAPH_LABEL_HEIGHT / 2, width: bottomLabelWidth, height: EASING_GRAPH_LABEL_HEIGHT, children: jsx_runtime_1.jsx("div", { style: getEasingGraphLabelStyle('right'), title: labels.start, children: labels.start }) })
359
+ ] }));
360
+ };
361
+ const EasingPresetButton = ({ currentEasing, disabled, onClick, preset }) => {
362
+ const selected = areEasingsEqual(currentEasing, preset.easing);
363
+ const path = (0, react_1.useMemo)(() => getPresetPreviewPath(preset.easing), [preset.easing]);
364
+ const style = (0, react_1.useMemo)(() => ({
365
+ ...presetButtonBase,
366
+ backgroundColor: selected ? 'rgba(11, 132, 243, 0.18)' : colors_1.INPUT_BACKGROUND,
367
+ borderColor: selected ? colors_1.BLUE : colors_1.INPUT_BORDER_COLOR_HOVERED,
368
+ cursor: disabled ? 'not-allowed' : 'pointer',
369
+ opacity: disabled ? 0.45 : 1,
370
+ }), [disabled, selected]);
371
+ const handleClick = (0, react_1.useCallback)(() => {
372
+ onClick(preset.easing);
373
+ }, [onClick, preset.easing]);
374
+ 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
+ };
376
+ const EasingEditor = ({ state, renderHeader }) => {
100
377
  const { previewServerState } = (0, react_1.useContext)(client_id_1.StudioServerConnectionCtx);
101
378
  const sequencesRef = (0, react_1.useContext)(remotion_1.Internals.SequenceManagerRefContext);
102
379
  const propStatusesRef = (0, react_1.useContext)(remotion_1.Internals.VisualModePropStatusesRefContext);
103
- const { setPropStatuses } = (0, react_1.useContext)(remotion_1.Internals.VisualModeSettersContext);
380
+ const { setDragOverrides, clearDragOverrides, setEffectDragOverrides, clearEffectDragOverrides, setPropStatuses, } = (0, react_1.useContext)(remotion_1.Internals.VisualModeSettersContext);
104
381
  const { overrideIdToNodePathMappings } = (0, react_1.useContext)(remotion_1.Internals.OverrideIdsToNodePathsGettersContext);
105
382
  const svgRef = (0, react_1.useRef)(null);
383
+ const [mode, setMode] = (0, react_1.useState)(() => easingToMode(state.initialEasing));
106
384
  const [bezier, setBezier] = (0, react_1.useState)(() => easingToBezier(state.initialEasing));
107
- const [saving, setSaving] = (0, react_1.useState)(false);
385
+ const [spring, setSpring] = (0, react_1.useState)(() => easingToSpring(state.initialEasing));
386
+ const bezierRef = (0, react_1.useRef)(bezier);
387
+ const springRef = (0, react_1.useRef)(spring);
388
+ const liveOverrideVersionRef = (0, react_1.useRef)(0);
389
+ const pendingOverrideTargetsRef = (0, react_1.useRef)([]);
108
390
  const [activeHandle, setActiveHandle] = (0, react_1.useState)(null);
109
- const close = (0, react_1.useCallback)(() => {
110
- setSelectedModal(null);
111
- }, [setSelectedModal]);
112
- const setCoordinate = (0, react_1.useCallback)((handle, coordinate, value) => {
113
- setBezier((previous) => {
114
- const next = [...previous];
115
- const index = handle === 0
116
- ? coordinate === 'x'
117
- ? 0
118
- : 1
119
- : coordinate === 'x'
120
- ? 2
121
- : 3;
122
- next[index] =
123
- coordinate === 'x' ? clamp(value, 0, 1) : clamp(value, Y_MIN, Y_MAX);
124
- return next;
391
+ (0, react_1.useEffect)(() => {
392
+ const nextBezier = easingToBezier(state.initialEasing);
393
+ const nextSpring = easingToSpring(state.initialEasing);
394
+ bezierRef.current = nextBezier;
395
+ springRef.current = nextSpring;
396
+ setMode(easingToMode(state.initialEasing));
397
+ setBezier(nextBezier);
398
+ setSpring(nextSpring);
399
+ }, [state.initialEasing]);
400
+ const getCurrentEasingUpdates = (0, react_1.useCallback)(() => {
401
+ return (0, update_selected_easing_1.getSelectedEasingUpdates)({
402
+ selections: state.selections,
403
+ sequences: sequencesRef.current,
404
+ overrideIdsToNodePaths: overrideIdToNodePathMappings,
405
+ propStatuses: propStatusesRef.current,
125
406
  });
126
- }, []);
407
+ }, [
408
+ overrideIdToNodePathMappings,
409
+ propStatusesRef,
410
+ sequencesRef,
411
+ state.selections,
412
+ ]);
413
+ const clearEasingDragOverrides = (0, react_1.useCallback)((updates) => {
414
+ const sequenceTargets = new Set();
415
+ const effectTargets = new Set();
416
+ for (const update of updates) {
417
+ const key = remotion_1.Internals.makeSequencePropsSubscriptionKey(update.nodePath);
418
+ if (update.type === 'sequence') {
419
+ if (!sequenceTargets.has(key)) {
420
+ sequenceTargets.add(key);
421
+ clearDragOverrides(update.nodePath);
422
+ }
423
+ continue;
424
+ }
425
+ const effectKey = `${key}.${update.effectIndex}`;
426
+ if (!effectTargets.has(effectKey)) {
427
+ effectTargets.add(effectKey);
428
+ clearEffectDragOverrides(update.nodePath, update.effectIndex);
429
+ }
430
+ }
431
+ }, [clearDragOverrides, clearEffectDragOverrides]);
432
+ const applyLiveEasing = (0, react_1.useCallback)((easing) => {
433
+ const updates = getCurrentEasingUpdates();
434
+ liveOverrideVersionRef.current++;
435
+ const version = liveOverrideVersionRef.current;
436
+ const nextTargetKeys = new Set(updates.map(getEasingUpdateTargetKey));
437
+ const staleTargets = pendingOverrideTargetsRef.current.filter((update) => !nextTargetKeys.has(getEasingUpdateTargetKey(update)));
438
+ clearEasingDragOverrides(staleTargets);
439
+ pendingOverrideTargetsRef.current = updates;
440
+ for (const update of updates) {
441
+ const dragOverrideValue = (0, update_selected_easing_1.makeEasingDragOverride)({
442
+ status: update.propStatus,
443
+ segmentIndex: update.segmentIndex,
444
+ easing,
445
+ });
446
+ if (update.type === 'sequence') {
447
+ setDragOverrides(update.nodePath, update.fieldKey, dragOverrideValue);
448
+ }
449
+ else {
450
+ setEffectDragOverrides(update.nodePath, update.effectIndex, update.fieldKey, dragOverrideValue);
451
+ }
452
+ }
453
+ return version;
454
+ }, [
455
+ clearEasingDragOverrides,
456
+ getCurrentEasingUpdates,
457
+ setDragOverrides,
458
+ setEffectDragOverrides,
459
+ ]);
460
+ const clearPendingEasingDragOverrides = (0, react_1.useCallback)((version) => {
461
+ if (version !== liveOverrideVersionRef.current) {
462
+ return;
463
+ }
464
+ clearEasingDragOverrides(pendingOverrideTargetsRef.current);
465
+ pendingOverrideTargetsRef.current = [];
466
+ }, [clearEasingDragOverrides]);
467
+ const commitEasing = (0, react_1.useCallback)((easing, version) => {
468
+ const updates = getCurrentEasingUpdates();
469
+ const hasChange = updates.some((update) => !areEasingsEqual(update.currentEasing, easing));
470
+ if (previewServerState.type !== 'connected' ||
471
+ updates.length === 0 ||
472
+ !hasChange) {
473
+ clearPendingEasingDragOverrides(version);
474
+ return;
475
+ }
476
+ const promise = (0, update_selected_easing_1.updateSelectedTimelineEasings)({
477
+ selections: state.selections,
478
+ sequences: sequencesRef.current,
479
+ overrideIdsToNodePaths: overrideIdToNodePathMappings,
480
+ propStatuses: propStatusesRef.current,
481
+ setPropStatuses,
482
+ clientId: previewServerState.clientId,
483
+ easing,
484
+ });
485
+ if (promise === null) {
486
+ clearPendingEasingDragOverrides(version);
487
+ return;
488
+ }
489
+ promise
490
+ .catch(() => undefined)
491
+ .finally(() => {
492
+ clearPendingEasingDragOverrides(version);
493
+ });
494
+ }, [
495
+ clearPendingEasingDragOverrides,
496
+ getCurrentEasingUpdates,
497
+ overrideIdToNodePathMappings,
498
+ previewServerState,
499
+ propStatusesRef,
500
+ sequencesRef,
501
+ setPropStatuses,
502
+ state.selections,
503
+ ]);
504
+ const setBezierAndPreview = (0, react_1.useCallback)((nextBezier) => {
505
+ const sanitized = sanitizeBezier(nextBezier);
506
+ bezierRef.current = sanitized;
507
+ setBezier(sanitized);
508
+ return applyLiveEasing(serializeBezier(sanitized));
509
+ }, [applyLiveEasing]);
510
+ const setSpringAndPreview = (0, react_1.useCallback)((nextSpring) => {
511
+ const sanitized = sanitizeSpring(nextSpring);
512
+ springRef.current = sanitized;
513
+ setSpring(sanitized);
514
+ return applyLiveEasing(serializeSpring(sanitized));
515
+ }, [applyLiveEasing]);
516
+ const setCoordinate = (0, react_1.useCallback)((handle, coordinate, value, commit) => {
517
+ const next = [...bezierRef.current];
518
+ const index = handle === 0
519
+ ? coordinate === 'x'
520
+ ? 0
521
+ : 1
522
+ : coordinate === 'x'
523
+ ? 2
524
+ : 3;
525
+ next[index] =
526
+ coordinate === 'x' ? clamp(value, 0, 1) : clamp(value, Y_MIN, Y_MAX);
527
+ const version = setBezierAndPreview(next);
528
+ if (commit) {
529
+ commitEasing(serializeBezier(next), version);
530
+ }
531
+ }, [commitEasing, setBezierAndPreview]);
532
+ const setSpringNumber = (0, react_1.useCallback)((key, value, commit) => {
533
+ const next = {
534
+ ...springRef.current,
535
+ [key]: sanitizeSpringValue(value, key, DEFAULT_SPRING_EASING[key]),
536
+ };
537
+ const version = setSpringAndPreview(next);
538
+ if (commit) {
539
+ commitEasing(serializeSpring(next), version);
540
+ }
541
+ }, [commitEasing, setSpringAndPreview]);
542
+ const setOvershootClamping = (0, react_1.useCallback)(() => {
543
+ const next = {
544
+ ...springRef.current,
545
+ overshootClamping: !springRef.current.overshootClamping,
546
+ };
547
+ const version = setSpringAndPreview(next);
548
+ commitEasing(serializeSpring(next), version);
549
+ }, [commitEasing, setSpringAndPreview]);
550
+ const switchMode = (0, react_1.useCallback)((nextMode) => {
551
+ setMode(nextMode);
552
+ if (mode === nextMode || previewServerState.type !== 'connected') {
553
+ return;
554
+ }
555
+ const easing = nextMode === 'spring'
556
+ ? serializeSpring(springRef.current)
557
+ : serializeBezier(bezierRef.current);
558
+ const version = applyLiveEasing(easing);
559
+ commitEasing(easing, version);
560
+ }, [applyLiveEasing, commitEasing, mode, previewServerState.type]);
561
+ const applyPreset = (0, react_1.useCallback)((easing) => {
562
+ if (previewServerState.type !== 'connected') {
563
+ return;
564
+ }
565
+ if (isSpringEasing(easing)) {
566
+ const nextSpring = sanitizeSpring(easing);
567
+ setMode('spring');
568
+ const springVersion = setSpringAndPreview(nextSpring);
569
+ commitEasing(serializeSpring(nextSpring), springVersion);
570
+ return;
571
+ }
572
+ const nextBezier = easingToBezier(easing);
573
+ setMode('bezier');
574
+ const bezierVersion = setBezierAndPreview(nextBezier);
575
+ commitEasing(serializeBezier(nextBezier), bezierVersion);
576
+ }, [
577
+ commitEasing,
578
+ previewServerState.type,
579
+ setBezierAndPreview,
580
+ setSpringAndPreview,
581
+ ]);
127
582
  const getValueFromPointer = (0, react_1.useCallback)((event) => {
128
583
  const svg = svgRef.current;
129
584
  if (!svg) {
@@ -141,19 +596,17 @@ const EasingEditorModal = ({ state }) => {
141
596
  if (!value) {
142
597
  return;
143
598
  }
144
- setBezier((previous) => {
145
- const next = [...previous];
146
- if (handle === 0) {
147
- next[0] = value.x;
148
- next[1] = value.y;
149
- }
150
- else {
151
- next[2] = value.x;
152
- next[3] = value.y;
153
- }
154
- return next;
155
- });
156
- }, [getValueFromPointer]);
599
+ const next = [...bezierRef.current];
600
+ if (handle === 0) {
601
+ next[0] = value.x;
602
+ next[1] = value.y;
603
+ }
604
+ else {
605
+ next[2] = value.x;
606
+ next[3] = value.y;
607
+ }
608
+ setBezierAndPreview(next);
609
+ }, [getValueFromPointer, setBezierAndPreview]);
157
610
  (0, react_1.useEffect)(() => {
158
611
  if (activeHandle === null) {
159
612
  return;
@@ -162,6 +615,7 @@ const EasingEditorModal = ({ state }) => {
162
615
  updateHandleFromPointer(activeHandle, event);
163
616
  };
164
617
  const onPointerUp = () => {
618
+ commitEasing(serializeBezier(bezierRef.current), liveOverrideVersionRef.current);
165
619
  setActiveHandle(null);
166
620
  };
167
621
  window.addEventListener('pointermove', onPointerMove);
@@ -170,78 +624,97 @@ const EasingEditorModal = ({ state }) => {
170
624
  window.removeEventListener('pointermove', onPointerMove);
171
625
  window.removeEventListener('pointerup', onPointerUp);
172
626
  };
173
- }, [activeHandle, updateHandleFromPointer]);
627
+ }, [activeHandle, commitEasing, updateHandleFromPointer]);
174
628
  const onHandlePointerDown = (0, react_1.useCallback)((handle, event) => {
629
+ if (previewServerState.type !== 'connected') {
630
+ return;
631
+ }
175
632
  event.preventDefault();
176
633
  event.stopPropagation();
177
634
  setActiveHandle(handle);
178
635
  updateHandleFromPointer(handle, event);
179
- }, [updateHandleFromPointer]);
180
- const onSave = (0, react_1.useCallback)(() => {
181
- if (previewServerState.type !== 'connected' || saving) {
182
- return;
183
- }
184
- setSaving(true);
185
- const promise = (0, update_selected_easing_1.updateSelectedTimelineEasings)({
186
- selections: state.selections,
187
- sequences: sequencesRef.current,
188
- overrideIdsToNodePaths: overrideIdToNodePathMappings,
189
- propStatuses: propStatusesRef.current,
190
- setPropStatuses,
191
- clientId: previewServerState.clientId,
192
- easing: serializeBezier(bezier),
636
+ }, [previewServerState.type, updateHandleFromPointer]);
637
+ (0, react_1.useEffect)(() => {
638
+ return () => {
639
+ clearEasingDragOverrides(pendingOverrideTargetsRef.current);
640
+ };
641
+ }, [clearEasingDragOverrides]);
642
+ const startPoint = (0, react_1.useMemo)(() => ({ x: xToSvg(0), y: yToSvg(0) }), []);
643
+ const endPoint = (0, react_1.useMemo)(() => ({ x: xToSvg(1), y: yToSvg(1) }), []);
644
+ const firstHandle = (0, react_1.useMemo)(() => pointFromBezier(bezier, 0), [bezier]);
645
+ const secondHandle = (0, react_1.useMemo)(() => pointFromBezier(bezier, 1), [bezier]);
646
+ const bezierPath = (0, react_1.useMemo)(() => {
647
+ return `M ${startPoint.x} ${startPoint.y} C ${firstHandle.x} ${firstHandle.y}, ${secondHandle.x} ${secondHandle.y}, ${endPoint.x} ${endPoint.y}`;
648
+ }, [endPoint, firstHandle, secondHandle, startPoint]);
649
+ const springPath = (0, react_1.useMemo)(() => {
650
+ const easing = remotion_1.Easing.spring({
651
+ damping: spring.damping,
652
+ mass: spring.mass,
653
+ overshootClamping: spring.overshootClamping,
654
+ stiffness: spring.stiffness,
193
655
  });
194
- if (promise === null) {
195
- setSaving(false);
196
- return;
656
+ const samples = 80;
657
+ const points = [];
658
+ for (let i = 0; i <= samples; i++) {
659
+ const t = i / samples;
660
+ const x = xToSvg(t);
661
+ const y = yToSvg(clamp(easing(t), Y_MIN, Y_MAX));
662
+ points.push(`${i === 0 ? 'M' : 'L'} ${x} ${y}`);
197
663
  }
198
- promise.catch(() => undefined);
199
- close();
200
- }, [
201
- bezier,
202
- close,
203
- overrideIdToNodePathMappings,
204
- previewServerState,
205
- propStatusesRef,
206
- saving,
207
- sequencesRef,
208
- setPropStatuses,
209
- state.selections,
210
- ]);
211
- const onSubmit = (0, react_1.useCallback)((event) => {
212
- event.preventDefault();
213
- onSave();
214
- }, [onSave]);
215
- const path = (0, react_1.useMemo)(() => {
216
- const curveFirstHandle = pointFromBezier(bezier, 0);
217
- const curveSecondHandle = pointFromBezier(bezier, 1);
218
- return `M ${startPoint.x} ${startPoint.y} C ${curveFirstHandle.x} ${curveFirstHandle.y}, ${curveSecondHandle.x} ${curveSecondHandle.y}, ${endPoint.x} ${endPoint.y}`;
219
- }, [bezier]);
220
- const firstHandle = pointFromBezier(bezier, 0);
221
- const secondHandle = pointFromBezier(bezier, 1);
222
- const yZero = yToSvg(0);
223
- const yOne = yToSvg(1);
224
- const saveDisabled = saving || previewServerState.type !== 'connected';
225
- return (jsx_runtime_1.jsxs(DismissableModal_1.DismissableModal, { children: [
226
- jsx_runtime_1.jsx(ModalHeader_1.ModalHeader, { title: "Edit easing" }), jsx_runtime_1.jsxs("form", { onSubmit: onSubmit, children: [
227
- jsx_runtime_1.jsxs("div", { style: container, children: [
228
- 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: [
229
- jsx_runtime_1.jsx("line", { x1: PLOT_LEFT, y1: yZero, x2: PLOT_LEFT + PLOT_WIDTH, y2: yZero, stroke: colors_1.INPUT_BORDER_COLOR_HOVERED, strokeWidth: 1 }), jsx_runtime_1.jsx("line", { x1: PLOT_LEFT, y1: yOne, x2: PLOT_LEFT + PLOT_WIDTH, y2: yOne, stroke: colors_1.INPUT_BORDER_COLOR_HOVERED, strokeWidth: 1 }), 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: path, 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: "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: "all", cursor: activeHandle === 1 ? 'grabbing' : 'default', onPointerDown: (event) => onHandlePointerDown(1, event) }), jsx_runtime_1.jsx("text", { x: PLOT_LEFT - 22, y: yZero + 3, fill: colors_1.LIGHT_TEXT, fontSize: 9, children: "0" }), jsx_runtime_1.jsx("text", { x: PLOT_LEFT - 22, y: yOne + 3, fill: colors_1.LIGHT_TEXT, fontSize: 9, children: "1" })
230
- ] }), jsx_runtime_1.jsxs("div", { style: coordinatesGrid, children: [
231
- jsx_runtime_1.jsxs("div", { style: coordinateRow, children: [
232
- 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), onValueChangeEnd: (value) => setCoordinate(0, 'x', value), onTextChange: () => undefined, min: 0, max: 1, step: 0.01, formatter: formatNumber, rightAlign: false, style: numberInputStyle, snapToStep: false }) })
233
- ] }), jsx_runtime_1.jsxs("div", { style: coordinateRow, children: [
234
- jsx_runtime_1.jsx("div", { style: coordinateLabel, children: "Y1" }), jsx_runtime_1.jsx("div", { style: coordinateInputWrapper, children: jsx_runtime_1.jsx(InputDragger_1.InputDragger, { type: "number", value: bezier[1], status: "ok", onValueChange: (value) => setCoordinate(0, 'y', value), onValueChangeEnd: (value) => setCoordinate(0, 'y', value), onTextChange: () => undefined, min: Y_MIN, max: Y_MAX, step: 0.01, formatter: formatNumber, rightAlign: false, style: numberInputStyle, snapToStep: false }) })
235
- ] }), jsx_runtime_1.jsxs("div", { style: coordinateRow, children: [
236
- jsx_runtime_1.jsx("div", { style: coordinateLabel, children: "X2" }), jsx_runtime_1.jsx("div", { style: coordinateInputWrapper, children: jsx_runtime_1.jsx(InputDragger_1.InputDragger, { type: "number", value: bezier[2], status: "ok", onValueChange: (value) => setCoordinate(1, 'x', value), onValueChangeEnd: (value) => setCoordinate(1, 'x', value), onTextChange: () => undefined, min: 0, max: 1, step: 0.01, formatter: formatNumber, rightAlign: false, style: numberInputStyle, snapToStep: false }) })
237
- ] }), jsx_runtime_1.jsxs("div", { style: coordinateRow, children: [
238
- jsx_runtime_1.jsx("div", { style: coordinateLabel, children: "Y2" }), jsx_runtime_1.jsx("div", { style: coordinateInputWrapper, children: jsx_runtime_1.jsx(InputDragger_1.InputDragger, { type: "number", value: bezier[3], status: "ok", onValueChange: (value) => setCoordinate(1, 'y', value), onValueChangeEnd: (value) => setCoordinate(1, 'y', value), onTextChange: () => undefined, min: Y_MIN, max: Y_MAX, step: 0.01, formatter: formatNumber, rightAlign: false, style: numberInputStyle, snapToStep: false }) })
239
- ] })
664
+ return points.join(' ');
665
+ }, [spring]);
666
+ const disabled = previewServerState.type !== 'connected';
667
+ const graphLabels = getEasingGraphLabels(getCurrentEasingUpdates());
668
+ const currentEasing = (0, react_1.useMemo)(() => mode === 'spring' ? serializeSpring(spring) : serializeBezier(bezier), [bezier, mode, spring]);
669
+ const modeItems = (0, react_1.useMemo)(() => {
670
+ return [
671
+ {
672
+ key: 'bezier',
673
+ label: 'Bezier',
674
+ onClick: () => switchMode('bezier'),
675
+ selected: mode === 'bezier',
676
+ },
677
+ {
678
+ key: 'spring',
679
+ label: 'Spring',
680
+ onClick: () => switchMode('spring'),
681
+ selected: mode === 'spring',
682
+ },
683
+ ];
684
+ }, [mode, switchMode]);
685
+ const coordinatesGrid = (0, react_1.useMemo)(() => ({
686
+ ...coordinatesGridBase,
687
+ gridTemplateColumns: 'repeat(2, minmax(0, 1fr))',
688
+ padding: '0 12px 12px',
689
+ }), []);
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: [
691
+ 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
+ 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: [
694
+ jsx_runtime_1.jsxs("div", { style: coordinateRow, children: [
695
+ 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
+ ] }), jsx_runtime_1.jsxs("div", { style: coordinateRow, children: [
697
+ jsx_runtime_1.jsx("div", { style: coordinateLabel, children: "Y1" }), jsx_runtime_1.jsx("div", { style: coordinateInputWrapper, children: jsx_runtime_1.jsx(InputDragger_1.InputDragger, { type: "number", value: bezier[1], status: "ok", onValueChange: (value) => setCoordinate(0, 'y', value, false), onValueChangeEnd: (value) => setCoordinate(0, 'y', value, true), onTextChange: () => undefined, min: Y_MIN, max: Y_MAX, step: 0.01, formatter: formatNumber, rightAlign: false, style: numberInputStyle, snapToStep: false, disabled: disabled }) })
698
+ ] }), jsx_runtime_1.jsxs("div", { style: coordinateRow, children: [
699
+ jsx_runtime_1.jsx("div", { style: coordinateLabel, children: "X2" }), jsx_runtime_1.jsx("div", { style: coordinateInputWrapper, children: jsx_runtime_1.jsx(InputDragger_1.InputDragger, { type: "number", value: bezier[2], status: "ok", onValueChange: (value) => setCoordinate(1, 'x', value, false), onValueChangeEnd: (value) => setCoordinate(1, 'x', value, true), onTextChange: () => undefined, min: 0, max: 1, step: 0.01, formatter: formatNumber, rightAlign: false, style: numberInputStyle, snapToStep: false, disabled: disabled }) })
700
+ ] }), jsx_runtime_1.jsxs("div", { style: coordinateRow, children: [
701
+ jsx_runtime_1.jsx("div", { style: coordinateLabel, children: "Y2" }), jsx_runtime_1.jsx("div", { style: coordinateInputWrapper, children: jsx_runtime_1.jsx(InputDragger_1.InputDragger, { type: "number", value: bezier[3], status: "ok", onValueChange: (value) => setCoordinate(1, 'y', value, false), onValueChangeEnd: (value) => setCoordinate(1, 'y', value, true), onTextChange: () => undefined, min: Y_MIN, max: Y_MAX, step: 0.01, formatter: formatNumber, rightAlign: false, style: numberInputStyle, snapToStep: false, disabled: disabled }) })
240
702
  ] })
241
- ] }), jsx_runtime_1.jsx(ModalFooter_1.ModalFooterContainer, { children: jsx_runtime_1.jsxs(layout_1.Row, { justify: "flex-end", align: "center", children: [
242
- jsx_runtime_1.jsx(Button_1.Button, { onClick: close, children: "Discard" }), jsx_runtime_1.jsx(layout_1.Spacing, { x: 1 }), jsx_runtime_1.jsx(ModalButton_1.ModalButton, { onClick: onSave, disabled: saveDisabled, children: "Save" })
243
- ] }) }), jsx_runtime_1.jsx("button", { type: "submit", style: hiddenSubmit, disabled: saveDisabled, "aria-hidden": true, tabIndex: -1 })
244
- ] })
245
- ] }));
703
+ ] })
704
+ ] })) : (jsx_runtime_1.jsxs(jsx_runtime_1.Fragment, { children: [
705
+ 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
+ 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: [
708
+ jsx_runtime_1.jsxs("div", { style: coordinateRow, children: [
709
+ 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
+ ] }), jsx_runtime_1.jsxs("div", { style: coordinateRow, children: [
711
+ 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
+ ] }), jsx_runtime_1.jsxs("div", { style: coordinateRow, children: [
713
+ 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 }) })
714
+ ] }), jsx_runtime_1.jsxs("div", { style: coordinateRow, children: [
715
+ 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" }) })
716
+ ] })
717
+ ] })
718
+ ] }))] }));
246
719
  };
247
- exports.EasingEditorModal = EasingEditorModal;
720
+ exports.EasingEditor = EasingEditor;