@hyperframes/studio 0.8.75 → 0.8.77

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 (130) hide show
  1. package/dist/assets/hyperframes-player-BSDTJ9EF.js +464 -0
  2. package/dist/assets/{index-BlUYw1MB.js → index-2GgHjQq-.js} +163 -163
  3. package/dist/assets/{index-0w6iIlwZ.css → index-BWJqxeaH.css} +1 -1
  4. package/dist/assets/{index-BiFKcNjs.js → index-CsFCRdMo.js} +1 -1
  5. package/dist/assets/{index-HjexPsny.js → index-DcxWQ7Za.js} +1 -1
  6. package/dist/{chunk-7BP32G62.js → chunk-5VANA2TG.js} +12 -1
  7. package/dist/chunk-5VANA2TG.js.map +1 -0
  8. package/dist/{domEditingLayers-UKMVYXT6.js → domEditingLayers-IGPLTI5E.js} +4 -2
  9. package/dist/index.d.ts +34 -69
  10. package/dist/index.html +2 -2
  11. package/dist/index.js +5287 -5277
  12. package/dist/index.js.map +1 -1
  13. package/dist/styles.css +1 -1
  14. package/package.json +7 -7
  15. package/src/components/StudioRightPanels.types.ts +0 -2
  16. package/src/components/editor/DomEditOverlay.test.ts +5 -0
  17. package/src/components/editor/LayersPanel.tsx +3 -14
  18. package/src/components/editor/PreviewOverlayProvider.test.tsx +6 -1
  19. package/src/components/editor/domEditing.ts +1 -0
  20. package/src/components/editor/domEditingElement.ts +13 -0
  21. package/src/components/editor/domEditingLayers.test.ts +27 -0
  22. package/src/components/editor/domEditingLayers.ts +9 -0
  23. package/src/components/editor/gsapLivePreview.test.ts +63 -0
  24. package/src/components/editor/gsapLivePreview.ts +2 -14
  25. package/src/components/editor/offCanvasIndicatorRefresh.test.tsx +57 -35
  26. package/src/components/editor/overlayFrameLoop.test.ts +16 -0
  27. package/src/components/editor/overlayFrameLoop.ts +11 -0
  28. package/src/components/editor/propertyPanelHelpers.ts +4 -6
  29. package/src/components/editor/usePreviewCompositionRect.test.tsx +6 -1
  30. package/src/components/nle/NLEPreview.test.ts +202 -22
  31. package/src/components/nle/NLEPreview.tsx +65 -46
  32. package/src/components/nle/PreviewPoster.tsx +99 -0
  33. package/src/components/nle/PreviewZoomOverlay.tsx +111 -0
  34. package/src/components/nle/previewZoom.test.ts +42 -0
  35. package/src/components/nle/previewZoom.ts +33 -0
  36. package/src/components/nle/useCanvasZOrderTimelineMirror.test.tsx +30 -49
  37. package/src/components/panels/VariablesPanel.tsx +0 -2
  38. package/src/components/sidebar/CompositionsTab.drag.test.tsx +5 -1
  39. package/src/components/sidebar/CompositionsTab.rootBadge.test.tsx +14 -0
  40. package/src/components/sidebar/CompositionsTab.test.ts +4 -6
  41. package/src/components/sidebar/CompositionsTab.tsx +28 -21
  42. package/src/components/studioColorGradingScope.ts +0 -3
  43. package/src/hooks/domEditDeleteMembers.ts +0 -2
  44. package/src/hooks/gsapScriptCommitTypes.ts +0 -2
  45. package/src/hooks/keyframeCacheAstLoad.test.ts +31 -1
  46. package/src/hooks/keyframeCacheAstLoad.ts +2 -1
  47. package/src/hooks/timelineAudioGroupCreate.ts +0 -1
  48. package/src/hooks/timelineEditingHelpers.ts +0 -3
  49. package/src/hooks/timelineTimingSync.ts +0 -1
  50. package/src/hooks/timelineTrackVisibility.ts +0 -1
  51. package/src/hooks/useBlockHandlers.ts +0 -2
  52. package/src/hooks/useClipboard.test.ts +69 -10
  53. package/src/hooks/useClipboard.ts +28 -19
  54. package/src/hooks/useDomEditCommits.test.tsx +0 -1
  55. package/src/hooks/useDomEditCommits.ts +0 -4
  56. package/src/hooks/useDomEditWiring.ts +1 -3
  57. package/src/hooks/useEditHistoryActions.test.tsx +31 -14
  58. package/src/hooks/useEditHistoryActions.ts +12 -7
  59. package/src/hooks/useEditorSave.ts +0 -3
  60. package/src/hooks/useElementLifecycleOps.ts +0 -1
  61. package/src/hooks/useEnableKeyframes.test.ts +9 -1
  62. package/src/hooks/useFileManager.ts +0 -2
  63. package/src/hooks/useGestureRecording.test.tsx +1 -1
  64. package/src/hooks/useGestureRecording.ts +6 -10
  65. package/src/hooks/useGroupCommits.ts +0 -1
  66. package/src/hooks/useGsapScriptCommits.ts +0 -1
  67. package/src/hooks/useGsapTweenCache.element.test.tsx +39 -0
  68. package/src/hooks/useGsapTweenCache.ts +5 -13
  69. package/src/hooks/useLintModal.test.tsx +34 -1
  70. package/src/hooks/useLintModal.ts +32 -30
  71. package/src/hooks/usePersistentEditHistory.test.ts +221 -315
  72. package/src/hooks/usePersistentEditHistory.ts +155 -342
  73. package/src/hooks/usePopulateKeyframeCacheForFile.test.tsx +2 -0
  74. package/src/hooks/usePreviewPersistence.ts +0 -2
  75. package/src/hooks/useProjectCompositionVariables.ts +1 -3
  76. package/src/hooks/useRazorSplit.history.test.tsx +46 -67
  77. package/src/hooks/useRazorSplit.test.ts +17 -47
  78. package/src/hooks/useRenderClipContent.test.ts +29 -0
  79. package/src/hooks/useRenderClipContent.ts +6 -3
  80. package/src/hooks/useSdkSession.lifecycle.test.tsx +34 -4
  81. package/src/hooks/useSdkSession.ts +8 -2
  82. package/src/hooks/useSlideshowPersist.ts +0 -2
  83. package/src/hooks/useStudioContextValue.ts +6 -1
  84. package/src/hooks/useTimelineAssetDropOps.ts +0 -1
  85. package/src/hooks/useTimelineDeleteOps.ts +0 -1
  86. package/src/hooks/useTimelineEditing.test.tsx +0 -1
  87. package/src/hooks/useTimelineEditingTypes.ts +0 -2
  88. package/src/hostMount.test.tsx +53 -22
  89. package/src/main.tsx +3 -0
  90. package/src/player/components/CompositionThumbnail.tsx +14 -0
  91. package/src/player/components/Player.test.ts +49 -0
  92. package/src/player/components/Player.tsx +21 -7
  93. package/src/player/components/timelineClipDragCommit.test.ts +7 -39
  94. package/src/player/hooks/timelineDomBinding.test.ts +50 -0
  95. package/src/player/hooks/timelineSyncHydration.test.ts +25 -1
  96. package/src/player/hooks/timelineSyncHydration.ts +3 -3
  97. package/src/player/hooks/useShadowPreviewReload.ts +1 -0
  98. package/src/player/hooks/useTimelinePlayer.play.test.ts +120 -0
  99. package/src/player/hooks/useTimelinePlayer.ts +1 -0
  100. package/src/player/hooks/useTimelineSyncCallbacks.ts +2 -0
  101. package/src/player/lib/automationStoreSync.test.ts +24 -0
  102. package/src/player/lib/automationStoreSync.ts +3 -9
  103. package/src/player/lib/playbackScrub.ts +1 -1
  104. package/src/player/lib/timelineDOM.test.ts +28 -0
  105. package/src/player/lib/timelineDOM.ts +4 -2
  106. package/src/player/lib/timelineElementHelpers.ts +78 -9
  107. package/src/player/lib/timelineIframeHelpers.test.ts +49 -5
  108. package/src/player/lib/timelineIframeHelpers.ts +13 -12
  109. package/src/player/store/playerStore.test.ts +35 -1
  110. package/src/player/store/playerStore.ts +29 -0
  111. package/src/player/store/readinessSlice.ts +34 -6
  112. package/src/utils/blockInstaller.ts +0 -3
  113. package/src/utils/previewPrefetch.test.ts +22 -0
  114. package/src/utils/previewPrefetch.ts +14 -0
  115. package/src/utils/razorSplitTransaction.test.ts +0 -1
  116. package/src/utils/razorSplitTransaction.ts +1 -1
  117. package/src/utils/sdkEditTransaction.ts +0 -3
  118. package/src/utils/studioFileHistory.test.ts +0 -9
  119. package/src/utils/studioFileHistory.ts +1 -5
  120. package/src/utils/studioUiPreferences.test.ts +9 -3
  121. package/src/utils/studioUiPreferences.ts +0 -20
  122. package/src/utils/timelineCompositionInsert.ts +0 -1
  123. package/dist/assets/hyperframes-player-WbBVbWuO.js +0 -464
  124. package/dist/chunk-7BP32G62.js.map +0 -1
  125. package/src/hooks/usePersistentEditHistory.projectOwnership.test.tsx +0 -60
  126. package/src/utils/editHistory.test.ts +0 -534
  127. package/src/utils/editHistory.ts +0 -236
  128. package/src/utils/editHistoryStorage.test.ts +0 -37
  129. package/src/utils/editHistoryStorage.ts +0 -99
  130. /package/dist/{domEditingLayers-UKMVYXT6.js.map → domEditingLayers-IGPLTI5E.js.map} +0 -0
@@ -0,0 +1,39 @@
1
+ // @vitest-environment happy-dom
2
+ import { act } from "react";
3
+ import { expect, it, vi } from "vitest";
4
+ import type { GsapAnimation } from "@hyperframes/core/gsap-parser";
5
+ import type { DomEditSelection } from "../components/editor/domEditingTypes";
6
+ import { installReactActEnvironment, mountReactHarness } from "./domSelectionTestHarness";
7
+
8
+ installReactActEnvironment();
9
+
10
+ const dotTween = {
11
+ id: "dots",
12
+ targetSelector: ".dot",
13
+ method: "from",
14
+ position: 0,
15
+ properties: { opacity: 0 },
16
+ } as GsapAnimation;
17
+ vi.mock("./keyframeCacheAstLoad", async (importOriginal) => ({
18
+ ...(await importOriginal<typeof import("./keyframeCacheAstLoad")>()),
19
+ fetchParsedAnimations: async () => ({ animations: [dotTween] }),
20
+ }));
21
+ const { useGsapAnimationsForElement } = await import("./useGsapTweenCache");
22
+
23
+ it("attributes a class tween by the selected element, not an earlier same-id copy in a sub-composition", async () => {
24
+ document.body.innerHTML =
25
+ '<div data-composition-id="strip" data-composition-src="compositions/strip.html"><div id="card"></div></div>' +
26
+ '<div id="card" class="dot"></div>';
27
+ const element = document.querySelectorAll<HTMLElement>("#card")[1];
28
+ const selection = { id: "card", sourceFile: "index.html", element } as DomEditSelection;
29
+ const iframeRef = { current: { contentDocument: document } as unknown as HTMLIFrameElement };
30
+ let animations: GsapAnimation[] = [];
31
+ function Harness() {
32
+ animations = useGsapAnimationsForElement("p", "index.html", selection, 0, iframeRef).animations;
33
+ return null;
34
+ }
35
+ const root = mountReactHarness(<Harness />);
36
+ await act(async () => {});
37
+ expect(animations.map((animation) => animation.id)).toEqual(["dots"]);
38
+ act(() => root.unmount());
39
+ });
@@ -1,6 +1,8 @@
1
1
  import { useEffect, useMemo, useRef, useState, useCallback } from "react";
2
2
  import type { GsapAnimation, GsapKeyframesData } from "@hyperframes/core/gsap-parser";
3
3
  import { usePlayerStore } from "../player/store/playerStore";
4
+ import type { DomEditSelection } from "../components/editor/domEditingTypes";
5
+ import { findPreviewNode } from "../components/editor/domEditingElement";
4
6
  import { readRuntimeKeyframes, scanAllRuntimeKeyframes } from "./gsapRuntimeBridge";
5
7
  import {
6
8
  clearKeyframeCacheForElement,
@@ -73,7 +75,7 @@ export function getAnimationsForElement(
73
75
  export function useGsapAnimationsForElement(
74
76
  projectId: string | null,
75
77
  sourceFile: string,
76
- target: GsapElementTarget | null,
78
+ target: DomEditSelection | null,
77
79
  version: number,
78
80
  iframeRef?: React.RefObject<HTMLIFrameElement | null>,
79
81
  ): {
@@ -157,24 +159,14 @@ export function useGsapAnimationsForElement(
157
159
  // gsap.from(".dot", {stagger})) attribute to every matching element, not
158
160
  // just the one whose exact selector equals the tween's. `version` re-runs
159
161
  // this after composition reloads.
160
- let element: Element | null = null;
161
- const doc = iframeRef?.current?.contentDocument;
162
- if (doc) {
163
- try {
164
- element =
165
- (targetId ? doc.getElementById(targetId) : null) ??
166
- (targetSelector ? doc.querySelector(targetSelector) : null);
167
- } catch {
168
- element = null;
169
- }
170
- }
162
+ const element = target ? findPreviewNode(iframeRef?.current?.contentDocument, target) : null;
171
163
  return getAnimationsForElement(
172
164
  allAnimations,
173
165
  { id: targetId, selector: targetSelector },
174
166
  element,
175
167
  );
176
168
  // eslint-disable-next-line react-hooks/exhaustive-deps
177
- }, [allAnimations, targetId, targetSelector, version, iframeRef]);
169
+ }, [allAnimations, target, version, iframeRef]);
178
170
 
179
171
  // fallow-ignore-next-line complexity
180
172
  const animations = useMemo(() => {
@@ -2,8 +2,9 @@
2
2
 
3
3
  import { act } from "react";
4
4
  import { createRoot, type Root } from "react-dom/client";
5
- import { afterEach, describe, expect, it, vi } from "vitest";
5
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
6
6
  import { useLintModal } from "./useLintModal";
7
+ import { usePlayerStore } from "../player/store/playerStore";
7
8
 
8
9
  (globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
9
10
 
@@ -15,6 +16,10 @@ function Probe() {
15
16
  return null;
16
17
  }
17
18
 
19
+ beforeEach(() => {
20
+ usePlayerStore.setState({ timelineProjectId: "demo", previewBooted: true });
21
+ });
22
+
18
23
  afterEach(() => {
19
24
  if (root) act(() => root?.unmount());
20
25
  root = null;
@@ -63,3 +68,31 @@ describe("useLintModal hasLintError", () => {
63
68
  expect(latest?.hasLintError).toBe(true);
64
69
  });
65
70
  });
71
+
72
+ describe("useLintModal background lint", () => {
73
+ it("waits for the live preview to boot before asking the server to lint", async () => {
74
+ usePlayerStore.setState({ previewBooted: false });
75
+ const fetchStub = vi.fn(lintResponse([]));
76
+ vi.stubGlobal("fetch", fetchStub);
77
+ root = createRoot(document.createElement("div"));
78
+ await act(async () => root?.render(<Probe />));
79
+ expect(fetchStub).not.toHaveBeenCalled();
80
+
81
+ await act(async () => usePlayerStore.getState().markPreviewBooted());
82
+ expect(fetchStub).toHaveBeenCalledTimes(1);
83
+ });
84
+
85
+ it("never lints a project the user left before its preview booted", async () => {
86
+ usePlayerStore.setState({ previewBooted: false });
87
+ const fetchStub = vi.fn(lintResponse([]));
88
+ vi.stubGlobal("fetch", fetchStub);
89
+ root = createRoot(document.createElement("div"));
90
+ await act(async () => root?.render(<Probe />));
91
+
92
+ await act(async () => {
93
+ usePlayerStore.getState().beginTimelineSession("another-project");
94
+ usePlayerStore.getState().markPreviewBooted();
95
+ });
96
+ expect(fetchStub).not.toHaveBeenCalled();
97
+ });
98
+ });
@@ -2,6 +2,7 @@ import { buildProjectApiPath } from "../utils/projectRouting";
2
2
  import { useState, useCallback, useEffect, useRef, useMemo } from "react";
3
3
  import type { LintFinding } from "../components/LintModal";
4
4
  import { usePlayerStore } from "../player";
5
+ import { isPreviewBooted, whenPreviewBooted } from "../player/store/playerStore";
5
6
 
6
7
  interface RawFinding {
7
8
  severity?: string;
@@ -23,6 +24,12 @@ function parseFinding(f: RawFinding): LintFinding & { elementId?: string; file?:
23
24
  };
24
25
  }
25
26
 
27
+ async function fetchLintFindings(projectId: string) {
28
+ const res = await fetch(buildProjectApiPath(projectId, `/lint`));
29
+ const data = await res.json();
30
+ return ((data.findings ?? []) as RawFinding[]).map(parseFinding);
31
+ }
32
+
26
33
  export function useLintModal(projectId: string | null, refreshKey?: number) {
27
34
  const [lintModal, setLintModal] = useState<LintFinding[] | null>(null);
28
35
  const [linting, setLinting] = useState(false);
@@ -31,33 +38,28 @@ export function useLintModal(projectId: string | null, refreshKey?: number) {
31
38
  >([]);
32
39
  const autoLintRanRef = useRef(false);
33
40
 
34
- const runLint = useCallback(
35
- async (opts?: { background?: boolean }) => {
36
- if (!projectId) return;
37
- if (!opts?.background) setLinting(true);
38
- try {
39
- const res = await fetch(buildProjectApiPath(projectId, `/lint`));
40
- const data = await res.json();
41
- const parsed = ((data.findings ?? []) as RawFinding[]).map(parseFinding);
42
- if (opts?.background) {
43
- setBackgroundFindings(parsed);
44
- } else {
45
- setLintModal(parsed);
46
- setBackgroundFindings(parsed);
47
- }
48
- } catch (err) {
49
- if (!opts?.background) {
50
- const msg = err instanceof Error ? err.message : String(err);
51
- setLintModal([{ severity: "error", message: `Failed to run lint: ${msg}` }]);
52
- }
53
- } finally {
54
- if (!opts?.background) setLinting(false);
55
- }
56
- },
57
- [projectId],
58
- );
41
+ const runBackgroundLint = useCallback(async () => {
42
+ if (!projectId) return;
43
+ if (!isPreviewBooted(projectId) && !(await whenPreviewBooted(projectId))) return;
44
+ try {
45
+ setBackgroundFindings(await fetchLintFindings(projectId));
46
+ } catch {}
47
+ }, [projectId]);
59
48
 
60
- const handleLint = useCallback(() => runLint(), [runLint]);
49
+ const handleLint = useCallback(async () => {
50
+ if (!projectId) return;
51
+ setLinting(true);
52
+ try {
53
+ const parsed = await fetchLintFindings(projectId);
54
+ setLintModal(parsed);
55
+ setBackgroundFindings(parsed);
56
+ } catch (err) {
57
+ const msg = err instanceof Error ? err.message : String(err);
58
+ setLintModal([{ severity: "error", message: `Failed to run lint: ${msg}` }]);
59
+ } finally {
60
+ setLinting(false);
61
+ }
62
+ }, [projectId]);
61
63
 
62
64
  const prevProjectIdRef = useRef(projectId);
63
65
  useEffect(() => {
@@ -67,14 +69,14 @@ export function useLintModal(projectId: string | null, refreshKey?: number) {
67
69
  }
68
70
  if (!projectId || autoLintRanRef.current) return;
69
71
  autoLintRanRef.current = true;
70
- void runLint({ background: true });
71
- }, [projectId, runLint]);
72
+ void runBackgroundLint();
73
+ }, [projectId, runBackgroundLint]);
72
74
 
73
75
  useEffect(() => {
74
76
  if (!projectId || !refreshKey) return;
75
- const timer = setTimeout(() => void runLint({ background: true }), 1000);
77
+ const timer = setTimeout(() => void runBackgroundLint(), 1000);
76
78
  return () => clearTimeout(timer);
77
- }, [projectId, refreshKey, runLint]);
79
+ }, [projectId, refreshKey, runBackgroundLint]);
78
80
 
79
81
  const closeLintModal = useCallback(() => setLintModal(null), []);
80
82