@hyperframes/studio 0.6.86 → 0.6.88

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 (87) hide show
  1. package/dist/assets/index-B9_ctmee.js +143 -0
  2. package/dist/assets/index-CGlIm_-E.css +1 -0
  3. package/dist/index.html +2 -2
  4. package/package.json +4 -4
  5. package/src/App.tsx +159 -6
  6. package/src/components/StudioHeader.tsx +20 -7
  7. package/src/components/StudioPreviewArea.tsx +6 -1
  8. package/src/components/StudioRightPanel.tsx +13 -0
  9. package/src/components/StudioToast.tsx +47 -7
  10. package/src/components/TimelineToolbar.tsx +12 -122
  11. package/src/components/editor/AnimationCard.tsx +64 -10
  12. package/src/components/editor/ArcPathControls.tsx +131 -0
  13. package/src/components/editor/BorderRadiusEditor.tsx +209 -0
  14. package/src/components/editor/DomEditOverlay.tsx +70 -11
  15. package/src/components/editor/DopesheetStrip.tsx +141 -0
  16. package/src/components/editor/EaseCurveSection.tsx +82 -7
  17. package/src/components/editor/GestureTrailOverlay.tsx +132 -0
  18. package/src/components/editor/GsapAnimationSection.tsx +14 -1
  19. package/src/components/editor/KeyframeDiamond.tsx +27 -12
  20. package/src/components/editor/LayersPanel.tsx +14 -12
  21. package/src/components/editor/MotionPathOverlay.tsx +146 -0
  22. package/src/components/editor/PropertyPanel.tsx +196 -66
  23. package/src/components/editor/SourceEditor.tsx +0 -1
  24. package/src/components/editor/StaggerControls.tsx +61 -0
  25. package/src/components/editor/domEditOverlayGeometry.test.ts +13 -0
  26. package/src/components/editor/domEditOverlayGeometry.ts +2 -1
  27. package/src/components/editor/domEditing.test.ts +43 -0
  28. package/src/components/editor/domEditing.ts +2 -0
  29. package/src/components/editor/domEditingElement.ts +25 -2
  30. package/src/components/editor/domEditingLayers.test.ts +78 -0
  31. package/src/components/editor/domEditingLayers.ts +33 -13
  32. package/src/components/editor/domEditingTypes.ts +1 -0
  33. package/src/components/editor/manualEditingAvailability.ts +1 -1
  34. package/src/components/editor/manualEdits.ts +3 -0
  35. package/src/components/editor/manualEditsDom.ts +23 -5
  36. package/src/components/editor/manualOffsetDrag.ts +59 -0
  37. package/src/components/editor/panelTokens.ts +10 -0
  38. package/src/components/editor/propertyPanelColor.tsx +2 -2
  39. package/src/components/editor/propertyPanelFill.tsx +1 -1
  40. package/src/components/editor/propertyPanelHelpers.ts +18 -2
  41. package/src/components/editor/propertyPanelMediaSection.tsx +1 -1
  42. package/src/components/editor/propertyPanelPrimitives.tsx +38 -25
  43. package/src/components/editor/propertyPanelSections.tsx +4 -6
  44. package/src/components/editor/propertyPanelStyleSections.tsx +30 -8
  45. package/src/components/editor/useDomEditOverlayRects.ts +46 -2
  46. package/src/components/renders/RenderQueue.tsx +121 -100
  47. package/src/components/renders/RenderQueueItem.tsx +13 -13
  48. package/src/contexts/DomEditContext.tsx +12 -0
  49. package/src/contexts/FileManagerContext.tsx +3 -0
  50. package/src/contexts/StudioContext.tsx +0 -4
  51. package/src/hooks/gsapKeyframeCommit.ts +92 -0
  52. package/src/hooks/gsapRuntimeBridge.ts +147 -85
  53. package/src/hooks/gsapRuntimeKeyframes.ts +75 -24
  54. package/src/hooks/gsapRuntimePreview.ts +19 -0
  55. package/src/hooks/useAppHotkeys.ts +18 -0
  56. package/src/hooks/useAskAgentModal.ts +2 -4
  57. package/src/hooks/useDomEditCommits.ts +11 -17
  58. package/src/hooks/useDomEditSession.ts +47 -4
  59. package/src/hooks/useEnableKeyframes.ts +171 -0
  60. package/src/hooks/useFileManager.ts +7 -0
  61. package/src/hooks/useGestureRecording.ts +340 -0
  62. package/src/hooks/useGsapScriptCommits.ts +171 -35
  63. package/src/hooks/useGsapSelectionHandlers.ts +27 -8
  64. package/src/hooks/useGsapTweenCache.ts +169 -11
  65. package/src/hooks/useKeyframeKeyboard.ts +103 -0
  66. package/src/hooks/useStudioContextValue.ts +5 -4
  67. package/src/hooks/useStudioUrlState.ts +1 -2
  68. package/src/hooks/useTimelineEditing.ts +50 -3
  69. package/src/hooks/useToast.ts +6 -1
  70. package/src/player/components/ShortcutsPanel.tsx +40 -0
  71. package/src/player/components/TimelineClipDiamonds.tsx +3 -3
  72. package/src/player/components/TimelinePropertyRows.tsx +120 -0
  73. package/src/player/lib/timelineDOM.test.ts +55 -0
  74. package/src/player/lib/timelineDOM.ts +13 -0
  75. package/src/player/lib/timelineIframeHelpers.test.ts +51 -0
  76. package/src/player/lib/timelineIframeHelpers.ts +1 -0
  77. package/src/player/store/playerStore.ts +43 -0
  78. package/src/utils/audioBeatDetection.ts +58 -0
  79. package/src/utils/globalTimeCompiler.test.ts +169 -0
  80. package/src/utils/globalTimeCompiler.ts +77 -0
  81. package/src/utils/gsapSoftReload.ts +30 -10
  82. package/src/utils/keyframeSnapping.test.ts +74 -0
  83. package/src/utils/keyframeSnapping.ts +63 -0
  84. package/src/utils/rdpSimplify.ts +183 -0
  85. package/src/utils/sourcePatcher.ts +2 -0
  86. package/dist/assets/index-BT9VHgSy.js +0 -140
  87. package/dist/assets/index-DHcptK1_.css +0 -1
@@ -1,10 +1,11 @@
1
1
  import { useCallback, useEffect, useRef } from "react";
2
- import type { ParsedGsap } from "@hyperframes/core/gsap-parser";
2
+ import type { GsapAnimation, ParsedGsap } from "@hyperframes/core/gsap-parser";
3
3
  import type { DomEditSelection } from "../components/editor/domEditingTypes";
4
4
  import type { EditHistoryKind } from "../utils/editHistory";
5
5
  import { applySoftReload } from "../utils/gsapSoftReload";
6
6
  import { executeOptimistic } from "../utils/optimisticUpdate";
7
7
  import { usePlayerStore, type KeyframeCacheEntry } from "../player/store/playerStore";
8
+ import { commitKeyframeAtTimeImpl } from "./gsapKeyframeCommit";
8
9
 
9
10
  const PROPERTY_DEFAULTS: Record<string, number> = {
10
11
  opacity: 1,
@@ -71,11 +72,69 @@ async function mutateGsapScript(
71
72
  return null;
72
73
  }
73
74
  }
74
-
75
+ function updateKeyframeCacheFromParsed(
76
+ animations: GsapAnimation[],
77
+ targetPath: string,
78
+ selectionId: string | undefined,
79
+ mutation: Record<string, unknown>,
80
+ ): void {
81
+ const { setKeyframeCache, elements } = usePlayerStore.getState();
82
+ const idsWithKeyframes = new Set<string>();
83
+ const merged = new Map<string, KeyframeCacheEntry>();
84
+ for (const anim of animations) {
85
+ const id = anim.targetSelector.match(/^#([\w-]+)/)?.[1];
86
+ if (!id || !anim.keyframes) continue;
87
+ idsWithKeyframes.add(id);
88
+
89
+ // Convert tween-relative percentages to clip-relative so diamonds
90
+ // render at the correct position within the timeline clip.
91
+ const tweenPos = typeof anim.position === "number" ? anim.position : 0;
92
+ const tweenDur = anim.duration ?? 1;
93
+ const timelineEl = elements.find(
94
+ (el) => el.domId === id || (el.key ?? el.id) === `${targetPath}#${id}`,
95
+ );
96
+ const elStart = timelineEl?.start ?? 0;
97
+ const elDuration = timelineEl?.duration ?? 4;
98
+ const clipKeyframes = anim.keyframes.keyframes.map((kf) => {
99
+ const absTime = tweenPos + (kf.percentage / 100) * tweenDur;
100
+ const clipPct =
101
+ elDuration > 0 ? Math.round(((absTime - elStart) / elDuration) * 1000) / 10 : kf.percentage;
102
+ return { ...kf, percentage: clipPct };
103
+ });
104
+
105
+ const existing = merged.get(id);
106
+ if (existing) {
107
+ const byPct = new Map<number, (typeof existing.keyframes)[0]>();
108
+ for (const kf of [...existing.keyframes, ...clipKeyframes]) {
109
+ const prev = byPct.get(kf.percentage);
110
+ if (prev) {
111
+ prev.properties = { ...prev.properties, ...kf.properties };
112
+ if (kf.ease) prev.ease = kf.ease;
113
+ } else {
114
+ byPct.set(kf.percentage, { ...kf, properties: { ...kf.properties } });
115
+ }
116
+ }
117
+ existing.keyframes = Array.from(byPct.values()).sort((a, b) => a.percentage - b.percentage);
118
+ } else {
119
+ merged.set(id, { ...anim.keyframes, keyframes: clipKeyframes });
120
+ }
121
+ }
122
+ for (const [id, entry] of merged) {
123
+ setKeyframeCache(`${targetPath}#${id}`, entry);
124
+ setKeyframeCache(id, entry);
125
+ if (targetPath !== "index.html") setKeyframeCache(`index.html#${id}`, entry);
126
+ }
127
+ const targetId =
128
+ (mutation as { targetSelector?: string }).targetSelector?.match(/^#([\w-]+)/)?.[1] ??
129
+ selectionId;
130
+ if (targetId && !idsWithKeyframes.has(targetId)) {
131
+ setKeyframeCache(`${targetPath}#${targetId}`, undefined);
132
+ if (targetPath !== "index.html") setKeyframeCache(`index.html#${targetId}`, undefined);
133
+ }
134
+ }
75
135
  function buildCacheKey(sourceFile: string, elementId: string): string {
76
136
  return `${sourceFile}#${elementId}`;
77
137
  }
78
-
79
138
  function readKeyframeSnapshot(
80
139
  sourceFile: string,
81
140
  elementId: string | null | undefined,
@@ -83,7 +142,6 @@ function readKeyframeSnapshot(
83
142
  if (!elementId) return undefined;
84
143
  return usePlayerStore.getState().keyframeCache.get(buildCacheKey(sourceFile, elementId));
85
144
  }
86
-
87
145
  function writeKeyframeCache(
88
146
  sourceFile: string,
89
147
  elementId: string | null | undefined,
@@ -92,7 +150,6 @@ function writeKeyframeCache(
92
150
  if (!elementId) return;
93
151
  usePlayerStore.getState().setKeyframeCache(buildCacheKey(sourceFile, elementId), data);
94
152
  }
95
-
96
153
  interface GsapScriptCommitsParams {
97
154
  projectIdRef: React.MutableRefObject<string | null>;
98
155
  activeCompPath: string | null;
@@ -108,8 +165,8 @@ interface GsapScriptCommitsParams {
108
165
  domEditSaveTimestampRef: React.MutableRefObject<number>;
109
166
  reloadPreview: () => void;
110
167
  onCacheInvalidate: () => void;
168
+ onFileContentChanged?: (path: string, content: string) => void;
111
169
  }
112
-
113
170
  const DEBOUNCE_MS = 150;
114
171
 
115
172
  // fallow-ignore-next-line complexity unit-size
@@ -121,6 +178,7 @@ export function useGsapScriptCommits({
121
178
  domEditSaveTimestampRef,
122
179
  reloadPreview,
123
180
  onCacheInvalidate,
181
+ onFileContentChanged,
124
182
  }: GsapScriptCommitsParams) {
125
183
  const pendingPropertyEditRef = useRef<{
126
184
  selection: DomEditSelection;
@@ -129,7 +187,6 @@ export function useGsapScriptCommits({
129
187
  value: number | string;
130
188
  } | null>(null);
131
189
  const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
132
-
133
190
  /** Send a mutation and record the edit in undo history. */
134
191
  const commitMutation = useCallback(
135
192
  // fallow-ignore-next-line complexity
@@ -162,21 +219,23 @@ export function useGsapScriptCommits({
162
219
  });
163
220
  }
164
221
 
165
- onCacheInvalidate();
166
-
167
- if (result.parsed?.animations) {
168
- const { setKeyframeCache } = usePlayerStore.getState();
169
- for (const anim of result.parsed.animations) {
170
- if (!anim.keyframes) continue;
171
- const id = anim.targetSelector.match(/^#([\w-]+)/)?.[1];
172
- if (!id) continue;
173
- setKeyframeCache(`${targetPath}#${id}`, anim.keyframes);
174
- if (targetPath !== "index.html") setKeyframeCache(`index.html#${id}`, anim.keyframes);
175
- }
222
+ if (result.after != null) {
223
+ onFileContentChanged?.(targetPath, result.after);
176
224
  }
177
225
 
178
226
  if (options.skipReload) return;
179
227
 
228
+ // Write the keyframe cache immediately from the parsed response
229
+ // (synchronous — the timeline diamonds appear on the next render).
230
+ if (result.parsed?.animations) {
231
+ updateKeyframeCacheFromParsed(
232
+ result.parsed.animations,
233
+ targetPath,
234
+ selection.id ?? undefined,
235
+ mutation,
236
+ );
237
+ }
238
+
180
239
  options.beforeReload?.();
181
240
 
182
241
  if (options.softReload && result.scriptText) {
@@ -186,6 +245,11 @@ export function useGsapScriptCommits({
186
245
  } else {
187
246
  reloadPreview();
188
247
  }
248
+
249
+ // Bump the cache version AFTER reload so the async re-fetch in
250
+ // useGsapAnimationsForElement reads the post-reload script, not
251
+ // the stale pre-reload version that would overwrite fresh data.
252
+ onCacheInvalidate();
189
253
  },
190
254
  [
191
255
  projectIdRef,
@@ -195,9 +259,9 @@ export function useGsapScriptCommits({
195
259
  domEditSaveTimestampRef,
196
260
  reloadPreview,
197
261
  onCacheInvalidate,
262
+ onFileContentChanged,
198
263
  ],
199
264
  );
200
-
201
265
  const flushPendingPropertyEdit = useCallback(() => {
202
266
  const pending = pendingPropertyEditRef.current;
203
267
  if (!pending) return;
@@ -227,7 +291,6 @@ export function useGsapScriptCommits({
227
291
  },
228
292
  [flushPendingPropertyEdit],
229
293
  );
230
-
231
294
  useEffect(() => {
232
295
  return () => {
233
296
  if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current);
@@ -252,7 +315,6 @@ export function useGsapScriptCommits({
252
315
  },
253
316
  [commitMutation],
254
317
  );
255
-
256
318
  const deleteGsapAnimation = useCallback(
257
319
  (selection: DomEditSelection, animationId: string) => {
258
320
  void commitMutation(
@@ -263,7 +325,6 @@ export function useGsapScriptCommits({
263
325
  },
264
326
  [commitMutation],
265
327
  );
266
-
267
328
  const addGsapAnimation = useCallback(
268
329
  // fallow-ignore-next-line complexity
269
330
  async (
@@ -285,6 +346,7 @@ export function useGsapScriptCommits({
285
346
  body: JSON.stringify({
286
347
  target: {
287
348
  id: selection.id,
349
+ hfId: selection.hfId,
288
350
  selector: selection.selector,
289
351
  selectorIndex: selection.selectorIndex,
290
352
  },
@@ -325,7 +387,6 @@ export function useGsapScriptCommits({
325
387
  },
326
388
  [commitMutation, projectIdRef, activeCompPath],
327
389
  );
328
-
329
390
  const addGsapProperty = useCallback(
330
391
  // fallow-ignore-next-line complexity
331
392
  (selection: DomEditSelection, animationId: string, property: string) => {
@@ -346,7 +407,6 @@ export function useGsapScriptCommits({
346
407
  },
347
408
  [commitMutation],
348
409
  );
349
-
350
410
  const removeGsapProperty = useCallback(
351
411
  (selection: DomEditSelection, animationId: string, property: string) => {
352
412
  void commitMutation(
@@ -357,7 +417,6 @@ export function useGsapScriptCommits({
357
417
  },
358
418
  [commitMutation],
359
419
  );
360
-
361
420
  const updateGsapFromProperty = useCallback(
362
421
  (
363
422
  selection: DomEditSelection,
@@ -376,7 +435,6 @@ export function useGsapScriptCommits({
376
435
  },
377
436
  [commitMutation],
378
437
  );
379
-
380
438
  const addGsapFromProperty = useCallback(
381
439
  (selection: DomEditSelection, animationId: string, property: string) => {
382
440
  const defaultValue = PROPERTY_DEFAULTS[property] ?? 0;
@@ -388,7 +446,6 @@ export function useGsapScriptCommits({
388
446
  },
389
447
  [commitMutation],
390
448
  );
391
-
392
449
  const removeGsapFromProperty = useCallback(
393
450
  (selection: DomEditSelection, animationId: string, property: string) => {
394
451
  void commitMutation(
@@ -399,7 +456,6 @@ export function useGsapScriptCommits({
399
456
  },
400
457
  [commitMutation],
401
458
  );
402
-
403
459
  const addKeyframe = useCallback(
404
460
  (
405
461
  selection: DomEditSelection,
@@ -435,7 +491,21 @@ export function useGsapScriptCommits({
435
491
  },
436
492
  [commitMutation, activeCompPath],
437
493
  );
438
-
494
+ const addKeyframeBatch = useCallback(
495
+ (
496
+ selection: DomEditSelection,
497
+ animationId: string,
498
+ percentage: number,
499
+ properties: Record<string, number | string>,
500
+ ) => {
501
+ return commitMutation(
502
+ selection,
503
+ { type: "add-keyframe", animationId, percentage, properties },
504
+ { label: `Add keyframe at ${percentage}%`, softReload: true },
505
+ );
506
+ },
507
+ [commitMutation],
508
+ );
439
509
  const removeKeyframe = useCallback(
440
510
  (selection: DomEditSelection, animationId: string, percentage: number) => {
441
511
  const sf = selection.sourceFile || activeCompPath || "index.html";
@@ -462,18 +532,20 @@ export function useGsapScriptCommits({
462
532
  },
463
533
  [commitMutation, activeCompPath],
464
534
  );
465
-
466
535
  const convertToKeyframes = useCallback(
467
- (selection: DomEditSelection, animationId: string) => {
468
- void commitMutation(
536
+ (
537
+ selection: DomEditSelection,
538
+ animationId: string,
539
+ resolvedFromValues?: Record<string, number | string>,
540
+ ) => {
541
+ return commitMutation(
469
542
  selection,
470
- { type: "convert-to-keyframes", animationId },
543
+ { type: "convert-to-keyframes", animationId, resolvedFromValues },
471
544
  { label: "Convert to keyframes" },
472
545
  );
473
546
  },
474
547
  [commitMutation],
475
548
  );
476
-
477
549
  const removeAllKeyframes = useCallback(
478
550
  (selection: DomEditSelection, animationId: string) => {
479
551
  void commitMutation(
@@ -484,7 +556,66 @@ export function useGsapScriptCommits({
484
556
  },
485
557
  [commitMutation],
486
558
  );
487
-
559
+ const setArcPath = useCallback(
560
+ (
561
+ selection: DomEditSelection,
562
+ animationId: string,
563
+ config: {
564
+ enabled: boolean;
565
+ autoRotate?: boolean | number;
566
+ segments?: Array<{
567
+ curviness: number;
568
+ cp1?: { x: number; y: number };
569
+ cp2?: { x: number; y: number };
570
+ }>;
571
+ },
572
+ ) => {
573
+ void commitMutation(
574
+ selection,
575
+ { type: "set-arc-path" as const, animationId, ...config },
576
+ { label: config.enabled ? "Enable arc path" : "Disable arc path", softReload: true },
577
+ );
578
+ },
579
+ [commitMutation],
580
+ );
581
+ const updateArcSegment = useCallback(
582
+ (
583
+ selection: DomEditSelection,
584
+ animationId: string,
585
+ segmentIndex: number,
586
+ update: {
587
+ curviness?: number;
588
+ cp1?: { x: number; y: number };
589
+ cp2?: { x: number; y: number };
590
+ },
591
+ ) => {
592
+ void commitMutation(
593
+ selection,
594
+ { type: "update-arc-segment" as const, animationId, segmentIndex, ...update },
595
+ { label: "Update arc segment", softReload: true },
596
+ );
597
+ },
598
+ [commitMutation],
599
+ );
600
+ const removeArcPath = useCallback(
601
+ (selection: DomEditSelection, animationId: string) => {
602
+ void commitMutation(
603
+ selection,
604
+ { type: "remove-arc-path" as const, animationId },
605
+ { label: "Remove arc path", softReload: true },
606
+ );
607
+ },
608
+ [commitMutation],
609
+ );
610
+ const commitKeyframeAtTime = useCallback(
611
+ (
612
+ selection: DomEditSelection,
613
+ absoluteTime: number,
614
+ animations: GsapAnimation[],
615
+ properties: Record<string, number | string>,
616
+ ) => commitKeyframeAtTimeImpl(selection, absoluteTime, animations, properties, commitMutation),
617
+ [commitMutation],
618
+ );
488
619
  return {
489
620
  commitMutation,
490
621
  updateGsapProperty,
@@ -497,8 +628,13 @@ export function useGsapScriptCommits({
497
628
  addGsapFromProperty,
498
629
  removeGsapFromProperty,
499
630
  addKeyframe,
631
+ addKeyframeBatch,
500
632
  removeKeyframe,
501
633
  convertToKeyframes,
502
634
  removeAllKeyframes,
635
+ setArcPath,
636
+ updateArcSegment,
637
+ removeArcPath,
638
+ commitKeyframeAtTime,
503
639
  };
504
640
  }
@@ -1,5 +1,6 @@
1
1
  import { useCallback } from "react";
2
2
  import type { DomEditSelection } from "../components/editor/domEditing";
3
+ import { usePlayerStore } from "../player";
3
4
 
4
5
  /**
5
6
  * Thin useCallback wrappers that guard on `domEditSelection` before
@@ -19,10 +20,10 @@ export function useGsapSelectionHandlers({
19
20
  addGsapFromProperty,
20
21
  removeGsapFromProperty,
21
22
  addKeyframe,
23
+ addKeyframeBatch,
22
24
  removeKeyframe,
23
25
  convertToKeyframes,
24
26
  removeAllKeyframes,
25
- currentTime,
26
27
  handleDomManualEditsReset,
27
28
  selectedGsapAnimations,
28
29
  }: {
@@ -61,10 +62,20 @@ export function useGsapSelectionHandlers({
61
62
  property: string,
62
63
  value: number | string,
63
64
  ) => void;
65
+ addKeyframeBatch: (
66
+ sel: DomEditSelection,
67
+ animId: string,
68
+ percentage: number,
69
+ properties: Record<string, number | string>,
70
+ ) => Promise<void>;
64
71
  removeKeyframe: (sel: DomEditSelection, animId: string, percentage: number) => void;
65
- convertToKeyframes: (sel: DomEditSelection, animId: string) => void;
72
+ convertToKeyframes: (
73
+ sel: DomEditSelection,
74
+ animId: string,
75
+ resolvedFromValues?: Record<string, number | string>,
76
+ ) => void;
66
77
  removeAllKeyframes: (sel: DomEditSelection, animId: string) => void;
67
- currentTime: number;
78
+
68
79
  handleDomManualEditsReset: (sel: DomEditSelection) => void;
69
80
  selectedGsapAnimations: { id: string; keyframes?: unknown }[];
70
81
  }) {
@@ -95,12 +106,12 @@ export function useGsapSelectionHandlers({
95
106
  const handleGsapAddAnimation = useCallback(
96
107
  (method: "to" | "from" | "set" | "fromTo") => {
97
108
  if (!domEditSelection) return;
98
- addGsapAnimation(domEditSelection, method, currentTime);
109
+ addGsapAnimation(domEditSelection, method, usePlayerStore.getState().currentTime);
99
110
  if (domEditSelection.element.hasAttribute("data-hf-studio-path-offset")) {
100
111
  handleDomManualEditsReset(domEditSelection);
101
112
  }
102
113
  },
103
- [domEditSelection, addGsapAnimation, currentTime, handleDomManualEditsReset],
114
+ [domEditSelection, addGsapAnimation, handleDomManualEditsReset],
104
115
  );
105
116
 
106
117
  const handleGsapAddProperty = useCallback(
@@ -151,6 +162,13 @@ export function useGsapSelectionHandlers({
151
162
  [domEditSelection, addKeyframe],
152
163
  );
153
164
 
165
+ const handleGsapAddKeyframeBatch = useCallback(
166
+ (animId: string, percentage: number, properties: Record<string, number | string>) => {
167
+ if (!domEditSelection) return Promise.resolve();
168
+ return addKeyframeBatch(domEditSelection, animId, percentage, properties);
169
+ },
170
+ [domEditSelection, addKeyframeBatch],
171
+ );
154
172
  const handleGsapRemoveKeyframe = useCallback(
155
173
  (animId: string, percentage: number) => {
156
174
  if (!domEditSelection) return;
@@ -160,9 +178,9 @@ export function useGsapSelectionHandlers({
160
178
  );
161
179
 
162
180
  const handleGsapConvertToKeyframes = useCallback(
163
- (animId: string) => {
164
- if (!domEditSelection) return;
165
- convertToKeyframes(domEditSelection, animId);
181
+ (animId: string, resolvedFromValues?: Record<string, number | string>) => {
182
+ if (!domEditSelection) return Promise.resolve();
183
+ return convertToKeyframes(domEditSelection, animId, resolvedFromValues);
166
184
  },
167
185
  [domEditSelection, convertToKeyframes],
168
186
  );
@@ -194,6 +212,7 @@ export function useGsapSelectionHandlers({
194
212
  handleGsapAddFromProperty,
195
213
  handleGsapRemoveFromProperty,
196
214
  handleGsapAddKeyframe,
215
+ handleGsapAddKeyframeBatch,
197
216
  handleGsapRemoveKeyframe,
198
217
  handleGsapConvertToKeyframes,
199
218
  handleGsapRemoveAllKeyframes,