@hyperframes/studio 0.8.137 → 0.8.139

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 (85) hide show
  1. package/dist/assets/{hyperframes-player-D_Mh6hbx.js → hyperframes-player-CckOoCJ8.js} +1 -1
  2. package/dist/assets/{index-CHEctGzc.js → index-BmIPrb8I.js} +1 -1
  3. package/dist/assets/{index-B3lJsA-e.js → index-Br1Whc0f.js} +189 -189
  4. package/dist/assets/{index-CC_E-Cbl.css → index-CVd4O6eH.css} +1 -1
  5. package/dist/assets/{index-rtccfg9b.js → index-DsEkE5_G.js} +1 -1
  6. package/dist/{chunk-4S3AYCOG.js → chunk-E422JDF5.js} +2 -2
  7. package/dist/{chunk-TDWGXBUT.js → chunk-TFLU2MGI.js} +1 -6
  8. package/dist/chunk-TFLU2MGI.js.map +1 -0
  9. package/dist/{chunk-7UQWVN52.js → chunk-UYWON5AX.js} +2 -2
  10. package/dist/{domEditingLayers-IEVLO54Z.js → domEditingLayers-KBTPQKGA.js} +3 -3
  11. package/dist/index.d.ts +2 -1
  12. package/dist/index.html +2 -2
  13. package/dist/index.js +867 -499
  14. package/dist/index.js.map +1 -1
  15. package/dist/player.js +2 -2
  16. package/dist/styles.css +1 -1
  17. package/package.json +9 -8
  18. package/src/App.tsx +11 -11
  19. package/src/components/editor/DomEditOverlay.tsx +9 -5
  20. package/src/components/editor/LayersPanel.test.ts +25 -0
  21. package/src/components/editor/LayersPanel.tsx +1 -1
  22. package/src/components/editor/PropertyPanel.test.tsx +600 -748
  23. package/src/components/editor/anchoredResizeCommitFeedsOffset.test.ts +2 -0
  24. package/src/components/editor/domEditOverlayGeometry.ts +7 -7
  25. package/src/components/editor/domEditOverlayGestures.ts +18 -0
  26. package/src/components/editor/domEditOverlayStartGesture.blocked.test.ts +1 -0
  27. package/src/components/editor/domEditOverlayStartGesture.test.ts +3 -0
  28. package/src/components/editor/domEditOverlayStartGesture.ts +9 -1
  29. package/src/components/editor/dragUndoPaint.test.ts +2 -0
  30. package/src/components/editor/gestureHoldsPreviewReload.test.tsx +432 -15
  31. package/src/components/editor/groupDropKeepsSelection.test.ts +1 -0
  32. package/src/components/editor/manualEditsDom.ts +7 -7
  33. package/src/components/editor/manualEditsTypes.ts +5 -0
  34. package/src/components/editor/manualOffsetDrag.test.ts +23 -0
  35. package/src/components/editor/manualOffsetDrag.ts +5 -0
  36. package/src/components/editor/previewGestureStarts.ts +284 -0
  37. package/src/components/editor/rotateInlineSave.test.ts +1 -0
  38. package/src/components/editor/shiftDragAxisLock.test.tsx +1 -0
  39. package/src/components/editor/useDomEditOverlayGestures.ts +67 -71
  40. package/src/components/nle/NLEContext.test.ts +26 -0
  41. package/src/components/nle/NLEContext.tsx +8 -3
  42. package/src/hooks/draggedGsapPosition.ts +8 -0
  43. package/src/hooks/editMoment.ts +13 -0
  44. package/src/hooks/gsapDragCommit.ts +2 -1
  45. package/src/hooks/gsapDragPositionCommit.ts +19 -7
  46. package/src/hooks/gsapPositionDetection.ts +5 -3
  47. package/src/hooks/gsapResizeIntercept.ts +11 -4
  48. package/src/hooks/gsapRuntimeBridge.ts +31 -11
  49. package/src/hooks/gsapValueAtPlayhead.test.ts +15 -0
  50. package/src/hooks/gsapValueAtPlayhead.ts +12 -5
  51. package/src/hooks/timelineEditingHelpers.test.ts +22 -0
  52. package/src/hooks/timelineEditingHelpers.ts +18 -2
  53. package/src/hooks/useConsoleErrorCapture.test.tsx +87 -0
  54. package/src/hooks/useConsoleErrorCapture.ts +18 -22
  55. package/src/hooks/useDomSelection.test.ts +21 -2
  56. package/src/hooks/useEditHistoryActions.test.tsx +20 -0
  57. package/src/hooks/useEditHistoryActions.ts +2 -0
  58. package/src/hooks/useGsapAwareEditing.test.tsx +38 -0
  59. package/src/hooks/useGsapAwareEditing.ts +148 -136
  60. package/src/hooks/useGsapAwareGroupMove.ts +126 -113
  61. package/src/hooks/useGsapScriptCommits.ts +5 -4
  62. package/src/hooks/usePreviewPersistence.history.test.tsx +7 -2
  63. package/src/hooks/usePreviewPersistence.ts +1 -4
  64. package/src/hooks/useTimelineDeleteOps.test.tsx +10 -0
  65. package/src/hooks/useTimelineDeleteOps.ts +9 -10
  66. package/src/index.ts +1 -0
  67. package/src/player/hooks/useShadowPreviewReload.ts +6 -6
  68. package/src/player/hooks/useTimelinePlayer.shadowReload.test.ts +14 -0
  69. package/src/player/hooks/useTimelinePlayer.ts +8 -5
  70. package/src/player/lib/thumbnailScheduler.test.ts +20 -1
  71. package/src/player/lib/thumbnailScheduler.ts +1 -1
  72. package/src/player/lib/timelineElementHelpers.ts +0 -11
  73. package/src/player/previewReloading.test.ts +48 -0
  74. package/src/player/previewReloading.ts +48 -0
  75. package/src/styles/studio.css +9 -0
  76. package/src/utils/gsapUndoRestore.browser.test.ts +21 -12
  77. package/src/utils/rootDuration.ts +5 -4
  78. package/src/utils/studioPendingEdits.test.ts +27 -0
  79. package/src/utils/studioPendingEdits.ts +9 -0
  80. package/src/utils/timelineAssetDrop.test.ts +13 -4
  81. package/src/utils/timelineAssetDrop.ts +6 -2
  82. package/dist/chunk-TDWGXBUT.js.map +0 -1
  83. /package/dist/{chunk-4S3AYCOG.js.map → chunk-E422JDF5.js.map} +0 -0
  84. /package/dist/{chunk-7UQWVN52.js.map → chunk-UYWON5AX.js.map} +0 -0
  85. /package/dist/{domEditingLayers-IEVLO54Z.js.map → domEditingLayers-KBTPQKGA.js.map} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/studio",
3
- "version": "0.8.137",
3
+ "version": "0.8.139",
4
4
  "description": "",
5
5
  "repository": {
6
6
  "type": "git",
@@ -60,12 +60,12 @@
60
60
  "dockview-react": "8.3.1",
61
61
  "gsap": "^3.13.0",
62
62
  "mediabunny": "^1.45.3",
63
- "@hyperframes/core": "0.8.137",
64
- "@hyperframes/sdk": "0.8.137",
65
- "@hyperframes/player": "0.8.137",
66
- "@hyperframes/parsers": "0.8.137",
67
- "@hyperframes/studio-server": "0.8.137",
68
- "@hyperframes/shader-transitions": "0.8.137"
63
+ "@hyperframes/core": "0.8.139",
64
+ "@hyperframes/player": "0.8.139",
65
+ "@hyperframes/shader-transitions": "0.8.139",
66
+ "@hyperframes/studio-server": "0.8.139",
67
+ "@hyperframes/sdk": "0.8.139",
68
+ "@hyperframes/parsers": "0.8.139"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@tailwindcss/vite": "^4.3.3",
@@ -73,6 +73,7 @@
73
73
  "@types/react-dom": "19",
74
74
  "@vitejs/plugin-react": "^4.0.0",
75
75
  "chokidar": "^4.0.3",
76
+ "esbuild": "^0.25.12",
76
77
  "fake-indexeddb": "^6.2.5",
77
78
  "puppeteer-core": "^25.2.1",
78
79
  "tailwindcss": "^4.3.3",
@@ -81,7 +82,7 @@
81
82
  "vite": "^6.4.2",
82
83
  "vitest": "^4.1.11",
83
84
  "zustand": "^5.0.0",
84
- "@hyperframes/producer": "0.8.137"
85
+ "@hyperframes/producer": "0.8.139"
85
86
  },
86
87
  "peerDependencies": {
87
88
  "react": "19",
package/src/App.tsx CHANGED
@@ -1,6 +1,6 @@
1
1
  import { useOwnPreviewIframe, usePreviewIframeStore } from "./player/store/previewIframeStore";
2
2
  import { buildProjectApiPath } from "./utils/projectRouting";
3
- import { useState, useCallback, useRef, useMemo } from "react";
3
+ import { useState, useCallback, useRef, useMemo, type SetStateAction } from "react";
4
4
  import { useStableHandlers } from "./hooks/useStableHandlers";
5
5
  import { useHistoryFlags, useToolbarSession } from "./hooks/useShellSlices";
6
6
  import { useDismissingTabSetter, useRightPanelIntent } from "./hooks/useRightPanelIntents";
@@ -62,6 +62,7 @@ import { useStudioSessionStart } from "./hooks/useStudioSessionStart";
62
62
  import { useTimelineAddAtPlayhead } from "./hooks/useTimelineAddAtPlayhead";
63
63
  import { readStudioUrlStateFromWindow, resolveMasterCompositionPath } from "./utils/studioUrlState";
64
64
  import { useActiveComposition } from "./hooks/useActiveComposition";
65
+ import { requestPreviewReload } from "./player/previewReloading";
65
66
  const getTimelineSelectionSet = () => usePlayerStore.getState().selectedElementIds;
66
67
 
67
68
  export interface StudioAppProps {
@@ -79,7 +80,7 @@ export function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason }: St
79
80
  const [compIdToSrc, setCompIdToSrc] = useState<Map<string, string>>(new Map());
80
81
  const previewIframe = useOwnPreviewIframe();
81
82
  const [compositionLoading, setCompositionLoading] = useState(true);
82
- const [refreshKey, setRefreshKey] = useState(0);
83
+ const [refreshKey, setRefreshKeyState] = useState(0);
83
84
  const [previewDocumentVersion, refreshPreviewDocumentVersion] = usePreviewDocumentVersion();
84
85
  const [blockPreview, setBlockPreview] = useState<BlockPreviewInfo | null>(null);
85
86
  const previewIframeRef = useRef<HTMLIFrameElement | null>(null);
@@ -104,7 +105,11 @@ export function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason }: St
104
105
  const handleDomZIndexReorderCommitRef = useRef<TimelineZIndexReorderCommit | null>(null);
105
106
  const pendingTimelineEditPathRef = useRef(new Set<string>());
106
107
  const isGestureRecordingRef = useRef(false);
107
- const reloadPreview = useCallback(() => setRefreshKey((k) => k + 1), []);
108
+ const setRefreshKey = useCallback((next: SetStateAction<number>) => {
109
+ requestPreviewReload();
110
+ setRefreshKeyState(next);
111
+ }, []);
112
+ const reloadPreview = useCallback(() => setRefreshKey((k) => k + 1), [setRefreshKey]);
108
113
  const fileManagerResult = useFileManager({
109
114
  projectId,
110
115
  showToast,
@@ -144,7 +149,7 @@ export function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason }: St
144
149
  recordEdit: editHistory.recordEdit,
145
150
  previewIframeRef,
146
151
  activeCompPathRef,
147
- reloadPreview: () => setRefreshKey((k) => k + 1),
152
+ reloadPreview,
148
153
  });
149
154
  const previewPersistence = useStableHandlers(previewPersistenceResult, projectId);
150
155
  const externalFileChanges = useStudioExternalFileChanges({
@@ -357,11 +362,7 @@ export function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason }: St
357
362
  waitForPendingDomEditSaves: previewPersistence.waitForPendingDomEditSaves,
358
363
  });
359
364
  const frameCapture = useStableHandlers(frameCaptureResult, projectId);
360
- const {
361
- consoleErrors,
362
- setConsoleErrors,
363
- resetErrors: resetConsoleErrors,
364
- } = useConsoleErrorCapture(previewIframe);
365
+ const { consoleErrors, setConsoleErrors } = useConsoleErrorCapture(previewIframe);
365
366
  const fileDrop = useGlobalFileDrop(timelineEditing.handleTimelineFileDrop);
366
367
  const handleToggleRecordingRef = useRef<(method?: "button" | "keyboard") => void>(() => {});
367
368
  const domEditSessionRef = useRef(domEditSession);
@@ -385,10 +386,9 @@ export function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason }: St
385
386
  previewIframeRef.current = iframe;
386
387
  usePreviewIframeStore.getState().setIframe(iframe);
387
388
  appHotkeys.syncPreviewHotkeys(iframe);
388
- resetConsoleErrors();
389
389
  refreshPreviewDocumentVersion();
390
390
  },
391
- [appHotkeys, resetConsoleErrors, refreshPreviewDocumentVersion],
391
+ [appHotkeys, refreshPreviewDocumentVersion],
392
392
  );
393
393
  const rightPanel = useRightPanelIntent();
394
394
  const { inspectorPanelActive, shouldShowMotionPath, shouldShowSelectedDomBounds } =
@@ -10,6 +10,7 @@ import { useZOrderCrossedFlash, ZOrderCrossedFlash } from "./useZOrderCrossedFla
10
10
  import { useCanvasContextMenuState } from "./useCanvasContextMenuState";
11
11
  import {
12
12
  type BlockedMoveState,
13
+ type WaitingPressState,
13
14
  type DomEditGroupPathOffsetCommit,
14
15
  type FocusableDomEditOverlay,
15
16
  type MoveCommitOptions,
@@ -166,6 +167,7 @@ export const DomEditOverlay = memo(function DomEditOverlay({
166
167
  const gestureRef = useRef<GestureState | null>(null);
167
168
  const groupGestureRef = useRef<GroupGestureState | null>(null);
168
169
  const blockedMoveRef = useRef<BlockedMoveState | null>(null);
170
+ const waitingPressRef = useRef<WaitingPressState | null>(null);
169
171
  const suppressNextBoxClickRef = useRef(false);
170
172
  const snapGuidesRef = useRef<SnapGuidesState | null>(null);
171
173
  const rafPausedRef = useRef(false);
@@ -273,6 +275,7 @@ export const DomEditOverlay = memo(function DomEditOverlay({
273
275
  });
274
276
 
275
277
  const gestures = createDomEditOverlayGestureHandlers({
278
+ activeCompositionPathRef,
276
279
  overlayRef,
277
280
  iframeRef,
278
281
  boxRef,
@@ -283,6 +286,7 @@ export const DomEditOverlay = memo(function DomEditOverlay({
283
286
  gestureRef,
284
287
  groupGestureRef,
285
288
  blockedMoveRef,
289
+ waitingPressRef,
286
290
  rafPausedRef,
287
291
  suppressNextBoxClickRef,
288
292
  setOverlayRect,
@@ -299,17 +303,17 @@ export const DomEditOverlay = memo(function DomEditOverlay({
299
303
  });
300
304
 
301
305
  useEffect(() => {
302
- if (readOnly) gestures.clearPointerState(selectionRef);
306
+ if (readOnly) gestures.clearPointerState(selectionRef, true);
303
307
  }, [gestures, readOnly, selectionRef]);
304
308
  // A gesture that loses its pointer, the window or the overlay is cancelled, so its mark goes too.
305
- const cancelGestureRef = useRef(() => {});
306
- cancelGestureRef.current = () => gestures.clearPointerState(selectionRef);
309
+ const cancelGestureRef = useRef((_dropQueued?: boolean) => {});
310
+ cancelGestureRef.current = (dropQueued) => gestures.clearPointerState(selectionRef, dropQueued);
307
311
  useMountEffect(() => {
308
312
  const cancel = () => cancelGestureRef.current();
309
313
  window.addEventListener("blur", cancel);
310
314
  return () => {
311
315
  window.removeEventListener("blur", cancel);
312
- cancel();
316
+ cancelGestureRef.current(true);
313
317
  };
314
318
  });
315
319
 
@@ -493,7 +497,7 @@ export const DomEditOverlay = memo(function DomEditOverlay({
493
497
  onPointerLeave={() => onCanvasPointerLeaveRef.current()}
494
498
  onPointerUp={marquee.onPointerUp}
495
499
  onPointerCancel={marquee.onPointerCancel}
496
- onLostPointerCapture={() => cancelGestureRef.current()}
500
+ onLostPointerCapture={gestures.onLostPointerCapture}
497
501
  onContextMenu={hostInput ? undefined : handleContextMenu}
498
502
  >
499
503
  {!hostInput && hoverSelection && hoverRect && compRect.width > 0 && (
@@ -306,4 +306,29 @@ describe("LayersPanel preview promotion", () => {
306
306
  expect(collectedRootIds()).toEqual(["a"]);
307
307
  fetchSpy.mockRestore();
308
308
  });
309
+
310
+ it("a row press starts no text selection, so dragging a row leaves no letters highlighted", () => {
311
+ const doc = new Window().document;
312
+ doc.body.innerHTML = `<div data-composition-id="a"><div id="back"></div><div id="front"></div></div>`;
313
+ const iframe = document.createElement("iframe");
314
+ Object.defineProperty(iframe, "contentDocument", { value: doc });
315
+ const item = (id: string) =>
316
+ ({
317
+ key: id,
318
+ element: doc.getElementById(id),
319
+ label: id,
320
+ depth: 0,
321
+ childCount: 0,
322
+ id,
323
+ }) as unknown as DomEditLayerItem;
324
+ mocks.collect.mockReset().mockReturnValue([item("back"), item("front")]);
325
+ mocks.previewIframeRef.current = iframe;
326
+ usePreviewIframeStore.setState({ iframe });
327
+ act(() => root.render(createElement(LayersPanel)));
328
+ act(() => void iframe.dispatchEvent(new Event("load")));
329
+
330
+ const rows = [...host.querySelectorAll("[data-layer-index]")];
331
+ expect(rows).toHaveLength(2);
332
+ expect(rows.every((row) => row.closest(".select-none"))).toBe(true);
333
+ });
309
334
  });
@@ -433,7 +433,7 @@ export const LayersPanel = memo(function LayersPanel({ host }: { host?: LayersPa
433
433
  </div>
434
434
  <div
435
435
  ref={scrollContainerRef}
436
- className="relative min-h-0 flex-1 overflow-y-auto py-1"
436
+ className="relative min-h-0 flex-1 select-none overflow-y-auto py-1"
437
437
  onPointerMove={handleContainerPointerMove}
438
438
  onPointerUp={handleContainerPointerUp}
439
439
  onPointerCancel={handleContainerPointerUp}