hyperframes 0.8.84 → 0.8.86

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.
@@ -13,6 +13,7 @@ import {
13
13
  applyPreviewVariablesToUrl,
14
14
  buildTimelineElementIdentity,
15
15
  buildTimelineElementKey,
16
+ computePinnedZoomPercent,
16
17
  createTimelineDomNodeResolver,
17
18
  createTimelineResetState,
18
19
  deriveTimelineStoreKey,
@@ -65,7 +66,7 @@ import {
65
66
  whenPreviewBooted,
66
67
  wrapTimeline,
67
68
  writeStudioUiPreferences
68
- } from "./chunk-7VDQIGOU.js";
69
+ } from "./chunk-7PLTC6SV.js";
69
70
  import {
70
71
  Button,
71
72
  ContextMenu,
@@ -2048,20 +2049,20 @@ function getTimelineInsertBoundaryBand(rowHeight) {
2048
2049
  var DRAG_EXTEND_MARGIN_PX = 160;
2049
2050
  var MIN_TIMELINE_EXTENT_S = 60;
2050
2051
  var FIT_ZOOM_HEADROOM = 1.2;
2052
+ function getTimelineFitSpan(effectiveDuration) {
2053
+ return Number.isFinite(effectiveDuration) && effectiveDuration > 0 ? effectiveDuration * FIT_ZOOM_HEADROOM : MIN_TIMELINE_EXTENT_S;
2054
+ }
2051
2055
  function getTimelineFitPps(viewportWidth, effectiveDuration, contentOrigin) {
2052
- const safeDuration = Number.isFinite(effectiveDuration) && effectiveDuration > 0 ? effectiveDuration : 0;
2053
- const span = Math.max(safeDuration * FIT_ZOOM_HEADROOM, MIN_TIMELINE_EXTENT_S);
2054
2056
  if (!Number.isFinite(viewportWidth) || viewportWidth <= contentOrigin) return 100;
2055
- return (viewportWidth - contentOrigin - 2) / span;
2057
+ return (viewportWidth - contentOrigin - 2) / getTimelineFitSpan(effectiveDuration);
2056
2058
  }
2057
2059
  function getTimelineDisplayContentWidth(input) {
2058
2060
  const safePps = Number.isFinite(input.pps) ? Math.max(input.pps, 0) : 0;
2059
2061
  return Math.max(
2060
- input.trackContentWidth,
2061
2062
  input.viewportWidth - input.contentOrigin - 2,
2062
2063
  input.dragGhostEndPx ?? 0,
2063
2064
  input.resizeGhostEndPx ?? 0,
2064
- MIN_TIMELINE_EXTENT_S * safePps
2065
+ getTimelineFitSpan(input.effectiveDuration) * safePps
2065
2066
  );
2066
2067
  }
2067
2068
  function getTimelineContentXFromClient(input) {
@@ -2887,7 +2888,7 @@ function useTimelinePlayhead({
2887
2888
  );
2888
2889
  useEffect5(() => {
2889
2890
  syncPlayheadPosition(currentTime);
2890
- }, [currentTime, pps, syncPlayheadPosition]);
2891
+ }, [currentTime, pps, syncPlayheadPosition, timelineReady, elementsLength]);
2891
2892
  useLayoutEffect(() => {
2892
2893
  const scroll = scrollRef.current;
2893
2894
  if (!scroll || zoomMode !== "fit") return;
@@ -7055,7 +7056,7 @@ function useTimelineStackingSync({
7055
7056
  }
7056
7057
 
7057
7058
  // src/player/components/useTimelineGeometry.ts
7058
- import { useEffect as useEffect12, useRef as useRef13 } from "react";
7059
+ import { useEffect as useEffect12, useLayoutEffect as useLayoutEffect2, useRef as useRef13 } from "react";
7059
7060
  function useTimelineGeometry({
7060
7061
  viewportWidth,
7061
7062
  effectiveDuration,
@@ -7074,11 +7075,10 @@ function useTimelineGeometry({
7074
7075
  const fitPps = getTimelineFitPps(viewportWidth, effectiveDuration, contentOrigin);
7075
7076
  const pps = getTimelinePixelsPerSecond(fitPps, zoomMode, manualZoomPercent);
7076
7077
  ppsRef.current = pps;
7077
- const trackContentWidth = Math.max(0, effectiveDuration * pps);
7078
7078
  const dragGhostEndPx = draggedClip?.started ? (draggedClip.previewStart + draggedClip.element.duration) * pps + DRAG_EXTEND_MARGIN_PX : 0;
7079
7079
  const resizeGhostEndPx = resizingClip?.started ? (resizingClip.previewStart + resizingClip.previewDuration) * pps + DRAG_EXTEND_MARGIN_PX : 0;
7080
7080
  const displayContentWidth = getTimelineDisplayContentWidth({
7081
- trackContentWidth,
7081
+ effectiveDuration,
7082
7082
  viewportWidth,
7083
7083
  contentOrigin,
7084
7084
  pps,
@@ -7107,6 +7107,15 @@ function useTimelineGeometry({
7107
7107
  useEffect12(() => {
7108
7108
  usePlayerStore.getState().setTimelineScale(pps, fitPps);
7109
7109
  }, [pps, fitPps]);
7110
+ const hasClips = expandedElements.length > 0;
7111
+ const lastScale = useRef13({ effectiveDuration, pps, hasClips });
7112
+ useLayoutEffect2(() => {
7113
+ const last = lastScale.current;
7114
+ lastScale.current = { effectiveDuration, pps, hasClips };
7115
+ if (zoomMode !== "manual" || !last.hasClips || !hasClips) return;
7116
+ if (last.effectiveDuration === effectiveDuration) return;
7117
+ usePlayerStore.setState({ manualZoomPercent: computePinnedZoomPercent(last.pps, fitPps) });
7118
+ });
7110
7119
  return {
7111
7120
  pps,
7112
7121
  fitPps,
@@ -11800,7 +11809,7 @@ function useTimelineClipRenderWindow({
11800
11809
  }
11801
11810
 
11802
11811
  // src/player/components/useTimelineActiveClips.ts
11803
- import { useCallback as useCallback16, useLayoutEffect as useLayoutEffect2, useRef as useRef22 } from "react";
11812
+ import { useCallback as useCallback16, useLayoutEffect as useLayoutEffect3, useRef as useRef22 } from "react";
11804
11813
  function isTimelineIntervalActive(interval, time) {
11805
11814
  return Number.isFinite(time) && !interval.hidden && Number.isFinite(interval.start) && Number.isFinite(interval.end) && time >= interval.start && time <= interval.end;
11806
11815
  }
@@ -11867,7 +11876,7 @@ function useTimelineActiveClips({
11867
11876
  },
11868
11877
  [scrollRef]
11869
11878
  );
11870
- useLayoutEffect2(() => {
11879
+ useLayoutEffect3(() => {
11871
11880
  refreshRecords(currentTime);
11872
11881
  }, [clipStateVersion, currentTime, elementStateVersion, refreshRecords]);
11873
11882
  useMountEffect(
@@ -11885,7 +11894,7 @@ function useTimelineLaneMoveRefresh() {
11885
11894
  }
11886
11895
 
11887
11896
  // src/player/components/useTimelineFocusCoordinator.ts
11888
- import { useEffect as useEffect18, useLayoutEffect as useLayoutEffect3, useMemo as useMemo17, useRef as useRef23 } from "react";
11897
+ import { useEffect as useEffect18, useLayoutEffect as useLayoutEffect4, useMemo as useMemo17, useRef as useRef23 } from "react";
11889
11898
 
11890
11899
  // src/player/components/timelineKeyboardNavigation.ts
11891
11900
  var NAVIGATION_KEYS = /* @__PURE__ */ new Set([
@@ -11914,7 +11923,8 @@ function clipItems(rowId, elements) {
11914
11923
  kind: "clip",
11915
11924
  rowId,
11916
11925
  elementId: id,
11917
- time: element.start + element.duration / 2
11926
+ time: element.start + element.duration / 2,
11927
+ element
11918
11928
  };
11919
11929
  });
11920
11930
  }
@@ -12304,7 +12314,7 @@ function useTimelineFocusCoordinator({
12304
12314
  resolvedRef.current = null;
12305
12315
  return null;
12306
12316
  }, [logicalRows, projectId, request, sessionEpoch]);
12307
- useLayoutEffect3(() => {
12317
+ useLayoutEffect4(() => {
12308
12318
  previousRowsRef.current = logicalRows;
12309
12319
  }, [logicalRows]);
12310
12320
  useEffect18(() => {
@@ -12422,7 +12432,7 @@ function useTimelineLogicalRows({
12422
12432
  // src/player/components/useTimelineRowVirtualization.ts
12423
12433
  import {
12424
12434
  useCallback as useCallback19,
12425
- useLayoutEffect as useLayoutEffect4,
12435
+ useLayoutEffect as useLayoutEffect5,
12426
12436
  useMemo as useMemo20,
12427
12437
  useRef as useRef24,
12428
12438
  useState as useState14
@@ -12602,7 +12612,7 @@ function useTimelineRowVirtualization({
12602
12612
  });
12603
12613
  const previousLayoutRef = useRef24(rowGeometry);
12604
12614
  const previousSessionEpochRef = useRef24(sessionEpoch);
12605
- useLayoutEffect4(() => {
12615
+ useLayoutEffect5(() => {
12606
12616
  const scroll = scrollRef.current;
12607
12617
  const previousGeometry = previousLayoutRef.current;
12608
12618
  if (previousSessionEpochRef.current !== sessionEpoch) {
@@ -20390,7 +20400,8 @@ function useTimelineKeyboardActor({
20390
20400
  focusedTargetId,
20391
20401
  rowGeometry,
20392
20402
  scrollRef,
20393
- onToggleRow
20403
+ onToggleRow,
20404
+ onDrillDown
20394
20405
  }) {
20395
20406
  const rovingTargetId = (focusedTargetId && locateTimelineLogicalTarget(logicalRows, focusedTargetId)?.target.id) ?? logicalRows[0]?.id ?? null;
20396
20407
  const logicalRowCountByTrack = useMemo27(() => {
@@ -20441,13 +20452,19 @@ function useTimelineKeyboardActor({
20441
20452
  openContextMenu(targetElement);
20442
20453
  return;
20443
20454
  }
20455
+ const composition = located.target.kind === "clip" && located.target.element;
20456
+ if (event.key === "Enter" && composition && composition.compositionSrc && onDrillDown) {
20457
+ event.preventDefault();
20458
+ onDrillDown(composition);
20459
+ return;
20460
+ }
20444
20461
  if (event.key !== "Enter" && event.key !== " " || located.target.kind !== "row" || !located.target.expandable) {
20445
20462
  return;
20446
20463
  }
20447
20464
  event.preventDefault();
20448
20465
  onToggleRow(located.target);
20449
20466
  },
20450
- [logicalRowCountByTrack, logicalRows, onToggleRow, rowGeometry, scrollRef]
20467
+ [logicalRowCountByTrack, logicalRows, onDrillDown, onToggleRow, rowGeometry, scrollRef]
20451
20468
  );
20452
20469
  return { rovingTargetId, onFocus, onKeyDown };
20453
20470
  }
@@ -20683,9 +20700,8 @@ function TimelineLanes({
20683
20700
  focusedTargetId,
20684
20701
  rowGeometry,
20685
20702
  scrollRef,
20686
- onToggleRow: (row) => {
20687
- if (row.elementId) toggleClipExpandedTracked(row.elementId);
20688
- }
20703
+ onToggleRow: (row) => row.elementId && toggleClipExpandedTracked(row.elementId),
20704
+ onDrillDown
20689
20705
  });
20690
20706
  return /* @__PURE__ */ jsx52(
20691
20707
  "div",
@@ -22341,7 +22357,7 @@ var Timeline = Object.assign(TimelineComposed, {
22341
22357
  import { memo as memo17, useMemo as useMemo30 } from "react";
22342
22358
 
22343
22359
  // src/hooks/useThumbnailLease.ts
22344
- import { useCallback as useCallback31, useLayoutEffect as useLayoutEffect5, useRef as useRef40, useSyncExternalStore as useSyncExternalStore3 } from "react";
22360
+ import { useCallback as useCallback31, useLayoutEffect as useLayoutEffect6, useRef as useRef40, useSyncExternalStore as useSyncExternalStore3 } from "react";
22345
22361
 
22346
22362
  // src/player/lib/thumbnailScheduler.ts
22347
22363
  var PRIORITY_SCORE = {
@@ -22737,7 +22753,7 @@ function useThumbnailLease(request, scheduler = thumbnailScheduler) {
22737
22753
  const current = requestRef.current;
22738
22754
  return current && identity !== null ? scheduler.getSnapshot(current) : IDLE;
22739
22755
  }, [identity, scheduler]);
22740
- useLayoutEffect5(() => {
22756
+ useLayoutEffect6(() => {
22741
22757
  if (priority) leaseRef.current?.updatePriority(priority);
22742
22758
  }, [priority]);
22743
22759
  return useSyncExternalStore3(subscribe, getSnapshot, () => IDLE);
@@ -26081,23 +26097,33 @@ function useCompositionStack({
26081
26097
  ]);
26082
26098
  const onCompositionChangeRef = useRef46(onCompositionChange);
26083
26099
  onCompositionChangeRef.current = onCompositionChange;
26100
+ const masterSeekRef = useRef46(0);
26101
+ const masterSeekProjectRef = useRef46(null);
26102
+ const projectIdRef = useRef46(projectId);
26103
+ projectIdRef.current = projectId;
26104
+ const stackRef = useRef46(compositionStack);
26084
26105
  const updateCompositionStack = useCallback38((action) => {
26085
- setCompositionStack((prev) => {
26086
- const next = typeof action === "function" ? action(prev) : action;
26087
- const id = next[next.length - 1]?.id;
26088
- queueMicrotask(() => onCompositionChangeRef.current?.(id === "master" ? null : id));
26089
- return next;
26090
- });
26106
+ const prev = stackRef.current;
26107
+ const next = typeof action === "function" ? action(prev) : action;
26108
+ stackRef.current = next;
26109
+ const player = usePlayerStore.getState();
26110
+ if (prev.length === 1 && next.length > 1) {
26111
+ masterSeekRef.current = player.isPlaying ? liveTime.latest() : player.currentTime;
26112
+ masterSeekProjectRef.current = projectIdRef.current;
26113
+ } else if (next.length === 1 && prev.length > 1) {
26114
+ if (masterSeekProjectRef.current === projectIdRef.current) {
26115
+ player.setCurrentTime(masterSeekRef.current);
26116
+ }
26117
+ }
26118
+ setCompositionStack(next);
26119
+ const id = next[next.length - 1]?.id;
26120
+ queueMicrotask(() => onCompositionChangeRef.current?.(id === "master" ? null : id));
26091
26121
  }, []);
26092
- const masterSeekRef = useRef46(0);
26093
26122
  const [compIdToSrc, setCompIdToSrc] = useState28(/* @__PURE__ */ new Map());
26094
26123
  const compIdToSrcRef = useRef46(compIdToSrc);
26095
26124
  compIdToSrcRef.current = compIdToSrc;
26096
26125
  const handleNavigateComposition = useCallback38(
26097
26126
  (index) => {
26098
- if (index === 0 && masterSeekRef.current > 0) {
26099
- usePlayerStore.getState().setCurrentTime(masterSeekRef.current);
26100
- }
26101
26127
  usePlayerStore.getState().setElements([]);
26102
26128
  updateCompositionStack((prev) => prev.slice(0, index + 1));
26103
26129
  },
@@ -26107,7 +26133,6 @@ function useCompositionStack({
26107
26133
  const handleDrillDown = useCallback38(
26108
26134
  (element) => {
26109
26135
  if (!element.compositionSrc) return;
26110
- masterSeekRef.current = usePlayerStore.getState().currentTime;
26111
26136
  const compId = element.id;
26112
26137
  let resolvedPath = compIdToSrcRef.current.get(compId);
26113
26138
  if (!resolvedPath) {
@@ -26473,7 +26498,7 @@ import {
26473
26498
  memo as memo19,
26474
26499
  useCallback as useCallback44,
26475
26500
  useEffect as useEffect34,
26476
- useLayoutEffect as useLayoutEffect6,
26501
+ useLayoutEffect as useLayoutEffect7,
26477
26502
  useRef as useRef51,
26478
26503
  useState as useState31
26479
26504
  } from "react";
@@ -26971,7 +26996,7 @@ var NLEPreview = memo19(function NLEPreview2({
26971
26996
  [drawNavigator]
26972
26997
  );
26973
26998
  const zoomProjectRef = useRef51(projectId);
26974
- useLayoutEffect6(() => {
26999
+ useLayoutEffect7(() => {
26975
27000
  if (zoomProjectRef.current === projectId) return;
26976
27001
  zoomProjectRef.current = projectId;
26977
27002
  zoomRef.current = DEFAULT_PREVIEW_ZOOM;
@@ -27300,7 +27325,7 @@ function CompositionBreadcrumb({ stack, onNavigate }) {
27300
27325
  "nav",
27301
27326
  {
27302
27327
  "aria-label": "Composition navigation",
27303
- className: "flex items-center gap-1 px-2 h-8 border-b border-neutral-800/50 bg-neutral-900/50 shrink-0",
27328
+ className: "flex items-center gap-1 px-2 h-8 border-b border-hairline bg-bg-1 shrink-0",
27304
27329
  children: [
27305
27330
  /* @__PURE__ */ jsx70(
27306
27331
  "button",
@@ -27313,7 +27338,7 @@ function CompositionBreadcrumb({ stack, onNavigate }) {
27313
27338
  });
27314
27339
  onNavigate(stack.length - 2);
27315
27340
  },
27316
- className: "flex items-center gap-1 px-1.5 py-0.5 rounded-sm text-xs text-neutral-400 hover:text-white hover:bg-neutral-800 active:scale-[0.98] transition-colors",
27341
+ className: "flex items-center gap-1 px-1.5 py-0.5 rounded-sm text-xs text-text-3 hover:text-text-0 hover:bg-hover active:scale-[0.98] transition-colors",
27317
27342
  title: "Back (Esc, or double-click empty timeline)",
27318
27343
  "aria-label": "Back to parent composition",
27319
27344
  children: /* @__PURE__ */ jsx70(ArrowLeft2, { size: 12, weight: "bold" })
@@ -27322,8 +27347,8 @@ function CompositionBreadcrumb({ stack, onNavigate }) {
27322
27347
  stack.map((level, i) => {
27323
27348
  const isLast = i === stack.length - 1;
27324
27349
  return /* @__PURE__ */ jsxs48("span", { className: "flex items-center gap-1", children: [
27325
- i > 0 && /* @__PURE__ */ jsx70(CaretRight2, { size: 10, className: "text-neutral-600 shrink-0" }),
27326
- isLast ? /* @__PURE__ */ jsx70("span", { className: "text-xs text-neutral-200 font-medium", children: level.label }) : /* @__PURE__ */ jsx70(
27350
+ i > 0 && /* @__PURE__ */ jsx70(CaretRight2, { size: 10, className: "text-text-4 shrink-0" }),
27351
+ isLast ? /* @__PURE__ */ jsx70("span", { className: "text-xs text-text-1 font-medium", children: level.label }) : /* @__PURE__ */ jsx70(
27327
27352
  "button",
27328
27353
  {
27329
27354
  type: "button",
@@ -27331,7 +27356,7 @@ function CompositionBreadcrumb({ stack, onNavigate }) {
27331
27356
  trackStudioEvent("navigation", { action: "breadcrumb", target: level.label });
27332
27357
  onNavigate(i);
27333
27358
  },
27334
- className: "text-xs text-neutral-500 hover:text-neutral-200 transition-colors",
27359
+ className: "text-xs text-text-3 hover:text-text-1 transition-colors",
27335
27360
  children: level.label
27336
27361
  }
27337
27362
  )
@@ -38172,7 +38197,7 @@ function ConnectedDomEditOverlay({
38172
38197
  // src/components/editor/TopologyLens.tsx
38173
38198
  import {
38174
38199
  useEffect as useEffect49,
38175
- useLayoutEffect as useLayoutEffect7,
38200
+ useLayoutEffect as useLayoutEffect8,
38176
38201
  useReducer,
38177
38202
  useRef as useRef71,
38178
38203
  useState as useState44,
@@ -39017,7 +39042,7 @@ function TopologyLens({ iframeRef, activeCompositionPath }) {
39017
39042
  const ownedCallIdRef = useRef71(callId);
39018
39043
  const pendingUnmountDismissRef = useRef71(null);
39019
39044
  ownedCallIdRef.current = callId;
39020
- useLayoutEffect7(() => {
39045
+ useLayoutEffect8(() => {
39021
39046
  if (!callId || !handle) {
39022
39047
  setMeasured(null);
39023
39048
  return;
@@ -49305,7 +49330,7 @@ function ColorGradingSection({
49305
49330
  import { useEffect as useEffect73, useRef as useRef92, useState as useState67 } from "react";
49306
49331
 
49307
49332
  // src/components/editor/propertyPanelColor.tsx
49308
- import { useCallback as useCallback78, useEffect as useEffect68, useLayoutEffect as useLayoutEffect8, useRef as useRef88, useState as useState59 } from "react";
49333
+ import { useCallback as useCallback78, useEffect as useEffect68, useLayoutEffect as useLayoutEffect9, useRef as useRef88, useState as useState59 } from "react";
49309
49334
  import { createPortal as createPortal8 } from "react-dom";
49310
49335
  import { jsx as jsx113, jsxs as jsxs81 } from "react/jsx-runtime";
49311
49336
  var COLOR_PICKER_SIZE = { width: 292, height: 386 };
@@ -49504,7 +49529,7 @@ function ColorField({
49504
49529
  )
49505
49530
  );
49506
49531
  }, []);
49507
- useLayoutEffect8(() => {
49532
+ useLayoutEffect9(() => {
49508
49533
  if (!open) return;
49509
49534
  updatePanelPosition();
49510
49535
  const handlePositionInvalidated = () => updatePanelPosition();
@@ -65562,7 +65587,7 @@ var FileTree = memo38(function FileTree2({
65562
65587
  });
65563
65588
 
65564
65589
  // src/App.tsx
65565
- import { useState as useState148, useCallback as useCallback181, useRef as useRef179, useMemo as useMemo77, useLayoutEffect as useLayoutEffect11 } from "react";
65590
+ import { useState as useState148, useCallback as useCallback181, useRef as useRef179, useMemo as useMemo77, useLayoutEffect as useLayoutEffect12 } from "react";
65566
65591
 
65567
65592
  // src/hooks/useRightPanelIntents.ts
65568
65593
  import { useCallback as useCallback88, useEffect as useEffect97, useRef as useRef114 } from "react";
@@ -82220,7 +82245,7 @@ import { useState as useState124, useRef as useRef162, useEffect as useEffect126
82220
82245
  import { classifyWebAudioMediaRoute } from "@hyperframes/core/runtime/web-audio-route";
82221
82246
 
82222
82247
  // src/components/sidebar/AssetContextMenu.tsx
82223
- import { useCallback as useCallback152, useEffect as useEffect125, useLayoutEffect as useLayoutEffect9, useRef as useRef161, useState as useState123 } from "react";
82248
+ import { useCallback as useCallback152, useEffect as useEffect125, useLayoutEffect as useLayoutEffect10, useRef as useRef161, useState as useState123 } from "react";
82224
82249
  import { Fragment as Fragment54, jsx as jsx193, jsxs as jsxs153 } from "react/jsx-runtime";
82225
82250
  function isValidAssetName(name) {
82226
82251
  return name.length > 0 && !/[/\\]/.test(name) && !name.includes("..");
@@ -82240,7 +82265,7 @@ function ContextMenu3({
82240
82265
  const [mode, setMode] = useState123("menu");
82241
82266
  const [renameDraft, setRenameDraft] = useState123(() => filename(asset));
82242
82267
  const [renameError, setRenameError] = useState123(null);
82243
- useLayoutEffect9(() => {
82268
+ useLayoutEffect10(() => {
82244
82269
  const el = menuRef.current;
82245
82270
  if (!el) return;
82246
82271
  const rect = el.getBoundingClientRect();
@@ -85928,7 +85953,7 @@ function ParamControl({
85928
85953
  import { memo as memo51 } from "react";
85929
85954
 
85930
85955
  // src/components/renders/RenderQueue.tsx
85931
- import { memo as memo50, useState as useState139, useRef as useRef173, useEffect as useEffect138, useLayoutEffect as useLayoutEffect10, useId as useId5 } from "react";
85956
+ import { memo as memo50, useState as useState139, useRef as useRef173, useEffect as useEffect138, useLayoutEffect as useLayoutEffect11, useId as useId5 } from "react";
85932
85957
  import { createPortal as createPortal10 } from "react-dom";
85933
85958
  import { CANVAS_DIMENSIONS } from "@hyperframes/parsers";
85934
85959
 
@@ -86313,7 +86338,7 @@ function FormatInfoTooltip({ format }) {
86313
86338
  timeoutRef.current = setTimeout(() => setOpen(false), 120);
86314
86339
  };
86315
86340
  useEffect138(() => () => clearTimeout(timeoutRef.current), []);
86316
- useLayoutEffect10(() => {
86341
+ useLayoutEffect11(() => {
86317
86342
  if (!open) return;
86318
86343
  const el = triggerRef.current;
86319
86344
  if (!el) return;
@@ -89362,7 +89387,13 @@ function StudioRightPanels({
89362
89387
 
89363
89388
  // src/components/TimelineToolbar.tsx
89364
89389
  import { useEffect as useEffect144, useRef as useRef176 } from "react";
89365
- import { Image as Image3, Magnet, MagnifyingGlassMinus, MagnifyingGlassPlus } from "@phosphor-icons/react";
89390
+ import {
89391
+ ArrowsOutLineHorizontal,
89392
+ Image as Image3,
89393
+ Magnet,
89394
+ MagnifyingGlassMinus,
89395
+ MagnifyingGlassPlus
89396
+ } from "@phosphor-icons/react";
89366
89397
 
89367
89398
  // src/hooks/useEnableKeyframes.ts
89368
89399
  import { useCallback as useCallback177 } from "react";
@@ -89823,7 +89854,7 @@ function TimelineHistoryButtons(props) {
89823
89854
  }
89824
89855
 
89825
89856
  // src/components/TimelineToolbar.tsx
89826
- import { jsx as jsx225, jsxs as jsxs179 } from "react/jsx-runtime";
89857
+ import { Fragment as Fragment63, jsx as jsx225, jsxs as jsxs179 } from "react/jsx-runtime";
89827
89858
  var NO_KEYFRAME_TOGGLE = {
89828
89859
  state: "none",
89829
89860
  isMotionPath: false,
@@ -89882,7 +89913,8 @@ function TimelineToolbar({
89882
89913
  domEditSession,
89883
89914
  onSplitElement,
89884
89915
  history: history2,
89885
- showAddBeat = true
89916
+ showAddBeat = true,
89917
+ showKeyframes = true
89886
89918
  }) {
89887
89919
  const activeTool = usePlayerStore((s) => s.activeTool);
89888
89920
  const setActiveTool = usePlayerStore((s) => s.setActiveTool);
@@ -89917,9 +89949,12 @@ function TimelineToolbar({
89917
89949
  onToggle: onToggleKeyframe
89918
89950
  } = useKeyframeToggle(domEditSession);
89919
89951
  useKeyframeKeyboard({
89920
- enabled: Boolean(onToggleKeyframe),
89952
+ enabled: showKeyframes && Boolean(onToggleKeyframe),
89921
89953
  onAddKeyframe: onToggleKeyframe
89922
89954
  });
89955
+ useEffect144(() => {
89956
+ if (!showKeyframes) setAutoKeyframeEnabled(false);
89957
+ }, [showKeyframes, setAutoKeyframeEnabled]);
89923
89958
  useEffect144(() => {
89924
89959
  const onKeyDown = (e) => {
89925
89960
  if (e.key !== "n" && e.key !== "N") return;
@@ -90002,61 +90037,63 @@ function TimelineToolbar({
90002
90037
  children: /* @__PURE__ */ jsx225(AudioMetersIcon, { size: 16 })
90003
90038
  }
90004
90039
  ) }),
90005
- /* @__PURE__ */ jsx225(
90006
- Tooltip,
90007
- {
90008
- label: keyframePathEndpoint ? "Motion path endpoints cannot be removed" : !onToggleKeyframe ? "Select an animated element to add keyframes" : keyframeIsMotionPath ? keyframeWillExtend ? "Extend motion path to playhead (K)" : keyframeState === "active" ? "Remove waypoint from motion path (K)" : "Add waypoint to motion path (K)" : keyframeState === "active" ? "Remove keyframe at playhead (K)" : keyframeState === "inactive" ? keyframeWillExtend ? "Add keyframe at playhead, extends animation (K)" : "Add keyframe at playhead (K)" : "Add keyframe (K)",
90009
- children: /* @__PURE__ */ jsx225(
90010
- "button",
90011
- {
90012
- type: "button",
90013
- disabled: !onToggleKeyframe,
90014
- onClick: onToggleKeyframe,
90015
- "aria-label": keyframePathEndpoint ? "Motion path endpoint" : keyframeIsMotionPath ? keyframeState === "active" ? "Remove motion path waypoint" : keyframeWillExtend ? "Extend motion path to playhead" : "Add motion path waypoint" : keyframeState === "active" ? "Remove keyframe at playhead" : "Add keyframe at playhead",
90016
- className: !onToggleKeyframe ? flatDisabled : `${flatBtn} active:scale-[0.98] hover:bg-white/6 ${keyframeState === "active" ? "text-studio-accent" : keyframeState === "inactive" ? "text-neutral-400 hover:text-studio-accent" : "text-neutral-600 hover:text-neutral-400"}`,
90017
- children: /* @__PURE__ */ jsx225("svg", { width: "16", height: "16", viewBox: "0 0 10 10", fill: "currentColor", children: keyframeState === "active" ? /* @__PURE__ */ jsx225("path", { d: "M5 0.5L9.5 5L5 9.5L0.5 5Z" }) : /* @__PURE__ */ jsx225(
90018
- "path",
90019
- {
90020
- d: "M5 1.2L8.8 5L5 8.8L1.2 5Z",
90021
- fill: "none",
90022
- stroke: "currentColor",
90023
- strokeWidth: "1.2"
90024
- }
90025
- ) })
90026
- }
90027
- )
90028
- }
90029
- ),
90030
- /* @__PURE__ */ jsx225(
90031
- Tooltip,
90032
- {
90033
- label: autoKeyframeEnabled ? "Auto-record manual edits as keyframes (click to turn off)" : "Manual edits will not be recorded as keyframes (click to turn on)",
90034
- children: /* @__PURE__ */ jsx225(
90035
- "button",
90036
- {
90037
- type: "button",
90038
- onClick: () => setAutoKeyframeEnabled(!autoKeyframeEnabled),
90039
- "aria-label": "Auto-record manual edits as keyframes",
90040
- "aria-pressed": autoKeyframeEnabled,
90041
- className: `${flatBtn} active:scale-[0.98] hover:bg-white/6 ${autoKeyframeEnabled ? "text-red-400 hover:text-red-300" : "text-neutral-600 hover:text-neutral-400"}`,
90042
- children: /* @__PURE__ */ jsxs179("svg", { width: "16", height: "16", viewBox: "0 0 10 10", fill: "none", children: [
90043
- /* @__PURE__ */ jsx225("path", { d: "M5 0.7L9.3 5L5 9.3L0.7 5Z", stroke: "currentColor", strokeWidth: "1" }),
90044
- /* @__PURE__ */ jsx225(
90045
- "circle",
90040
+ showKeyframes && /* @__PURE__ */ jsxs179(Fragment63, { children: [
90041
+ /* @__PURE__ */ jsx225(
90042
+ Tooltip,
90043
+ {
90044
+ label: keyframePathEndpoint ? "Motion path endpoints cannot be removed" : !onToggleKeyframe ? "Select an animated element to add keyframes" : keyframeIsMotionPath ? keyframeWillExtend ? "Extend motion path to playhead (K)" : keyframeState === "active" ? "Remove waypoint from motion path (K)" : "Add waypoint to motion path (K)" : keyframeState === "active" ? "Remove keyframe at playhead (K)" : keyframeState === "inactive" ? keyframeWillExtend ? "Add keyframe at playhead, extends animation (K)" : "Add keyframe at playhead (K)" : "Add keyframe (K)",
90045
+ children: /* @__PURE__ */ jsx225(
90046
+ "button",
90047
+ {
90048
+ type: "button",
90049
+ disabled: !onToggleKeyframe,
90050
+ onClick: onToggleKeyframe,
90051
+ "aria-label": keyframePathEndpoint ? "Motion path endpoint" : keyframeIsMotionPath ? keyframeState === "active" ? "Remove motion path waypoint" : keyframeWillExtend ? "Extend motion path to playhead" : "Add motion path waypoint" : keyframeState === "active" ? "Remove keyframe at playhead" : "Add keyframe at playhead",
90052
+ className: !onToggleKeyframe ? flatDisabled : `${flatBtn} active:scale-[0.98] hover:bg-white/6 ${keyframeState === "active" ? "text-studio-accent" : keyframeState === "inactive" ? "text-neutral-400 hover:text-studio-accent" : "text-neutral-600 hover:text-neutral-400"}`,
90053
+ children: /* @__PURE__ */ jsx225("svg", { width: "16", height: "16", viewBox: "0 0 10 10", fill: "currentColor", children: keyframeState === "active" ? /* @__PURE__ */ jsx225("path", { d: "M5 0.5L9.5 5L5 9.5L0.5 5Z" }) : /* @__PURE__ */ jsx225(
90054
+ "path",
90046
90055
  {
90047
- cx: "5",
90048
- cy: "5",
90049
- r: "1.8",
90050
- fill: autoKeyframeEnabled ? "currentColor" : "none",
90056
+ d: "M5 1.2L8.8 5L5 8.8L1.2 5Z",
90057
+ fill: "none",
90051
90058
  stroke: "currentColor",
90052
- strokeWidth: "1"
90059
+ strokeWidth: "1.2"
90053
90060
  }
90054
- )
90055
- ] })
90056
- }
90057
- )
90058
- }
90059
- ),
90061
+ ) })
90062
+ }
90063
+ )
90064
+ }
90065
+ ),
90066
+ /* @__PURE__ */ jsx225(
90067
+ Tooltip,
90068
+ {
90069
+ label: autoKeyframeEnabled ? "Auto-record manual edits as keyframes (click to turn off)" : "Manual edits will not be recorded as keyframes (click to turn on)",
90070
+ children: /* @__PURE__ */ jsx225(
90071
+ "button",
90072
+ {
90073
+ type: "button",
90074
+ onClick: () => setAutoKeyframeEnabled(!autoKeyframeEnabled),
90075
+ "aria-label": "Auto-record manual edits as keyframes",
90076
+ "aria-pressed": autoKeyframeEnabled,
90077
+ className: `${flatBtn} active:scale-[0.98] hover:bg-white/6 ${autoKeyframeEnabled ? "text-red-400 hover:text-red-300" : "text-neutral-600 hover:text-neutral-400"}`,
90078
+ children: /* @__PURE__ */ jsxs179("svg", { width: "16", height: "16", viewBox: "0 0 10 10", fill: "none", children: [
90079
+ /* @__PURE__ */ jsx225("path", { d: "M5 0.7L9.3 5L5 9.3L0.7 5Z", stroke: "currentColor", strokeWidth: "1" }),
90080
+ /* @__PURE__ */ jsx225(
90081
+ "circle",
90082
+ {
90083
+ cx: "5",
90084
+ cy: "5",
90085
+ r: "1.8",
90086
+ fill: autoKeyframeEnabled ? "currentColor" : "none",
90087
+ stroke: "currentColor",
90088
+ strokeWidth: "1"
90089
+ }
90090
+ )
90091
+ ] })
90092
+ }
90093
+ )
90094
+ }
90095
+ )
90096
+ ] }),
90060
90097
  onSplitElement && (() => {
90061
90098
  const el = selectedElementId ? elements.find((e) => (e.key ?? e.id) === selectedElementId) : null;
90062
90099
  const splittable = el != null && canSplitElement(el);
@@ -90154,8 +90191,10 @@ function TimelineToolbar({
90154
90191
  {
90155
90192
  type: "button",
90156
90193
  onClick: () => setZoomMode("fit"),
90194
+ "aria-label": "Fit timeline to width",
90195
+ "aria-pressed": zoomMode === "fit",
90157
90196
  className: `h-7 px-2 rounded-md text-[11px] font-medium transition-colors ${zoomMode === "fit" ? "bg-studio-accent/10 text-studio-accent" : "text-neutral-400 hover:bg-white/6 hover:text-neutral-200"}`,
90158
- children: "Fit"
90197
+ children: /* @__PURE__ */ jsx225(ArrowsOutLineHorizontal, { size: 16, "aria-hidden": "true" })
90159
90198
  }
90160
90199
  ) }),
90161
90200
  /* @__PURE__ */ jsx225(Tooltip, { label: "Zoom out", children: /* @__PURE__ */ jsx225(
@@ -90498,7 +90537,7 @@ function useActiveComposition({
90498
90537
  }
90499
90538
 
90500
90539
  // src/App.tsx
90501
- import { Fragment as Fragment63, jsx as jsx227, jsxs as jsxs181 } from "react/jsx-runtime";
90540
+ import { Fragment as Fragment64, jsx as jsx227, jsxs as jsxs181 } from "react/jsx-runtime";
90502
90541
  var getTimelineSelectionSet = () => usePlayerStore.getState().selectedElementIds;
90503
90542
  function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason } = {}) {
90504
90543
  const { projectId, resolving, waitingForServer } = useServerConnection();
@@ -90794,7 +90833,7 @@ function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason } = {}) {
90794
90833
  });
90795
90834
  handleToggleRecordingRef.current = handleToggleRecording;
90796
90835
  const canvasRectRef = useRef179(null);
90797
- useLayoutEffect11(() => {
90836
+ useLayoutEffect12(() => {
90798
90837
  if (gestureState !== "recording" || !previewIframe) {
90799
90838
  canvasRectRef.current = null;
90800
90839
  return;
@@ -90913,7 +90952,7 @@ function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason } = {}) {
90913
90952
  {
90914
90953
  readOnlyPreview,
90915
90954
  readOnlyPreviewReason,
90916
- panels: /* @__PURE__ */ jsxs181(Fragment63, { children: [
90955
+ panels: /* @__PURE__ */ jsxs181(Fragment64, { children: [
90917
90956
  /* @__PURE__ */ jsx227(
90918
90957
  StudioLeftPanels,
90919
90958
  {
@@ -91481,6 +91520,7 @@ export {
91481
91520
  resolveSourceFile,
91482
91521
  snapEngagedForTravel,
91483
91522
  useClipboard,
91523
+ useCompositionStack,
91484
91524
  useDomEditSelectionContext,
91485
91525
  useDomEditSession,
91486
91526
  useEditHistoryActions,