Files
hyperframes/packages/studio/src/utils/timelineElementSplit.ts
T
Miguel Ángel 2b65b4efce fix(studio): harden composition timeline reliability (#2615)
* fix(studio): preserve composition playback continuity

* feat(studio): drag compositions into the timeline

* fix(studio): collapse expanded composition move aliases

* fix(studio): make timeline cuts atomic

* fix(studio): group inspector gesture history

* test(studio): cover masked text selection

* fix(studio): harden composition timeline reliability

* fix(studio): satisfy CI source gates

* fix(studio): harden composition mutation requests
2026-07-17 14:15:30 -04:00

55 lines
1.9 KiB
TypeScript

import type { TimelineElement } from "../player/store/playerStore";
export { buildPatchTarget, readFileContent } from "../hooks/timelineEditingHelpers";
/** Minimum distance (seconds) from clip boundaries to allow a split. */
export const SPLIT_BOUNDARY_EPSILON_S = 0.03;
/**
* True when splitTime leaves at least SPLIT_BOUNDARY_EPSILON_S on both sides
* of the cut. Inclusive at the epsilon offsets: the timeline canvas clamps
* edge clicks to exactly start/end ± epsilon, so the clamped value must pass.
*/
export function isSplitTimeWithinBounds(
splitTime: number,
clipStart: number,
clipDuration: number,
): boolean {
return (
splitTime >= clipStart + SPLIT_BOUNDARY_EPSILON_S &&
splitTime <= clipStart + clipDuration - SPLIT_BOUNDARY_EPSILON_S
);
}
export function canSplitElement(el: TimelineElement): boolean {
const hasStableIdentity = Boolean(el.hfId || el.domId || el.selector);
const hasValidRate =
el.playbackRate == null || (Number.isFinite(el.playbackRate) && el.playbackRate > 0);
return (
!el.timelineLocked &&
el.timingSource !== "implicit" &&
hasStableIdentity &&
hasValidRate &&
!!el.duration &&
Number.isFinite(el.duration)
);
}
/**
* True when `el` can be split AND `splitTime` lies within its boundary epsilon.
* Shared by the single-clip and split-all razor paths so both honor the same
* minimum-distance rule (split-all previously used raw `>`/`<`, letting cuts
* land inside the epsilon margin and produce a degenerate slice).
*/
export function canSplitElementAt(el: TimelineElement, splitTime: number): boolean {
return canSplitElement(el) && isSplitTimeWithinBounds(splitTime, el.start, el.duration);
}
/** Elements that the split-all razor action can cut at `splitTime`. */
export function selectSplittableElements(
elements: TimelineElement[],
splitTime: number,
): TimelineElement[] {
return elements.filter((el) => canSplitElementAt(el, splitTime));
}