@remotion/studio-server 4.0.473 → 4.0.475

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.
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.allApiRoutes = void 0;
4
4
  const add_effect_1 = require("./routes/add-effect");
5
5
  const add_effect_keyframe_1 = require("./routes/add-effect-keyframe");
6
+ const add_keyframes_1 = require("./routes/add-keyframes");
6
7
  const add_render_1 = require("./routes/add-render");
7
8
  const add_sequence_keyframe_1 = require("./routes/add-sequence-keyframe");
8
9
  const apply_codemod_1 = require("./routes/apply-codemod");
@@ -71,6 +72,7 @@ exports.allApiRoutes = {
71
72
  '/api/move-keyframes': move_keyframes_1.moveKeyframesHandler,
72
73
  '/api/add-sequence-keyframe': add_sequence_keyframe_1.addSequenceKeyframeHandler,
73
74
  '/api/add-effect-keyframe': add_effect_keyframe_1.addEffectKeyframeHandler,
75
+ '/api/add-keyframes': add_keyframes_1.addKeyframesHandler,
74
76
  '/api/update-sequence-keyframe-settings': update_sequence_keyframe_settings_1.updateSequenceKeyframeSettingsHandler,
75
77
  '/api/update-effect-keyframe-settings': update_effect_keyframe_settings_1.updateEffectKeyframeSettingsHandler,
76
78
  '/api/delete-effect': delete_effect_1.deleteEffectHandler,
@@ -0,0 +1,7 @@
1
+ import type { AddKeyframesRequest, AddKeyframesResponse } from '@remotion/studio-shared';
2
+ import type { ApiHandler } from '../api-types';
3
+ export declare const addKeyframes: ({ sequenceKeyframes, effectKeyframes, clientId, remotionRoot, logLevel, }: AddKeyframesRequest & {
4
+ readonly remotionRoot: string;
5
+ readonly logLevel: "error" | "info" | "trace" | "verbose" | "warn";
6
+ }) => Promise<void>;
7
+ export declare const addKeyframesHandler: ApiHandler<AddKeyframesRequest, AddKeyframesResponse>;
@@ -0,0 +1,226 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.addKeyframesHandler = exports.addKeyframes = void 0;
4
+ const node_fs_1 = require("node:fs");
5
+ const renderer_1 = require("@remotion/renderer");
6
+ const update_keyframes_1 = require("../../codemods/update-keyframes/update-keyframes");
7
+ const file_watcher_1 = require("../../file-watcher");
8
+ const resolve_file_inside_project_1 = require("../../helpers/resolve-file-inside-project");
9
+ const undo_stack_1 = require("../undo-stack");
10
+ const watch_ignore_next_change_1 = require("../watch-ignore-next-change");
11
+ const log_effect_update_1 = require("./log-updates/log-effect-update");
12
+ const log_update_1 = require("./log-updates/log-update");
13
+ const save_props_mutex_1 = require("./save-props-mutex");
14
+ const groupBy = (items, getKey) => {
15
+ var _a;
16
+ const groups = new Map();
17
+ for (const item of items) {
18
+ const key = getKey(item);
19
+ const group = (_a = groups.get(key)) !== null && _a !== void 0 ? _a : [];
20
+ group.push(item);
21
+ groups.set(key, group);
22
+ }
23
+ return [...groups.values()];
24
+ };
25
+ const getBatchDescription = ({ totalKeyframes, firstKeyframe, }) => {
26
+ if (totalKeyframes === 1) {
27
+ return {
28
+ undoMessage: `↩️ ${firstKeyframe.key} keyframe removed at frame ${firstKeyframe.frame}`,
29
+ redoMessage: `↪️ ${firstKeyframe.key} keyframe added at frame ${firstKeyframe.frame}`,
30
+ };
31
+ }
32
+ return {
33
+ undoMessage: `↩️ ${totalKeyframes} keyframes removed`,
34
+ redoMessage: `↪️ ${totalKeyframes} keyframes added`,
35
+ };
36
+ };
37
+ const addKeyframes = async ({ sequenceKeyframes, effectKeyframes, clientId, remotionRoot, logLevel, }) => {
38
+ var _a, _b;
39
+ const totalKeyframes = sequenceKeyframes.length + effectKeyframes.length;
40
+ if (totalKeyframes === 0) {
41
+ throw new Error('No keyframes were specified for adding');
42
+ }
43
+ const fileGroups = new Map();
44
+ for (const [index, keyframe] of sequenceKeyframes.entries()) {
45
+ const { absolutePath, fileRelativeToRoot } = (0, resolve_file_inside_project_1.resolveFileInsideProject)({
46
+ remotionRoot,
47
+ fileName: keyframe.fileName,
48
+ action: 'modify',
49
+ });
50
+ const group = (_a = fileGroups.get(absolutePath)) !== null && _a !== void 0 ? _a : {
51
+ fileRelativeToRoot,
52
+ sequenceKeyframes: [],
53
+ effectKeyframes: [],
54
+ };
55
+ group.sequenceKeyframes.push({
56
+ ...keyframe,
57
+ index,
58
+ absolutePath,
59
+ fileRelativeToRoot,
60
+ });
61
+ fileGroups.set(absolutePath, group);
62
+ }
63
+ for (const [index, keyframe] of effectKeyframes.entries()) {
64
+ const { absolutePath, fileRelativeToRoot } = (0, resolve_file_inside_project_1.resolveFileInsideProject)({
65
+ remotionRoot,
66
+ fileName: keyframe.fileName,
67
+ action: 'modify',
68
+ });
69
+ const group = (_b = fileGroups.get(absolutePath)) !== null && _b !== void 0 ? _b : {
70
+ fileRelativeToRoot,
71
+ sequenceKeyframes: [],
72
+ effectKeyframes: [],
73
+ };
74
+ group.effectKeyframes.push({
75
+ ...keyframe,
76
+ index,
77
+ absolutePath,
78
+ fileRelativeToRoot,
79
+ });
80
+ fileGroups.set(absolutePath, group);
81
+ }
82
+ const snapshots = [];
83
+ const sequenceLogs = [];
84
+ const effectLogs = [];
85
+ for (const [absolutePath, group] of fileGroups) {
86
+ const fileContents = (0, node_fs_1.readFileSync)(absolutePath, 'utf-8');
87
+ let output = fileContents;
88
+ let firstLogLine = Number.POSITIVE_INFINITY;
89
+ for (const keyframeGroup of groupBy(group.sequenceKeyframes, (keyframe) => JSON.stringify(keyframe.nodePath.nodePath))) {
90
+ const [firstSequenceKeyframe] = keyframeGroup;
91
+ if (!firstSequenceKeyframe) {
92
+ continue;
93
+ }
94
+ const result = await (0, update_keyframes_1.updateSequenceKeyframes)({
95
+ input: output,
96
+ nodePath: firstSequenceKeyframe.nodePath.nodePath,
97
+ schema: firstSequenceKeyframe.schema,
98
+ updates: keyframeGroup.map((keyframe) => ({
99
+ key: keyframe.key,
100
+ operation: {
101
+ type: 'add',
102
+ frame: keyframe.frame,
103
+ value: JSON.parse(keyframe.value),
104
+ },
105
+ })),
106
+ });
107
+ output = result.output;
108
+ firstLogLine = Math.min(firstLogLine, result.logLine);
109
+ for (const [keyframeIndex, keyframe] of keyframeGroup.entries()) {
110
+ sequenceLogs.push({
111
+ fileRelativeToRoot: keyframe.fileRelativeToRoot,
112
+ line: result.logLine,
113
+ key: keyframe.key,
114
+ oldValueString: result.oldValueStrings[keyframeIndex],
115
+ newValueString: result.newValueStrings[keyframeIndex],
116
+ formatted: result.formatted,
117
+ });
118
+ }
119
+ }
120
+ for (const keyframeGroup of groupBy(group.effectKeyframes, (keyframe) => `${JSON.stringify(keyframe.sequenceNodePath.nodePath)}:${keyframe.effectIndex}`)) {
121
+ const [firstEffectKeyframe] = keyframeGroup;
122
+ if (!firstEffectKeyframe) {
123
+ continue;
124
+ }
125
+ const result = await (0, update_keyframes_1.updateEffectKeyframes)({
126
+ input: output,
127
+ sequenceNodePath: firstEffectKeyframe.sequenceNodePath.nodePath,
128
+ effectIndex: firstEffectKeyframe.effectIndex,
129
+ schema: firstEffectKeyframe.schema,
130
+ updates: keyframeGroup.map((keyframe) => ({
131
+ key: keyframe.key,
132
+ operation: {
133
+ type: 'add',
134
+ frame: keyframe.frame,
135
+ value: JSON.parse(keyframe.value),
136
+ },
137
+ })),
138
+ });
139
+ output = result.output;
140
+ firstLogLine = Math.min(firstLogLine, result.logLine);
141
+ for (const [keyframeIndex, keyframe] of keyframeGroup.entries()) {
142
+ effectLogs.push({
143
+ fileRelativeToRoot: keyframe.fileRelativeToRoot,
144
+ line: result.logLine,
145
+ effectName: result.effectCallee,
146
+ propKey: keyframe.key,
147
+ oldValueString: result.oldValueStrings[keyframeIndex],
148
+ newValueString: result.newValueStrings[keyframeIndex],
149
+ formatted: result.formatted,
150
+ });
151
+ }
152
+ }
153
+ snapshots.push({
154
+ filePath: absolutePath,
155
+ oldContents: fileContents,
156
+ newContents: output,
157
+ logLine: Number.isFinite(firstLogLine) ? firstLogLine : 1,
158
+ });
159
+ }
160
+ const [firstKeyframe] = sequenceKeyframes.length > 0 ? sequenceKeyframes : effectKeyframes;
161
+ if (!firstKeyframe) {
162
+ throw new Error('No keyframes were specified for adding');
163
+ }
164
+ (0, undo_stack_1.pushTransactionToUndoStack)({
165
+ snapshots,
166
+ logLevel,
167
+ remotionRoot,
168
+ description: getBatchDescription({ totalKeyframes, firstKeyframe }),
169
+ entryType: sequenceKeyframes.length > 0 && effectKeyframes.length > 0
170
+ ? // Dead code for now: sequence props and effect props cannot be selected
171
+ // together yet. Keep the mixed transaction type for the planned UI.
172
+ 'keyframe-add'
173
+ : sequenceKeyframes.length > 0
174
+ ? 'sequence-props'
175
+ : 'effect-props',
176
+ suppressHmrOnFileRestore: true,
177
+ });
178
+ for (const snapshot of snapshots) {
179
+ (0, undo_stack_1.suppressUndoStackInvalidation)(snapshot.filePath);
180
+ (0, watch_ignore_next_change_1.suppressBundlerUpdateForFile)(snapshot.filePath);
181
+ (0, file_watcher_1.writeFileAndNotifyFileWatchers)(snapshot.filePath, snapshot.newContents, clientId);
182
+ }
183
+ for (const log of sequenceLogs) {
184
+ (0, log_update_1.logUpdate)({
185
+ fileRelativeToRoot: log.fileRelativeToRoot,
186
+ line: log.line,
187
+ key: log.key,
188
+ oldValueString: log.oldValueString,
189
+ newValueString: log.newValueString,
190
+ defaultValueString: null,
191
+ formatted: log.formatted,
192
+ logLevel,
193
+ removedProps: [],
194
+ addedProps: [],
195
+ });
196
+ }
197
+ for (const log of effectLogs) {
198
+ (0, log_effect_update_1.logEffectUpdate)({
199
+ fileRelativeToRoot: log.fileRelativeToRoot,
200
+ line: log.line,
201
+ effectName: log.effectName,
202
+ propKey: log.propKey,
203
+ oldValueString: log.oldValueString,
204
+ newValueString: log.newValueString,
205
+ defaultValueString: null,
206
+ formatted: log.formatted,
207
+ logLevel,
208
+ removedProps: [],
209
+ addedProps: [],
210
+ });
211
+ }
212
+ (0, undo_stack_1.printUndoHint)(logLevel);
213
+ };
214
+ exports.addKeyframes = addKeyframes;
215
+ const addKeyframesHandler = ({ input: { sequenceKeyframes, effectKeyframes, clientId }, remotionRoot, logLevel, }) => (0, save_props_mutex_1.withSavePropsLock)(async () => {
216
+ renderer_1.RenderInternals.Log.trace({ indent: false, logLevel }, `[add-keyframes] Received request to add ${sequenceKeyframes.length + effectKeyframes.length} keyframe(s)`);
217
+ await (0, exports.addKeyframes)({
218
+ sequenceKeyframes,
219
+ effectKeyframes,
220
+ clientId,
221
+ remotionRoot,
222
+ logLevel,
223
+ });
224
+ return { success: true };
225
+ });
226
+ exports.addKeyframesHandler = addKeyframesHandler;
@@ -260,9 +260,6 @@ const getInterpolationMetadata = (interpolationFunction, callExpression, keyfram
260
260
  }
261
261
  const value = property.value;
262
262
  if (key === 'easing') {
263
- if (interpolationFunction === 'interpolateColors') {
264
- return null;
265
- }
266
263
  const parsedEasing = getKeyframeEasingArray({
267
264
  easingNode: value,
268
265
  segments,
@@ -406,7 +403,6 @@ const getComputedStatus = (node, ast) => {
406
403
  }
407
404
  return {
408
405
  status: 'keyframed',
409
- codeValue: undefined,
410
406
  interpolationFunction: interpolation.interpolationFunction,
411
407
  keyframes: interpolation.keyframes,
412
408
  easing: interpolation.easing,
@@ -553,11 +549,11 @@ const getNestedPropStatus = (jsxElement, ast, parentKey, childKey) => {
553
549
  if (!(0, exports.isStaticValue)(propValue)) {
554
550
  return (0, exports.getComputedStatus)(propValue, ast);
555
551
  }
556
- const codeValue = (0, exports.extractStaticValue)(propValue);
557
- if (!validateStyleValue(childKey, codeValue)) {
552
+ const propStatus = (0, exports.extractStaticValue)(propValue);
553
+ if (!validateStyleValue(childKey, propStatus)) {
558
554
  return computedStatus();
559
555
  }
560
- return staticStatus(codeValue);
556
+ return staticStatus(propStatus);
561
557
  };
562
558
  const computeEffectsForJsx = ({ ast, jsxElement, effects, }) => {
563
559
  return effects.map((effect, effectIndex) => (0, can_update_effect_props_1.computeEffectPropStatus)({
@@ -255,6 +255,7 @@ const downloadRemoteAssetHandler = async ({ input, publicDir, request }) => {
255
255
  type: 'asset',
256
256
  assetType: fileType.type === 'gif' ? 'gif' : 'image',
257
257
  src: assetPath,
258
+ srcType: 'static',
258
259
  dimensions: fileType.dimensions,
259
260
  };
260
261
  return {
@@ -1,3 +1,3 @@
1
- import type { InsertJsxElementRequest, InsertJsxElementResponse } from '@remotion/studio-shared';
1
+ import { type InsertJsxElementRequest, type InsertJsxElementResponse } from '@remotion/studio-shared';
2
2
  import type { ApiHandler } from '../api-types';
3
3
  export declare const insertJsxElementHandler: ApiHandler<InsertJsxElementRequest, InsertJsxElementResponse>;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.insertJsxElementHandler = void 0;
4
4
  const renderer_1 = require("@remotion/renderer");
5
+ const studio_shared_1 = require("@remotion/studio-shared");
5
6
  const file_watcher_1 = require("../../file-watcher");
6
7
  const resolve_composition_component_1 = require("../../helpers/resolve-composition-component");
7
8
  const format_log_file_location_1 = require("../format-log-file-location");
@@ -20,7 +21,12 @@ const validateElement = (element) => {
20
21
  return;
21
22
  }
22
23
  if (element.type === 'asset') {
23
- if (!element.src || element.src.includes('\\')) {
24
+ if (element.srcType === 'remote') {
25
+ if (!(0, studio_shared_1.isUrl)(element.src)) {
26
+ throw new Error('Remote asset source must be a URL');
27
+ }
28
+ }
29
+ else if (!element.src || element.src.includes('\\')) {
24
30
  throw new Error('Asset path must be a static file path');
25
31
  }
26
32
  if (element.dimensions) {
@@ -29,6 +35,21 @@ const validateElement = (element) => {
29
35
  }
30
36
  return;
31
37
  }
38
+ if (element.type === 'component') {
39
+ if (!(0, studio_shared_1.isComponentIdentifier)(element.componentName)) {
40
+ throw new Error('Unsupported component name');
41
+ }
42
+ if (!(0, studio_shared_1.isComponentIdentifier)(element.importName)) {
43
+ throw new Error('Unsupported component import name');
44
+ }
45
+ if (!(0, studio_shared_1.isComponentImportPath)(element.importPath)) {
46
+ throw new Error('Unsupported component import path');
47
+ }
48
+ if (!(0, studio_shared_1.areComponentProps)(element.props)) {
49
+ throw new Error('Unsupported component props');
50
+ }
51
+ return;
52
+ }
32
53
  throw new Error('Unsupported element type');
33
54
  };
34
55
  const getElementLabel = (element) => {
@@ -49,6 +70,9 @@ const getElementLabel = (element) => {
49
70
  return '<Audio>';
50
71
  }
51
72
  }
73
+ if (element.type === 'component') {
74
+ return `<${element.componentName}>`;
75
+ }
52
76
  throw new Error('Unsupported element type');
53
77
  };
54
78
  const insertJsxElementHandler = ({ input: { compositionFile, compositionId, element }, remotionRoot, logLevel, }) => (0, save_props_mutex_1.withSavePropsLock)(async () => {
@@ -10,7 +10,7 @@ const formatInnerPropChange = ({ key, oldValueString, newValueString, defaultVal
10
10
  if (defaultValueString !== null && oldValueString === defaultValueString) {
11
11
  return (0, formatting_1.formatAddition)({ valueString: newValueString, key });
12
12
  }
13
- return `${(0, formatting_1.formatPropDelta)({ valueString: oldValueString, key })} \u2192 ${(0, formatting_1.formatPropDelta)({ valueString: newValueString, key })}`;
13
+ return (0, formatting_1.formatPropChangeDelta)({ key, oldValueString, newValueString });
14
14
  };
15
15
  const formatPropChange = ({ key, oldValueString, newValueString, defaultValueString, removedProps, addedProps, }) => {
16
16
  const suffix = (0, format_side_props_1.formatSideProps)({ removedProps, addedProps });
@@ -7,6 +7,7 @@ export declare const equals: (str: string) => string;
7
7
  export declare const punctuation: (str: string) => string;
8
8
  export declare const stringValue: (str: string) => string;
9
9
  export declare const numberValue: (str: string) => string;
10
+ export declare const inlineAddition: (str: string) => string;
10
11
  export declare const strikeThrough: (str: string) => string;
11
12
  export declare const strikeThroughOrRemovedPrefix: (str: string) => string;
12
13
  export declare const addedPrefixIfNoColor: (str: string) => string;
@@ -15,5 +16,10 @@ export type PropDelta = {
15
16
  valueString: string;
16
17
  };
17
18
  export declare const formatPropDelta: ({ key, valueString }: PropDelta) => string;
19
+ export declare const formatPropChangeDelta: ({ key, oldValueString, newValueString, }: {
20
+ key: string;
21
+ oldValueString: string;
22
+ newValueString: string;
23
+ }) => string;
18
24
  export declare const formatDeletion: (prop: PropDelta) => string;
19
25
  export declare const formatAddition: (prop: PropDelta) => string;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.formatAddition = exports.formatDeletion = exports.formatPropDelta = exports.addedPrefixIfNoColor = exports.strikeThroughOrRemovedPrefix = exports.strikeThrough = exports.numberValue = exports.stringValue = exports.punctuation = exports.equals = exports.attrName = exports.bg = exports.fg = exports.colorValue = exports.colorEnabled = void 0;
3
+ exports.formatAddition = exports.formatDeletion = exports.formatPropChangeDelta = exports.formatPropDelta = exports.addedPrefixIfNoColor = exports.strikeThroughOrRemovedPrefix = exports.strikeThrough = exports.inlineAddition = exports.numberValue = exports.stringValue = exports.punctuation = exports.equals = exports.attrName = exports.bg = exports.fg = exports.colorValue = exports.colorEnabled = void 0;
4
4
  const renderer_1 = require("@remotion/renderer");
5
5
  const colorEnabled = () => renderer_1.RenderInternals.chalk.enabled();
6
6
  exports.colorEnabled = colorEnabled;
@@ -35,6 +35,8 @@ const stringValue = (str) => (0, exports.fg)(230, 219, 116, str);
35
35
  exports.stringValue = stringValue;
36
36
  const numberValue = (str) => (0, exports.fg)(174, 129, 255, str);
37
37
  exports.numberValue = numberValue;
38
+ const inlineAddition = (str) => (0, exports.fg)(166, 226, 46, str);
39
+ exports.inlineAddition = inlineAddition;
38
40
  const strikeThrough = (str) => `\u001b[9m\u001b[38;2;255;85;85m${stripAnsi(str)}\u001b[39m\u001b[29m`;
39
41
  exports.strikeThrough = strikeThrough;
40
42
  const strikeThroughOrRemovedPrefix = (str) => (0, exports.colorEnabled)() ? (0, exports.strikeThrough)(str) : 'removed: ' + str;
@@ -49,6 +51,25 @@ const formatSimpleProp = (key, value) => {
49
51
  const formatNestedProp = (parentKey, childKey, value) => {
50
52
  return `${(0, exports.attrName)(parentKey)}${(0, exports.equals)('=')}${(0, exports.punctuation)('{{')}${(0, exports.punctuation)(childKey)}${(0, exports.punctuation)(':')} ${(0, exports.colorValue)(value)}${(0, exports.punctuation)('}}')}`;
51
53
  };
54
+ const formatValueDelta = ({ oldValueString, newValueString, }) => {
55
+ const withoutUnchangedOptions = shortenUnchangedInterpolateOptions({
56
+ oldValueString,
57
+ newValueString,
58
+ });
59
+ const shortened = removeUnchangedInterpolateOptionProperties(withoutUnchangedOptions);
60
+ const inlineDelta = formatInlineAdditionOrRemoval(shortened);
61
+ if (inlineDelta !== null) {
62
+ return inlineDelta;
63
+ }
64
+ return `${(0, exports.colorValue)(shortened.oldValueString)} ${(0, exports.punctuation)('→')} ${(0, exports.colorValue)(shortened.newValueString)}`;
65
+ };
66
+ const formatSimplePropChange = ({ key, oldValueString, newValueString, }) => {
67
+ return `${(0, exports.attrName)(key)}${(0, exports.equals)('=')}${(0, exports.punctuation)('{')}${formatValueDelta({ oldValueString, newValueString })}${(0, exports.punctuation)('}')}`;
68
+ };
69
+ const formatNestedPropChange = ({ key, oldValueString, newValueString, }) => {
70
+ const dotIdx = key.indexOf('.');
71
+ return `${(0, exports.attrName)(key.slice(0, dotIdx))}${(0, exports.equals)('=')}${(0, exports.punctuation)('{{')}${(0, exports.punctuation)(key.slice(dotIdx + 1))}${(0, exports.punctuation)(':')} ${formatValueDelta({ oldValueString, newValueString })}${(0, exports.punctuation)('}}')}`;
72
+ };
52
73
  const formatPropDelta = ({ key, valueString }) => {
53
74
  const dotIdx = key.indexOf('.');
54
75
  if (dotIdx === -1) {
@@ -57,6 +78,14 @@ const formatPropDelta = ({ key, valueString }) => {
57
78
  return formatNestedProp(key.slice(0, dotIdx), key.slice(dotIdx + 1), valueString);
58
79
  };
59
80
  exports.formatPropDelta = formatPropDelta;
81
+ const formatPropChangeDelta = ({ key, oldValueString, newValueString, }) => {
82
+ const dotIdx = key.indexOf('.');
83
+ if (dotIdx === -1) {
84
+ return formatSimplePropChange({ key, oldValueString, newValueString });
85
+ }
86
+ return formatNestedPropChange({ key, oldValueString, newValueString });
87
+ };
88
+ exports.formatPropChangeDelta = formatPropChangeDelta;
60
89
  const formatDeletion = (prop) => {
61
90
  const formatted = (0, exports.formatPropDelta)(prop);
62
91
  return (0, exports.strikeThroughOrRemovedPrefix)(formatted);
@@ -67,3 +96,229 @@ const formatAddition = (prop) => {
67
96
  return (0, exports.addedPrefixIfNoColor)(formatted);
68
97
  };
69
98
  exports.formatAddition = formatAddition;
99
+ const callStart = 'interpolate(';
100
+ const normalizeArg = (arg) => {
101
+ return arg
102
+ .replace(/\s+/g, ' ')
103
+ .replace(/,(\s*[}\]])/g, '$1')
104
+ .trim();
105
+ };
106
+ const shortenUnchangedOptionProperties = new Set([
107
+ 'extrapolateLeft',
108
+ 'extrapolateRight',
109
+ ]);
110
+ const splitTopLevelArgs = (argsSource) => {
111
+ const args = [];
112
+ let depth = 0;
113
+ let quote = null;
114
+ let start = 0;
115
+ for (let i = 0; i < argsSource.length; i++) {
116
+ const char = argsSource[i];
117
+ const previous = argsSource[i - 1];
118
+ if (quote) {
119
+ if (char === quote && previous !== '\\') {
120
+ quote = null;
121
+ }
122
+ continue;
123
+ }
124
+ if (char === "'" || char === '"' || char === '`') {
125
+ quote = char;
126
+ continue;
127
+ }
128
+ if (char === '(' || char === '[' || char === '{') {
129
+ depth++;
130
+ continue;
131
+ }
132
+ if (char === ')' || char === ']' || char === '}') {
133
+ depth--;
134
+ continue;
135
+ }
136
+ if (char === ',' && depth === 0) {
137
+ args.push(argsSource.slice(start, i).trim());
138
+ start = i + 1;
139
+ }
140
+ }
141
+ args.push(argsSource.slice(start).trim());
142
+ return args;
143
+ };
144
+ const getCommonPrefixLength = (oldValueString, newValueString) => {
145
+ const maxLength = Math.min(oldValueString.length, newValueString.length);
146
+ let index = 0;
147
+ while (index < maxLength && oldValueString[index] === newValueString[index]) {
148
+ index++;
149
+ }
150
+ return index;
151
+ };
152
+ const getCommonSuffixLength = ({ oldValueString, newValueString, prefixLength, }) => {
153
+ const maxLength = Math.min(oldValueString.length, newValueString.length) - prefixLength;
154
+ let index = 0;
155
+ while (index < maxLength &&
156
+ oldValueString[oldValueString.length - 1 - index] ===
157
+ newValueString[newValueString.length - 1 - index]) {
158
+ index++;
159
+ }
160
+ return index;
161
+ };
162
+ const minSharedInlineDeltaChars = 12;
163
+ const formatInlineAdditionOrRemoval = ({ oldValueString, newValueString, }) => {
164
+ if (!(0, exports.colorEnabled)()) {
165
+ return null;
166
+ }
167
+ const prefixLength = getCommonPrefixLength(oldValueString, newValueString);
168
+ const suffixLength = getCommonSuffixLength({
169
+ oldValueString,
170
+ newValueString,
171
+ prefixLength,
172
+ });
173
+ if (prefixLength + suffixLength < minSharedInlineDeltaChars) {
174
+ return null;
175
+ }
176
+ const oldMiddleEnd = oldValueString.length - suffixLength;
177
+ const newMiddleEnd = newValueString.length - suffixLength;
178
+ const oldMiddle = oldValueString.slice(prefixLength, oldMiddleEnd);
179
+ const newMiddle = newValueString.slice(prefixLength, newMiddleEnd);
180
+ if (oldMiddle.length === 0 && newMiddle.length > 0) {
181
+ return `${newValueString.slice(0, prefixLength)}${(0, exports.inlineAddition)(newMiddle)}${newValueString.slice(newMiddleEnd)}`;
182
+ }
183
+ if (newMiddle.length === 0 && oldMiddle.length > 0) {
184
+ return `${oldValueString.slice(0, prefixLength)}${(0, exports.strikeThrough)(oldMiddle)}${oldValueString.slice(oldMiddleEnd)}`;
185
+ }
186
+ return null;
187
+ };
188
+ const getObjectPropertyKey = (propertySource) => {
189
+ const colonIndex = propertySource.indexOf(':');
190
+ if (colonIndex === -1) {
191
+ return null;
192
+ }
193
+ const key = propertySource.slice(0, colonIndex).trim();
194
+ if (/^[A-Za-z_$][\w$]*$/.test(key)) {
195
+ return key;
196
+ }
197
+ if ((key.startsWith("'") && key.endsWith("'")) ||
198
+ (key.startsWith('"') && key.endsWith('"'))) {
199
+ return key.slice(1, -1);
200
+ }
201
+ return null;
202
+ };
203
+ const parseTopLevelObjectProperties = (objectSource) => {
204
+ const trimmed = objectSource.trim();
205
+ if (!trimmed.startsWith('{') || !trimmed.endsWith('}')) {
206
+ return null;
207
+ }
208
+ const properties = splitTopLevelArgs(trimmed.slice(1, -1))
209
+ .filter(Boolean)
210
+ .map((propertySource) => {
211
+ const key = getObjectPropertyKey(propertySource);
212
+ if (key === null) {
213
+ return null;
214
+ }
215
+ const colonIndex = propertySource.indexOf(':');
216
+ return {
217
+ key,
218
+ value: normalizeArg(propertySource.slice(colonIndex + 1)),
219
+ source: propertySource.trim(),
220
+ };
221
+ });
222
+ if (properties.some((property) => property === null)) {
223
+ return null;
224
+ }
225
+ return properties;
226
+ };
227
+ const parseInterpolateCall = (valueString) => {
228
+ const trimmed = valueString.trim();
229
+ if (!trimmed.startsWith(callStart) || !trimmed.endsWith(')')) {
230
+ return null;
231
+ }
232
+ let depth = 0;
233
+ let quote = null;
234
+ for (let i = 'interpolate'.length; i < trimmed.length; i++) {
235
+ const char = trimmed[i];
236
+ const previous = trimmed[i - 1];
237
+ if (quote) {
238
+ if (char === quote && previous !== '\\') {
239
+ quote = null;
240
+ }
241
+ continue;
242
+ }
243
+ if (char === "'" || char === '"' || char === '`') {
244
+ quote = char;
245
+ continue;
246
+ }
247
+ if (char === '(' || char === '[' || char === '{') {
248
+ depth++;
249
+ continue;
250
+ }
251
+ if (char === ')' || char === ']' || char === '}') {
252
+ depth--;
253
+ if (depth === 0 && i !== trimmed.length - 1) {
254
+ return null;
255
+ }
256
+ }
257
+ }
258
+ if (depth !== 0) {
259
+ return null;
260
+ }
261
+ return {
262
+ args: splitTopLevelArgs(trimmed.slice(callStart.length, -1)),
263
+ };
264
+ };
265
+ const shortenUnchangedInterpolateOptions = ({ oldValueString, newValueString, }) => {
266
+ const oldCall = parseInterpolateCall(oldValueString);
267
+ const newCall = parseInterpolateCall(newValueString);
268
+ if (!oldCall ||
269
+ !newCall ||
270
+ oldCall.args.length <= 3 ||
271
+ newCall.args.length <= 3) {
272
+ return { oldValueString, newValueString };
273
+ }
274
+ const oldOptions = oldCall.args.slice(3).map(normalizeArg);
275
+ const newOptions = newCall.args.slice(3).map(normalizeArg);
276
+ if (oldOptions.length !== newOptions.length ||
277
+ oldOptions.some((option, index) => option !== newOptions[index])) {
278
+ return { oldValueString, newValueString };
279
+ }
280
+ return {
281
+ oldValueString: `interpolate(${oldCall.args.slice(0, 3).join(', ')})`,
282
+ newValueString: `interpolate(${newCall.args.slice(0, 3).join(', ')})`,
283
+ };
284
+ };
285
+ const removeUnchangedInterpolateOptionProperties = ({ oldValueString, newValueString, }) => {
286
+ const oldCall = parseInterpolateCall(oldValueString);
287
+ const newCall = parseInterpolateCall(newValueString);
288
+ if (!oldCall ||
289
+ !newCall ||
290
+ oldCall.args.length <= 3 ||
291
+ newCall.args.length <= 3) {
292
+ return { oldValueString, newValueString };
293
+ }
294
+ const oldProperties = parseTopLevelObjectProperties(oldCall.args[3]);
295
+ const newProperties = parseTopLevelObjectProperties(newCall.args[3]);
296
+ if (!oldProperties || !newProperties) {
297
+ return { oldValueString, newValueString };
298
+ }
299
+ const propertiesToRemove = [...shortenUnchangedOptionProperties].filter((propertyName) => {
300
+ const oldProperty = oldProperties.find((property) => property.key === propertyName);
301
+ const newProperty = newProperties.find((property) => property.key === propertyName);
302
+ return (oldProperty !== undefined &&
303
+ newProperty !== undefined &&
304
+ oldProperty.value === newProperty.value);
305
+ });
306
+ if (propertiesToRemove.length === 0) {
307
+ return { oldValueString, newValueString };
308
+ }
309
+ const removeProperties = (call, properties) => {
310
+ const nextOptions = properties.filter((property) => !propertiesToRemove.includes(property.key));
311
+ const nextArgs = nextOptions.length === 0
312
+ ? [...call.args.slice(0, 3), ...call.args.slice(4)]
313
+ : [
314
+ ...call.args.slice(0, 3),
315
+ `{${nextOptions.map((property) => property.source).join(', ')}}`,
316
+ ...call.args.slice(4),
317
+ ];
318
+ return `interpolate(${nextArgs.join(', ')})`;
319
+ };
320
+ return {
321
+ oldValueString: removeProperties(oldCall, oldProperties),
322
+ newValueString: removeProperties(newCall, newProperties),
323
+ };
324
+ };