@hyperframes/studio 0.2.8 → 0.3.0

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
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>HyperFrames Studio</title>
7
- <script type="module" crossorigin src="/assets/index-ByOno-cZ.js"></script>
7
+ <script type="module" crossorigin src="/assets/index-QlToZFln.js"></script>
8
8
  <link rel="stylesheet" crossorigin href="/assets/index-DHr9yo58.css">
9
9
  </head>
10
10
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/studio",
3
- "version": "0.2.8",
3
+ "version": "0.3.0",
4
4
  "description": "",
5
5
  "repository": {
6
6
  "type": "git",
@@ -32,8 +32,8 @@
32
32
  "@phosphor-icons/react": "^2.1.10",
33
33
  "codemirror": "^6.0.1",
34
34
  "motion": "^12.38.0",
35
- "@hyperframes/player": "0.2.7",
36
- "@hyperframes/core": "0.2.5"
35
+ "@hyperframes/core": "0.3.0",
36
+ "@hyperframes/player": "0.3.0"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@types/react": "^19.0.0",
@@ -47,7 +47,7 @@
47
47
  "vite": "^6.4.2",
48
48
  "vitest": "^3.2.4",
49
49
  "zustand": "^5.0.0",
50
- "@hyperframes/producer": "0.2.5"
50
+ "@hyperframes/producer": "0.3.0"
51
51
  },
52
52
  "peerDependencies": {
53
53
  "react": "^18.0.0 || ^19.0.0",
@@ -1,7 +1,9 @@
1
1
  import { forwardRef, useRef } from "react";
2
2
  import { useMountEffect } from "../../hooks/useMountEffect";
3
3
  import type { HyperframesPlayer } from "@hyperframes/player";
4
- import "@hyperframes/player"; // registers <hyperframes-player> custom element
4
+ // NOTE: importing "@hyperframes/player" registers a class extending HTMLElement
5
+ // at module load, which throws under SSR. Defer the import to the mount effect
6
+ // so it only runs in the browser.
5
7
 
6
8
  interface PlayerProps {
7
9
  projectId?: string;
@@ -27,55 +29,68 @@ export const Player = forwardRef<HTMLIFrameElement, PlayerProps>(
27
29
  const container = containerRef.current;
28
30
  if (!container) return;
29
31
 
30
- // Create the web component imperatively to avoid JSX custom-element typing.
31
- const player = document.createElement("hyperframes-player") as HyperframesPlayer;
32
- const src = directUrl || `/api/projects/${projectId}/preview`;
33
- player.setAttribute("src", src);
34
- player.setAttribute("width", String(portrait ? 1080 : 1920));
35
- player.setAttribute("height", String(portrait ? 1920 : 1080));
36
- player.style.width = "100%";
37
- player.style.height = "100%";
38
- player.style.display = "block";
39
- container.appendChild(player);
32
+ let canceled = false;
33
+ let cleanup: (() => void) | undefined;
40
34
 
41
- // Bridge the inner iframe to the forwarded ref for useTimelinePlayer.
42
- const iframe = player.iframeElement;
43
- if (typeof ref === "function") {
44
- ref(iframe);
45
- } else if (ref) {
46
- (ref as React.MutableRefObject<HTMLIFrameElement | null>).current = iframe;
47
- }
35
+ // Dynamic import registers the custom element in the browser only.
36
+ import("@hyperframes/player").then(() => {
37
+ if (canceled) return;
48
38
 
49
- // Prevent the web component's built-in click-to-toggle behavior.
50
- // The studio manages playback exclusively via useTimelinePlayer.
51
- const preventToggle = (e: Event) => e.stopImmediatePropagation();
52
- player.addEventListener("click", preventToggle, { capture: true });
39
+ // Create the web component imperatively to avoid JSX custom-element typing.
40
+ const player = document.createElement("hyperframes-player") as HyperframesPlayer;
41
+ const src = directUrl || `/api/projects/${projectId}/preview`;
42
+ player.setAttribute("src", src);
43
+ player.setAttribute("width", String(portrait ? 1080 : 1920));
44
+ player.setAttribute("height", String(portrait ? 1920 : 1080));
45
+ player.style.width = "100%";
46
+ player.style.height = "100%";
47
+ player.style.display = "block";
48
+ container.appendChild(player);
53
49
 
54
- // Forward the iframe's native load event to the studio's onIframeLoad.
55
- const handleLoad = () => {
56
- loadCountRef.current++;
57
- // Reveal animation on reload (hot-reload, composition switch)
58
- if (loadCountRef.current > 1) {
59
- container.classList.remove("preview-revealing");
60
- void container.offsetWidth;
61
- container.classList.add("preview-revealing");
62
- const onEnd = () => container.classList.remove("preview-revealing");
63
- container.addEventListener("animationend", onEnd, { once: true });
64
- }
65
- onLoad();
66
- };
67
- iframe.addEventListener("load", handleLoad);
68
-
69
- return () => {
70
- iframe.removeEventListener("load", handleLoad);
71
- player.removeEventListener("click", preventToggle, { capture: true });
72
- container.removeChild(player);
73
- // Clear the forwarded ref
50
+ // Bridge the inner iframe to the forwarded ref for useTimelinePlayer.
51
+ const iframe = player.iframeElement;
74
52
  if (typeof ref === "function") {
75
- ref(null);
53
+ ref(iframe);
76
54
  } else if (ref) {
77
- (ref as React.MutableRefObject<HTMLIFrameElement | null>).current = null;
55
+ (ref as React.MutableRefObject<HTMLIFrameElement | null>).current = iframe;
78
56
  }
57
+
58
+ // Prevent the web component's built-in click-to-toggle behavior.
59
+ // The studio manages playback exclusively via useTimelinePlayer.
60
+ const preventToggle = (e: Event) => e.stopImmediatePropagation();
61
+ player.addEventListener("click", preventToggle, { capture: true });
62
+
63
+ // Forward the iframe's native load event to the studio's onIframeLoad.
64
+ const handleLoad = () => {
65
+ loadCountRef.current++;
66
+ // Reveal animation on reload (hot-reload, composition switch)
67
+ if (loadCountRef.current > 1) {
68
+ container.classList.remove("preview-revealing");
69
+ void container.offsetWidth;
70
+ container.classList.add("preview-revealing");
71
+ const onEnd = () => container.classList.remove("preview-revealing");
72
+ container.addEventListener("animationend", onEnd, { once: true });
73
+ }
74
+ onLoad();
75
+ };
76
+ iframe.addEventListener("load", handleLoad);
77
+
78
+ cleanup = () => {
79
+ iframe.removeEventListener("load", handleLoad);
80
+ player.removeEventListener("click", preventToggle, { capture: true });
81
+ container.removeChild(player);
82
+ // Clear the forwarded ref
83
+ if (typeof ref === "function") {
84
+ ref(null);
85
+ } else if (ref) {
86
+ (ref as React.MutableRefObject<HTMLIFrameElement | null>).current = null;
87
+ }
88
+ };
89
+ });
90
+
91
+ return () => {
92
+ canceled = true;
93
+ cleanup?.();
79
94
  };
80
95
  });
81
96