@dailies-studio/shot-editor 0.1.0 → 0.2.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.
Files changed (432) hide show
  1. package/dist/.vite/manifest.json +382 -0
  2. package/dist/assets/AgentPanel-CQejLbiX.js +2 -0
  3. package/dist/assets/AgentPanel-DJEphah0.css +1 -0
  4. package/dist/assets/AgentQuestionDialog-BqkD0kXF.js +1 -0
  5. package/dist/assets/CodeStudio-Dv4ihHpz.js +23 -0
  6. package/dist/assets/CommentsPanel-B4yhhT1T.js +1 -0
  7. package/dist/assets/ComponentLibraryRail-Ch_dsAru.js +1 -0
  8. package/dist/assets/ComponentStage-BRhRotyH.js +1 -0
  9. package/dist/assets/CurvePane-BjFVabKX.css +1 -0
  10. package/dist/assets/CurvePane-R22WzllJ.js +1 -0
  11. package/dist/assets/ExportDialog-CWLQWTmG.js +2 -0
  12. package/dist/assets/FontPicker-DWKaYL7F.js +1 -0
  13. package/dist/assets/FontPicker-v0IOIArM.css +1 -0
  14. package/dist/assets/GenerateDrawer-Bh3SqDKt.js +2 -0
  15. package/dist/assets/InsertDrawer-Ce1fod1L.js +1 -0
  16. package/dist/assets/LibraryBrowser-D9sEGz5w.js +2 -0
  17. package/dist/assets/MakeItYoursSection-CMO4319_.js +2 -0
  18. package/dist/assets/MaterialGraphCanvas-BFzl4wWR.js +23 -0
  19. package/dist/assets/MaterialGraphCanvas-BIUD59ik.css +1 -0
  20. package/dist/assets/MaterialWorkspace-CqTWqaRA.js +14 -0
  21. package/dist/assets/PreferencesDialog-CBf9_OoD.js +1 -0
  22. package/dist/assets/PresetsSection-CUEaoCVU.js +1 -0
  23. package/dist/assets/SoundHistory-BeS8EW1h.js +1 -0
  24. package/dist/assets/SoundMode-BmcZru3i.js +3 -0
  25. package/dist/assets/StockSounds-DdAUGRYK.js +1 -0
  26. package/dist/assets/TemplateFieldSection-CaeXpLFo.js +1 -0
  27. package/dist/assets/VersionHistoryPanel-CxG6XhNP.js +1 -0
  28. package/dist/assets/board-B88EPuLO.css +1 -0
  29. package/dist/assets/board-CL-he7SL.js +3 -0
  30. package/dist/assets/components-0oOSPN0l.js +1 -0
  31. package/dist/assets/googleFontRanges-gp32wJMo.js +1 -0
  32. package/dist/assets/googleFonts-BNon6M7X.js +1 -0
  33. package/dist/assets/iconsAndAliases-DL7gXP5j.js +1863 -0
  34. package/dist/assets/index-4F5NQmI8.css +1 -0
  35. package/dist/assets/index-BGHQGV5z.js +117 -0
  36. package/dist/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  37. package/dist/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  38. package/dist/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  39. package/dist/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  40. package/dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  41. package/dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  42. package/dist/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  43. package/dist/assets/libraryModel-CpIwfFha.js +1 -0
  44. package/dist/assets/lucide-6iK64tTB.js +9319 -0
  45. package/dist/assets/motionStamp-CH-Hew4E.js +1 -0
  46. package/dist/assets/takeMeasure-Tdg0TxEi.js +1 -0
  47. package/dist/assets/webmcp-BsB9gxWC.js +2 -0
  48. package/dist/index.html +34 -3
  49. package/package.json +12 -9
  50. package/src/addFamilies.ts +66 -0
  51. package/src/api.ts +1631 -55
  52. package/src/app/App.tsx +977 -198
  53. package/src/app/lazyPanel.tsx +137 -0
  54. package/src/app/lazySurfaces.tsx +203 -0
  55. package/src/audio/index.tsx +41 -10
  56. package/src/audio/meter.ts +485 -0
  57. package/src/audio/meterMath.ts +273 -0
  58. package/src/audio/meterStore.ts +155 -0
  59. package/src/audio/offlineMix.ts +289 -0
  60. package/src/audio/preview.ts +928 -118
  61. package/src/bridge.ts +15 -5
  62. package/src/bridgeTypes.ts +1611 -36
  63. package/src/channels.ts +17 -32
  64. package/src/fonts/googleFonts.ts +4 -0
  65. package/src/hostApi.ts +174 -0
  66. package/src/icons.tsx +770 -0
  67. package/src/layerWords.ts +61 -0
  68. package/src/main.tsx +56 -4
  69. package/src/panels/ResizeHandle.tsx +33 -43
  70. package/src/panels/agent/AgentDots.tsx +36 -0
  71. package/src/panels/agent/AgentPanel.tsx +119 -43
  72. package/src/panels/agent/AgentQuestionDialog.tsx +202 -0
  73. package/src/panels/agent/agent.css +113 -97
  74. package/src/panels/agent/agentGate.ts +35 -0
  75. package/src/panels/code/CodeDefaultsPanel.tsx +345 -0
  76. package/src/panels/code/CodeStudio.tsx +373 -0
  77. package/src/panels/code/FileTree.tsx +265 -0
  78. package/src/panels/code/codeBufferGuard.ts +52 -0
  79. package/src/panels/code/codeParts.tsx +321 -0
  80. package/src/panels/comments/CommentComposer.tsx +239 -0
  81. package/src/panels/comments/CommentThread.tsx +346 -0
  82. package/src/panels/comments/CommentsPanel.tsx +336 -0
  83. package/src/panels/comments/InboxButton.tsx +131 -0
  84. package/src/panels/comments/MentionMenu.tsx +98 -0
  85. package/src/panels/comments/parts.tsx +171 -0
  86. package/src/panels/export/ExportDialog.tsx +919 -96
  87. package/src/panels/export/ExportScrim.tsx +57 -0
  88. package/src/panels/files/AssetsTab.tsx +656 -0
  89. package/src/panels/files/FilePanel.tsx +1466 -216
  90. package/src/panels/files/LayerMarks.tsx +40 -0
  91. package/src/panels/files/LeftColumn.tsx +37 -0
  92. package/src/panels/files/McpSection.tsx +690 -47
  93. package/src/panels/files/MissingAssetsNotice.tsx +60 -0
  94. package/src/panels/files/assetDrag.ts +79 -0
  95. package/src/panels/files/resourceParts.tsx +255 -0
  96. package/src/panels/fonts/FontPicker.tsx +762 -0
  97. package/src/panels/fonts/faceCache.ts +207 -0
  98. package/src/panels/fonts/fontPicker.css +271 -0
  99. package/src/panels/fonts/pickerModel.ts +265 -0
  100. package/src/panels/fonts/pickerRequest.ts +46 -0
  101. package/src/panels/header/AccountAvatar.tsx +76 -0
  102. package/src/panels/header/AgentChip.tsx +77 -0
  103. package/src/panels/header/AppMenu.tsx +213 -0
  104. package/src/panels/header/Header.tsx +564 -84
  105. package/src/panels/header/HistoryControls.tsx +31 -5
  106. package/src/panels/header/PresenceStack.tsx +329 -0
  107. package/src/panels/header/ProjectTitle.tsx +232 -0
  108. package/src/panels/header/ShareButton.tsx +59 -0
  109. package/src/panels/header/StatusBadges.tsx +358 -27
  110. package/src/panels/header/SyncDeletedDialog.tsx +88 -0
  111. package/src/panels/header/ZoomControls.tsx +46 -15
  112. package/src/panels/header/agentHandoff.ts +52 -0
  113. package/src/panels/header/leave.ts +26 -0
  114. package/src/panels/help/HelpSection.tsx +189 -163
  115. package/src/panels/insert/InsertDrawer.tsx +723 -0
  116. package/src/panels/insert/iconSet.ts +109 -0
  117. package/src/panels/inspector/ExposeDot.tsx +117 -0
  118. package/src/panels/inspector/FramingRows.tsx +314 -44
  119. package/src/panels/inspector/InspectorFooter.tsx +91 -0
  120. package/src/panels/inspector/InspectorHead.tsx +425 -0
  121. package/src/panels/inspector/InspectorPanel.tsx +408 -124
  122. package/src/panels/inspector/KeyframeDot.tsx +373 -0
  123. package/src/panels/inspector/KeyframeSection.tsx +257 -0
  124. package/src/panels/inspector/MarkerSection.tsx +55 -11
  125. package/src/panels/inspector/ParamRow.tsx +84 -53
  126. package/src/panels/inspector/RightColumn.tsx +152 -0
  127. package/src/panels/inspector/TimeSection.tsx +866 -0
  128. package/src/panels/inspector/animate/EaseControl.tsx +244 -0
  129. package/src/panels/inspector/animate/PresetsSection.tsx +135 -0
  130. package/src/panels/inspector/animate/ShotSection.tsx +562 -48
  131. package/src/panels/inspector/design/AlignRow.tsx +95 -31
  132. package/src/panels/inspector/design/AppearanceSection.tsx +118 -9
  133. package/src/panels/inspector/design/ClonerSection.tsx +323 -62
  134. package/src/panels/inspector/design/ComponentSection.tsx +50 -43
  135. package/src/panels/inspector/design/CornerSection.tsx +45 -14
  136. package/src/panels/inspector/design/DefinitionProps.tsx +315 -0
  137. package/src/panels/inspector/design/DefinitionSection.tsx +136 -0
  138. package/src/panels/inspector/design/DeviceSection.tsx +210 -0
  139. package/src/panels/inspector/design/FillSection.tsx +83 -51
  140. package/src/panels/inspector/design/FontField.tsx +196 -0
  141. package/src/panels/inspector/design/HtmlSection.tsx +23 -12
  142. package/src/panels/inspector/design/InstanceSection.tsx +373 -154
  143. package/src/panels/inspector/design/MaskSection.tsx +84 -0
  144. package/src/panels/inspector/design/MaterialSection.tsx +177 -0
  145. package/src/panels/inspector/design/MediaSection.tsx +273 -120
  146. package/src/panels/inspector/design/MeshSection.tsx +910 -82
  147. package/src/panels/inspector/design/PaintRows.tsx +550 -0
  148. package/src/panels/inspector/design/PlateStyleSection.tsx +177 -0
  149. package/src/panels/inspector/design/ShadowSection.tsx +221 -48
  150. package/src/panels/inspector/design/SplineFollowSection.tsx +235 -0
  151. package/src/panels/inspector/design/StrokeSection.tsx +228 -43
  152. package/src/panels/inspector/design/TemplateFieldSection.tsx +57 -0
  153. package/src/panels/inspector/design/TransformSection.tsx +244 -50
  154. package/src/panels/inspector/design/TypographySection.tsx +679 -110
  155. package/src/panels/inspector/layout.ts +295 -0
  156. package/src/panels/inspector/propValue.ts +199 -0
  157. package/src/panels/inspector/scene/CameraSection.tsx +232 -22
  158. package/src/panels/inspector/scene/EffectsSection.tsx +722 -71
  159. package/src/panels/inspector/scene/MakeItYoursSection.tsx +180 -0
  160. package/src/panels/inspector/scene/SceneDocSection.tsx +191 -0
  161. package/src/panels/inspector/scene/SceneSection.tsx +219 -44
  162. package/src/panels/inspector/scene/TransitionSection.tsx +523 -72
  163. package/src/panels/inspector/storyboard/StoryboardReferenceSection.tsx +593 -0
  164. package/src/panels/inspector/storyboard/VideoStill.tsx +120 -0
  165. package/src/panels/inspector/timeModel.ts +190 -0
  166. package/src/panels/inspector/useDocWrite.ts +438 -146
  167. package/src/panels/library/LibraryBrowser.tsx +435 -0
  168. package/src/panels/library/LibraryGrid.tsx +190 -0
  169. package/src/panels/library/LibraryPreviewPanel.tsx +349 -0
  170. package/src/panels/library/LibraryRail.tsx +84 -0
  171. package/src/panels/library/LibrarySoundPanel.tsx +293 -0
  172. package/src/panels/library/SoundHistory.tsx +245 -0
  173. package/src/panels/library/StockSounds.tsx +390 -0
  174. package/src/panels/library/libraryModel.ts +147 -0
  175. package/src/panels/library/stockSearch.ts +77 -0
  176. package/src/panels/maskMarks.tsx +117 -0
  177. package/src/panels/material/MaterialCommentNode.tsx +54 -0
  178. package/src/panels/material/MaterialGraphCanvas.tsx +861 -0
  179. package/src/panels/material/MaterialGraphMenu.tsx +45 -0
  180. package/src/panels/material/MaterialGroupNode.tsx +56 -0
  181. package/src/panels/material/MaterialLibrary.tsx +253 -0
  182. package/src/panels/material/MaterialNode.tsx +453 -0
  183. package/src/panels/material/MaterialNodePalette.tsx +349 -0
  184. package/src/panels/material/MaterialParameterControls.tsx +525 -0
  185. package/src/panels/material/MaterialRerouteNode.tsx +42 -0
  186. package/src/panels/material/MaterialWorkspace.tsx +501 -0
  187. package/src/panels/material/NodePreview.tsx +70 -0
  188. package/src/panels/material/flowModel.ts +441 -0
  189. package/src/panels/material/parameterValues.ts +231 -0
  190. package/src/panels/material/previewScheduler.ts +235 -0
  191. package/src/panels/material/reactFlow.css +6 -0
  192. package/src/panels/material/socketGlyph.ts +85 -0
  193. package/src/panels/material/viewportMemory.ts +58 -0
  194. package/src/panels/preferences/PreferencesDialog.tsx +215 -0
  195. package/src/panels/share/ShareComponentDialog.tsx +247 -0
  196. package/src/panels/stage/StageMenu.tsx +35 -5
  197. package/src/panels/stage/StagePanel.tsx +338 -23
  198. package/src/panels/stage/cellSlots.tsx +70 -0
  199. package/src/panels/storyboard/BoardCanvas.tsx +739 -0
  200. package/src/panels/storyboard/BoardCursors.tsx +158 -0
  201. package/src/panels/storyboard/BoardDropDialog.tsx +161 -0
  202. package/src/panels/storyboard/BoardPlan.tsx +42 -0
  203. package/src/panels/storyboard/ComponentSheet.tsx +187 -0
  204. package/src/panels/storyboard/ComponentStage.tsx +235 -0
  205. package/src/panels/storyboard/MiniSketch.tsx +173 -0
  206. package/src/panels/storyboard/PartPicker.tsx +64 -0
  207. package/src/panels/storyboard/PlanPanel.tsx +617 -0
  208. package/src/panels/storyboard/ReferencePreview.tsx +217 -0
  209. package/src/panels/storyboard/ShotBrief.tsx +360 -0
  210. package/src/panels/storyboard/ShotCard.tsx +251 -0
  211. package/src/panels/storyboard/StagePreview.tsx +348 -0
  212. package/src/panels/storyboard/StillThumbs.tsx +162 -0
  213. package/src/panels/storyboard/StoryboardView.tsx +79 -0
  214. package/src/panels/storyboard/board.css +1065 -0
  215. package/src/panels/storyboard/board.ts +12 -0
  216. package/src/panels/storyboard/boardClipboard.ts +111 -0
  217. package/src/panels/storyboard/boardOrder.ts +68 -0
  218. package/src/panels/storyboard/boardRows.tsx +425 -0
  219. package/src/panels/storyboard/liveCap.ts +90 -0
  220. package/src/panels/storyboard/plan.css +295 -0
  221. package/src/panels/storyboard/posterCache.ts +133 -0
  222. package/src/panels/storyboard/shotboard/ComponentPicker.tsx +373 -0
  223. package/src/panels/storyboard/shotboard/ImagePicker.tsx +145 -0
  224. package/src/panels/storyboard/shotboard/ShotBoard.tsx +292 -0
  225. package/src/panels/storyboard/shotboard/SketchCanvas.tsx +443 -0
  226. package/src/panels/storyboard/shotboard/SketchInspector.tsx +471 -0
  227. package/src/panels/storyboard/shotboard/SketchItem.tsx +240 -0
  228. package/src/panels/storyboard/shotboard/SketchLayers.tsx +130 -0
  229. package/src/panels/storyboard/shotboard/SketchPlay.tsx +108 -0
  230. package/src/panels/storyboard/shotboard/SketchStrip.tsx +178 -0
  231. package/src/panels/storyboard/shotboard/SketchToolFooter.tsx +99 -0
  232. package/src/panels/storyboard/shotboard/sketch.css +1392 -0
  233. package/src/panels/storyboard/shotboard/sketchModel.ts +219 -0
  234. package/src/panels/storyboard/storyboardDrop.ts +164 -0
  235. package/src/panels/studio/ComponentLibraryRail.tsx +346 -0
  236. package/src/panels/studio/ComponentSidePanel.tsx +355 -0
  237. package/src/panels/studio/ComponentStudio.tsx +243 -0
  238. package/src/panels/studio/ComponentsHome.tsx +318 -0
  239. package/src/panels/studio/ComponentsPreview.tsx +179 -0
  240. package/src/panels/studio/EveryUse.tsx +244 -0
  241. package/src/panels/studio/ThisUsePanel.tsx +679 -0
  242. package/src/panels/studio/componentParts.tsx +341 -0
  243. package/src/panels/studio/components.ts +6 -0
  244. package/src/panels/studio/newCodeComponent.ts +66 -0
  245. package/src/panels/timeline/AudioFxButton.tsx +290 -0
  246. package/src/panels/timeline/AudioLane.tsx +674 -85
  247. package/src/panels/timeline/BlockRow.tsx +154 -85
  248. package/src/panels/timeline/ChannelRow.tsx +378 -129
  249. package/src/panels/timeline/CurvePane.tsx +508 -0
  250. package/src/panels/timeline/Filmstrip.tsx +361 -0
  251. package/src/panels/timeline/GenerateDrawer.tsx +407 -0
  252. package/src/panels/timeline/KeyStretch.tsx +115 -0
  253. package/src/panels/timeline/LifespanRow.tsx +730 -47
  254. package/src/panels/timeline/MarkerLane.tsx +46 -8
  255. package/src/panels/timeline/PlayButton.tsx +91 -0
  256. package/src/panels/timeline/PropertyPicker.tsx +254 -0
  257. package/src/panels/timeline/ShotRow.tsx +400 -0
  258. package/src/panels/timeline/SoundMode.tsx +1406 -0
  259. package/src/panels/timeline/TimelineDock.tsx +2338 -272
  260. package/src/panels/timeline/TransportVolume.tsx +163 -0
  261. package/src/panels/timeline/beatRuler.ts +74 -0
  262. package/src/panels/timeline/curveModel.ts +500 -0
  263. package/src/panels/timeline/duckUnderVoice.ts +68 -0
  264. package/src/panels/timeline/keyframeMenu.ts +138 -0
  265. package/src/panels/timeline/labelWidth.ts +49 -0
  266. package/src/panels/timeline/marquee.ts +155 -0
  267. package/src/panels/timeline/model.ts +1860 -199
  268. package/src/panels/timeline/snap.ts +125 -43
  269. package/src/panels/timeline/staggerPreview.tsx +92 -0
  270. package/src/panels/timeline/takeMeasure.ts +72 -0
  271. package/src/panels/timeline/useLive.ts +48 -0
  272. package/src/panels/timeline/viewport.ts +234 -0
  273. package/src/panels/toolbar/BackChip.tsx +116 -0
  274. package/src/panels/toolbar/CameraViewBar.tsx +118 -49
  275. package/src/panels/toolbar/CanvasBar.tsx +117 -0
  276. package/src/panels/toolbar/InsertButton.tsx +38 -0
  277. package/src/panels/toolbar/IsolationTrail.tsx +156 -0
  278. package/src/panels/toolbar/PanelToggles.tsx +86 -0
  279. package/src/panels/toolbar/ShadingBar.tsx +119 -26
  280. package/src/panels/toolbar/ToolDockBar.tsx +329 -80
  281. package/src/panels/toolbar/ToolFooter.tsx +102 -0
  282. package/src/panels/toolbar/ViewModeBar.tsx +247 -38
  283. package/src/panels/toolbar/WorkPlaneBar.tsx +94 -0
  284. package/src/panels/toolbar/useAddRows.tsx +326 -0
  285. package/src/panels/toolbar/useDismiss.ts +122 -0
  286. package/src/panels/versions/VersionHistoryPanel.tsx +274 -0
  287. package/src/panels/versions/versionModel.ts +103 -0
  288. package/src/panels/virtualWindow.ts +271 -0
  289. package/src/shotTokens.css +151 -60
  290. package/src/stage/FrustumGizmo.tsx +504 -84
  291. package/src/stage/GridPlane.tsx +225 -16
  292. package/src/stage/NodePreviews.tsx +628 -0
  293. package/src/stage/PerfHud.tsx +437 -47
  294. package/src/stage/SplitRenderer.tsx +816 -169
  295. package/src/stage/StageApp.tsx +4274 -486
  296. package/src/stage/StageRootBoundary.tsx +114 -0
  297. package/src/stage/StageTools.tsx +651 -151
  298. package/src/stage/TransformGizmo.tsx +696 -0
  299. package/src/stage/adaptivePresentation.ts +145 -0
  300. package/src/stage/bridgeClient.ts +2522 -41
  301. package/src/stage/exportWorker.ts +9 -0
  302. package/src/stage/feed.ts +305 -14
  303. package/src/stage/fontPreview.ts +176 -0
  304. package/src/stage/frustumGeometry.ts +319 -1
  305. package/src/stage/furnitureTokens.ts +33 -0
  306. package/src/stage/gizmoMath.ts +508 -16
  307. package/src/stage/gpuTimer.ts +276 -0
  308. package/src/stage/instanceModules.ts +194 -33
  309. package/src/stage/interactions/move.ts +24 -5
  310. package/src/stage/isolation.ts +291 -33
  311. package/src/stage/jsSelfProfiler.ts +325 -0
  312. package/src/stage/main.tsx +116 -16
  313. package/src/stage/modalState.ts +222 -23
  314. package/src/stage/moduleEpoch.ts +60 -0
  315. package/src/stage/motionPathStage.ts +279 -0
  316. package/src/stage/motionPreview.ts +74 -0
  317. package/src/stage/overlays/Overlays.tsx +1112 -159
  318. package/src/stage/overlays/Rulers.tsx +495 -0
  319. package/src/stage/panes.ts +159 -10
  320. package/src/stage/project.ts +68 -3
  321. package/src/stage/renderScaleController.ts +730 -0
  322. package/src/stage/rulers.ts +129 -0
  323. package/src/stage/sceneExtent.ts +75 -0
  324. package/src/stage/shading.ts +20 -0
  325. package/src/stage/splineStage.ts +632 -0
  326. package/src/stage/stagePerformanceTelemetry.ts +717 -0
  327. package/src/stage/stageTheme.ts +36 -0
  328. package/src/stage/stageWarmup.ts +11 -0
  329. package/src/stage/textAutoSize.ts +124 -0
  330. package/src/stage/tier1Export.ts +641 -0
  331. package/src/stage/tokenColor.ts +14 -9
  332. package/src/stage/view.ts +56 -9
  333. package/src/stage/workingView.ts +277 -33
  334. package/src/state/accountPrefs.ts +261 -0
  335. package/src/state/agentActions.ts +173 -15
  336. package/src/state/agentActivity.ts +198 -0
  337. package/src/state/agentQuestion.ts +239 -0
  338. package/src/state/bootOpen.ts +42 -0
  339. package/src/state/cameraVerbs.ts +139 -0
  340. package/src/state/clientId.ts +6 -1
  341. package/src/state/comments.ts +1099 -0
  342. package/src/state/context.tsx +19 -1
  343. package/src/state/editorPrefs.ts +220 -0
  344. package/src/state/errorReports.ts +121 -0
  345. package/src/state/fieldWrite.ts +161 -0
  346. package/src/state/fontRecents.ts +70 -0
  347. package/src/state/fonts.ts +656 -0
  348. package/src/state/graphClipboard.ts +414 -0
  349. package/src/state/imageThumbs.ts +216 -0
  350. package/src/state/insert.ts +444 -67
  351. package/src/state/isolationTrail.ts +92 -0
  352. package/src/state/keyMoves.ts +134 -0
  353. package/src/state/keyframeClipboard.ts +177 -0
  354. package/src/state/keymap.ts +1153 -117
  355. package/src/state/layoutPrefs.ts +272 -0
  356. package/src/state/layouts.ts +205 -0
  357. package/src/state/measureMedia.ts +167 -0
  358. package/src/state/menu.ts +2341 -28
  359. package/src/state/motionStamp.ts +7 -0
  360. package/src/state/panelSize.ts +375 -5
  361. package/src/state/pendingChanges.ts +119 -0
  362. package/src/state/playHold.ts +78 -0
  363. package/src/state/playbackPreparation.ts +562 -0
  364. package/src/state/posterCapture.ts +242 -0
  365. package/src/state/presence.ts +254 -0
  366. package/src/state/projectComponents.ts +160 -0
  367. package/src/state/renderEvents.ts +298 -18
  368. package/src/state/runtimeErrors.ts +46 -0
  369. package/src/state/selection.ts +12 -0
  370. package/src/state/shortcuts.ts +1606 -0
  371. package/src/state/sketchGuide.ts +51 -0
  372. package/src/state/splineRefs.ts +87 -0
  373. package/src/state/stageHealth.ts +259 -0
  374. package/src/state/stageWatchdog.ts +47 -0
  375. package/src/state/stillStage.ts +330 -0
  376. package/src/state/stillThumbs.ts +474 -0
  377. package/src/state/store.ts +23591 -2982
  378. package/src/state/storyboardModel.ts +528 -0
  379. package/src/state/sync.ts +1257 -0
  380. package/src/state/syncCache.ts +159 -0
  381. package/src/state/triangles.ts +13 -0
  382. package/src/state/types.ts +1698 -55
  383. package/src/state/unsavedGuard.ts +49 -0
  384. package/src/state/viewMode.ts +200 -0
  385. package/src/state/webmcp.ts +1128 -0
  386. package/src/state/workspace.ts +1147 -0
  387. package/src/styles/assets-tab.css +304 -0
  388. package/src/styles/canvas.css +545 -0
  389. package/src/styles/comments.css +1435 -0
  390. package/src/styles/controls.css +162 -0
  391. package/src/styles/dialogs.css +390 -0
  392. package/src/styles/insert-drawer.css +586 -0
  393. package/src/styles/inspector-board.css +207 -0
  394. package/src/styles/inspector-object.css +486 -0
  395. package/src/styles/inspector-shot.css +252 -0
  396. package/src/styles/inspector.css +360 -0
  397. package/src/styles/left-column.css +1628 -0
  398. package/src/styles/library-preview.css +575 -0
  399. package/src/styles/library.css +512 -0
  400. package/src/styles/modes/code.css +569 -0
  401. package/src/styles/modes/component.css +1561 -0
  402. package/src/styles/modes/material.css +1201 -0
  403. package/src/styles/modes/storyboard.css +518 -0
  404. package/src/styles/share-component.css +49 -0
  405. package/src/styles/shell.css +653 -0
  406. package/src/styles/shot-brief.css +360 -0
  407. package/src/styles/states.css +76 -0
  408. package/src/styles/sync.css +380 -0
  409. package/src/styles/timeline-graph.css +384 -0
  410. package/src/styles/timeline-meters.css +89 -0
  411. package/src/styles/timeline-rows.css +1959 -0
  412. package/src/styles/timeline-sound.css +1003 -0
  413. package/src/styles/timeline.css +754 -0
  414. package/src/styles/tool-footer.css +525 -0
  415. package/src/styles/topbar.css +886 -0
  416. package/src/styles/versions.css +262 -0
  417. package/src/styles.css +72 -2921
  418. package/src/tools.ts +108 -23
  419. package/dist/assets/index-B7notge7.css +0 -1
  420. package/dist/assets/index-BBE6Ta9L.js +0 -52
  421. package/src/panels/files/icons.tsx +0 -118
  422. package/src/panels/header/DocSwitcher.tsx +0 -85
  423. package/src/panels/header/TransportBar.tsx +0 -86
  424. package/src/panels/inspector/animate/BlockTiming.tsx +0 -341
  425. package/src/panels/inspector/animate/DuringBlocks.tsx +0 -85
  426. package/src/panels/inspector/animate/InOutPresets.tsx +0 -57
  427. package/src/panels/inspector/animate/ResolvedChannels.tsx +0 -150
  428. package/src/panels/inspector/animate/useBlocks.ts +0 -43
  429. package/src/panels/material/MaterialPicker.tsx +0 -172
  430. package/src/panels/timeline/SceneStrip.tsx +0 -145
  431. package/src/panels/toolbar/AddMenu.tsx +0 -230
  432. package/src/stage/MoveGizmo.tsx +0 -411
@@ -1,36 +1,35 @@
1
- // The channel rows under an expanded layer: one row per animated property,
2
- // diamonds at the ends — the Figma/Lottie read, and since the preset-named
3
- // block lane left the expansion, the ONLY expanded read.
1
+ // The channel rows under an expanded layer: one row per keyframed property.
4
2
  //
5
- // ── A VIEW OVER BLOCKS, NEVER A SECOND MODEL ────────────────────────────
6
- //
7
- // Every span here is a block's footprint on one channel (model.ts's
8
- // `channelRows`), and every gesture routes through the SAME store ops the
9
- // block lane uses — `dragBlock`, `selectBlock`. Dragging the "opacity
10
- // keyframes" therefore moves the Fade block on every channel it writes, in one
11
- // undo entry. The preset's name lives in the span's tooltip and the Animate
12
- // tab. There is nothing a channel row can do that a block cannot, which is
13
- // what keeps the document blocks-not-tracks.
3
+ // Every row is one stored track (model.ts's `channelRows`), drawn as diamonds,
4
+ // and every diamond gesture routes through the track ops. `dragKeyframe` shares
5
+ // the dock's scrub bracket, so a diamond drag is one undo entry like everything
6
+ // else in this dock.
14
7
  //
15
8
  // ── THE LIVE VALUE IS A LEAF ────────────────────────────────────────────
16
9
  //
17
- // The label cell shows the channel's resolved value AT THE PLAYHEAD. That
10
+ // A transform row's label cell shows the resolved value AT THE PLAYHEAD. That
18
11
  // number changes ~30 times a second during playback, so it is its own
19
12
  // component with its own `frame` subscription — the dock's DockTime rule. The
20
13
  // rows themselves never see the frame.
21
- import { frameToPx, startDrag } from "@dailies-studio/timeline";
14
+ import { EASE_PRESETS, frameToPx, matchPreset, startDrag } from "@dailies-studio/timeline";
15
+ import { useContextMenu } from "@dailies-studio/ui";
22
16
  import { resolveLayerAt } from "@dailies-studio/shot/doc";
23
- import type { Layer3D } from "@dailies-studio/shot/doc";
24
- import type { Rational } from "@dailies-studio/engine";
25
- import type { PointerEvent as ReactPointerEvent } from "react";
26
- import { useMemo } from "react";
27
- import { CHANNEL_BY_KEY } from "../../channels.js";
17
+ import type { Layer3D, Scene3D } from "@dailies-studio/shot/doc";
18
+ import type { Ease, Rational } from "@dailies-studio/engine";
19
+ import type { CSSProperties, MouseEvent as ReactMouseEvent, PointerEvent as ReactPointerEvent } from "react";
20
+ import { memo, useMemo } from "react";
21
+ import { TRANSFORM_CHANNELS } from "../../channels.js";
22
+ import type { ChannelSpec } from "../../channels.js";
28
23
  import { useShotEditor, useStore } from "../../state/context.js";
29
24
  import { useCoalescedFrame } from "../inspector/useDocWrite.js";
30
- import { channelRows, deadSpans, frameDelta } from "./model.js";
31
- import type { ChannelRowModel, ChannelSpan, Lifespan } from "./model.js";
25
+ import { PropertyPicker } from "./PropertyPicker.js";
26
+ import { keyframeMenuEntries, keyframeMenuTargets } from "./keyframeMenu.js";
27
+ import { channelRowsCached, frameDelta, sceneChannelRows } from "./model.js";
28
+ import type { ChannelRowModel, TrackKey } from "./model.js";
32
29
  import type { SnapPress } from "./snap.js";
33
30
  import { isSnapSuppressed } from "./snap.js";
31
+ import { StaggerChip, setStaggerPreview, useStaggerReadout } from "./staggerPreview.js";
32
+ import { sameKeyframeSel } from "../../state/types.js";
34
33
 
35
34
  export function ChannelRows({
36
35
  layer,
@@ -39,8 +38,8 @@ export function ChannelRows({
39
38
  ppf,
40
39
  labelWidth,
41
40
  offsetFrames,
41
+ originFrames,
42
42
  fps,
43
- lifespan,
44
43
  beginSnap,
45
44
  }: {
46
45
  layer: Layer3D;
@@ -50,94 +49,276 @@ export function ChannelRows({
50
49
  labelWidth: number;
51
50
  /** The scene's master start. Drawing only, like every bar in this lane. */
52
51
  offsetFrames: number;
52
+ originFrames: number;
53
53
  fps: Rational;
54
- /** The owning layer's lifespan. A span outside it is kept and drawn dead,
55
- * the block lane's own rule. */
56
- lifespan?: Lifespan | undefined;
57
54
  beginSnap?: SnapPress | undefined;
58
55
  }) {
59
56
  // Derived per layer identity: the rows change exactly when the layer's
60
- // blocks or transform do, and the dock re-renders for ppf/guide changes
61
- // that should not pay a re-derive.
62
- const rows = useMemo(() => channelRows(layer, fps), [layer, fps]);
57
+ // tracks do, and the dock re-renders for ppf/guide
58
+ // changes that should not pay a re-derive.
59
+ const rows = channelRowsCached(layer);
60
+ // ONE MENU PER LAYER, not one per diamond. The hook holds an open/closed
61
+ // state and a focus-restore ref, and a document with a keyframed position
62
+ // over sixty frames would otherwise mount sixty of them for one menu that can
63
+ // only ever be open once.
64
+ const menu = useContextMenu();
65
+ const selectedKeys = useSelectedKeyframeSet();
66
+ const status = useMemo(() => selectedKeyStatus(rows, sceneId, layer.id, selectedKeys), [rows, sceneId, layer.id, selectedKeys]);
63
67
  // A layer with no animation still answers the disclosure: the hint is where
64
- // the first block comes from, and an empty expansion reads as broken.
68
+ // the first keyframe comes from, and an empty expansion reads as broken. The
69
+ // picker below it is the answer to the hint, in reach of the sentence.
65
70
  if (rows.length === 0) {
66
71
  return (
67
- <div className="channel-row channel-empty" data-testid="channel-empty">
68
- <div className="channel-label-cell" style={{ width: labelWidth, paddingLeft: 8 + depth * 12 }}>
69
- <span className="channel-name">No animation</span>
70
- </div>
71
- <div className="channel-lane channel-empty-hint">
72
- Toggle ⇧A and edit — the change becomes keyframes ending at the playhead.
72
+ <>
73
+ <div className="channel-row channel-empty" data-testid="channel-empty">
74
+ <div className="channel-label-cell" style={{ width: labelWidth, "--depth": depth } as CSSProperties}>
75
+ <span className="channel-name">No animation</span>
76
+ </div>
77
+ <div className="channel-lane channel-empty-hint">
78
+ Pick a property below, turn on Animate (⇧A) and edit, or press ◆ in the inspector.
79
+ </div>
73
80
  </div>
74
- </div>
81
+ <PropertyPicker
82
+ subject={{ kind: "layer", layer }}
83
+ containerId={sceneId}
84
+ labelWidth={labelWidth}
85
+ depth={depth}
86
+ />
87
+ </>
75
88
  );
76
89
  }
77
90
  return (
78
91
  <>
92
+ {menu.menu}
79
93
  {rows.map((row) => (
80
94
  <ChannelRow
81
- key={row.key}
95
+ key={row.path}
82
96
  row={row}
83
97
  layer={layer}
98
+ layerId={layer.id}
84
99
  sceneId={sceneId}
85
100
  depth={depth}
86
101
  ppf={ppf}
87
102
  labelWidth={labelWidth}
88
103
  offsetFrames={offsetFrames}
104
+ originFrames={originFrames}
89
105
  fps={fps}
90
- lifespan={lifespan}
91
106
  beginSnap={beginSnap}
107
+ openMenu={menu.open}
108
+ selectedKeys={selectedKeys}
92
109
  />
93
110
  ))}
111
+ <PropertyPicker
112
+ subject={{ kind: "layer", layer }}
113
+ containerId={sceneId}
114
+ labelWidth={labelWidth}
115
+ depth={depth}
116
+ status={status}
117
+ />
94
118
  </>
95
119
  );
96
120
  }
97
121
 
122
+ /**
123
+ * The SHOT's own channel rows (P5): its background colour and its post stack.
124
+ *
125
+ * The same `ChannelRow`, with no layer: no transform readout corresponds to a
126
+ * background colour, so the live-value cell has nothing to show. Rendering nothing at all
127
+ * when the shot has no tracks is the opt-in the layer rows follow: a property
128
+ * lane appears once it carries animation and not before.
129
+ */
130
+ export function SceneChannelRows({
131
+ scene,
132
+ depth,
133
+ ppf,
134
+ labelWidth,
135
+ offsetFrames,
136
+ originFrames,
137
+ fps,
138
+ beginSnap,
139
+ }: {
140
+ scene: Scene3D;
141
+ depth: number;
142
+ ppf: number;
143
+ labelWidth: number;
144
+ offsetFrames: number;
145
+ originFrames: number;
146
+ fps: Rational;
147
+ beginSnap?: SnapPress | undefined;
148
+ }) {
149
+ const rows = useMemo(() => sceneChannelRows(scene), [scene]);
150
+ const menu = useContextMenu();
151
+ const selectedKeys = useSelectedKeyframeSet();
152
+ const status = useMemo(() => selectedKeyStatus(rows, scene.id, null, selectedKeys), [rows, scene.id, selectedKeys]);
153
+ return (
154
+ <>
155
+ {menu.menu}
156
+ {rows.map((row) => (
157
+ <ChannelRow
158
+ key={row.path}
159
+ row={row}
160
+ layer={null}
161
+ layerId={null}
162
+ sceneId={scene.id}
163
+ depth={depth}
164
+ ppf={ppf}
165
+ labelWidth={labelWidth}
166
+ offsetFrames={offsetFrames}
167
+ originFrames={originFrames}
168
+ fps={fps}
169
+ beginSnap={beginSnap}
170
+ openMenu={menu.open}
171
+ selectedKeys={selectedKeys}
172
+ />
173
+ ))}
174
+ {/* Drawn even with no rows above it, which is why this component no
175
+ longer returns null on an untracked shot: the picker is the way a
176
+ shot's FIRST post-stack keyframe gets made from the timeline. */}
177
+ <PropertyPicker
178
+ subject={{ kind: "shot", scene }}
179
+ containerId={scene.id}
180
+ labelWidth={labelWidth}
181
+ depth={depth}
182
+ status={status}
183
+ />
184
+ </>
185
+ );
186
+ }
187
+
188
+ /** The `useContextMenu` opener, threaded down to the diamonds. */
189
+ type OpenMenu = ReturnType<typeof useContextMenu>["open"];
190
+
191
+ /**
192
+ * The selected keyframes as one set of addresses, built ONCE per selection
193
+ * change by the rows' parent.
194
+ *
195
+ * Every diamond used to hold its own `useShotEditor` selector scanning
196
+ * `selectedKeyframes` for itself: five hundred diamonds times ten selected
197
+ * keyframes was five thousand comparisons per cold notification, i.e. per
198
+ * rAF frame of a keyframe drag and per keystroke. Membership is now one `has`
199
+ * per diamond, and a memoised diamond re-renders only when ITS bit flips.
200
+ */
201
+ function keyframeAddress(containerId: string, layerId: string | null, path: string, frame: number): string {
202
+ return `${containerId}|${layerId ?? ""}|${path}@${frame}`;
203
+ }
204
+
205
+ /**
206
+ * The status line beside "+ property": what is selected on THESE rows, in the
207
+ * words a diamond's own title uses. Null when nothing here is selected, so the
208
+ * line is absent rather than saying "nothing".
209
+ *
210
+ * Read from the selection set and the rows, never from the frame: it changes
211
+ * when the selection or the keys do, and playback does not touch it.
212
+ */
213
+ function selectedKeyStatus(
214
+ rows: readonly ChannelRowModel[],
215
+ containerId: string,
216
+ layerId: string | null,
217
+ selected: ReadonlySet<string>,
218
+ ): string | null {
219
+ if (selected.size === 0) return null;
220
+ let count = 0;
221
+ let only: { label: string; key: TrackKey } | null = null;
222
+ for (const row of rows) {
223
+ for (const key of row.keys) {
224
+ if (!selected.has(keyframeAddress(containerId, layerId, row.path, key.frame))) continue;
225
+ count += 1;
226
+ only ??= { label: row.label, key };
227
+ }
228
+ }
229
+ if (only === null) return null;
230
+ if (count > 1) return `${count} keys selected`;
231
+ return `${only.label} ${only.key.value} · frame ${only.key.frame} · ${easeLabel(only.key.ease)}`;
232
+ }
233
+
234
+ function useSelectedKeyframeSet(): ReadonlySet<string> {
235
+ const selected = useShotEditor((s) => s.selectedKeyframes);
236
+ return useMemo(
237
+ () => new Set(selected.map((at) => keyframeAddress(at.containerId, at.layerId, at.path, at.frame))),
238
+ [selected],
239
+ );
240
+ }
241
+
98
242
  function ChannelRow({
99
243
  row,
100
244
  layer,
245
+ layerId,
101
246
  sceneId,
102
247
  depth,
103
248
  ppf,
104
249
  labelWidth,
105
250
  offsetFrames,
251
+ originFrames,
106
252
  fps,
107
- lifespan,
108
253
  beginSnap,
254
+ openMenu,
255
+ selectedKeys,
109
256
  }: {
110
257
  row: ChannelRowModel;
111
- layer: Layer3D;
258
+ /** Null when the subject is the SHOT (P5): its background and its post stack
259
+ * are tracked on the scene, and no transform readout corresponds to either,
260
+ * so the live-value readout below simply has nothing to read. */
261
+ layer: Layer3D | null;
262
+ layerId: string | null;
112
263
  sceneId: string;
113
264
  depth: number;
114
265
  ppf: number;
115
266
  labelWidth: number;
116
267
  offsetFrames: number;
268
+ originFrames: number;
117
269
  fps: Rational;
118
- lifespan?: Lifespan | undefined;
119
270
  beginSnap?: SnapPress | undefined;
271
+ openMenu: OpenMenu;
272
+ selectedKeys: ReadonlySet<string>;
120
273
  }) {
274
+ const store = useStore();
275
+ const readout = useStaggerReadout(staggerRowId(sceneId, layerId, row.path));
276
+ const spec = TRANSFORM_CHANNELS.get(row.path);
121
277
  return (
122
- <div className="channel-row" data-channel-row={row.key} data-row-scene={sceneId}>
123
- <div className="channel-label-cell" style={{ width: labelWidth, paddingLeft: 8 + depth * 12 }}>
278
+ <div className="channel-row" data-channel-row={row.path} data-row-scene={sceneId}>
279
+ <div className="channel-label-cell" style={{ width: labelWidth, "--depth": depth } as CSSProperties}>
124
280
  <span className="channel-name">{row.label}</span>
125
- <ChannelValue layer={layer} channel={row.key} offsetFrames={offsetFrames} fps={fps} />
281
+ {/* NO `originFrames` HERE. This readout converts the MASTER playhead
282
+ into scene-local time (`frame - offsetFrames`), which is a real
283
+ clock conversion and not a drawing offset — the shot context's
284
+ re-zero must not reach it. */}
285
+ {spec === undefined || layer === null ? null : (
286
+ <ChannelValue layer={layer} path={row.path} spec={spec} offsetFrames={offsetFrames} fps={fps} />
287
+ )}
288
+ {/* REMOVE ANIMATION. The verb is on the row rather than only in the
289
+ inspector because this is where the author is looking at the thing
290
+ they want gone, and because deleting keys one at a time to clear a
291
+ track is a chore with a cliff at the last one (the last delete
292
+ removes the track anyway). */}
293
+ <button
294
+ type="button"
295
+ className="channel-clear"
296
+ data-testid={`channel-clear-${row.path}`}
297
+ title={`Remove the ${row.label} keyframes: the layer keeps the value it was authored with`}
298
+ aria-label={`Remove ${row.label} keyframes`}
299
+ onClick={() => store.clearTrack(sceneId, layerId, row.path)}
300
+ >
301
+ ×
302
+ </button>
126
303
  </div>
127
304
  <div className="channel-lane">
128
- {row.spans.map((span) => (
129
- <ChannelSpanView
130
- key={span.blockId}
131
- span={span}
132
- channelLabel={row.label}
133
- layerId={layer.id}
134
- sceneId={sceneId}
305
+ {row.keys.map((key) => (
306
+ <TrackKeyView
307
+ key={key.id}
308
+ trackKey={key}
309
+ rowLabel={row.label}
310
+ path={row.path}
311
+ layerId={layerId}
312
+ containerId={sceneId}
313
+ selected={selectedKeys.has(keyframeAddress(sceneId, layerId, row.path, key.frame))}
135
314
  ppf={ppf}
136
315
  offsetFrames={offsetFrames}
137
- lifespan={lifespan}
316
+ originFrames={originFrames}
138
317
  beginSnap={beginSnap}
318
+ openMenu={openMenu}
139
319
  />
140
320
  ))}
321
+ {readout === null ? null : <StaggerChip step={readout.step} left={readout.anchor ?? 0} />}
141
322
  </div>
142
323
  </div>
143
324
  );
@@ -148,119 +329,187 @@ function ChannelRow({
148
329
  * outside this scene simply shows the layer's held value. */
149
330
  function ChannelValue({
150
331
  layer,
151
- channel,
332
+ path,
333
+ spec,
152
334
  offsetFrames,
153
335
  fps,
154
336
  }: {
155
337
  layer: Layer3D;
156
- channel: ChannelRowModel["key"];
338
+ path: string;
339
+ spec: ChannelSpec;
157
340
  offsetFrames: number;
158
341
  fps: Rational;
159
342
  }) {
160
343
  // The COALESCED frame, not the raw one — the inspector's posture, applied
161
- // here for the inspector's reason: this runs a full `resolveLayerAt` block
344
+ // here for the inspector's reason: this runs a full `resolveLayerAt` track
162
345
  // fold in render, one instance per open channel row, and the raw frame ran
163
346
  // every one of them per playback tick (~750 folds/s with five layers
164
347
  // expanded). Held while playing, ~10 Hz while scrubbing, leading edge on a
165
348
  // lone seek — a readout, not an animation.
166
349
  const frame = useCoalescedFrame();
167
- const spec = CHANNEL_BY_KEY.get(channel);
168
- if (spec === undefined) return null;
169
350
  return (
170
- <span className="channel-value" data-testid={`channel-value-${channel}`}>
351
+ <span className="channel-value" data-testid={`channel-value-${path}`}>
171
352
  {spec.read(resolveLayerAt(layer, frame - offsetFrames, fps))}
172
353
  </span>
173
354
  );
174
355
  }
175
356
 
176
- function ChannelSpanView({
177
- span,
178
- channelLabel,
357
+ /**
358
+ * ONE KEYFRAME.
359
+ *
360
+ * Drag moves it (through the dock's scrub bracket, so it is one undo
361
+ * entry and Escape restores the frame it started on), a plain press selects it,
362
+ * and a right-click sets the ease of the segment LEAVING it — which is what
363
+ * `easeOut` means and why the menu is on the key rather than between two.
364
+ */
365
+ const TrackKeyView = memo(function TrackKeyView({
366
+ trackKey,
367
+ rowLabel,
368
+ path,
179
369
  layerId,
180
- sceneId,
370
+ containerId,
181
371
  ppf,
182
372
  offsetFrames,
183
- lifespan,
373
+ originFrames,
184
374
  beginSnap,
375
+ openMenu,
376
+ selected,
185
377
  }: {
186
- span: ChannelSpan;
187
- channelLabel: string;
188
- layerId: string;
189
- sceneId: string;
378
+ trackKey: TrackKey;
379
+ rowLabel: string;
380
+ path: string;
381
+ /** Membership in the selection, decided by the parent's one set. */
382
+ selected: boolean;
383
+ /** Null = the shot itself, the address `selectedKeyframe` and every keyframe
384
+ * store verb already take. */
385
+ layerId: string | null;
386
+ containerId: string;
190
387
  ppf: number;
191
388
  offsetFrames: number;
192
- lifespan?: Lifespan | undefined;
389
+ originFrames: number;
193
390
  beginSnap?: SnapPress | undefined;
391
+ openMenu: OpenMenu;
194
392
  }) {
195
393
  const store = useStore();
196
- const selected = useShotEditor((s) => s.selectedBlock?.blockId === span.blockId);
197
- const left = frameToPx(span.frame + offsetFrames, ppf);
198
- const width = Math.max(4, frameToPx(span.durationFrames, ppf));
199
- const dead = deadSpans(span, lifespan);
200
394
 
201
- // BlockBarView's drag, verbatim in shape: values read at press, a signed
202
- // delta never through pxToFrame, the snap frozen with THIS block excluded.
203
- const drag =
204
- (edge: "body" | "in" | "out") =>
205
- (e: ReactPointerEvent<HTMLElement>): void => {
206
- e.preventDefault();
207
- e.stopPropagation();
208
- store.focusScene(sceneId);
209
- store.selectBlock({ sceneId, layerId, blockId: span.blockId });
395
+ const drag = (e: ReactPointerEvent<HTMLElement>): void => {
396
+ e.preventDefault();
397
+ e.stopPropagation();
398
+ store.focusScene(containerId);
399
+ const at = { containerId, layerId, path, frame: trackKey.frame };
400
+ // ── ⌥ ON A SELECTED KEY, WITH OTHERS SELECTED, FANS THEM OUT ─────────
401
+ //
402
+ // Paper page 23, "8 · Stagger". ⌥ is free on a key otherwise, so a lone key
403
+ // or an unselected one falls through to the plain drag below. No snap: each
404
+ // key moves a different distance, so a guide would describe none of them.
405
+ const held = store.getState().selectedKeyframes;
406
+ if (e.altKey && held.length > 1 && held.some((k) => sameKeyframeSel(k, at))) {
210
407
  const startX = e.clientX;
211
- const startFrame = span.frame;
212
- const startEnd = span.frame + span.durationFrames;
213
- const snap = beginSnap?.({ sceneId, layerId, blockId: span.blockId }, isSnapSuppressed(e));
214
- const place = (anchor: number, delta: number): number =>
215
- snap === undefined ? anchor + delta : snap(anchor + delta + offsetFrames) - offsetFrames;
216
- store.beginBlockDrag();
408
+ const grabbed = staggerRowId(containerId, layerId, path);
409
+ const anchor = frameToPx(trackKey.frame + offsetFrames - originFrames, ppf);
410
+ if (!store.beginBlockDrag()) return;
411
+ setStaggerPreview({ from: {}, grabbed, step: 0, anchor });
217
412
  startDrag(e, {
218
413
  onMove(ev) {
219
- const delta = frameDelta(ev.clientX - startX, ppf);
220
- if (edge === "out") store.dragBlock(sceneId, layerId, span.blockId, "out", place(startEnd, delta));
221
- else store.dragBlock(sceneId, layerId, span.blockId, edge, place(startFrame, delta));
414
+ const step = frameDelta(ev.clientX - startX, ppf);
415
+ // A refused capture leaves the keys on their press frames: +0f.
416
+ const took = store.dragKeyStagger(step);
417
+ setStaggerPreview({ from: {}, grabbed, step: took ? step : 0, anchor });
418
+ },
419
+ onEnd: (commit) => {
420
+ setStaggerPreview(null);
421
+ store.endBlockDrag(commit);
222
422
  },
223
- onEnd: (commit) => store.endBlockDrag(commit),
224
423
  });
225
- };
424
+ return;
425
+ }
426
+ // ── A MODIFIED PRESS EXTENDS THE SELECTION AND DOES NOT DRAG ──────────
427
+ //
428
+ // Shift adds, ctrl/meta toggles — the same two modifiers `CurveGraph`
429
+ // reads off a click, so building a selection means one thing on both faces
430
+ // of the dock. It returns BEFORE `startDrag`, deliberately: ctrl is also
431
+ // this row's snap suppressor (`isSnapSuppressed`), so a modified press that
432
+ // also began a drag would move the key the author was only trying to add.
433
+ if (e.shiftKey || e.ctrlKey || e.metaKey) {
434
+ store.selectKeyframes(at, e.shiftKey ? "add" : "toggle");
435
+ return;
436
+ }
437
+ store.selectKeyframe(at);
438
+ const startX = e.clientX;
439
+ const from = trackKey.frame;
440
+ // THE SAME SNAP the bars use, excluding NOTHING — and that is correct
441
+ // rather than an omission. A bar has to exclude itself because its own two
442
+ // edges are snap targets, so it would be pinned at zero travel; a keyframe
443
+ // frame is not a target at all (`collectSnapTargets` collects lifespans,
444
+ // clips, markers, the playhead and shot boundaries), so there is
445
+ // nothing of its own for it to catch on.
446
+ const snap = beginSnap?.(undefined, isSnapSuppressed(e));
447
+ store.beginBlockDrag();
448
+ startDrag(e, {
449
+ onMove(ev) {
450
+ const delta = frameDelta(ev.clientX - startX, ppf);
451
+ // Through `offsetFrames`, never `originFrames`: `beginSnap` freezes
452
+ // MASTER targets, so the conversion is to master time and back. The
453
+ // re-zero is a drawing offset and must not reach an edit.
454
+ const raw = from + delta;
455
+ const at = snap === undefined ? raw : snap(raw + offsetFrames) - offsetFrames;
456
+ store.dragKeyframe(containerId, layerId, path, from, at);
457
+ },
458
+ onEnd: (commit) => store.endBlockDrag(commit),
459
+ });
460
+ };
461
+
462
+ const contextMenu = (e: ReactMouseEvent<HTMLElement>): void => {
463
+ e.preventDefault();
464
+ e.stopPropagation();
465
+ // Point at the key under the cursor, the way the strip's menu selects the
466
+ // bar it opened on. So the Delete item and a following Backspace address
467
+ // the same thing.
468
+ // POINT AT THE KEY UNDER THE CURSOR — unless it is already IN the
469
+ // selection, in which case the menu is about the whole set. Right-clicking
470
+ // one of four selected keys to ease all four is the gesture every timeline
471
+ // has; collapsing to one here would silently drop three.
472
+ const at = { containerId, layerId, path, frame: trackKey.frame };
473
+ const targets = keyframeMenuTargets(store.getState().selectedKeyframes, at);
474
+ if (targets.length === 1) store.selectKeyframe(at);
475
+ // THE SHARED BUILDER: the graph's circles open the same menu, and the three
476
+ // kind rules it enforces are not the sort that survive being written twice.
477
+ openMenu(
478
+ { x: e.clientX, y: e.clientY },
479
+ keyframeMenuEntries({
480
+ store,
481
+ key: { kind: trackKey.kind, ease: trackKey.ease, last: trackKey.last, curved: trackKey.curved },
482
+ targets,
483
+ at,
484
+ }),
485
+ );
486
+ };
226
487
 
227
488
  return (
228
- <div
229
- className={`channel-span slot-${span.slot}${selected ? " is-selected" : ""}${dead.length > 0 ? " is-clipped" : ""}`}
230
- style={{ left, width }}
231
- data-channel-span={span.blockId}
232
- data-clipped={dead.length > 0 ? "true" : undefined}
233
- onPointerDown={drag("body")}
234
- title={
235
- `${span.preset} · ${channelLabel}: ${span.from} → ${span.to}` +
236
- (dead.length > 0 ? " · outside the layer's lifespan, so this part never plays" : "")
237
- }
238
- >
239
- <span className="channel-connector" aria-hidden="true" />
240
- {/* KEPT, NOT DELETED — the block lane's rule, restated per channel:
241
- shortening a lifespan is a trim, and the part that can never play is
242
- dashed in place so the author can see how far to pull the edge back. */}
243
- {dead.map((d) => (
244
- <span
245
- key={d.from}
246
- className="channel-dead"
247
- aria-hidden="true"
248
- style={{ left: frameToPx(d.from - span.frame, ppf), width: frameToPx(d.to - d.from, ppf) }}
249
- />
250
- ))}
251
- <span className="channel-key channel-key-in" data-key="in" onPointerDown={drag("in")} aria-hidden="true" />
252
- {span.spring ? (
253
- // The settle tail, same statement the block bar makes: a spring keeps
254
- // moving past its window, and its end is physics rather than a handle.
255
- <span className="channel-spring-tail" aria-hidden="true" />
256
- ) : (
257
- <span
258
- className="channel-key channel-key-out"
259
- data-key="out"
260
- onPointerDown={drag("out")}
261
- aria-hidden="true"
262
- />
263
- )}
264
- </div>
489
+ <span
490
+ className={`channel-key ease-${trackKey.ease.type}${selected ? " is-selected" : ""}`}
491
+ style={{ left: frameToPx(trackKey.frame + offsetFrames - originFrames, ppf) }}
492
+ data-track-key={`${path}@${trackKey.frame}`}
493
+ data-testid={`track-key-${path}-${trackKey.frame}`}
494
+ role="button"
495
+ tabIndex={-1}
496
+ aria-label={`${rowLabel} keyframe at frame ${trackKey.frame}`}
497
+ title={`${rowLabel} ${trackKey.value} · frame ${trackKey.frame} · ${easeLabel(trackKey.ease)}`}
498
+ onPointerDown={drag}
499
+ onContextMenu={contextMenu}
500
+ />
265
501
  );
502
+ });
503
+
504
+ /** The readout's slot for a key row: one per track. */
505
+ function staggerRowId(containerId: string, layerId: string | null, path: string): string {
506
+ return `keys:${containerId}|${layerId ?? ""}|${path}`;
507
+ }
508
+
509
+ /** The ease's own name, from the shared palette — never a second spelling of
510
+ * "Ease In-Out" living in this file. `matchPreset` compares bezier handles
511
+ * exactly, so a hand-authored curve comes back null and reads as "Custom". */
512
+ function easeLabel(ease: Ease): string {
513
+ const id = matchPreset(ease);
514
+ return EASE_PRESETS.find((p) => p.id === id)?.label ?? "Custom";
266
515
  }