@hyperframes/studio 0.8.131 → 0.8.133

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.d.ts CHANGED
@@ -272,6 +272,8 @@ interface PlaybackReadinessSlice {
272
272
  previewBooted: boolean;
273
273
  setTimelineReady: (ready: boolean) => void;
274
274
  markPreviewBooted: () => void;
275
+ playLocked: boolean;
276
+ setPlayLocked: (locked: boolean) => void;
275
277
  /** Sets timelineReady once doc's readiness inputs settle, or immediately
276
278
  * if doc is null. A wait a later call supersedes never wins the race.
277
279
  * Waits for doc's load step first: it pauses and rewinds the preview, so a
package/dist/index.html CHANGED
@@ -14,7 +14,7 @@
14
14
  document.documentElement.dataset.theme = "paper";
15
15
  }
16
16
  </script>
17
- <script type="module" crossorigin src="/assets/index-BEcHKkGY.js"></script>
17
+ <script type="module" crossorigin src="/assets/index-Dj21zXWA.js"></script>
18
18
  <link rel="stylesheet" crossorigin href="/assets/index-D11atkBt.css">
19
19
  </head>
20
20
  <body>
package/dist/index.js CHANGED
@@ -39,7 +39,7 @@ import {
39
39
  usePreviewVariablesStore,
40
40
  whenPreviewBooted,
41
41
  writeStudioUiPreferences
42
- } from "./chunk-DRVWEPO5.js";
42
+ } from "./chunk-FOEID72M.js";
43
43
  import {
44
44
  Button,
45
45
  ContextMenu,
@@ -9099,100 +9099,37 @@ function paintsAbove(a, b) {
9099
9099
  if (a.domIndex != null && b.domIndex != null) return a.domIndex > b.domIndex;
9100
9100
  return false;
9101
9101
  }
9102
- function boundaryZ(neighbours2, reduce) {
9103
- return neighbours2.length > 0 ? reduce(neighbours2.map((o) => o.zIndex)) : null;
9104
- }
9105
- function resolveEditedZ(edited, overlapping, overlappersOf, patchZ) {
9106
- const visualOverlap = overlapping.filter((o) => !o.isAudio);
9107
- if (visualOverlap.length === 0) return false;
9108
- const below = visualOverlap.filter((o) => laneIsAbove(edited, o));
9109
- const above = visualOverlap.filter((o) => laneIsAbove(o, edited));
9110
- const correct = below.every((o) => paintsAbove(edited, o)) && above.every((o) => paintsAbove(o, edited));
9111
- if (correct) return false;
9112
- const maxBelow = boundaryZ(below, (zs) => Math.max(...zs));
9113
- const single = trySingleZ(edited, below, above);
9114
- if (single != null) {
9115
- if (single !== edited.zIndex) patchZ(edited, single);
9116
- return single !== edited.zIndex;
9117
- }
9118
- const target = maxBelow != null ? maxBelow + 1 : 0;
9119
- const clamped = Math.max(0, target);
9120
- if (clamped !== edited.zIndex) patchZ(edited, clamped);
9121
- liftAbove(edited, overlappersOf, patchZ);
9122
- return true;
9123
- }
9124
- function zBetweenNeighbours(maxBelow, minAbove, correctAt) {
9125
- if (minAbove - maxBelow >= 2) {
9126
- const mid = Math.floor((maxBelow + minAbove) / 2);
9127
- return mid > maxBelow && mid < minAbove ? mid : null;
9128
- }
9129
- return correctAt(minAbove) ? minAbove : null;
9130
- }
9131
- function zBelowOnly(minAbove, correctAt) {
9132
- const candidate = minAbove - 1;
9133
- if (candidate >= 0) return candidate;
9134
- return correctAt(minAbove) ? minAbove : null;
9135
- }
9136
- function trySingleZ(edited, below, above) {
9137
- const maxBelow = boundaryZ(below, (zs) => Math.max(...zs));
9138
- const minAbove = boundaryZ(above, (zs) => Math.min(...zs));
9139
- const correctAt = (z) => {
9140
- const probe2 = { ...edited, zIndex: z };
9141
- return below.every((b) => paintsAbove(probe2, b)) && above.every((a) => paintsAbove(a, probe2));
9142
- };
9143
- if (maxBelow != null && minAbove != null)
9144
- return zBetweenNeighbours(maxBelow, minAbove, correctAt);
9145
- if (maxBelow != null) return maxBelow + 1;
9146
- if (minAbove != null) return zBelowOnly(minAbove, correctAt);
9147
- return null;
9102
+ function raisedZ(clip, overlapping) {
9103
+ const below = overlapping.filter((o) => laneIsAbove(clip, o));
9104
+ if (below.every((o) => paintsAbove(clip, o))) return null;
9105
+ return Math.max(...below.map((o) => o.zIndex)) + 1;
9148
9106
  }
9149
- function liftAbove(edited, overlappersOf, patchZ) {
9150
- const queue2 = [edited];
9151
- const raiseAbove = (clip, floor) => {
9152
- if (paintsAbove(clip, floor)) return;
9153
- patchZ(clip, floor.zIndex + 1);
9154
- queue2.push(clip);
9155
- };
9156
- while (queue2.length > 0) {
9157
- const floor = queue2.shift();
9158
- for (const other of overlappersOf(floor)) {
9159
- if (laneIsAbove(other, floor) && !paintsAbove(other, floor)) raiseAbove(other, floor);
9160
- }
9161
- }
9107
+ function loweredZ(clip, overlapping) {
9108
+ const above = overlapping.filter((o) => laneIsAbove(o, clip));
9109
+ if (above.every((o) => paintsAbove(o, clip))) return null;
9110
+ const keepOver = overlapping.filter((o) => laneIsAbove(clip, o) && paintsAbove(clip, o));
9111
+ const floor = Math.max(
9112
+ 0,
9113
+ ...keepOver.map(
9114
+ (o) => paintsAbove({ ...clip, zIndex: o.zIndex }, o) ? o.zIndex : o.zIndex + 1
9115
+ )
9116
+ );
9117
+ const z = Math.max(floor, Math.min(...above.map((o) => o.zIndex)) - 1);
9118
+ return z < clip.zIndex ? z : null;
9162
9119
  }
9163
- function computeStackingPatches(elements, editedKeys) {
9120
+ function computeStackingPatches(elements, editedKeys, direction) {
9164
9121
  const editedSet = new Set(editedKeys);
9165
- if (editedSet.size === 0) return [];
9166
- const allResolved = elements.filter((e) => Number.isFinite(e.zIndex));
9167
- const editedScopes = new Set(allResolved.filter((e) => editedSet.has(e.key)).map(paintScopeKey));
9168
- const resolved2 = allResolved.filter((e) => editedScopes.has(paintScopeKey(e)));
9169
- const byKey = new Map(resolved2.map((e) => [e.key, { ...e }]));
9170
- const edited = resolved2.filter((e) => editedSet.has(e.key) && !e.isAudio).map((e) => byKey.get(e.key)).sort((a, b) => b.track - a.track);
9171
- const changed = /* @__PURE__ */ new Map();
9172
- const patchZ = (clip, z) => {
9173
- clip.zIndex = z;
9174
- changed.set(clip.key, z);
9175
- };
9176
- const all = [...byKey.values()];
9177
- const overlappersOf = (clip) => all.filter(
9178
- (o) => o.key !== clip.key && !o.isAudio && samePaintScope(clip, o) && overlapsInTime(clip, o)
9179
- );
9180
- for (const clip of edited) {
9181
- resolveEditedZ(clip, overlappersOf(clip), overlappersOf, patchZ);
9182
- }
9183
- const emitted = /* @__PURE__ */ new Set();
9122
+ const live = elements.filter((e) => Number.isFinite(e.zIndex) && !e.isAudio).map((e) => ({ ...e }));
9123
+ const edited = live.filter((e) => editedSet.has(e.key)).sort((a, b) => direction === "up" ? b.track - a.track : a.track - b.track);
9184
9124
  const patches = [];
9185
9125
  for (const clip of edited) {
9186
- if (changed.has(clip.key) && !emitted.has(clip.key)) {
9187
- patches.push({ key: clip.key, zIndex: changed.get(clip.key) });
9188
- emitted.add(clip.key);
9189
- }
9190
- }
9191
- for (const [key2, zIndex] of changed) {
9192
- if (!emitted.has(key2)) {
9193
- patches.push({ key: key2, zIndex });
9194
- emitted.add(key2);
9195
- }
9126
+ const overlapping = live.filter(
9127
+ (o) => o.key !== clip.key && overlapsInTime(clip, o) && samePaintScope(clip, o)
9128
+ );
9129
+ const zIndex = direction === "up" ? raisedZ(clip, overlapping) : loweredZ(clip, overlapping);
9130
+ if (zIndex === null) continue;
9131
+ clip.zIndex = zIndex;
9132
+ patches.push({ key: clip.key, zIndex });
9196
9133
  }
9197
9134
  return patches;
9198
9135
  }
@@ -9841,7 +9778,6 @@ function commitDraggedClipMove(drag, deps) {
9841
9778
  if (multi?.keys.has(keyOf6(e))) return { ...e, start: multi.movedStart(e) };
9842
9779
  return e;
9843
9780
  });
9844
- const multiKeys = multi ? multi.keys : null;
9845
9781
  if (!isVertical || !deps.readZIndex || !deps.onStackingPatches) {
9846
9782
  void refreshAfterDurableLaneMove(
9847
9783
  persistMoveEdits(edits, deps, coalesceKey, "lane-reorder"),
@@ -9857,7 +9793,6 @@ function commitDraggedClipMove(drag, deps) {
9857
9793
  dragKey,
9858
9794
  drag.element.track,
9859
9795
  drag.previewTrack,
9860
- multiKeys,
9861
9796
  deps,
9862
9797
  coalesceKey
9863
9798
  )
@@ -9947,7 +9882,6 @@ function commitTrackInsert(drag, deps, multi) {
9947
9882
  dragKey,
9948
9883
  drag.element.track,
9949
9884
  drag.insertRow,
9950
- multi ? multi.keys : null,
9951
9885
  deps,
9952
9886
  coalesceKey
9953
9887
  )
@@ -9975,7 +9909,7 @@ function commitZMirrorLaneMove(element, move, deps, coalesceKey) {
9975
9909
  deps
9976
9910
  );
9977
9911
  }
9978
- function syncStackingForEdit(candidate, dragKey, currentLane, aimedLane, multiKeys, deps, coalesceKey) {
9912
+ function syncStackingForEdit(candidate, dragKey, currentLane, aimedLane, deps, coalesceKey) {
9979
9913
  const { readZIndex, onStackingPatches } = deps;
9980
9914
  if (!readZIndex || !onStackingPatches) return Promise.resolve();
9981
9915
  if (aimedLane === currentLane) return Promise.resolve();
@@ -9990,11 +9924,11 @@ function syncStackingForEdit(candidate, dragKey, currentLane, aimedLane, multiKe
9990
9924
  domIndex,
9991
9925
  stackingContextId: el.stackingContextId ?? null
9992
9926
  }));
9993
- const editedKeys = [dragKey];
9994
- if (multiKeys) {
9995
- for (const k of multiKeys) if (k !== dragKey) editedKeys.push(k);
9996
- }
9997
- const patches = computeStackingPatches(stackingEls, editedKeys);
9927
+ const patches = computeStackingPatches(
9928
+ stackingEls,
9929
+ [dragKey],
9930
+ aimedLane < currentLane ? "up" : "down"
9931
+ );
9998
9932
  if (patches.length === 0) return Promise.resolve();
9999
9933
  return Promise.resolve(onStackingPatches(patches, coalesceKey)).then(() => void 0);
10000
9934
  }
@@ -28278,6 +28212,7 @@ function usePlaybackKeyboard({
28278
28212
  );
28279
28213
  const shuttle = useCallback38(
28280
28214
  (direction) => {
28215
+ if (usePlayerStore.getState().playLocked) return;
28281
28216
  if (shuttleDirectionRef.current === direction) {
28282
28217
  shuttleSpeedIndexRef.current = Math.min(
28283
28218
  shuttleSpeedIndexRef.current + 1,
@@ -31213,7 +31148,7 @@ function useTimelinePlayerLoop({
31213
31148
  const looping = usePlayerStore.getState().loopEnabled && dur > 0;
31214
31149
  setLoopStart(looping ? loopStart : null);
31215
31150
  if (time >= loopEnd) {
31216
- if (looping) {
31151
+ if (looping && !usePlayerStore.getState().playLocked) {
31217
31152
  adapter.seek(loopStart, { keepPlaying: true });
31218
31153
  liveTime.notify(loopStart);
31219
31154
  adapter.play();
@@ -31506,7 +31441,7 @@ function pumpProbeQueue() {
31506
31441
 
31507
31442
  // src/player/lib/playbackSeek.ts
31508
31443
  function shouldResumeForwardPlaybackAfterSeek(input) {
31509
- return input.keepPlaying && !input.wasReverseShuttle && input.storeWasPlaying && (input.duration <= 0 || input.nextTime < input.duration);
31444
+ return !input.playLocked && input.keepPlaying && !input.wasReverseShuttle && input.storeWasPlaying && (input.duration <= 0 || input.nextTime < input.duration);
31510
31445
  }
31511
31446
  function shouldStopAfterSeek(input) {
31512
31447
  return !input.keepPlaying || input.wasReverseShuttle;
@@ -31709,7 +31644,8 @@ function useTimelinePlayer({
31709
31644
  applyPreviewAudioFlags(iframeRef.current, audioMuted, audioVolume);
31710
31645
  }, []);
31711
31646
  const play = useCallback42(() => {
31712
- if (!usePlayerStore.getState().timelineReady) return;
31647
+ const { timelineReady, playLocked } = usePlayerStore.getState();
31648
+ if (!timelineReady || playLocked) return;
31713
31649
  stopRAFLoop();
31714
31650
  stopReverseLoop();
31715
31651
  stopScrubPreviewAudio();
@@ -31793,9 +31729,8 @@ function useTimelinePlayer({
31793
31729
  const pause = useCallback42(() => {
31794
31730
  stopReverseLoop();
31795
31731
  const adapter = getAdapter();
31796
- if (!adapter) return;
31797
- adapter.pause();
31798
- setCurrentTime(adapter.getTime());
31732
+ adapter?.pause();
31733
+ if (adapter) setCurrentTime(adapter.getTime());
31799
31734
  setIsPlaying(false);
31800
31735
  shuttleDirectionRef.current = null;
31801
31736
  shuttleSpeedIndexRef.current = 0;
@@ -31813,17 +31748,19 @@ function useTimelinePlayer({
31813
31748
  const duration = Math.max(0, adapter.getDuration());
31814
31749
  const nextTime = clampToDuration(time, duration);
31815
31750
  const keepPlaying = options?.keepPlaying === true;
31751
+ const { playLocked, isPlaying: storeWasPlaying } = usePlayerStore.getState();
31816
31752
  const shouldResumeAfterSeek = shouldResumeForwardPlaybackAfterSeek({
31817
31753
  keepPlaying,
31818
31754
  wasReverseShuttle,
31819
- storeWasPlaying: usePlayerStore.getState().isPlaying,
31755
+ storeWasPlaying,
31820
31756
  duration,
31821
- nextTime
31757
+ nextTime,
31758
+ playLocked
31822
31759
  });
31823
31760
  adapter.seek(nextTime, options);
31824
31761
  publishSeek(nextTime, options);
31825
31762
  setCurrentTime(nextTime);
31826
- if (!shouldResumeAfterSeek && !keepPlaying) scrubMusicAtSeek(iframeRef.current, nextTime);
31763
+ if (!keepPlaying && !playLocked) scrubMusicAtSeek(iframeRef.current, nextTime);
31827
31764
  if (shouldResumeAfterSeek) {
31828
31765
  stopRAFLoop();
31829
31766
  applyPlaybackRate(usePlayerStore.getState().playbackRate);
@@ -31855,6 +31792,12 @@ function useTimelinePlayer({
31855
31792
  shuttleSpeedIndexRef
31856
31793
  ]
31857
31794
  );
31795
+ useEffect35(
31796
+ () => usePlayerStore.subscribe((state, prev) => {
31797
+ if (state.playLocked && !prev.playLocked && state.isPlaying) pause();
31798
+ }),
31799
+ [pause]
31800
+ );
31858
31801
  useEffect35(() => {
31859
31802
  return usePlayerStore.subscribe((state, prev) => {
31860
31803
  if (state.requestedSeekTime !== null && state.requestedSeekTime !== prev.requestedSeekTime) {