@remotion/studio 4.0.486 → 4.0.487

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 (57) hide show
  1. package/dist/components/ContextMenu.js +2 -1
  2. package/dist/components/CurrentComposition.js +43 -2
  3. package/dist/components/EditorContexts.js +2 -1
  4. package/dist/components/GlobalKeybindings.js +19 -0
  5. package/dist/components/InspectorSourceLocation.d.ts +1 -0
  6. package/dist/components/InspectorSourceLocation.js +18 -3
  7. package/dist/components/KeyboardShortcutsExplainer.js +4 -0
  8. package/dist/components/Preview.js +1 -1
  9. package/dist/components/PreviewToolbar.js +2 -1
  10. package/dist/components/RenderButton.js +1 -0
  11. package/dist/components/RenderModal/OptionExplainer.d.ts +11 -1
  12. package/dist/components/RenderModal/OptionExplainer.js +31 -11
  13. package/dist/components/RenderModal/OptionExplainerBubble.d.ts +15 -1
  14. package/dist/components/RenderModal/OptionExplainerBubble.js +17 -3
  15. package/dist/components/RenderModal/SchemaEditor/Fieldset.d.ts +1 -0
  16. package/dist/components/RenderModal/SchemaEditor/Fieldset.js +6 -1
  17. package/dist/components/RenderModal/SchemaEditor/SchemaEditor.js +1 -2
  18. package/dist/components/RenderModal/WebRenderModal.js +5 -2
  19. package/dist/components/RenderModal/WebRenderModalAdvanced.d.ts +3 -0
  20. package/dist/components/RenderModal/WebRenderModalAdvanced.js +92 -5
  21. package/dist/components/RenderModal/WebRenderModalBasic.js +2 -2
  22. package/dist/components/RenderQueue/ClientRenderQueueProcessor.js +1 -0
  23. package/dist/components/RenderQueue/client-side-render-types.d.ts +2 -1
  24. package/dist/components/SelectedOutlineElement.d.ts +4 -0
  25. package/dist/components/SelectedOutlineElement.js +56 -11
  26. package/dist/components/SelectedOutlineOverlay.d.ts +2 -0
  27. package/dist/components/SelectedOutlineOverlay.js +53 -2
  28. package/dist/components/SelectedOutlineUvControls.js +13 -6
  29. package/dist/components/SnappingProvider.d.ts +4 -0
  30. package/dist/components/SnappingProvider.js +24 -0
  31. package/dist/components/SnappingToggle.d.ts +2 -0
  32. package/dist/components/SnappingToggle.js +26 -0
  33. package/dist/components/Splitter/SplitterContext.d.ts +1 -1
  34. package/dist/components/Timeline/TimelineSelection.js +5 -0
  35. package/dist/components/VisualControls/ClickableFileName.js +2 -2
  36. package/dist/components/VisualControls/VisualControlsContent.js +9 -1
  37. package/dist/components/effect-drag-and-drop.js +30 -9
  38. package/dist/components/import-assets.d.ts +1 -1
  39. package/dist/components/options-sidebar-tabs.d.ts +1 -0
  40. package/dist/components/options-sidebar-tabs.js +6 -1
  41. package/dist/components/selected-outline-snap.d.ts +36 -0
  42. package/dist/components/selected-outline-snap.js +158 -0
  43. package/dist/esm/{chunk-975rp13y.js → chunk-y2t24cx0.js} +4776 -3933
  44. package/dist/esm/internals.mjs +4776 -3933
  45. package/dist/esm/previewEntry.mjs +5095 -4252
  46. package/dist/esm/renderEntry.mjs +1 -1
  47. package/dist/helpers/retry-payload.js +1 -0
  48. package/dist/helpers/use-menu-structure.js +43 -2
  49. package/dist/icons/magnet.d.ts +3 -0
  50. package/dist/icons/magnet.js +8 -0
  51. package/dist/icons/react.d.ts +5 -0
  52. package/dist/icons/react.js +8 -0
  53. package/dist/state/editor-guides.d.ts +2 -2
  54. package/dist/state/editor-snapping.d.ts +8 -0
  55. package/dist/state/editor-snapping.js +18 -0
  56. package/dist/state/modals.d.ts +2 -1
  57. package/package.json +12 -12
@@ -13,7 +13,24 @@ const OptionExplainerBubble_1 = require("./OptionExplainerBubble");
13
13
  const tabContainer = {
14
14
  flex: 1,
15
15
  };
16
- const WebRenderModalAdvanced = ({ renderMode, delayRenderTimeout, setDelayRenderTimeout, mediaCacheSizeInBytes, setMediaCacheSizeInBytes, hardwareAcceleration, setHardwareAcceleration, allowHtmlInCanvas, setAllowHtmlInCanvas, }) => {
16
+ const paragraph = {
17
+ margin: 0,
18
+ marginBottom: 8,
19
+ };
20
+ const lastParagraph = {
21
+ margin: 0,
22
+ };
23
+ const PageResponsivenessDescription = () => {
24
+ return (jsx_runtime_1.jsxs(jsx_runtime_1.Fragment, { children: [
25
+ jsx_runtime_1.jsx("p", { style: paragraph, children: "The Web Renderer runs in the same browser tab as the Studio. Rendering can block the tab while Remotion captures frames." }), jsx_runtime_1.jsxs("p", { style: paragraph, children: ["The default is ",
26
+ jsx_runtime_1.jsx("code", { children: "Medium" }),
27
+ ", which tries to free the event loop every 33ms of rendering work so progress updates and UI interactions can stay responsive."] }), jsx_runtime_1.jsxs("p", { style: lastParagraph, children: ["Choose ",
28
+ jsx_runtime_1.jsx("code", { children: "Disabled" }),
29
+ " to prioritize render speed, or", ' ', jsx_runtime_1.jsx("code", { children: "High" }),
30
+ " to give the browser more chances to update."] })
31
+ ] }));
32
+ };
33
+ const WebRenderModalAdvanced = ({ renderMode, delayRenderTimeout, setDelayRenderTimeout, mediaCacheSizeInBytes, setMediaCacheSizeInBytes, hardwareAcceleration, setHardwareAcceleration, allowHtmlInCanvas, setAllowHtmlInCanvas, pageResponsiveness, setPageResponsiveness, }) => {
17
34
  const toggleCustomMediaCacheSizeInBytes = (0, react_1.useCallback)(() => {
18
35
  setMediaCacheSizeInBytes((previous) => {
19
36
  if (previous === null) {
@@ -73,18 +90,88 @@ const WebRenderModalAdvanced = ({ renderMode, delayRenderTimeout, setDelayRender
73
90
  },
74
91
  ];
75
92
  }, [hardwareAcceleration, setHardwareAcceleration]);
93
+ const selectedPageResponsiveness = typeof pageResponsiveness === 'number' ? 'custom' : pageResponsiveness;
94
+ const pageResponsivenessOptions = (0, react_1.useMemo)(() => {
95
+ return [
96
+ {
97
+ label: 'Disabled',
98
+ onClick: () => setPageResponsiveness('disabled'),
99
+ leftItem: selectedPageResponsiveness === 'disabled' ? jsx_runtime_1.jsx(Checkmark_1.Checkmark, {}) : null,
100
+ id: 'disabled',
101
+ keyHint: null,
102
+ quickSwitcherLabel: null,
103
+ subMenu: null,
104
+ type: 'item',
105
+ value: 'disabled',
106
+ },
107
+ {
108
+ label: 'Low (100ms)',
109
+ onClick: () => setPageResponsiveness('low'),
110
+ leftItem: selectedPageResponsiveness === 'low' ? jsx_runtime_1.jsx(Checkmark_1.Checkmark, {}) : null,
111
+ id: 'low',
112
+ keyHint: null,
113
+ quickSwitcherLabel: null,
114
+ subMenu: null,
115
+ type: 'item',
116
+ value: 'low',
117
+ },
118
+ {
119
+ label: 'Medium (33ms)',
120
+ onClick: () => setPageResponsiveness('medium'),
121
+ leftItem: selectedPageResponsiveness === 'medium' ? jsx_runtime_1.jsx(Checkmark_1.Checkmark, {}) : null,
122
+ id: 'medium',
123
+ keyHint: null,
124
+ quickSwitcherLabel: null,
125
+ subMenu: null,
126
+ type: 'item',
127
+ value: 'medium',
128
+ },
129
+ {
130
+ label: 'High (16ms)',
131
+ onClick: () => setPageResponsiveness('high'),
132
+ leftItem: selectedPageResponsiveness === 'high' ? jsx_runtime_1.jsx(Checkmark_1.Checkmark, {}) : null,
133
+ id: 'high',
134
+ keyHint: null,
135
+ quickSwitcherLabel: null,
136
+ subMenu: null,
137
+ type: 'item',
138
+ value: 'high',
139
+ },
140
+ {
141
+ label: 'Custom',
142
+ onClick: () => setPageResponsiveness((previous) => typeof previous === 'number' ? previous : 33),
143
+ leftItem: selectedPageResponsiveness === 'custom' ? jsx_runtime_1.jsx(Checkmark_1.Checkmark, {}) : null,
144
+ id: 'custom',
145
+ keyHint: null,
146
+ quickSwitcherLabel: null,
147
+ subMenu: null,
148
+ type: 'item',
149
+ value: 'custom',
150
+ },
151
+ ];
152
+ }, [selectedPageResponsiveness, setPageResponsiveness]);
153
+ const setCustomPageResponsiveness = (0, react_1.useCallback)((value) => {
154
+ setPageResponsiveness((previous) => {
155
+ const currentValue = typeof previous === 'number' ? previous : 33;
156
+ return typeof value === 'function' ? value(currentValue) : value;
157
+ });
158
+ }, [setPageResponsiveness]);
76
159
  return (jsx_runtime_1.jsxs("div", { style: tabContainer, children: [
77
160
  jsx_runtime_1.jsx(NumberSetting_1.NumberSetting, { name: "Delay Render Timeout", formatter: (v) => `${v}ms`, min: 0, max: 1000000000, step: 1000, value: delayRenderTimeout, onValueChanged: setDelayRenderTimeout, hint: "delayRenderTimeoutInMillisecondsOption" }), jsx_runtime_1.jsxs("div", { style: layout_2.optionRow, children: [
78
161
  jsx_runtime_1.jsxs("div", { style: layout_2.label, children: ["Custom @remotion/media cache size ",
79
- jsx_runtime_1.jsx(layout_1.Spacing, { x: 0.5 }), jsx_runtime_1.jsx(OptionExplainerBubble_1.OptionExplainerBubble, { id: "mediaCacheSizeInBytesOption" })
162
+ jsx_runtime_1.jsx(layout_1.Spacing, { x: 0.5 }), jsx_runtime_1.jsx(OptionExplainerBubble_1.WebRendererOptionExplainerBubble, { apiName: "mediaCacheSizeInBytes", id: "mediaCacheSizeInBytesOption" })
80
163
  ] }), jsx_runtime_1.jsx("div", { style: layout_2.rightRow, children: jsx_runtime_1.jsx(Checkbox_1.Checkbox, { checked: mediaCacheSizeInBytes !== null, onChange: toggleCustomMediaCacheSizeInBytes, name: "media-cache-size" }) })
81
164
  ] }), mediaCacheSizeInBytes === null ? null : (jsx_runtime_1.jsx(NumberSetting_1.NumberSetting, { name: "@remotion/media cache size", formatter: (w) => `${w} bytes`, min: 0, max: 10000000000, step: 10 * 1024 * 1024, value: mediaCacheSizeInBytes, onValueChanged: changeMediaCacheSizeInBytes })), renderMode === 'video' ? (jsx_runtime_1.jsxs("div", { style: layout_2.optionRow, children: [
82
165
  jsx_runtime_1.jsx("div", { style: layout_2.label, children: "Hardware Acceleration" }), jsx_runtime_1.jsx("div", { style: layout_2.rightRow, children: jsx_runtime_1.jsx(ComboBox_1.Combobox, { values: hardwareAccelerationOptions, selectedId: hardwareAcceleration, title: "Hardware Acceleration" }) })
83
166
  ] })) : null, jsx_runtime_1.jsxs("div", { style: layout_2.optionRow, children: [
84
167
  jsx_runtime_1.jsxs("div", { style: layout_2.label, children: ["Allow HTML-in-canvas ",
85
- jsx_runtime_1.jsx(layout_1.Spacing, { x: 0.5 }), jsx_runtime_1.jsx(OptionExplainerBubble_1.OptionExplainerBubble, { id: "allowHtmlInCanvasOption" })
168
+ jsx_runtime_1.jsx(layout_1.Spacing, { x: 0.5 }), jsx_runtime_1.jsx(OptionExplainerBubble_1.WebRendererOptionExplainerBubble, { apiName: "allowHtmlInCanvas", id: "allowHtmlInCanvasOption" })
86
169
  ] }), jsx_runtime_1.jsx("div", { style: layout_2.rightRow, children: jsx_runtime_1.jsx(Checkbox_1.Checkbox, { checked: allowHtmlInCanvas, onChange: toggleAllowHtmlInCanvas, name: "allow-html-in-canvas" }) })
87
- ] })
88
- ] }));
170
+ ] }), renderMode === 'still' ? null : (jsx_runtime_1.jsxs(jsx_runtime_1.Fragment, { children: [
171
+ jsx_runtime_1.jsxs("div", { style: layout_2.optionRow, children: [
172
+ jsx_runtime_1.jsxs("div", { style: layout_2.label, children: ["Page Responsiveness ",
173
+ jsx_runtime_1.jsx(layout_1.Spacing, { x: 0.5 }), jsx_runtime_1.jsx(OptionExplainerBubble_1.WebRendererCustomOptionExplainerBubble, { apiName: "pageResponsiveness", description: jsx_runtime_1.jsx(PageResponsivenessDescription, {}), docLink: "https://www.remotion.dev/docs/client-side-rendering/page-responsiveness", name: "Page responsiveness" })
174
+ ] }), jsx_runtime_1.jsx("div", { style: layout_2.rightRow, children: jsx_runtime_1.jsx(ComboBox_1.Combobox, { values: pageResponsivenessOptions, selectedId: selectedPageResponsiveness, title: "Page Responsiveness" }) })
175
+ ] }), typeof pageResponsiveness === 'number' ? (jsx_runtime_1.jsx(NumberSetting_1.NumberSetting, { name: "Responsiveness Interval", formatter: (v) => `${v}ms`, min: 1, max: 1000000000, step: 1, value: pageResponsiveness, onValueChanged: setCustomPageResponsiveness })) : null] }))] }));
89
176
  };
90
177
  exports.WebRenderModalAdvanced = WebRenderModalAdvanced;
@@ -118,12 +118,12 @@ const WebRenderModalBasic = ({ renderMode, resolvedComposition, imageFormat, set
118
118
  jsx_runtime_1.jsx("div", { style: layout_2.label, children: "Container" }), jsx_runtime_1.jsx("div", { style: layout_2.rightRow, children: jsx_runtime_1.jsx(ComboBox_1.Combobox, { values: containerOptions, selectedId: container, title: "Container" }) })
119
119
  ] }), renderMode === 'video' ? (jsx_runtime_1.jsxs("div", { style: layout_2.optionRow, children: [
120
120
  jsx_runtime_1.jsxs("div", { style: layout_2.label, children: ["Codec",
121
- jsx_runtime_1.jsx(layout_1.Spacing, { x: 0.5 }), jsx_runtime_1.jsx(OptionExplainerBubble_1.OptionExplainerBubble, { id: "videoCodecOption" })
121
+ jsx_runtime_1.jsx(layout_1.Spacing, { x: 0.5 }), jsx_runtime_1.jsx(OptionExplainerBubble_1.WebRendererOptionExplainerBubble, { apiName: "codec", id: "videoCodecOption" })
122
122
  ] }), jsx_runtime_1.jsx("div", { style: layout_2.rightRow, children: jsx_runtime_1.jsx(ComboBox_1.Combobox, { values: codecOptions, selectedId: effectiveVideoCodec, title: "Codec" }) })
123
123
  ] })) : null, jsx_runtime_1.jsx(FrameRangeSetting_1.FrameRangeSetting, { durationInFrames: resolvedComposition.durationInFrames, startFrame: startFrame !== null && startFrame !== void 0 ? startFrame : 0, endFrame: endFrame !== null && endFrame !== void 0 ? endFrame : resolvedComposition.durationInFrames - 1, setStartFrame: setStartFrame, setEndFrame: setEndFrame })
124
124
  ] })), jsx_runtime_1.jsx(RenderModalOutputName_1.RenderModalOutputName, { existence: false, inputStyle: layout_2.input, outName: outName, onValueChange: onOutNameChange, validationMessage: validationMessage, label: window.remotion_isReadOnlyStudio ? 'Download name' : 'Output name' }), jsx_runtime_1.jsxs("div", { style: layout_2.optionRow, children: [
125
125
  jsx_runtime_1.jsxs("div", { style: layout_2.label, children: ["Log Level ",
126
- jsx_runtime_1.jsx(layout_1.Spacing, { x: 0.5 }), jsx_runtime_1.jsx(OptionExplainerBubble_1.OptionExplainerBubble, { id: "logLevelOption" })
126
+ jsx_runtime_1.jsx(layout_1.Spacing, { x: 0.5 }), jsx_runtime_1.jsx(OptionExplainerBubble_1.WebRendererOptionExplainerBubble, { apiName: "logLevel", id: "logLevelOption" })
127
127
  ] }), jsx_runtime_1.jsx("div", { style: layout_2.rightRow, children: jsx_runtime_1.jsx(ComboBox_1.Combobox, { values: logLevelOptions, selectedId: logLevel, title: "Log Level" }) })
128
128
  ] })
129
129
  ] }));
@@ -98,6 +98,7 @@ const ClientRenderQueueProcessor = () => {
98
98
  outputTarget: 'web-fs',
99
99
  licenseKey: (_c = job.licenseKey) !== null && _c !== void 0 ? _c : undefined,
100
100
  allowHtmlInCanvas: job.allowHtmlInCanvas,
101
+ pageResponsiveness: job.pageResponsiveness,
101
102
  });
102
103
  return {
103
104
  getBlob,
@@ -1,5 +1,5 @@
1
1
  import type { CompletedClientRender } from '@remotion/studio-shared';
2
- import type { RenderStillOnWebImageFormat, WebRendererAudioCodec, WebRendererContainer, WebRendererQuality, WebRendererVideoCodec } from '@remotion/web-renderer';
2
+ import type { RenderStillOnWebImageFormat, WebRendererAudioCodec, WebRendererContainer, WebRendererPageResponsiveness, WebRendererQuality, WebRendererVideoCodec } from '@remotion/web-renderer';
3
3
  import type { LogLevel } from 'remotion';
4
4
  export type ClientRenderJobProgress = {
5
5
  encodedFrames: number;
@@ -60,6 +60,7 @@ export type ClientVideoRenderJob = ClientRenderJobBase & {
60
60
  keyframeIntervalInSeconds: number;
61
61
  transparent: boolean;
62
62
  muted: boolean;
63
+ pageResponsiveness: WebRendererPageResponsiveness;
63
64
  } & ClientRenderJobDynamicStatus;
64
65
  export type RestoredClientRenderJob = CompletedClientRender & {
65
66
  status: 'done';
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
2
  import type { SelectedOutline } from './selected-outline-geometry';
3
+ import { type SelectedOutlineSnapPoint, type SelectedOutlineSnapTarget } from './selected-outline-snap';
3
4
  import { type SelectedOutlineDragTarget, type SelectedOutlineRotationDragTarget, type SelectedOutlineScaleDragTarget, type SelectedOutlineTarget } from './selected-outline-types';
4
5
  import type { TimelineSelection, TimelineSelectionInteraction } from './Timeline/TimelineSelection';
5
6
  export declare const SelectedOutlineTransformOriginHandle: React.FC<{
@@ -9,6 +10,7 @@ export declare const SelectedOutlineTransformOriginHandle: React.FC<{
9
10
  }>;
10
11
  export declare const SelectedOutlineElement: React.FC<{
11
12
  readonly allDragTargets: readonly SelectedOutlineDragTarget[];
13
+ readonly allDragOutlines: readonly SelectedOutline[];
12
14
  readonly allRotationDragTargets: readonly SelectedOutlineRotationDragTarget[];
13
15
  readonly allScaleDragTargets: readonly SelectedOutlineScaleDragTarget[];
14
16
  readonly dragging: boolean;
@@ -16,7 +18,9 @@ export declare const SelectedOutlineElement: React.FC<{
16
18
  readonly outline: SelectedOutline;
17
19
  readonly onDraggingChange: (dragging: boolean) => void;
18
20
  readonly onHoverChange: (key: string | null) => void;
21
+ readonly onSnapPointsChange: (snapPoints: readonly SelectedOutlineSnapPoint[]) => void;
19
22
  readonly onSelect: (item: TimelineSelection, interaction: TimelineSelectionInteraction) => void;
20
23
  readonly scale: number;
24
+ readonly snapTargets: readonly SelectedOutlineSnapTarget[];
21
25
  readonly target: SelectedOutlineTarget | undefined;
22
26
  }>;
@@ -43,6 +43,7 @@ const client_id_1 = require("../helpers/client-id");
43
43
  const colors_1 = require("../helpers/colors");
44
44
  const format_file_location_1 = require("../helpers/format-file-location");
45
45
  const open_in_editor_1 = require("../helpers/open-in-editor");
46
+ const editor_snapping_1 = require("../state/editor-snapping");
46
47
  const modals_1 = require("../state/modals");
47
48
  const call_api_1 = require("./call-api");
48
49
  const ConfirmationDialog_1 = require("./ConfirmationDialog");
@@ -53,6 +54,7 @@ const NotificationCenter_1 = require("./Notifications/NotificationCenter");
53
54
  const options_sidebar_tabs_1 = require("./options-sidebar-tabs");
54
55
  const selected_outline_drag_1 = require("./selected-outline-drag");
55
56
  const selected_outline_measurement_1 = require("./selected-outline-measurement");
57
+ const selected_outline_snap_1 = require("./selected-outline-snap");
56
58
  const selected_outline_types_1 = require("./selected-outline-types");
57
59
  const selected_outline_uv_1 = require("./selected-outline-uv");
58
60
  const call_add_keyframe_1 = require("./Timeline/call-add-keyframe");
@@ -70,6 +72,7 @@ const emptyContextMenuValues = [];
70
72
  const SelectedOutlineTransformOriginHandle = ({ outline, onDraggingChange, target }) => {
71
73
  var _a;
72
74
  const { setDragOverrides, clearDragOverrides, setPropStatuses } = (0, react_1.useContext)(remotion_1.Internals.VisualModeSettersContext);
75
+ const { editorSnapping } = (0, react_1.useContext)(editor_snapping_1.EditorSnappingContext);
73
76
  const transformOriginDrag = (_a = target === null || target === void 0 ? void 0 : target.transformOriginDrag) !== null && _a !== void 0 ? _a : null;
74
77
  const parsed = (0, react_1.useMemo)(() => transformOriginDrag === null
75
78
  ? null
@@ -142,11 +145,13 @@ const SelectedOutlineTransformOriginHandle = ({ outline, onDraggingChange, targe
142
145
  const snapPoint = lockedAxis === null
143
146
  ? point
144
147
  : (0, selected_outline_uv_1.getUvHandlePosition)(outline.points, axisLockedUv);
145
- const snappedUv = (0, selected_outline_drag_1.snapSelectedOutlineTransformOriginUv)({
146
- point: snapPoint,
147
- points: outline.points,
148
- uv: axisLockedUv,
149
- });
148
+ const snappedUv = editorSnapping
149
+ ? (0, selected_outline_drag_1.snapSelectedOutlineTransformOriginUv)({
150
+ point: snapPoint,
151
+ points: outline.points,
152
+ uv: axisLockedUv,
153
+ })
154
+ : axisLockedUv;
150
155
  const nextUv = (0, selected_outline_drag_1.applySelectedOutlineTransformOriginAxisLock)({
151
156
  lockedAxis,
152
157
  startUv: uv,
@@ -293,6 +298,7 @@ const SelectedOutlineTransformOriginHandle = ({ outline, onDraggingChange, targe
293
298
  window.addEventListener('keyup', onKeyChange);
294
299
  }, [
295
300
  clearDragOverrides,
301
+ editorSnapping,
296
302
  onDraggingChange,
297
303
  outline,
298
304
  parsed,
@@ -314,10 +320,11 @@ const SelectedOutlineTransformOriginHandle = ({ outline, onDraggingChange, targe
314
320
  ] }));
315
321
  };
316
322
  exports.SelectedOutlineTransformOriginHandle = SelectedOutlineTransformOriginHandle;
317
- const SelectedOutlinePolygon = ({ allDragTargets, contextMenuValues, dragging, hovered, onContextMenuOpen, outline, onDraggingChange, onHoverChange, onSelect, onTextEditStart, scale, target, }) => {
323
+ const SelectedOutlinePolygon = ({ allDragTargets, allDragOutlines, contextMenuValues, dragging, hovered, onContextMenuOpen, outline, onDraggingChange, onHoverChange, onSnapPointsChange, onSelect, onTextEditStart, scale, snapTargets, target, }) => {
318
324
  var _a, _b, _c, _d;
319
325
  const { getDragOverrides } = (0, react_1.useContext)(remotion_1.Internals.VisualModeDragOverridesContext);
320
326
  const { setPropStatuses, setDragOverrides, clearDragOverrides } = (0, react_1.useContext)(remotion_1.Internals.VisualModeSettersContext);
327
+ const { editorSnapping } = (0, react_1.useContext)(editor_snapping_1.EditorSnappingContext);
321
328
  const timelinePosition = remotion_1.Internals.Timeline.useTimelinePosition();
322
329
  const timelinePositionRef = (0, react_1.useRef)(timelinePosition);
323
330
  timelinePositionRef.current = timelinePosition;
@@ -358,6 +365,7 @@ const SelectedOutlinePolygon = ({ allDragTargets, contextMenuValues, dragging, h
358
365
  let currentPointerY = startPointerY;
359
366
  let axisLocked = false;
360
367
  let dragStarted = false;
368
+ let snappingDisabled = event.metaKey || event.ctrlKey;
361
369
  const updateDragOverrides = () => {
362
370
  const screenDeltaX = currentPointerX - startPointerX;
363
371
  const screenDeltaY = currentPointerY - startPointerY;
@@ -370,16 +378,44 @@ const SelectedOutlinePolygon = ({ allDragTargets, contextMenuValues, dragging, h
370
378
  }
371
379
  dragStarted = true;
372
380
  onDraggingChange(true);
381
+ (0, ForceSpecificCursor_1.forceSpecificCursor)('default');
373
382
  }
383
+ const axisLockedDirection = axisLocked
384
+ ? Math.abs(screenDeltaX) >= Math.abs(screenDeltaY)
385
+ ? 'horizontal'
386
+ : 'vertical'
387
+ : null;
374
388
  const dragDelta = (0, selected_outline_drag_1.applySelectedOutlineDragAxisLock)({
375
389
  deltaX: screenDeltaX / scale,
376
390
  deltaY: screenDeltaY / scale,
377
391
  axisLocked,
378
392
  });
393
+ let { deltaX, deltaY } = dragDelta;
394
+ if (editorSnapping && !snappingDisabled) {
395
+ const snapResult = (0, selected_outline_snap_1.findSelectedOutlineSnap)({
396
+ allowX: axisLockedDirection !== 'vertical',
397
+ allowY: axisLockedDirection !== 'horizontal',
398
+ deltaX,
399
+ deltaY,
400
+ outlines: selected ? allDragOutlines : [outline],
401
+ scale,
402
+ targets: snapTargets,
403
+ });
404
+ if (snapResult.snapOffsetX !== null) {
405
+ deltaX += snapResult.snapOffsetX;
406
+ }
407
+ if (snapResult.snapOffsetY !== null) {
408
+ deltaY += snapResult.snapOffsetY;
409
+ }
410
+ onSnapPointsChange(snapResult.activeSnapPoints);
411
+ }
412
+ else {
413
+ onSnapPointsChange([]);
414
+ }
379
415
  lastValues = (0, selected_outline_drag_1.getSelectedOutlineDragValues)({
380
416
  dragStates,
381
- deltaX: dragDelta.deltaX,
382
- deltaY: dragDelta.deltaY,
417
+ deltaX,
418
+ deltaY,
383
419
  });
384
420
  for (const dragState of dragStates) {
385
421
  const value = lastValues.get(dragState.key);
@@ -403,6 +439,7 @@ const SelectedOutlinePolygon = ({ allDragTargets, contextMenuValues, dragging, h
403
439
  currentPointerX = moveEvent.clientX;
404
440
  currentPointerY = moveEvent.clientY;
405
441
  axisLocked = moveEvent.shiftKey;
442
+ snappingDisabled = moveEvent.metaKey || moveEvent.ctrlKey;
406
443
  updateDragOverrides();
407
444
  };
408
445
  const onKeyChange = (keyEvent) => {
@@ -423,6 +460,7 @@ const SelectedOutlinePolygon = ({ allDragTargets, contextMenuValues, dragging, h
423
460
  window.removeEventListener('keydown', onKeyChange);
424
461
  window.removeEventListener('keyup', onKeyChange);
425
462
  if (dragStarted) {
463
+ (0, ForceSpecificCursor_1.stopForcingSpecificCursor)();
426
464
  onDraggingChange(false);
427
465
  }
428
466
  const changes = (0, selected_outline_drag_1.getSelectedOutlineDragChanges)({
@@ -474,15 +512,20 @@ const SelectedOutlinePolygon = ({ allDragTargets, contextMenuValues, dragging, h
474
512
  window.addEventListener('keyup', onKeyChange);
475
513
  }, [
476
514
  allDragTargets,
515
+ allDragOutlines,
477
516
  clearDragOverrides,
478
517
  drag,
518
+ editorSnapping,
479
519
  getDragOverrides,
480
520
  onDraggingChange,
481
521
  onSelect,
522
+ onSnapPointsChange,
523
+ outline,
482
524
  scale,
483
525
  selected,
484
526
  setPropStatuses,
485
527
  setDragOverrides,
528
+ snapTargets,
486
529
  target,
487
530
  ]);
488
531
  const onDoubleClick = react_1.default.useCallback((event) => {
@@ -718,6 +761,7 @@ const SelectedOutlineRotationCornerHandle = ({ allRotationDragTargets, contextMe
718
761
  var _a, _b;
719
762
  const { getDragOverrides } = (0, react_1.useContext)(remotion_1.Internals.VisualModeDragOverridesContext);
720
763
  const { setPropStatuses, setDragOverrides, clearDragOverrides } = (0, react_1.useContext)(remotion_1.Internals.VisualModeSettersContext);
764
+ const { editorSnapping } = (0, react_1.useContext)(editor_snapping_1.EditorSnappingContext);
721
765
  const timelinePosition = remotion_1.Internals.Timeline.useTimelinePosition();
722
766
  const timelinePositionRef = (0, react_1.useRef)(timelinePosition);
723
767
  timelinePositionRef.current = timelinePosition;
@@ -770,7 +814,7 @@ const SelectedOutlineRotationCornerHandle = ({ allRotationDragTargets, contextMe
770
814
  let lastValues = new Map();
771
815
  let dragStarted = false;
772
816
  const updateRotationDragOverrides = () => {
773
- const rotationDeltaDegrees = rotationLocked
817
+ const rotationDeltaDegrees = rotationLocked && editorSnapping
774
818
  ? (0, selected_outline_drag_1.snapSelectedOutlineRotationDeltaDegrees)({
775
819
  dragStates,
776
820
  rotationDeltaDegrees: accumulatedDelta,
@@ -904,6 +948,7 @@ const SelectedOutlineRotationCornerHandle = ({ allRotationDragTargets, contextMe
904
948
  allRotationDragTargets,
905
949
  clearDragOverrides,
906
950
  cornerInfo,
951
+ editorSnapping,
907
952
  getDragOverrides,
908
953
  onDraggingChange,
909
954
  outline.dimensions,
@@ -930,7 +975,7 @@ const SelectedOutlineRotationCornerHandle = ({ allRotationDragTargets, contextMe
930
975
  }, onPointerDown: onPointerDown }), jsx_runtime_1.jsx(ContextMenu_1.ContextMenuForTarget, { triggerRef: circleRef, values: [...contextMenuValues], onOpen: onContextMenuOpen })
931
976
  ] }));
932
977
  };
933
- const SelectedOutlineElement = ({ allDragTargets, allRotationDragTargets, allScaleDragTargets, dragging, hovered, outline, onDraggingChange, onHoverChange, onSelect, scale, target, }) => {
978
+ const SelectedOutlineElement = ({ allDragTargets, allDragOutlines, allRotationDragTargets, allScaleDragTargets, dragging, hovered, outline, onDraggingChange, onHoverChange, onSnapPointsChange, onSelect, scale, snapTargets, target, }) => {
934
979
  const { previewServerState } = (0, react_1.useContext)(client_id_1.StudioServerConnectionCtx);
935
980
  const { setPropStatuses } = (0, react_1.useContext)(remotion_1.Internals.VisualModeSettersContext);
936
981
  const updateResolvedStackTrace = (0, react_1.useContext)(remotion_1.Internals.SequenceStackTracesUpdateContext);
@@ -1114,7 +1159,7 @@ const SelectedOutlineElement = ({ allDragTargets, allRotationDragTargets, allSca
1114
1159
  updateResolvedStackTrace,
1115
1160
  ]);
1116
1161
  return (jsx_runtime_1.jsxs(jsx_runtime_1.Fragment, { children: [
1117
- jsx_runtime_1.jsx(SelectedOutlinePolygon, { allDragTargets: allDragTargets, contextMenuValues: emptyContextMenuValues, dragging: dragging, hovered: hovered, outline: outline, onContextMenuOpen: onContextMenuOpen, onDraggingChange: onDraggingChange, onHoverChange: onHoverChange, onSelect: onSelect, onTextEditStart: onTextEditStart, scale: scale, target: target }), (target === null || target === void 0 ? void 0 : target.containsSelection) || hovered
1162
+ jsx_runtime_1.jsx(SelectedOutlinePolygon, { allDragTargets: allDragTargets, allDragOutlines: allDragOutlines, contextMenuValues: emptyContextMenuValues, dragging: dragging, hovered: hovered, outline: outline, onContextMenuOpen: onContextMenuOpen, onDraggingChange: onDraggingChange, onHoverChange: onHoverChange, onSnapPointsChange: onSnapPointsChange, onSelect: onSelect, onTextEditStart: onTextEditStart, scale: scale, snapTargets: snapTargets, target: target }), (target === null || target === void 0 ? void 0 : target.containsSelection) || hovered
1118
1163
  ? ['top', 'right', 'bottom', 'left'].map((edge) => (jsx_runtime_1.jsx(SelectedOutlineScaleEdgeLine, { allScaleDragTargets: allScaleDragTargets, contextMenuValues: emptyContextMenuValues, dragging: dragging, edge: edge, outline: outline, onContextMenuOpen: onContextMenuOpen, onDraggingChange: onDraggingChange, onHoverChange: onHoverChange, onSelect: onSelect, target: target }, edge)))
1119
1164
  : null, (target === null || target === void 0 ? void 0 : target.containsSelection) || hovered
1120
1165
  ? ['top-left', 'top-right', 'bottom-right', 'bottom-left'].map((corner) => (jsx_runtime_1.jsx(SelectedOutlineRotationCornerHandle, { allRotationDragTargets: allRotationDragTargets, contextMenuValues: emptyContextMenuValues, corner: corner, dragging: dragging, outline: outline, onContextMenuOpen: onContextMenuOpen, onDraggingChange: onDraggingChange, onHoverChange: onHoverChange, onSelect: onSelect, target: target }, corner)))
@@ -10,6 +10,8 @@ export declare const orderOutlinesForRendering: ({ outlines, targetsByKey, }: {
10
10
  readonly targetsByKey: ReadonlyMap<string, SelectedOutlineTarget>;
11
11
  }) => readonly SelectedOutline[];
12
12
  export declare const SelectedOutlineOverlay: React.FC<{
13
+ readonly compositionHeight: number;
14
+ readonly compositionWidth: number;
13
15
  readonly scale: number;
14
16
  readonly translationX: number;
15
17
  readonly translationY: number;
@@ -41,11 +41,13 @@ const remotion_1 = require("remotion");
41
41
  const no_react_1 = require("remotion/no-react");
42
42
  const client_id_1 = require("../helpers/client-id");
43
43
  const use_keybinding_1 = require("../helpers/use-keybinding");
44
+ const editor_guides_1 = require("../state/editor-guides");
44
45
  const editor_outlines_1 = require("../state/editor-outlines");
45
46
  const scale_lock_1 = require("../state/scale-lock");
46
47
  const NotificationCenter_1 = require("./Notifications/NotificationCenter");
47
48
  const selected_outline_drag_1 = require("./selected-outline-drag");
48
49
  const selected_outline_measurement_1 = require("./selected-outline-measurement");
50
+ const selected_outline_snap_1 = require("./selected-outline-snap");
49
51
  const selected_outline_types_1 = require("./selected-outline-types");
50
52
  const selected_outline_uv_1 = require("./selected-outline-uv");
51
53
  const SelectedOutlineElement_1 = require("./SelectedOutlineElement");
@@ -95,6 +97,19 @@ const outlineContainer = {
95
97
  pointerEvents: 'none',
96
98
  overflow: 'visible',
97
99
  };
100
+ const SelectedOutlineSnapIndicators = ({ activeSnapPoints, compositionHeight, compositionWidth, scale }) => {
101
+ if (activeSnapPoints.length === 0) {
102
+ return null;
103
+ }
104
+ return (jsx_runtime_1.jsx("g", { pointerEvents: "none", children: activeSnapPoints.map((snapPoint) => {
105
+ if (snapPoint.target.axis === 'x') {
106
+ const x = snapPoint.target.position * scale;
107
+ return (jsx_runtime_1.jsx("line", { x1: x, x2: x, y1: 0, y2: compositionHeight * scale, stroke: selected_outline_snap_1.selectedOutlineSnapIndicatorColor, strokeWidth: 1, vectorEffect: "non-scaling-stroke" }, `${snapPoint.target.axis}-${snapPoint.target.type}-${snapPoint.target.position}-${snapPoint.edge}`));
108
+ }
109
+ const y = snapPoint.target.position * scale;
110
+ return (jsx_runtime_1.jsx("line", { x1: 0, x2: compositionWidth * scale, y1: y, y2: y, stroke: selected_outline_snap_1.selectedOutlineSnapIndicatorColor, strokeWidth: 1, vectorEffect: "non-scaling-stroke" }, `${snapPoint.target.axis}-${snapPoint.target.type}-${snapPoint.target.position}-${snapPoint.edge}`));
111
+ }) }));
112
+ };
98
113
  const orderOutlinesForRendering = ({ outlines, targetsByKey, }) => {
99
114
  return [...outlines].sort((a, b) => {
100
115
  var _a, _b;
@@ -105,9 +120,10 @@ const orderOutlinesForRendering = ({ outlines, targetsByKey, }) => {
105
120
  });
106
121
  };
107
122
  exports.orderOutlinesForRendering = orderOutlinesForRendering;
108
- const SelectedOutlineOverlay = ({ scale, translationX, translationY }) => {
123
+ const SelectedOutlineOverlay = ({ compositionHeight, compositionWidth, scale, translationX, translationY, }) => {
109
124
  const { selectedItems, selectItem } = (0, TimelineSelection_1.useTimelineSelection)();
110
125
  const { sequences } = (0, react_1.useContext)(remotion_1.Internals.SequenceManager);
126
+ const { canvasContent } = (0, react_1.useContext)(remotion_1.Internals.CompositionManager);
111
127
  const { propStatuses } = (0, react_1.useContext)(remotion_1.Internals.VisualModePropStatusesContext);
112
128
  const { previewServerState } = (0, react_1.useContext)(client_id_1.StudioServerConnectionCtx);
113
129
  const { overrideIdToNodePathMappings } = (0, react_1.useContext)(remotion_1.Internals.OverrideIdsToNodePathsGettersContext);
@@ -115,12 +131,14 @@ const SelectedOutlineOverlay = ({ scale, translationX, translationY }) => {
115
131
  const { setPropStatuses, setDragOverrides, clearDragOverrides } = (0, react_1.useContext)(remotion_1.Internals.VisualModeSettersContext);
116
132
  const { getScaleLockState } = (0, react_1.useContext)(scale_lock_1.ScaleLockContext);
117
133
  const { editorShowOutlines } = (0, react_1.useContext)(editor_outlines_1.EditorShowOutlinesContext);
134
+ const { editorShowGuides, guidesList } = (0, react_1.useContext)(editor_guides_1.EditorShowGuidesContext);
118
135
  const { frameBack, frameForward, getCurrentFrame, seek } = player_1.PlayerInternals.usePlayer();
119
136
  const keybindings = (0, use_keybinding_1.useKeybinding)();
120
137
  const timelinePosition = remotion_1.Internals.Timeline.useTimelinePosition();
121
138
  const [outlines, setOutlines] = (0, react_1.useState)([]);
122
139
  const [hoveredOutlineKey, setHoveredOutlineKey] = (0, react_1.useState)(null);
123
140
  const [draggingOutline, setDraggingOutline] = (0, react_1.useState)(false);
141
+ const [activeSnapPoints, setActiveSnapPoints] = (0, react_1.useState)([]);
124
142
  const overlayRef = (0, react_1.useRef)(null);
125
143
  const keyboardNudgeSessionRef = (0, react_1.useRef)(null);
126
144
  const saveKeyboardNudgeSessionRef = (0, react_1.useRef)(() => undefined);
@@ -129,6 +147,12 @@ const SelectedOutlineOverlay = ({ scale, translationX, translationY }) => {
129
147
  if (dragging) {
130
148
  setHoveredOutlineKey(null);
131
149
  }
150
+ else {
151
+ setActiveSnapPoints([]);
152
+ }
153
+ }, []);
154
+ const onSnapPointsChange = (0, react_1.useCallback)((snapPoints) => {
155
+ setActiveSnapPoints(snapPoints);
132
156
  }, []);
133
157
  const selectOutlineItem = (0, react_1.useCallback)((item, interaction) => {
134
158
  selectItem(item, interaction, undefined, { reveal: true });
@@ -391,11 +415,24 @@ const SelectedOutlineOverlay = ({ scale, translationX, translationY }) => {
391
415
  const outlinesForRendering = (0, react_1.useMemo)(() => {
392
416
  return (0, exports.orderOutlinesForRendering)({ outlines, targetsByKey });
393
417
  }, [outlines, targetsByKey]);
418
+ const outlinesByKey = (0, react_1.useMemo)(() => {
419
+ return new Map(outlines.map((outline) => [outline.key, outline]));
420
+ }, [outlines]);
394
421
  const allDragTargets = (0, react_1.useMemo)(() => {
395
422
  return outlineTargets.flatMap((target) => (target.selected || target.containsSelection) && target.drag !== null
396
423
  ? [target.drag]
397
424
  : []);
398
425
  }, [outlineTargets]);
426
+ const allDragOutlines = (0, react_1.useMemo)(() => {
427
+ return outlineTargets.flatMap((target) => {
428
+ if ((!target.selected && !target.containsSelection) ||
429
+ target.drag === null) {
430
+ return [];
431
+ }
432
+ const outline = outlinesByKey.get(target.key);
433
+ return outline === undefined ? [] : [outline];
434
+ });
435
+ }, [outlineTargets, outlinesByKey]);
399
436
  const allScaleDragTargets = (0, react_1.useMemo)(() => {
400
437
  return outlineTargets.flatMap((target) => target.selected && target.scaleDrag !== null ? [target.scaleDrag] : []);
401
438
  }, [outlineTargets]);
@@ -404,6 +441,19 @@ const SelectedOutlineOverlay = ({ scale, translationX, translationY }) => {
404
441
  ? [target.rotationDrag]
405
442
  : []);
406
443
  }, [outlineTargets]);
444
+ const guidesForSnap = (0, react_1.useMemo)(() => {
445
+ if (!editorShowGuides || (canvasContent === null || canvasContent === void 0 ? void 0 : canvasContent.type) !== 'composition') {
446
+ return [];
447
+ }
448
+ return guidesList.filter((guide) => guide.compositionId === canvasContent.compositionId);
449
+ }, [canvasContent, editorShowGuides, guidesList]);
450
+ const snapTargets = (0, react_1.useMemo)(() => {
451
+ return (0, selected_outline_snap_1.getSelectedOutlineSnapTargets)({
452
+ compositionHeight,
453
+ compositionWidth,
454
+ guides: guidesForSnap,
455
+ });
456
+ }, [compositionHeight, compositionWidth, guidesForSnap]);
407
457
  const saveKeyboardNudgeSession = (0, react_1.useCallback)(() => {
408
458
  const session = keyboardNudgeSessionRef.current;
409
459
  if (session === null) {
@@ -671,6 +721,7 @@ const SelectedOutlineOverlay = ({ scale, translationX, translationY }) => {
671
721
  if (outlineTargets.length === 0) {
672
722
  return null;
673
723
  }
674
- return (jsx_runtime_1.jsxs("svg", { ref: overlayRef, style: outlineContainer, width: "100%", height: "100%", "aria-hidden": "true", children: [outlinesForRendering.map((outline) => (jsx_runtime_1.jsx(SelectedOutlineElement_1.SelectedOutlineElement, { allDragTargets: allDragTargets, allRotationDragTargets: allRotationDragTargets, allScaleDragTargets: allScaleDragTargets, dragging: draggingOutline, hovered: hoveredOutlineKey === outline.key, outline: outline, onDraggingChange: onDraggingChange, onHoverChange: setHoveredOutlineKey, onSelect: selectOutlineItem, scale: scale, target: targetsByKey.get(outline.key) }, outline.key))), outlinesForRendering.map((outline) => (jsx_runtime_1.jsx(SelectedOutlineElement_1.SelectedOutlineTransformOriginHandle, { outline: outline, onDraggingChange: onDraggingChange, target: targetsByKey.get(outline.key) }, `${outline.key}-transform-origin`))), outlinesForRendering.map((outline) => (jsx_runtime_1.jsx(SelectedOutlineUvControls_1.SelectedOutlineUvHandleConnectionLayer, { outline: outline, target: targetsByKey.get(outline.key) }, `${outline.key}-uv-connection-lines`))), outlinesForRendering.map((outline) => (jsx_runtime_1.jsx(SelectedOutlineUvControls_1.SelectedOutlineUvHandleCircleLayer, { onDraggingChange: onDraggingChange, onSelect: selectOutlineItem, outline: outline, target: targetsByKey.get(outline.key) }, `${outline.key}-uv-handles`)))] }));
724
+ return (jsx_runtime_1.jsxs("svg", { ref: overlayRef, style: outlineContainer, width: "100%", height: "100%", "aria-hidden": "true", children: [
725
+ jsx_runtime_1.jsx(SelectedOutlineSnapIndicators, { activeSnapPoints: activeSnapPoints, compositionHeight: compositionHeight, compositionWidth: compositionWidth, scale: scale }), outlinesForRendering.map((outline) => (jsx_runtime_1.jsx(SelectedOutlineElement_1.SelectedOutlineElement, { allDragTargets: allDragTargets, allDragOutlines: allDragOutlines, allRotationDragTargets: allRotationDragTargets, allScaleDragTargets: allScaleDragTargets, dragging: draggingOutline, hovered: hoveredOutlineKey === outline.key, outline: outline, onDraggingChange: onDraggingChange, onHoverChange: setHoveredOutlineKey, onSnapPointsChange: onSnapPointsChange, onSelect: selectOutlineItem, scale: scale, snapTargets: snapTargets, target: targetsByKey.get(outline.key) }, outline.key))), outlinesForRendering.map((outline) => (jsx_runtime_1.jsx(SelectedOutlineElement_1.SelectedOutlineTransformOriginHandle, { outline: outline, onDraggingChange: onDraggingChange, target: targetsByKey.get(outline.key) }, `${outline.key}-transform-origin`))), outlinesForRendering.map((outline) => (jsx_runtime_1.jsx(SelectedOutlineUvControls_1.SelectedOutlineUvHandleConnectionLayer, { outline: outline, target: targetsByKey.get(outline.key) }, `${outline.key}-uv-connection-lines`))), outlinesForRendering.map((outline) => (jsx_runtime_1.jsx(SelectedOutlineUvControls_1.SelectedOutlineUvHandleCircleLayer, { onDraggingChange: onDraggingChange, onSelect: selectOutlineItem, outline: outline, target: targetsByKey.get(outline.key) }, `${outline.key}-uv-handles`)))] }));
675
726
  };
676
727
  exports.SelectedOutlineOverlay = SelectedOutlineOverlay;
@@ -38,6 +38,7 @@ const jsx_runtime_1 = require("react/jsx-runtime");
38
38
  const react_1 = __importStar(require("react"));
39
39
  const remotion_1 = require("remotion");
40
40
  const colors_1 = require("../helpers/colors");
41
+ const editor_snapping_1 = require("../state/editor-snapping");
41
42
  const ForceSpecificCursor_1 = require("./ForceSpecificCursor");
42
43
  const selected_outline_drag_1 = require("./selected-outline-drag");
43
44
  const selected_outline_measurement_1 = require("./selected-outline-measurement");
@@ -384,6 +385,7 @@ const trimLineToCircleEdges = ({ from, fromRadius, to, toRadius, }) => {
384
385
  };
385
386
  const SelectedUvEllipseRotationHandle = ({ control, onDraggingChange }) => {
386
387
  const { setEffectDragOverrides, clearEffectDragOverrides, setPropStatuses } = (0, react_1.useContext)(remotion_1.Internals.VisualModeSettersContext);
388
+ const { editorSnapping } = (0, react_1.useContext)(editor_snapping_1.EditorSnappingContext);
387
389
  const { rotationControl } = control;
388
390
  const field = rotationControl === null || rotationControl === void 0 ? void 0 : rotationControl.field;
389
391
  const onPointerDown = react_1.default.useCallback((event) => {
@@ -413,7 +415,7 @@ const SelectedUvEllipseRotationHandle = ({ control, onDraggingChange }) => {
413
415
  let lastValue = null;
414
416
  const updateRotationDragOverride = () => {
415
417
  const rawValue = control.rotation + accumulatedDelta;
416
- const snappedValue = rotationLocked
418
+ const snappedValue = rotationLocked && editorSnapping
417
419
  ? snapRotationDegrees(rawValue)
418
420
  : rawValue;
419
421
  const nextValue = (0, selected_outline_uv_1.roundNumericUvEllipseValue)(snappedValue, field.fieldSchema);
@@ -497,6 +499,7 @@ const SelectedUvEllipseRotationHandle = ({ control, onDraggingChange }) => {
497
499
  }, [
498
500
  clearEffectDragOverrides,
499
501
  control,
502
+ editorSnapping,
500
503
  field,
501
504
  onDraggingChange,
502
505
  rotationControl,
@@ -522,6 +525,7 @@ const SelectedUvEllipseRotationHandle = ({ control, onDraggingChange }) => {
522
525
  };
523
526
  const SelectedUvHandleCircle = ({ handle, nodePathInfo, onDraggingChange, onSelect, outline }) => {
524
527
  const { setEffectDragOverrides, clearEffectDragOverrides, setPropStatuses } = (0, react_1.useContext)(remotion_1.Internals.VisualModeSettersContext);
528
+ const { editorSnapping } = (0, react_1.useContext)(editor_snapping_1.EditorSnappingContext);
525
529
  const position = (0, react_1.useMemo)(() => (0, selected_outline_uv_1.getUvHandlePosition)(outline.points, handle.value), [handle.value, outline.points]);
526
530
  const onPointerDown = react_1.default.useCallback((event) => {
527
531
  if (event.button !== 0) {
@@ -556,11 +560,13 @@ const SelectedUvHandleCircle = ({ handle, nodePathInfo, onDraggingChange, onSele
556
560
  rect: svgRect,
557
561
  });
558
562
  const rawUv = (0, selected_outline_uv_1.getUvCoordinateForPoint)(outline.points, point);
559
- const snappedUv = (0, selected_outline_drag_1.snapSelectedOutlineUv)({
560
- point,
561
- points: outline.points,
562
- uv: rawUv,
563
- });
563
+ const snappedUv = editorSnapping
564
+ ? (0, selected_outline_drag_1.snapSelectedOutlineUv)({
565
+ point,
566
+ points: outline.points,
567
+ uv: rawUv,
568
+ })
569
+ : rawUv;
564
570
  const nextValue = (0, selected_outline_uv_1.roundUvCoordinate)((0, selected_outline_uv_1.constrainUv)(snappedUv, handle.fieldSchema), handle.fieldSchema);
565
571
  lastValue = nextValue;
566
572
  setEffectDragOverrides(handle.nodePath, handle.effectIndex, handle.fieldKey, handle.propStatus.status === 'keyframed'
@@ -644,6 +650,7 @@ const SelectedUvHandleCircle = ({ handle, nodePathInfo, onDraggingChange, onSele
644
650
  window.addEventListener('pointercancel', onPointerUp);
645
651
  }, [
646
652
  clearEffectDragOverrides,
653
+ editorSnapping,
647
654
  handle,
648
655
  nodePathInfo,
649
656
  onDraggingChange,
@@ -0,0 +1,4 @@
1
+ import React from 'react';
2
+ export declare const SnappingProvider: React.FC<{
3
+ readonly children: React.ReactNode;
4
+ }>;
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SnappingProvider = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("react");
6
+ const editor_snapping_1 = require("../state/editor-snapping");
7
+ const SnappingProvider = ({ children }) => {
8
+ const [editorSnapping, setEditorSnappingState] = (0, react_1.useState)(() => (0, editor_snapping_1.loadEditorSnappingOption)());
9
+ const setEditorSnapping = (0, react_1.useCallback)((newValue) => {
10
+ setEditorSnappingState((prevState) => {
11
+ const newVal = newValue(prevState);
12
+ (0, editor_snapping_1.persistEditorSnappingOption)(newVal);
13
+ return newVal;
14
+ });
15
+ }, []);
16
+ const editorSnappingCtx = (0, react_1.useMemo)(() => {
17
+ return {
18
+ editorSnapping,
19
+ setEditorSnapping,
20
+ };
21
+ }, [editorSnapping, setEditorSnapping]);
22
+ return (jsx_runtime_1.jsx(editor_snapping_1.EditorSnappingContext.Provider, { value: editorSnappingCtx, children: children }));
23
+ };
24
+ exports.SnappingProvider = SnappingProvider;
@@ -0,0 +1,2 @@
1
+ import React from 'react';
2
+ export declare const SnappingToggle: React.FC;