@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
@@ -0,0 +1,274 @@
1
+ // The Version history panel: the right column's occupant while it is open
2
+ // (RightColumn). The open autosave group heads the list as "Current version";
3
+ // the closed groups and the named versions follow under their days, newest
4
+ // first. A click previews a row on the stage, read-only; Restore writes it
5
+ // back as one undo step. A viewer reads and previews, and nothing else.
6
+ import { useEffect, useRef, useState } from "react";
7
+ import { Avatar, useContextMenu, usePrompt } from "@dailies-studio/ui";
8
+ import type { MenuEntry } from "@dailies-studio/ui";
9
+ import type { ShotVersion } from "../../api.js";
10
+ import { useShotEditor, useStore } from "../../state/context.js";
11
+ import { shortcutFor } from "../../state/shortcuts.js";
12
+ import { VIEW_ONLY } from "../../state/workspace.js";
13
+ import {
14
+ authorLine,
15
+ groupByDay,
16
+ personHue,
17
+ personName,
18
+ versionNameError,
19
+ versionSub,
20
+ versionTitle,
21
+ } from "./versionModel.js";
22
+
23
+ export function VersionHistoryPanel() {
24
+ const store = useStore();
25
+ const list = useShotEditor((s) => s.versionList);
26
+ const preview = useShotEditor((s) => s.versionPreview);
27
+ const roleReadOnly = useShotEditor((s) => s.versionPreview?.roleReadOnly ?? s.readOnly);
28
+ const menu = useContextMenu();
29
+ const { prompt, dialog } = usePrompt();
30
+ const people = list?.people ?? {};
31
+ const versions = list?.versions ?? [];
32
+ const head = list?.head ?? null;
33
+ const previewId = preview?.version.id ?? null;
34
+
35
+ const openMenu = (v: ShotVersion, button: HTMLElement): void => {
36
+ const r = button.getBoundingClientRect();
37
+ const locked = roleReadOnly ? { disabled: true, title: VIEW_ONLY } : {};
38
+ const items: MenuEntry[] = [
39
+ {
40
+ kind: "action",
41
+ id: "version.rename",
42
+ label: "Rename…",
43
+ ...locked,
44
+ onSelect: () => {
45
+ void prompt({
46
+ title: v.name === undefined ? "Name this version" : "Rename version",
47
+ label: "Name",
48
+ initialValue: v.name ?? "",
49
+ confirmLabel: v.name === undefined ? "Name" : "Rename",
50
+ validate: versionNameError,
51
+ }).then((label) => {
52
+ if (label !== null) void store.renameVersion(v.id, label.trim());
53
+ });
54
+ },
55
+ },
56
+ {
57
+ kind: "action",
58
+ id: "version.preview",
59
+ label: "Preview",
60
+ onSelect: () => void store.previewVersion(v.id),
61
+ },
62
+ {
63
+ kind: "action",
64
+ id: "version.restore",
65
+ label: "Restore",
66
+ ...locked,
67
+ onSelect: () => void store.restoreVersion(v.id),
68
+ },
69
+ ];
70
+ menu.open({ x: r.right, y: r.bottom + 6 }, items);
71
+ };
72
+
73
+ return (
74
+ <section className="versions-panel" data-testid="versions-panel" aria-label="Version history">
75
+ {dialog}
76
+ {menu.menu}
77
+ <header className="versions-head">
78
+ <h2 className="versions-title">Version history</h2>
79
+ <button
80
+ type="button"
81
+ className="btn is-icon"
82
+ data-testid="versions-close"
83
+ aria-label="Close the version history"
84
+ title="Close the version history"
85
+ onClick={() => store.openVersions(false)}
86
+ >
87
+ ×
88
+ </button>
89
+ </header>
90
+ {roleReadOnly ? null : <SaveVersion />}
91
+ <div className="versions-list" data-testid="versions-list">
92
+ <button
93
+ type="button"
94
+ className="version-row is-current"
95
+ aria-pressed={previewId === null}
96
+ data-testid="version-current"
97
+ onClick={() => store.exitVersionPreview()}
98
+ >
99
+ <Faces ids={head?.authors ?? []} people={people} />
100
+ <span className="version-text">
101
+ <span className="version-name">Current version</span>
102
+ <span className="version-sub">
103
+ {head === null || head.authors.length === 0
104
+ ? "Editing now"
105
+ : `${authorLine(head.authors, people)} · editing now`}
106
+ </span>
107
+ </span>
108
+ </button>
109
+ {list === null ? (
110
+ <p className="versions-note">Loading versions…</p>
111
+ ) : versions.length === 0 ? (
112
+ <p className="versions-note">No earlier versions yet. Autosaves gather here as you work.</p>
113
+ ) : (
114
+ groupByDay(versions, Date.now()).map((group) => (
115
+ <div key={group.day} className="versions-day" role="group" aria-label={group.day}>
116
+ <h3 className="versions-day-head">{group.day}</h3>
117
+ {group.versions.map((v) => (
118
+ <div key={v.id} className="version-item" data-selected={v.id === previewId ? "" : undefined}>
119
+ <button
120
+ type="button"
121
+ className={`version-row${v.name === undefined ? "" : " is-named"}`}
122
+ aria-pressed={v.id === previewId}
123
+ data-testid={`version-row-${v.id}`}
124
+ onClick={() => void store.previewVersion(v.id)}
125
+ >
126
+ <Faces ids={v.authors} people={people} />
127
+ <span className="version-text">
128
+ <span className="version-name">{versionTitle(v, versions)}</span>
129
+ <span className="version-sub">{versionSub(v, people)}</span>
130
+ </span>
131
+ {v.name === undefined ? null : <span className="version-badge">Named</span>}
132
+ </button>
133
+ <button
134
+ type="button"
135
+ className="btn is-icon version-more"
136
+ data-testid={`version-more-${v.id}`}
137
+ aria-label={`More for ${versionTitle(v, versions)}`}
138
+ title="More"
139
+ onClick={(e) => openMenu(v, e.currentTarget)}
140
+ >
141
+ …
142
+ </button>
143
+ </div>
144
+ ))}
145
+ </div>
146
+ ))
147
+ )}
148
+ </div>
149
+ {preview === null ? null : (
150
+ <footer className="versions-preview" data-testid="versions-preview">
151
+ <span className="versions-caps">Previewing</span>
152
+ <span className="versions-preview-what">
153
+ {`${preview.version.name === undefined ? versionTitle(preview.version, versions) : `"${preview.version.name}"`} · ${versionSub(preview.version, people)}`}
154
+ </span>
155
+ <div className="versions-preview-verbs">
156
+ <button
157
+ type="button"
158
+ className="btn is-outline"
159
+ data-testid="versions-back"
160
+ onClick={() => store.exitVersionPreview()}
161
+ >
162
+ Back to current
163
+ </button>
164
+ <button
165
+ type="button"
166
+ className="btn is-primary"
167
+ data-testid="versions-restore"
168
+ disabled={roleReadOnly}
169
+ title={roleReadOnly ? VIEW_ONLY : "Restore this version. Undo takes it back."}
170
+ onClick={() => void store.restoreVersion(preview.version.id)}
171
+ >
172
+ Restore
173
+ </button>
174
+ </div>
175
+ </footer>
176
+ )}
177
+ </section>
178
+ );
179
+ }
180
+
181
+ /** ⌘S lands here: the name field, focused. Otherwise one button opens it. */
182
+ function SaveVersion() {
183
+ const store = useStore();
184
+ const ask = useShotEditor((s) => s.versionNameAsk);
185
+ const [label, setLabel] = useState("");
186
+ const [busy, setBusy] = useState(false);
187
+ const field = useRef<HTMLInputElement>(null);
188
+ useEffect(() => {
189
+ if (!ask) return;
190
+ // A second ⌘S while the field is up selects what is typed, ready to retype.
191
+ field.current?.focus();
192
+ field.current?.select();
193
+ }, [ask]);
194
+ const error = label === "" ? null : versionNameError(label);
195
+ const save = async (): Promise<void> => {
196
+ if (busy || label.trim() === "" || error !== null) return;
197
+ setBusy(true);
198
+ try {
199
+ if (await store.saveVersionNamed(label.trim())) setLabel("");
200
+ } finally {
201
+ setBusy(false);
202
+ }
203
+ };
204
+ const combo = shortcutFor("file.save");
205
+ if (!ask) {
206
+ return (
207
+ <div className="versions-save">
208
+ <button
209
+ type="button"
210
+ className="btn is-outline"
211
+ data-testid="versions-save-open"
212
+ title={combo === undefined ? undefined : `Save version (${combo})`}
213
+ onClick={() => store.setVersionNameAsk(true)}
214
+ >
215
+ Save version…
216
+ </button>
217
+ </div>
218
+ );
219
+ }
220
+ return (
221
+ <div className="versions-save">
222
+ <label className="versions-caps" htmlFor="versions-save-name">
223
+ Save version
224
+ </label>
225
+ <div className="versions-save-field">
226
+ <input
227
+ ref={field}
228
+ id="versions-save-name"
229
+ className="text-input"
230
+ data-testid="versions-save-name"
231
+ placeholder="Name this version"
232
+ value={label}
233
+ maxLength={80}
234
+ aria-invalid={error !== null}
235
+ onChange={(e) => setLabel(e.currentTarget.value)}
236
+ onKeyDown={(e) => {
237
+ if (e.key === "Enter") {
238
+ e.preventDefault();
239
+ void save();
240
+ } else if (e.key === "Escape") {
241
+ e.preventDefault();
242
+ e.stopPropagation();
243
+ store.setVersionNameAsk(false);
244
+ }
245
+ }}
246
+ />
247
+ <button
248
+ type="button"
249
+ className="btn is-primary"
250
+ data-testid="versions-save"
251
+ disabled={busy || label.trim() === "" || error !== null}
252
+ onClick={() => void save()}
253
+ >
254
+ Save
255
+ </button>
256
+ </div>
257
+ <p className="versions-hint">{error ?? "Enter saves. Esc closes the field."}</p>
258
+ </div>
259
+ );
260
+ }
261
+
262
+ /** The writers of a row, overlapped, first writer on top. */
263
+ function Faces({ ids, people }: { ids: readonly string[]; people: Readonly<Record<string, string>> }) {
264
+ const shown = ids.slice(0, 3);
265
+ return (
266
+ <span className="version-faces" aria-hidden="true">
267
+ {shown.map((id) => (
268
+ <span key={id} className="version-face" style={{ background: `hsl(${personHue(id)} 45% 45%)` }}>
269
+ <Avatar url={null} label={personName(id, people)} className="version-face-mark" />
270
+ </span>
271
+ ))}
272
+ </span>
273
+ );
274
+ }
@@ -0,0 +1,103 @@
1
+ // What the Version history panel says about each row: who, when, and under
2
+ // which day. Pure, so the words are tested without a DOM.
3
+ import type { ShotVersion, ShotVersionHead } from "../../api.js";
4
+
5
+ /** The CLI's one user (packages/store/src/shotVersions.ts). */
6
+ const LOCAL_USER = "local";
7
+
8
+ /** A person's display name. The CLI's one user is "You"; a hosted id the
9
+ * server could not name (a member who left) is "Former member". */
10
+ export function personName(id: string, people: Readonly<Record<string, string>>): string {
11
+ if (id === LOCAL_USER) return "You";
12
+ return people[id] ?? "Former member";
13
+ }
14
+
15
+ /** "Priya", "Tom, Kanishak", "Tom, Kanishak +2". Empty when no writer was
16
+ * recorded (an agent apply, a save from before authors). */
17
+ export function authorLine(ids: readonly string[], people: Readonly<Record<string, string>>): string {
18
+ const names = ids.map((id) => personName(id, people));
19
+ return names.length <= 2 ? names.join(", ") : `${names.slice(0, 2).join(", ")} +${names.length - 2}`;
20
+ }
21
+
22
+ const TIME = new Intl.DateTimeFormat("en-US", {
23
+ hour: "numeric",
24
+ minute: "2-digit",
25
+ });
26
+
27
+ /** "6:05 PM", or "3:40 to 3:52 PM" for a group that ran across minutes. */
28
+ export function timeSpan(from: number, at: number): string {
29
+ const end = TIME.format(at);
30
+ const start = TIME.format(from);
31
+ if (start === end) return end;
32
+ const period = (s: string): string => s.slice(s.lastIndexOf(" ") + 1);
33
+ return `${period(start) === period(end) ? start.slice(0, start.lastIndexOf(" ")) : start} to ${end}`;
34
+ }
35
+
36
+ const DAY = new Intl.DateTimeFormat("en-US", {
37
+ weekday: "short",
38
+ month: "short",
39
+ day: "numeric",
40
+ });
41
+ const DAY_YEAR = new Intl.DateTimeFormat("en-US", {
42
+ month: "short",
43
+ day: "numeric",
44
+ year: "numeric",
45
+ });
46
+
47
+ /** The group header a row sits under: "Today", "Yesterday", "Mon, Oct 6",
48
+ * or "Oct 6, 2025" for another year. Local days. */
49
+ export function dayLabel(at: number, now: number): string {
50
+ const day = new Date(at);
51
+ const today = new Date(now);
52
+ const startOf = (d: Date): number => new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime();
53
+ const days = Math.round((startOf(today) - startOf(day)) / 86_400_000);
54
+ if (days === 0) return "Today";
55
+ if (days === 1) return "Yesterday";
56
+ return day.getFullYear() === today.getFullYear() ? DAY.format(day) : DAY_YEAR.format(day);
57
+ }
58
+
59
+ /** Rows under their day headers, newest first, as the list arrives. */
60
+ export function groupByDay(versions: readonly ShotVersion[], now: number): { day: string; versions: ShotVersion[] }[] {
61
+ const groups: { day: string; versions: ShotVersion[] }[] = [];
62
+ for (const v of versions) {
63
+ const day = dayLabel(v.at, now);
64
+ const last = groups.at(-1);
65
+ if (last?.day === day) last.versions.push(v);
66
+ else groups.push({ day, versions: [v] });
67
+ }
68
+ return groups;
69
+ }
70
+
71
+ /** A row's title: its name, what a restore brought back, or "Autosaved". */
72
+ export function versionTitle(v: ShotVersion, all: readonly ShotVersion[]): string {
73
+ if (v.name !== undefined) return v.name;
74
+ if (v.restoredFrom !== undefined) {
75
+ const from = all.find((r) => r.id === v.restoredFrom);
76
+ return from?.name === undefined ? "Restored an earlier version" : `Restored "${from.name}"`;
77
+ }
78
+ return "Autosaved";
79
+ }
80
+
81
+ /** "Priya · 3:40 to 3:52 PM", or the time alone with no recorded writer. */
82
+ export function versionSub(v: ShotVersionHead, people: Readonly<Record<string, string>>): string {
83
+ const who = authorLine(v.authors, people);
84
+ const when = timeSpan(v.from, v.at);
85
+ return who === "" ? when : `${who} · ${when}`;
86
+ }
87
+
88
+ /** The server's rule (packages/store/src/shotVersions.ts `versionName`):
89
+ * trimmed, 1 to 80 characters, one line. Null when it passes. */
90
+ export function versionNameError(raw: string): string | null {
91
+ const name = raw.trim();
92
+ if (name === "") return "Give the version a name.";
93
+ if (name.length > 80) return "A version name is at most 80 characters.";
94
+ if (/[\u0000-\u001f\u007f]/.test(name)) return "A version name is one line.";
95
+ return null;
96
+ }
97
+
98
+ /** A stable hue per person, so one face keeps one colour in every row. */
99
+ export function personHue(id: string): number {
100
+ let h = 0;
101
+ for (let i = 0; i < id.length; i++) h = (h * 31 + id.charCodeAt(i)) >>> 0;
102
+ return h % 360;
103
+ }
@@ -0,0 +1,271 @@
1
+ import { useLayoutEffect, useMemo, useRef, useState } from "react";
2
+ import type { RefObject } from "react";
3
+
4
+ export interface VirtualWindow {
5
+ start: number;
6
+ end: number;
7
+ before: number;
8
+ after: number;
9
+ }
10
+
11
+ function prefixHeights(heights: readonly number[]): number[] {
12
+ const prefix = [0];
13
+ for (const height of heights) prefix.push(prefix[prefix.length - 1]! + Math.max(1, height));
14
+ return prefix;
15
+ }
16
+
17
+ function lowerBound(values: readonly number[], target: number): number {
18
+ let low = 0;
19
+ let high = values.length;
20
+ while (low < high) {
21
+ const middle = (low + high) >>> 1;
22
+ if (values[middle]! < target) low = middle + 1;
23
+ else high = middle;
24
+ }
25
+ return low;
26
+ }
27
+
28
+ function upperBound(values: readonly number[], target: number): number {
29
+ let low = 0;
30
+ let high = values.length;
31
+ while (low < high) {
32
+ const middle = (low + high) >>> 1;
33
+ if (values[middle]! <= target) low = middle + 1;
34
+ else high = middle;
35
+ }
36
+ return low;
37
+ }
38
+
39
+ /** Variable-height form used where one expanded timeline row contains child
40
+ * lanes. Prefix sums keep scroll updates logarithmic. */
41
+ function variableVirtualWindowFromPrefix(
42
+ prefix: readonly number[],
43
+ viewportStart: number,
44
+ viewportHeight: number,
45
+ overscanPx: number,
46
+ ): VirtualWindow {
47
+ const count = prefix.length - 1;
48
+ if (count <= 0) return { start: 0, end: 0, before: 0, after: 0 };
49
+ const total = prefix[prefix.length - 1]!;
50
+ const from = Math.max(0, viewportStart - overscanPx);
51
+ const to = Math.min(total, Math.max(from + 1, viewportStart + viewportHeight + overscanPx));
52
+ const start = Math.min(count - 1, Math.max(0, upperBound(prefix, from) - 1));
53
+ const end = Math.min(count, Math.max(start + 1, lowerBound(prefix, to)));
54
+ return { start, end, before: prefix[start]!, after: total - prefix[end]! };
55
+ }
56
+
57
+ const INITIAL_ROWS = 24;
58
+
59
+ /** Fixed-height range math shared by the outline and timeline. `end` is
60
+ * exclusive and spacers retain the full scroll geometry. */
61
+ export function fixedVirtualWindow(
62
+ count: number,
63
+ rowHeight: number,
64
+ viewportStart: number,
65
+ viewportHeight: number,
66
+ overscanRows = 8,
67
+ ): VirtualWindow {
68
+ if (count <= 0) return { start: 0, end: 0, before: 0, after: 0 };
69
+ const safeHeight = Math.max(1, rowHeight);
70
+ const first = Math.min(count - 1, Math.floor(Math.max(0, viewportStart) / safeHeight));
71
+ const visible = Math.max(1, Math.ceil(Math.max(0, viewportHeight) / safeHeight));
72
+ const start = Math.max(0, first - overscanRows);
73
+ const end = Math.min(count, first + visible + overscanRows);
74
+ return {
75
+ start,
76
+ end,
77
+ before: start * safeHeight,
78
+ after: Math.max(0, (count - end) * safeHeight),
79
+ };
80
+ }
81
+
82
+ export interface WindowSpan {
83
+ start: number;
84
+ end: number;
85
+ }
86
+
87
+ /** Merge the viewport with the few rows whose DOM identity is currently
88
+ * load-bearing (a focused, renamed or dragged outline row, the picker's active
89
+ * option). Disjoint spacers keep the list's exact fixed-row geometry, so
90
+ * pinning a row costs one row rather than filling the entire distance between
91
+ * it and the viewport. */
92
+ export function pinnedWindowSpans(
93
+ range: { start: number; end: number },
94
+ count: number,
95
+ pinnedIndexes: readonly number[],
96
+ ): WindowSpan[] {
97
+ const windowStart = Math.min(count, Math.max(0, range.start));
98
+ const windowEnd = Math.min(count, Math.max(windowStart, range.end));
99
+ const candidates: WindowSpan[] = [
100
+ { start: windowStart, end: windowEnd },
101
+ ...pinnedIndexes
102
+ .filter((index) => index >= 0 && index < count)
103
+ .map((index) => ({ start: index, end: index + 1 })),
104
+ ]
105
+ .filter((span) => span.end > span.start)
106
+ .sort((a, b) => a.start - b.start);
107
+ const merged: WindowSpan[] = [];
108
+ for (const candidate of candidates) {
109
+ const previous = merged[merged.length - 1];
110
+ if (previous === undefined || candidate.start > previous.end) {
111
+ merged.push({ ...candidate });
112
+ } else {
113
+ previous.end = Math.max(previous.end, candidate.end);
114
+ }
115
+ }
116
+ return merged;
117
+ }
118
+
119
+ /** Internal fixed-row windowing with no dependency. The list keeps its total
120
+ * height through spacers, while scroll and resize only update the mounted
121
+ * slice. Small lists retain their complete DOM for compatibility. */
122
+ export function useFixedVirtualWindow(args: {
123
+ listRef: RefObject<HTMLElement | null>;
124
+ scrollSelector: string;
125
+ count: number;
126
+ rowHeight: number;
127
+ enabled: boolean;
128
+ overscanRows?: number;
129
+ }): VirtualWindow {
130
+ const { listRef, scrollSelector, count, rowHeight, enabled, overscanRows = 8 } = args;
131
+ const [range, setRange] = useState<VirtualWindow>(() =>
132
+ enabled
133
+ ? fixedVirtualWindow(count, rowHeight, 0, INITIAL_ROWS * rowHeight, overscanRows)
134
+ : { start: 0, end: count, before: 0, after: 0 },
135
+ );
136
+
137
+ useLayoutEffect(() => {
138
+ if (!enabled) {
139
+ setRange((current) =>
140
+ current.start === 0 && current.end === count && current.before === 0 && current.after === 0
141
+ ? current
142
+ : { start: 0, end: count, before: 0, after: 0 },
143
+ );
144
+ return;
145
+ }
146
+ const list = listRef.current;
147
+ const scroller = list?.closest(scrollSelector);
148
+ if (!(list instanceof HTMLElement) || !(scroller instanceof HTMLElement)) {
149
+ setRange(fixedVirtualWindow(count, rowHeight, 0, INITIAL_ROWS * rowHeight, overscanRows));
150
+ return;
151
+ }
152
+ let lastScrollTop = scroller.scrollTop;
153
+ const measure = (): void => {
154
+ lastScrollTop = scroller.scrollTop;
155
+ const listBox = list.getBoundingClientRect();
156
+ const scrollBox = scroller.getBoundingClientRect();
157
+ const viewportStart = Math.max(0, scrollBox.top - listBox.top);
158
+ const viewportHeight = scroller.clientHeight || INITIAL_ROWS * rowHeight;
159
+ const next = fixedVirtualWindow(count, rowHeight, viewportStart, viewportHeight, overscanRows);
160
+ setRange((current) =>
161
+ current.start === next.start && current.end === next.end &&
162
+ current.before === next.before && current.after === next.after
163
+ ? current
164
+ : next,
165
+ );
166
+ };
167
+ const onScroll = (): void => {
168
+ // The timeline's playhead follow writes `scrollLeft` while playing. A
169
+ // native scroll event does not say which axis moved, so measuring here
170
+ // unconditionally forced two vertical layout reads per followed frame
171
+ // for every mounted window. Vertical range math changes only with the
172
+ // vertical axis; resize/layout changes have their observer below.
173
+ if (scroller.scrollTop === lastScrollTop) return;
174
+ measure();
175
+ };
176
+ measure();
177
+ scroller.addEventListener("scroll", onScroll, { passive: true });
178
+ const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(measure);
179
+ observer?.observe(scroller);
180
+ observer?.observe(list);
181
+ window.addEventListener("resize", measure);
182
+ return () => {
183
+ scroller.removeEventListener("scroll", onScroll);
184
+ observer?.disconnect();
185
+ window.removeEventListener("resize", measure);
186
+ };
187
+ }, [count, enabled, listRef, overscanRows, rowHeight, scrollSelector]);
188
+
189
+ return range;
190
+ }
191
+
192
+ export function useVariableVirtualWindow(args: {
193
+ listRef: RefObject<HTMLElement | null>;
194
+ scrollSelector: string;
195
+ heights: readonly number[];
196
+ enabled: boolean;
197
+ overscanPx?: number;
198
+ }): VirtualWindow {
199
+ const { listRef, scrollSelector, heights, enabled, overscanPx = 224 } = args;
200
+ const prefix = useMemo(() => prefixHeights(heights), [heights]);
201
+ const [range, setRange] = useState<VirtualWindow>(() =>
202
+ enabled
203
+ ? variableVirtualWindowFromPrefix(prefix, 0, INITIAL_ROWS * 28, overscanPx)
204
+ : { start: 0, end: heights.length, before: 0, after: 0 },
205
+ );
206
+
207
+ // The heights change identity on every document edit (they are derived from
208
+ // the document), and the observer effect below used to list them as a
209
+ // dependency: every rAF frame of a keyframe drag tore down and rebuilt the
210
+ // scroll listener and a two-element `ResizeObserver`, then measured twice.
211
+ // The prefix rides a ref instead, and a change to it re-measures once.
212
+ const prefixRef = useRef(prefix);
213
+ prefixRef.current = prefix;
214
+ const measureRef = useRef<(() => void) | null>(null);
215
+ useLayoutEffect(() => {
216
+ measureRef.current?.();
217
+ }, [prefix]);
218
+
219
+ useLayoutEffect(() => {
220
+ if (!enabled) {
221
+ setRange((current) =>
222
+ current.start === 0 && current.end === heights.length && current.before === 0 && current.after === 0
223
+ ? current
224
+ : { start: 0, end: heights.length, before: 0, after: 0 },
225
+ );
226
+ return;
227
+ }
228
+ const list = listRef.current;
229
+ const scroller = list?.closest(scrollSelector);
230
+ if (!(list instanceof HTMLElement) || !(scroller instanceof HTMLElement)) {
231
+ setRange(variableVirtualWindowFromPrefix(prefixRef.current, 0, INITIAL_ROWS * 28, overscanPx));
232
+ return;
233
+ }
234
+ let lastScrollTop = scroller.scrollTop;
235
+ const measure = (): void => {
236
+ lastScrollTop = scroller.scrollTop;
237
+ const listBox = list.getBoundingClientRect();
238
+ const scrollBox = scroller.getBoundingClientRect();
239
+ const viewportStart = Math.max(0, scrollBox.top - listBox.top);
240
+ const viewportHeight = scroller.clientHeight || INITIAL_ROWS * 28;
241
+ const next = variableVirtualWindowFromPrefix(prefixRef.current, viewportStart, viewportHeight, overscanPx);
242
+ setRange((current) =>
243
+ current.start === next.start && current.end === next.end &&
244
+ current.before === next.before && current.after === next.after
245
+ ? current
246
+ : next,
247
+ );
248
+ };
249
+ const onScroll = (): void => {
250
+ // See the fixed-row form above. Horizontal follow must stay a single
251
+ // `scrollLeft` write, never a vertical virtualization measurement.
252
+ if (scroller.scrollTop === lastScrollTop) return;
253
+ measure();
254
+ };
255
+ measureRef.current = measure;
256
+ measure();
257
+ scroller.addEventListener("scroll", onScroll, { passive: true });
258
+ const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(measure);
259
+ observer?.observe(scroller);
260
+ observer?.observe(list);
261
+ window.addEventListener("resize", measure);
262
+ return () => {
263
+ if (measureRef.current === measure) measureRef.current = null;
264
+ scroller.removeEventListener("scroll", onScroll);
265
+ observer?.disconnect();
266
+ window.removeEventListener("resize", measure);
267
+ };
268
+ }, [enabled, heights.length, listRef, overscanPx, scrollSelector]);
269
+
270
+ return range;
271
+ }