@hyperframes/studio 0.6.28 → 0.6.29

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.
package/dist/index.html CHANGED
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
6
6
  <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
7
7
  <title>HyperFrames Studio</title>
8
- <script type="module" crossorigin src="/assets/index-EdfhuQ5T.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-C-kAqQVb.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="/assets/index-DVpLGNHi.css">
10
10
  </head>
11
11
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/studio",
3
- "version": "0.6.28",
3
+ "version": "0.6.29",
4
4
  "description": "",
5
5
  "repository": {
6
6
  "type": "git",
@@ -31,8 +31,8 @@
31
31
  "@codemirror/view": "6.40.0",
32
32
  "@phosphor-icons/react": "^2.1.10",
33
33
  "mediabunny": "^1.45.3",
34
- "@hyperframes/core": "0.6.28",
35
- "@hyperframes/player": "0.6.28"
34
+ "@hyperframes/core": "0.6.29",
35
+ "@hyperframes/player": "0.6.29"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@types/react": "19",
@@ -46,7 +46,7 @@
46
46
  "vite": "^6.4.2",
47
47
  "vitest": "^3.2.4",
48
48
  "zustand": "^5.0.0",
49
- "@hyperframes/producer": "0.6.28"
49
+ "@hyperframes/producer": "0.6.29"
50
50
  },
51
51
  "peerDependencies": {
52
52
  "react": "19",
@@ -12,6 +12,7 @@ import type {
12
12
  } from "./domEditingTypes";
13
13
  import {
14
14
  buildStableSelector,
15
+ findClosestByAttribute,
15
16
  getCuratedComputedStyles,
16
17
  getDataAttributes,
17
18
  getInlineStyles,
@@ -175,18 +176,21 @@ export function resolveDomEditCapabilities(args: {
175
176
  inlineStyles: Record<string, string>;
176
177
  computedStyles: Record<string, string>;
177
178
  isCompositionHost: boolean;
179
+ isInsideLockedComposition: boolean;
178
180
  isMasterView: boolean;
179
181
  }): DomEditCapabilities {
180
- if (!args.selector) {
182
+ if (!args.selector || args.isInsideLockedComposition) {
181
183
  return {
182
- canSelect: false,
184
+ canSelect: !args.isInsideLockedComposition,
183
185
  canEditStyles: false,
184
186
  canMove: false,
185
187
  canResize: false,
186
188
  canApplyManualOffset: false,
187
189
  canApplyManualSize: false,
188
190
  canApplyManualRotation: false,
189
- reasonIfDisabled: "Studio could not resolve a stable patch target for this element.",
191
+ reasonIfDisabled: args.isInsideLockedComposition
192
+ ? "This element belongs to a locked composition."
193
+ : "Studio could not resolve a stable patch target for this element.",
190
194
  };
191
195
  }
192
196
 
@@ -298,6 +302,7 @@ export function resolveDomEditSelection(
298
302
  const inlineStyles = getInlineStyles(current);
299
303
  const computedStyles = getCuratedComputedStyles(current);
300
304
  const textFields = collectDomEditTextFields(current);
305
+ const isInsideLocked = Boolean(findClosestByAttribute(current, ["data-timeline-locked"]));
301
306
  const capabilities = resolveDomEditCapabilities({
302
307
  selector,
303
308
  tagName: current.tagName.toLowerCase(),
@@ -305,6 +310,7 @@ export function resolveDomEditSelection(
305
310
  inlineStyles,
306
311
  computedStyles,
307
312
  isCompositionHost: Boolean(compositionSrc),
313
+ isInsideLockedComposition: isInsideLocked,
308
314
  isMasterView: options.isMasterView,
309
315
  });
310
316
  const rect = current.getBoundingClientRect();
@@ -318,6 +324,7 @@ export function resolveDomEditSelection(
318
324
  compositionPath,
319
325
  compositionSrc,
320
326
  isCompositionHost: Boolean(compositionSrc),
327
+ isInsideLockedComposition: isInsideLocked,
321
328
  label: buildElementLabel(current),
322
329
  tagName: current.tagName.toLowerCase(),
323
330
  boundingBox: {
@@ -488,7 +495,11 @@ export function getDomEditTargetKey(
488
495
  }
489
496
 
490
497
  export function isTextEditableSelection(selection: DomEditSelection): boolean {
491
- return selection.textFields.length > 0 && !selection.isCompositionHost;
498
+ return (
499
+ selection.textFields.length > 0 &&
500
+ !selection.isCompositionHost &&
501
+ !selection.isInsideLockedComposition
502
+ );
492
503
  }
493
504
 
494
505
  // buildElementAgentPrompt is in domEditingAgentPrompt.ts
@@ -78,6 +78,7 @@ export interface DomEditSelection extends PatchTarget {
78
78
  compositionPath: string;
79
79
  compositionSrc?: string;
80
80
  isCompositionHost: boolean;
81
+ isInsideLockedComposition: boolean;
81
82
  boundingBox: { x: number; y: number; width: number; height: number };
82
83
  textContent: string | null;
83
84
  dataAttributes: Record<string, string>;
@@ -263,6 +263,22 @@ describe("getTimelineEditCapabilities", () => {
263
263
  });
264
264
  });
265
265
 
266
+ it("locks all timeline edits for clips with data-timeline-locked", () => {
267
+ expect(
268
+ getTimelineEditCapabilities({
269
+ tag: "div",
270
+ duration: 8,
271
+ selector: '[data-composition-id="caption-highlight"]',
272
+ compositionSrc: "compositions/components/caption-highlight.html",
273
+ timelineLocked: true,
274
+ }),
275
+ ).toEqual({
276
+ canMove: false,
277
+ canTrimStart: false,
278
+ canTrimEnd: false,
279
+ });
280
+ });
281
+
266
282
  it("allows full editing of explicitly authored generic elements", () => {
267
283
  expect(
268
284
  getTimelineEditCapabilities({
@@ -211,8 +211,9 @@ export function getTimelineEditCapabilities(input: {
211
211
  playbackStartAttr?: "media-start" | "playback-start";
212
212
  sourceDuration?: number;
213
213
  timingSource?: "authored" | "implicit";
214
+ timelineLocked?: boolean;
214
215
  }): TimelineEditCapabilities {
215
- if (input.timingSource === "implicit") {
216
+ if (input.timingSource === "implicit" || input.timelineLocked) {
216
217
  return {
217
218
  canMove: false,
218
219
  canTrimStart: false,
@@ -280,6 +280,10 @@ export function parseTimelineFromDOM(doc: Document, rootDuration: number): Timel
280
280
  applyMediaMetadataFromElement(entry, el);
281
281
  }
282
282
 
283
+ if (el.hasAttribute("data-timeline-locked")) {
284
+ entry.timelineLocked = true;
285
+ }
286
+
283
287
  // Sub-compositions
284
288
  const compSrc =
285
289
  el.getAttribute("data-composition-src") || el.getAttribute("data-composition-file");
@@ -26,6 +26,8 @@ export interface TimelineElement {
26
26
  compositionSrc?: string;
27
27
  /** Whether this row came from authored clip timing or Studio's full-duration layer fallback. */
28
28
  timingSource?: "authored" | "implicit";
29
+ /** Set by data-timeline-locked on the host element — disables move and trim in Studio. */
30
+ timelineLocked?: boolean;
29
31
  }
30
32
 
31
33
  export type ZoomMode = "fit" | "manual";