Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
4e5aa82
feat(core): spring physics solver + runtime fixes + spring ease editor
miguel-heygen Jun 3, 2026
911b08a
feat(core): spring physics solver + runtime fixes + spring ease editor
miguel-heygen Jun 4, 2026
9260681
ci: trigger regression run
miguel-heygen Jun 4, 2026
5c13487
test(producer): regenerate heygen-promo-preview-assets and style-9-pr…
miguel-heygen Jun 5, 2026
d6a1c9d
feat(studio): design panel integration, timeline polish, feature flag
miguel-heygen Jun 3, 2026
8d58cb5
fix(studio): rotation-aware drag + auto-keyframing for resize and rot…
miguel-heygen Jun 3, 2026
cdb500f
fix(studio): counter-rotate drag offset for css-rotated elements
miguel-heygen Jun 3, 2026
55f1013
feat(studio): add 'delete all keyframes' to diamond context menu
miguel-heygen Jun 3, 2026
59c000d
fix(studio): include all animated properties in every keyframe commit
miguel-heygen Jun 3, 2026
4f429d4
feat(core): spring physics solver + runtime fixes + spring ease editor
miguel-heygen Jun 3, 2026
2822929
feat(core): spring physics solver + runtime fixes + spring ease editor
miguel-heygen Jun 4, 2026
2320111
ci: trigger regression run
miguel-heygen Jun 4, 2026
5a83f79
feat(core): spring physics solver + runtime fixes + spring ease editor
miguel-heygen Jun 3, 2026
123c124
feat(core): spring physics solver + runtime fixes + spring ease editor
miguel-heygen Jun 4, 2026
b9b867c
ci: trigger regression run
miguel-heygen Jun 4, 2026
16a1bac
ci: trigger regression run
miguel-heygen Jun 4, 2026
69dacae
feat(studio): design panel integration, timeline polish, feature flag
miguel-heygen Jun 3, 2026
451bf7d
fix(studio): rotation-aware drag + auto-keyframing for resize and rot…
miguel-heygen Jun 3, 2026
3fcc928
feat(studio): add 'delete all keyframes' to diamond context menu
miguel-heygen Jun 3, 2026
6ced873
ci: trigger regression run
miguel-heygen Jun 4, 2026
ce77298
ci: trigger regression run
miguel-heygen Jun 4, 2026
72ade4b
ci: trigger regression run
miguel-heygen Jun 4, 2026
9465dad
fix(studio): overlay jump, delete-all-keyframes, split wiring, reappl…
miguel-heygen Jun 4, 2026
1400d14
fix(studio): block split on sub-compositions
miguel-heygen Jun 4, 2026
cbd113f
fix(studio): no toast when split is unavailable for compositions
miguel-heygen Jun 4, 2026
f6a3c24
fix(studio): remove defensive toast from split handler — UI gates are…
miguel-heygen Jun 4, 2026
3f969ce
fix(studio): hide split button for sub-compositions, use scissors icon
miguel-heygen Jun 4, 2026
166ab92
ci: trigger regression run
miguel-heygen Jun 4, 2026
8d72aed
feat(studio): runtime-synced design panel values + 3D transform prope…
miguel-heygen Jun 4, 2026
4f6ad68
fix(studio): read ALL animated properties from runtime, not just hard…
miguel-heygen Jun 4, 2026
1d00942
fix(studio): stronger clip selection border + wider keyframe playhead…
miguel-heygen Jun 4, 2026
4b41078
fix(studio): sync DOM selection to timeline selectedElementId on cold…
miguel-heygen Jun 4, 2026
d72229a
fix(studio): use Phosphor Scissors icon for split button
miguel-heygen Jun 4, 2026
66df31d
fix(studio): restrict split to media elements only (video, audio, img)
miguel-heygen Jun 4, 2026
ce9a289
feat(studio): unified commitAnimatedProperty for all GSAP property edits
miguel-heygen Jun 5, 2026
4e11a7e
fix(studio): wire all KeyframeNavigation diamonds through commitAnima…
miguel-heygen Jun 5, 2026
eb8f448
chore: remove committed plan files
miguel-heygen Jun 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion packages/core/src/parsers/gsapConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/

export const SUPPORTED_PROPS = [
// Transforms
// 2D Transforms
"x",
"y",
"scale",
Expand All @@ -15,6 +15,13 @@ export const SUPPORTED_PROPS = [
"rotation",
"skewX",
"skewY",
// 3D Transforms
"z",
"rotationX",
"rotationY",
"rotationZ",
"perspective",
"transformOrigin",
// Visibility
"opacity",
"visibility",
Expand Down
3 changes: 3 additions & 0 deletions packages/studio/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import { useState, useCallback, useRef, useMemo, useEffect } from "react";
import type { LeftSidebarHandle, SidebarTab } from "./components/sidebar/LeftSidebar";
import { useRenderQueue } from "./components/renders/useRenderQueue";
Expand Down Expand Up @@ -286,6 +286,7 @@
const appHotkeys = useAppHotkeys({
toggleTimelineVisibility,
handleTimelineElementDelete: timelineEditing.handleTimelineElementDelete,
handleTimelineElementSplit: timelineEditing.handleTimelineElementSplit,
handleDomEditElementDelete: domEditDeleteBridge,
domEditSelectionRef: domEditSelectionBridgeRef,
clearDomSelectionRef,
Expand Down Expand Up @@ -489,6 +490,7 @@
<TimelineToolbar
toggleTimelineVisibility={toggleTimelineVisibility}
domEditSession={domEditSession}
onSplitElement={timelineEditing.handleTimelineElementSplit}
/>
);
return (
Expand Down Expand Up @@ -532,6 +534,7 @@
handleTimelineElementMove={timelineEditing.handleTimelineElementMove}
handleTimelineElementResize={timelineEditing.handleTimelineElementResize}
handleBlockedTimelineEdit={timelineEditing.handleBlockedTimelineEdit}
handleTimelineElementSplit={timelineEditing.handleTimelineElementSplit}
setCompIdToSrc={setCompIdToSrc}
setCompositionLoading={setCompositionLoading}
shouldShowSelectedDomBounds={shouldShowSelectedDomBounds}
Expand Down
10 changes: 7 additions & 3 deletions packages/studio/src/components/StudioPreviewArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export interface StudioPreviewAreaProps {
updates: Pick<TimelineElement, "start" | "duration" | "playbackStart">,
) => Promise<void> | void;
handleBlockedTimelineEdit: (element: TimelineElement, intent: BlockedTimelineEditIntent) => void;
handleTimelineElementSplit: (element: TimelineElement, splitTime: number) => Promise<void> | void;
setCompIdToSrc: (map: Map<string, string>) => void;
setCompositionLoading: (loading: boolean) => void;
shouldShowSelectedDomBounds: boolean;
Expand All @@ -67,6 +68,7 @@ export function StudioPreviewArea({
handleTimelineElementMove,
handleTimelineElementResize,
handleBlockedTimelineEdit,
handleTimelineElementSplit,
setCompIdToSrc,
setCompositionLoading,
shouldShowSelectedDomBounds,
Expand Down Expand Up @@ -107,7 +109,7 @@ export function StudioPreviewArea({
handleGsapUpdateMeta,
handleGsapAddKeyframe,
handleGsapConvertToKeyframes,
handleGsapRemoveAllKeyframes,
handleGsapDeleteAnimation,
} = useDomEditContext();

return (
Expand All @@ -127,10 +129,12 @@ export function StudioPreviewArea({
onMoveElement={handleTimelineElementMove}
onResizeElement={handleTimelineElementResize}
onBlockedEditAttempt={handleBlockedTimelineEdit}
onSplitElement={handleTimelineElementSplit}
onSelectTimelineElement={handleTimelineElementSelect}
onDeleteAllKeyframes={(_elId) => {
const anim = selectedGsapAnimations.find((a) => a.keyframes);
if (anim) handleGsapRemoveAllKeyframes(anim.id);
const anim =
selectedGsapAnimations.find((a) => a.keyframes) ?? selectedGsapAnimations[0];
if (anim) handleGsapDeleteAnimation(anim.id);
}}
onDeleteKeyframe={(_elId, pct) => {
const anim = selectedGsapAnimations.find((a) => a.keyframes);
Expand Down
3 changes: 3 additions & 0 deletions packages/studio/src/components/StudioRightPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ export function StudioRightPanel({
handleGsapUpdateFromProperty,
handleGsapAddFromProperty,
handleGsapRemoveFromProperty,
commitAnimatedProperty,
} = useDomEditContext();

const { assets, fontAssets, projectDir, handleImportFiles, handleImportFonts } =
Expand Down Expand Up @@ -211,6 +212,7 @@ export function StudioRightPanel({
onImportAssets={handleImportFiles}
fontAssets={fontAssets}
onImportFonts={handleImportFonts}
previewIframeRef={previewIframeRef}
gsapAnimations={selectedGsapAnimations}
gsapMultipleTimelines={gsapMultipleTimelines}
gsapUnsupportedTimelinePattern={gsapUnsupportedTimelinePattern}
Expand All @@ -223,6 +225,7 @@ export function StudioRightPanel({
onAddGsapFromProperty={handleGsapAddFromProperty}
onRemoveGsapFromProperty={handleGsapRemoveFromProperty}
onAddGsapAnimation={handleGsapAddAnimation}
onCommitAnimatedProperty={commitAnimatedProperty}
/>
) : motionPanelActive ? (
<MotionPanel
Expand Down
62 changes: 30 additions & 32 deletions packages/studio/src/components/TimelineToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { getTimelineToggleTitle } from "../utils/timelineDiscovery";
import { usePlayerStore, type TimelineElement } from "../player";
import { STUDIO_KEYFRAMES_ENABLED } from "./editor/manualEditingAvailability";
import { Tooltip } from "./ui";
import { Scissors } from "../icons/SystemIcons";
import type { GsapAnimation, GsapPercentageKeyframe } from "@hyperframes/core/gsap-parser";
import type { DomEditSelection } from "./editor/domEditingTypes";

Expand Down Expand Up @@ -218,38 +219,35 @@ export function TimelineToolbar({
</button>
</Tooltip>
)}
{onSplitElement && (
<Tooltip label="Split clip at playhead (S)">
<button
type="button"
onClick={() => {
const { selectedElementId, elements, currentTime } = usePlayerStore.getState();
if (!selectedElementId) return;
const el = elements.find((e) => (e.key ?? e.id) === selectedElementId);
if (el && currentTime > el.start && currentTime < el.start + el.duration) {
onSplitElement(el, currentTime);
}
}}
className="flex h-7 w-7 items-center justify-center rounded text-neutral-500 transition-colors hover:text-neutral-200"
>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
stroke="currentColor"
strokeWidth="1.4"
strokeLinecap="round"
>
<line x1="8" y1="2" x2="8" y2="14" />
<polyline points="5,5 3,2" />
<polyline points="11,5 13,2" />
<polyline points="5,11 3,14" />
<polyline points="11,11 13,14" />
</svg>
</button>
</Tooltip>
)}
{onSplitElement &&
(() => {
const { selectedElementId, elements, currentTime } = usePlayerStore.getState();
const el = selectedElementId
? elements.find((e) => (e.key ?? e.id) === selectedElementId)
: null;
const splittable =
el && !el.compositionSrc && ["video", "audio", "img"].includes(el.tag);
if (!splittable) return null;
const canSplit = currentTime > el.start && currentTime < el.start + el.duration;
return (
<Tooltip label="Split clip at playhead (S)">
<button
type="button"
disabled={!canSplit}
onClick={() => {
if (canSplit) onSplitElement(el, currentTime);
}}
className={`flex h-7 w-7 items-center justify-center rounded transition-colors ${
canSplit
? "text-neutral-500 hover:text-neutral-200"
: "text-neutral-700 cursor-not-allowed"
}`}
>
<Scissors size={15} />
</button>
</Tooltip>
);
})()}
</div>
<div className="flex items-center gap-1">
<Tooltip label="Fit timeline to width">
Expand Down
Loading
Loading