@hyperframes/studio 0.6.90 → 0.6.92

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 (58) hide show
  1. package/dist/assets/{index-DSLrl2tB.js → index-CDy8BuGq.js} +24 -24
  2. package/dist/assets/index-CmRIkCwI.js +251 -0
  3. package/dist/assets/index-rm9tn9nH.css +1 -0
  4. package/dist/index.html +2 -2
  5. package/package.json +4 -4
  6. package/src/App.tsx +2 -0
  7. package/src/components/StudioPreviewArea.tsx +54 -13
  8. package/src/components/TimelineToolbar.tsx +52 -35
  9. package/src/components/editor/DomEditOverlay.tsx +79 -0
  10. package/src/components/editor/PropertyPanel.tsx +19 -10
  11. package/src/components/editor/gsapAnimatesProperty.ts +30 -0
  12. package/src/components/editor/manualEditingAvailability.test.ts +12 -0
  13. package/src/components/editor/manualEditingAvailability.ts +16 -0
  14. package/src/components/editor/manualEditsDom.ts +25 -5
  15. package/src/components/editor/manualEditsDomPatches.test.ts +1 -0
  16. package/src/components/editor/manualEditsDomPatches.ts +17 -1
  17. package/src/components/editor/manualEditsSnapshot.ts +16 -0
  18. package/src/components/editor/propertyPanel3dTransform.tsx +19 -4
  19. package/src/components/editor/useOffScreenIndicators.ts +197 -0
  20. package/src/components/nle/NLELayout.tsx +22 -32
  21. package/src/components/nle/TimelineEditorNotice.tsx +2 -25
  22. package/src/contexts/DomEditContext.tsx +4 -0
  23. package/src/hooks/gsapDragCommit.ts +119 -43
  24. package/src/hooks/gsapKeyframeCacheHelpers.ts +9 -4
  25. package/src/hooks/gsapRuntimeBridge.ts +266 -41
  26. package/src/hooks/gsapRuntimeReaders.ts +16 -2
  27. package/src/hooks/useAnimatedPropertyCommit.ts +11 -5
  28. package/src/hooks/useAppHotkeys.ts +48 -1
  29. package/src/hooks/useContextMenuDismiss.ts +29 -0
  30. package/src/hooks/useDomEditCommits.ts +7 -1
  31. package/src/hooks/useDomEditSession.ts +20 -4
  32. package/src/hooks/useEnableKeyframes.ts +3 -1
  33. package/src/hooks/useGestureCommit.ts +99 -13
  34. package/src/hooks/useGestureRecording.ts +18 -2
  35. package/src/hooks/useGsapScriptCommits.ts +24 -3
  36. package/src/hooks/useGsapSelectionHandlers.ts +19 -3
  37. package/src/hooks/useGsapTweenCache.ts +30 -10
  38. package/src/hooks/useRazorSplit.ts +298 -0
  39. package/src/hooks/useTimelineEditing.ts +15 -98
  40. package/src/player/components/ClipContextMenu.tsx +14 -25
  41. package/src/player/components/KeyframeDiamondContextMenu.tsx +16 -112
  42. package/src/player/components/PlayheadIndicator.tsx +43 -0
  43. package/src/player/components/Timeline.tsx +45 -38
  44. package/src/player/components/TimelineCanvas.tsx +29 -22
  45. package/src/player/components/TimelineClipDiamonds.tsx +3 -1
  46. package/src/player/components/timelineCallbacks.ts +44 -0
  47. package/src/player/components/timelineDragDrop.ts +2 -14
  48. package/src/player/components/useTimelineZoom.ts +18 -0
  49. package/src/player/store/playerStore.ts +20 -0
  50. package/src/utils/globalTimeCompiler.test.ts +2 -2
  51. package/src/utils/globalTimeCompiler.ts +2 -1
  52. package/src/utils/gsapSoftReload.test.ts +16 -0
  53. package/src/utils/gsapSoftReload.ts +43 -8
  54. package/src/utils/rdpSimplify.ts +3 -2
  55. package/src/utils/timelineElementSplit.test.ts +50 -0
  56. package/src/utils/timelineElementSplit.ts +32 -0
  57. package/dist/assets/index-BKuDHMYl.js +0 -146
  58. package/dist/assets/index-D2NkPomd.css +0 -1
@@ -97,7 +97,7 @@ function simplifyTimeSeries(
97
97
  export function simplifyGestureSamples(
98
98
  samples: Array<{ time: number; properties: Record<string, number> }>,
99
99
  totalDuration: number,
100
- epsilon: number,
100
+ epsilon: number | ((key: string) => number),
101
101
  ): Map<number, Record<string, number>> {
102
102
  if (samples.length === 0) return new Map();
103
103
  if (totalDuration <= 0) return new Map();
@@ -120,7 +120,8 @@ export function simplifyGestureSamples(
120
120
  series.push({ time: s.time, value: s.properties[key] });
121
121
  }
122
122
  }
123
- const simplified = simplifyTimeSeries(series, epsilon);
123
+ const keyEpsilon = typeof epsilon === "function" ? epsilon(key) : epsilon;
124
+ const simplified = simplifyTimeSeries(series, keyEpsilon);
124
125
  for (const pt of simplified) {
125
126
  survivingTimes.add(pt.time);
126
127
  }
@@ -0,0 +1,50 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { SPLIT_BOUNDARY_EPSILON_S, isSplitTimeWithinBounds } from "./timelineElementSplit";
3
+
4
+ describe("isSplitTimeWithinBounds", () => {
5
+ const start = 1;
6
+ const duration = 4;
7
+ const end = start + duration;
8
+
9
+ it("accepts the exact lower clamp boundary", () => {
10
+ // The timeline canvas clamps an edge click to exactly
11
+ // start + SPLIT_BOUNDARY_EPSILON_S, so that value must be splittable.
12
+ expect(isSplitTimeWithinBounds(start + SPLIT_BOUNDARY_EPSILON_S, start, duration)).toBe(true);
13
+ });
14
+
15
+ it("accepts the exact upper clamp boundary", () => {
16
+ expect(
17
+ isSplitTimeWithinBounds(start + duration - SPLIT_BOUNDARY_EPSILON_S, start, duration),
18
+ ).toBe(true);
19
+ });
20
+
21
+ it("accepts an interior split time", () => {
22
+ expect(isSplitTimeWithinBounds(3, start, duration)).toBe(true);
23
+ });
24
+
25
+ it("rejects times at or outside the clip edges", () => {
26
+ expect(isSplitTimeWithinBounds(start, start, duration)).toBe(false);
27
+ expect(isSplitTimeWithinBounds(end, start, duration)).toBe(false);
28
+ expect(isSplitTimeWithinBounds(start - 1, start, duration)).toBe(false);
29
+ expect(isSplitTimeWithinBounds(end + 1, start, duration)).toBe(false);
30
+ });
31
+
32
+ it("rejects times inside the epsilon margins", () => {
33
+ expect(isSplitTimeWithinBounds(start + SPLIT_BOUNDARY_EPSILON_S / 2, start, duration)).toBe(
34
+ false,
35
+ );
36
+ expect(isSplitTimeWithinBounds(end - SPLIT_BOUNDARY_EPSILON_S / 2, start, duration)).toBe(
37
+ false,
38
+ );
39
+ });
40
+
41
+ it("rejects every time on a clip shorter than two epsilons", () => {
42
+ // Math.max(min, Math.min(max, t)) collapses to min when the clip is too
43
+ // short for the clamp range; that collapsed value must still be rejected.
44
+ const shortDuration = SPLIT_BOUNDARY_EPSILON_S;
45
+ expect(isSplitTimeWithinBounds(start + SPLIT_BOUNDARY_EPSILON_S, start, shortDuration)).toBe(
46
+ false,
47
+ );
48
+ expect(isSplitTimeWithinBounds(start + shortDuration / 2, start, shortDuration)).toBe(false);
49
+ });
50
+ });
@@ -0,0 +1,32 @@
1
+ import type { TimelineElement } from "../player/store/playerStore";
2
+
3
+ export { buildPatchTarget, readFileContent } from "../hooks/timelineEditingHelpers";
4
+
5
+ /** Minimum distance (seconds) from clip boundaries to allow a split. */
6
+ export const SPLIT_BOUNDARY_EPSILON_S = 0.03;
7
+
8
+ /**
9
+ * True when splitTime leaves at least SPLIT_BOUNDARY_EPSILON_S on both sides
10
+ * of the cut. Inclusive at the epsilon offsets: the timeline canvas clamps
11
+ * edge clicks to exactly start/end ± epsilon, so the clamped value must pass.
12
+ */
13
+ export function isSplitTimeWithinBounds(
14
+ splitTime: number,
15
+ clipStart: number,
16
+ clipDuration: number,
17
+ ): boolean {
18
+ return (
19
+ splitTime >= clipStart + SPLIT_BOUNDARY_EPSILON_S &&
20
+ splitTime <= clipStart + clipDuration - SPLIT_BOUNDARY_EPSILON_S
21
+ );
22
+ }
23
+
24
+ export function canSplitElement(el: TimelineElement): boolean {
25
+ return (
26
+ !el.timelineLocked &&
27
+ el.timingSource !== "implicit" &&
28
+ !el.compositionSrc &&
29
+ !!el.duration &&
30
+ Number.isFinite(el.duration)
31
+ );
32
+ }