@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,326 @@
1
+ // THE ADD ROWS, their file picker and the media tool's file claim, shared by
2
+ // the Studio dock's Insert button (InsertButton.tsx) and the Insert drawer
3
+ // (InsertDrawer.tsx). The drawer mounts beside the button and opts out of the
4
+ // claim (`claimImagePick: false`), so there is still ONE claimant of
5
+ // `imagePick` on screen at a time.
6
+ //
7
+ // ── THE ROWS ARE GATED ON THE SCHEMA AT RUNTIME ─────────────────────────
8
+ //
9
+ // `hasLayerKind` reads `LAYER_KINDS`, so a kind removed from the document model
10
+ // removes its row here instead of leaving a button that mints a layer nothing
11
+ // can render. It has to be a runtime test and not `LAYER_KINDS.includes("mesh")`
12
+ // inline: against the literal tuple that call is a TYPE error the day the kind
13
+ // goes, which is a build nobody can ship: the menu could not degrade, only
14
+ // fail to compile.
15
+ //
16
+ // ── ONE FILE INPUT, TWO PICKERS ─────────────────────────────────────────
17
+ //
18
+ // The model row needs one, and so does the MEDIA TOOL (`image`, labelled
19
+ // "Media" because the file it takes may be an image or a video), which draws
20
+ // its box on the canvas and only then needs a file, so its geometry parks in
21
+ // the store (`imagePick`) and is claimed here. A dialog can only be opened by a
22
+ // click on a real `<input>`, and the store is not one.
23
+ //
24
+ // The rail's Audio button asks the same input for a sound (`pickAudio`), so
25
+ // the rail still renders one input for every file it takes.
26
+ import { useEffect, useRef, useState } from "react";
27
+ import type { ReactElement } from "react";
28
+ import { DEVICE_PRESET_IDS, MESH_PRIMITIVE_KINDS } from "@dailies-studio/shot/doc";
29
+ import { useShotEditor, useStore } from "../../state/context.js";
30
+ import { hasLayerKind } from "../../state/insert.js";
31
+ import { AUDIO_FILE_ACCEPT } from "../timeline/AudioLane.js";
32
+ // The menu's grouping (the AUTHORING taxonomy, not the schema's) and the
33
+ // particle builtins promoted onto its Generate shelf. Both were written out
34
+ // here and again in `icons.tsx`, which keys its family and builtin glyph maps
35
+ // on the same names; the classification rule and the promoted ids are stated
36
+ // once in the leaf module both files now read.
37
+ import { promotedBuiltinLabel } from "../../addFamilies.js";
38
+ import type { AddFamily } from "../../addFamilies.js";
39
+
40
+ /**
41
+ * What the hidden input is being asked for, or null when idle.
42
+ *
43
+ * An OBJECT and not a bare string, so asking for the same kind twice is a new
44
+ * value: a dialog the author cancelled fires no event at all, and with a string
45
+ * the second "image" would be `===` the stale first one, the effect would not
46
+ * re-run, and the picker would silently never open again.
47
+ */
48
+ type Pick = { kind: "image" } | { kind: "model" } | { kind: "audio" };
49
+
50
+ // EXTENSIONS, NOT `image/*` OR `video/*`. The "sniff" on the far end is a
51
+ // filename extension map (`KIND_BY_EXT` in cli/src/assetStore.ts), and
52
+ // `storeAsset` writes the bytes BEFORE the kind is consulted. So a file the
53
+ // picker offered and the map does not know (.mkv, .heic) is stored, then
54
+ // refused, leaving an orphan blob in the content-addressed store that nothing
55
+ // ever collects. The lists are exactly what the map answers "image" or "video"
56
+ // for. SVG is one: the map says image, and the player's texture loader sizes
57
+ // an SVG explicitly and refuses one with no drawable size (textureCache.ts).
58
+ const IMAGE_EXTENSIONS = ".png,.jpg,.jpeg,.webp,.avif,.gif,.svg";
59
+ const VIDEO_EXTENSIONS = ".mp4,.webm,.mov,.m4v";
60
+ const IMAGE_TYPES = "image/png,image/jpeg,image/webp,image/avif,image/gif,image/svg+xml";
61
+ const VIDEO_TYPES = "video/mp4,video/webm,video/quicktime";
62
+ const MODEL_ACCEPT = ".glb,.gltf,model/gltf-binary";
63
+ /** One kind's picker filter. Shared with the inspector's Replace (MediaSection). */
64
+ export const IMAGE_ACCEPT = `${IMAGE_EXTENSIONS},${IMAGE_TYPES}`;
65
+ export const VIDEO_ACCEPT = `${VIDEO_EXTENSIONS},${VIDEO_TYPES}`;
66
+
67
+ function acceptFor(want: Pick | null): string {
68
+ if (want?.kind === "model") return MODEL_ACCEPT;
69
+ if (want?.kind === "audio") return AUDIO_FILE_ACCEPT;
70
+ return `${IMAGE_EXTENSIONS},${VIDEO_EXTENSIONS},${IMAGE_TYPES},${VIDEO_TYPES}`;
71
+ }
72
+
73
+ /** One insert a menu or rail offers. `family` is the Add menu's shelf;
74
+ * `section` is set on the rows the component picker lists (D3). */
75
+ export interface AddRow {
76
+ key: string;
77
+ family: AddFamily;
78
+ section?: "In this project" | "Library" | "Effects" | "Particles";
79
+ label: string;
80
+ disabled?: boolean;
81
+ title?: string;
82
+ run: () => void;
83
+ }
84
+
85
+ export function useAddRows(o: {
86
+ /** Fetch the component registry now. The menu asks when it first opens,
87
+ * the rail when it mounts, so a chrome that never adds a component never
88
+ * makes the HTTP call. */
89
+ wantLibrary: boolean;
90
+ /** Called when a file pick starts an insert, so a host can close its menu. */
91
+ onPicked?: () => void;
92
+ /** Answer the media tool's parked box (`imagePick`). True by default. The
93
+ * Insert drawer passes false: it mounts beside the dock's own claimant, and
94
+ * two claimants would open the dialog twice. */
95
+ claimImagePick?: boolean;
96
+ }): {
97
+ rows: AddRow[];
98
+ busy: boolean;
99
+ /** Open the picker for a sound, imported at the playhead into the entered
100
+ * Shot (or onto the film's own lane when none is). */
101
+ pickAudio: () => void;
102
+ /** The ONE hidden input. The host renders it once. */
103
+ fileInput: ReactElement;
104
+ } {
105
+ const store = useStore();
106
+ const imagePick = useShotEditor((s) => s.imagePick);
107
+ const hasSelection = useShotEditor((s) => s.selection.length > 0);
108
+ const definitions = useShotEditor((s) => s.doc?.components);
109
+ const library = useShotEditor((s) => s.library);
110
+ const libraryLoaded = useShotEditor((s) => s.libraryLoaded);
111
+ const [busy, setBusy] = useState(false);
112
+ const [want, setWant] = useState<Pick | null>(null);
113
+ const fileRef = useRef<HTMLInputElement | null>(null);
114
+
115
+ // A drawn image box is waiting on a file: open the dialog for it. An EFFECT
116
+ // is the seam because the draw happened in the STAGE's document, so there is
117
+ // no click of ours to hang it off. User activation propagates from a
118
+ // same-origin iframe, so the dialog is still allowed to open; if a browser
119
+ // ever stops propagating it, the ceiling is that the image tool needs a
120
+ // second click here rather than anything breaking.
121
+ const claims = o.claimImagePick !== false;
122
+ useEffect(() => {
123
+ if (claims && imagePick !== null) setWant({ kind: "image" });
124
+ }, [claims, imagePick]);
125
+
126
+ useEffect(() => {
127
+ if (want !== null) fileRef.current?.click();
128
+ }, [want]);
129
+
130
+ // A failure leaves the instance rows absent (the error surfaces in the
131
+ // inspector, which is the surface that can say what it means for a layer).
132
+ useEffect(() => {
133
+ if (o.wantLibrary && !libraryLoaded) void store.refreshLibrary();
134
+ }, [o.wantLibrary, libraryLoaded, store]);
135
+
136
+ const report = (e: unknown): void =>
137
+ store.reportError(e instanceof Error ? e.message : String(e));
138
+
139
+ // SEVERAL MEDIA FILES, in the order picked. The first claims the parked box;
140
+ // each next one parks the same box again and goes through the same claim,
141
+ // so they land where the box was, one at a time. A failed file is reported
142
+ // and the rest still land.
143
+ const placeMedia = async (files: readonly File[]): Promise<void> => {
144
+ const box = store.getState().imagePick;
145
+ await store.completeImageDraw(files[0] ?? null).catch(report);
146
+ for (const file of files.slice(1)) {
147
+ if (box === null) return;
148
+ store.stageHandlers.onDraw("image", box);
149
+ await store.completeImageDraw(file).catch(report);
150
+ }
151
+ };
152
+
153
+ const chose = (files: readonly File[]): void => {
154
+ const asked = want;
155
+ setWant(null);
156
+ if (asked === null) return;
157
+ o.onPicked?.();
158
+ setBusy(true);
159
+ const s = store.getState();
160
+ const file = files[0] ?? null;
161
+ const done =
162
+ asked.kind === "image"
163
+ ? placeMedia(files)
164
+ : file === null
165
+ ? Promise.resolve()
166
+ : asked.kind === "audio"
167
+ ? store.importAudio(file, s.frame, s.enteredScene ?? undefined)
168
+ : store.addMeshLayer(file);
169
+ void done.catch(report).finally(() => setBusy(false));
170
+ };
171
+
172
+ // A CANCELLED dialog is a pick of nothing, so `imagePick` is released rather
173
+ // than left parked for the next mount to reopen the dialog over. Native,
174
+ // because React does not wire `onCancel` on an `<input>`.
175
+ useEffect(() => {
176
+ const el = fileRef.current;
177
+ if (el === null || want === null) return;
178
+ const onCancel = (): void => chose([]);
179
+ el.addEventListener("cancel", onCancel);
180
+ return () => el.removeEventListener("cancel", onCancel);
181
+ // eslint-disable-next-line react-hooks/exhaustive-deps
182
+ }, [want]);
183
+
184
+ // It goes through `onDraw("image", …)` and not through a new store action,
185
+ // because that is where the box-waits-for-a-file handshake already lives:
186
+ // the geometry parks in `imagePick`, the effect above claims it, and the
187
+ // hidden input finishes the job. A second path would be a second place that
188
+ // knows how a media insert is staged.
189
+ //
190
+ // TWO IDENTICAL POINTS: a click, not a drag. `boxOf` re-shapes that to the
191
+ // source's own aspect once the file has been measured (insert.ts), which is
192
+ // a better box than any rectangle this menu could guess.
193
+ const pickMedia = (): void => {
194
+ // A menu row expresses no position: it lands centred on what the frame
195
+ // shows, like every other add (F54).
196
+ const centre = store.insertOrigin();
197
+ store.stageHandlers.onDraw("image", [centre, centre]);
198
+ };
199
+
200
+ // Rows carry their FAMILY, and the hosts group them, in the taxonomy's own
201
+ // order.
202
+ const rows: AddRow[] = [];
203
+ if (hasLayerKind("image") || hasLayerKind("video")) {
204
+ // THE ONE CAPABILITY THIS PRODUCT IS BUILT AROUND, and until this row it was
205
+ // reachable only by arming a dock tool labelled "Media" and rubber-banding a
206
+ // box. The Add menu listed html, every mesh primitive, a 3D model, every
207
+ // device preset, both particle builtins, a cloner, a group and the whole
208
+ // component registry, and not the one thing an author opens the app to do.
209
+ rows.push({ key: "media", family: "Draw", label: "Image or video…", run: () => pickMedia() });
210
+ }
211
+ if (hasLayerKind("html")) {
212
+ rows.push({ key: "html", family: "Draw", label: "Html snippet", run: () => store.addHtmlLayer() });
213
+ }
214
+ if (hasLayerKind("mesh")) {
215
+ // The parametric shapes (schema v11), from the schema's own list, by the
216
+ // DEVICE_PRESET_IDS rule: a hardcoded copy is a second place that knows
217
+ // what a shape is.
218
+ for (const kind of MESH_PRIMITIVE_KINDS) {
219
+ rows.push({
220
+ key: `shape:${kind}`,
221
+ family: "Solid",
222
+ label: kind.charAt(0).toUpperCase() + kind.slice(1),
223
+ run: () => store.addShapeLayer(kind),
224
+ });
225
+ }
226
+ rows.push({ key: "mesh", family: "Solid", label: "3D model…", run: () => setWant({ kind: "model" }) });
227
+ }
228
+ if (hasLayerKind("device")) {
229
+ // FROM THE REGISTRY, never a list written here: a hardcoded one is a second
230
+ // place that knows what a device is, and `validateShotDoc` refuses a preset
231
+ // the registry does not have, so the two disagreeing means a menu row that
232
+ // can only produce a document the author cannot save.
233
+ for (const preset of DEVICE_PRESET_IDS) {
234
+ rows.push({
235
+ key: `device:${preset}`,
236
+ family: "Solid",
237
+ label: preset.charAt(0).toUpperCase() + preset.slice(1),
238
+ run: () => store.addDeviceLayer(preset),
239
+ });
240
+ }
241
+ }
242
+ if (hasLayerKind("instance")) {
243
+ // The CURATED Generate shelf: the particle builtins surface as first-class
244
+ // basics with their own names, not as two entries lost in the component
245
+ // list. A chrome curation (which two ids read as "particles" is a product
246
+ // decision), while the rows themselves still come from the live registry:
247
+ // a build without the particles package simply has no rows to promote.
248
+ for (const row of library) {
249
+ if (row.host !== "three") continue;
250
+ const label = promotedBuiltinLabel(row.id);
251
+ if (label === null) continue;
252
+ rows.push({
253
+ key: `instance:${row.id}`,
254
+ family: "Generate",
255
+ section: "Particles",
256
+ label,
257
+ title: row.id,
258
+ run: () => void store.addInstanceLayer(row.id).catch(report),
259
+ });
260
+ }
261
+ }
262
+ if (hasLayerKind("cloner")) {
263
+ rows.push({
264
+ key: "cloner",
265
+ family: "Generate",
266
+ label: "Cloner",
267
+ // No empty cloner, ever: `validateShotDoc` refuses one with no children,
268
+ // so the row that inserted it would be a row that breaks saving.
269
+ disabled: !hasSelection,
270
+ title: hasSelection ? "Wrap the selection in a cloner" : "Select a layer to clone first",
271
+ run: () => store.addClonerLayer(),
272
+ });
273
+ }
274
+ // ONE COMPONENT PICKER (F56/F59): this Project's own definitions first, then
275
+ // the registry (its TSL effects and particles in sections of their own).
276
+ // Grouping the selection and making a component are verbs on a selection,
277
+ // not inserts, so they live on Ctrl/Cmd+G, the layer menu and the picker's
278
+ // "Make component" footer, never in this list.
279
+ if (hasLayerKind("instance")) {
280
+ for (const def of Object.values(definitions ?? {})) {
281
+ rows.push({
282
+ key: `def:${def.id}`,
283
+ family: "Structure",
284
+ section: "In this project",
285
+ label: def.name,
286
+ title: "A component of this Project",
287
+ run: () => store.placeComponent(def.id),
288
+ });
289
+ }
290
+ // FROM THE LIVE REGISTRY, filtered to the only host that can mount inside
291
+ // the shot's 3D canvas. Same reason the device rows come from
292
+ // DEVICE_PRESET_IDS: a hardcoded list would be a second place that knows
293
+ // what a component is, and the save gate refuses anything else, so a row
294
+ // offering a "dom" component could only ever produce a document the author
295
+ // cannot save.
296
+ for (const row of library.filter((r) => r.host === "three" && promotedBuiltinLabel(r.id) === null)) {
297
+ rows.push({
298
+ key: `instance:${row.id}`,
299
+ family: "Structure",
300
+ section: row.effect === undefined ? "Library" : "Effects",
301
+ label: row.name,
302
+ title: row.id,
303
+ run: () => void store.addInstanceLayer(row.id).catch(report),
304
+ });
305
+ }
306
+ }
307
+
308
+ const fileInput = (
309
+ <input
310
+ ref={fileRef}
311
+ className="audio-file-input"
312
+ type="file"
313
+ accept={acceptFor(want)}
314
+ multiple={want?.kind === "image"}
315
+ hidden
316
+ data-testid="add-file-input"
317
+ onChange={(e) => {
318
+ chose(Array.from(e.currentTarget.files ?? []));
319
+ // Cleared so re-picking the SAME file fires change again.
320
+ e.currentTarget.value = "";
321
+ }}
322
+ />
323
+ );
324
+
325
+ return { rows, busy, pickAudio: () => setWant({ kind: "audio" }), fileInput };
326
+ }
@@ -0,0 +1,122 @@
1
+ // ONE DISMISS RULE for the tool menus (the dock's Shapes and Add, the board
2
+ // rail's flyouts): while open, a press outside the anchor, Escape anywhere, or
3
+ // the window losing focus closes it. The listeners mirror ui `Popover`'s pair.
4
+ //
5
+ // THE STAGE IS ANOTHER DOCUMENT. A press in its iframe never reaches this
6
+ // window, so the pair is also set on each same-origin frame's window, in the
7
+ // capture phase there, where the press and the Escape stop: an outside press
8
+ // on the stage only dismisses, with no pick, no deselect and no drag under it.
9
+ // The window `blur` is skipped when focus went INTO one of those frames (a
10
+ // cross-origin one still dismisses through it): the stage takes
11
+ // focus as the pointer crosses it (StagePanel `focusStage`), which closed a
12
+ // menu the author was only moving towards.
13
+ //
14
+ // The stage's own capture-phase Escape lanes (pen, spline, G/R/S) were
15
+ // registered on that window first, so they run before this one: while a menu
16
+ // is open the frame's root carries `TOOL_MENU_ATTR` and they yield Escape
17
+ // (`toolMenuOpen`).
18
+ //
19
+ // The frame half tells `close` which frame window the press or Escape came
20
+ // from, so a menu can hand focus to that frame (the font picker does).
21
+ //
22
+ // A MENU ON A ui `Popover` (the font picker) takes only the frame half
23
+ // (`window: false`): the Popover already closes on a press outside it and on
24
+ // Escape in this window. It also keeps `blur: false` when it opens the OS file
25
+ // dialog, which blurs the window.
26
+ import { useEffect, useRef } from "react";
27
+ import type { RefObject } from "react";
28
+
29
+ const TOOL_MENU_ATTR = "data-tool-menu-open";
30
+
31
+ /** A chrome tool menu is open over this document: Escape is the menu's. */
32
+ export function toolMenuOpen(): boolean {
33
+ return document.documentElement.hasAttribute(TOOL_MENU_ATTR);
34
+ }
35
+
36
+ export interface DismissOptions {
37
+ /** Close on a press outside `anchor` and on Escape in this window. Default
38
+ * true; false for a menu whose `Popover` does both already. */
39
+ window?: boolean;
40
+ /** Close when this window loses focus to anything but a stage frame.
41
+ * Default true. */
42
+ blur?: boolean;
43
+ }
44
+
45
+ export function useDismiss(
46
+ open: boolean,
47
+ anchor: RefObject<HTMLElement | null>,
48
+ close: (frame?: Window) => void,
49
+ options: DismissOptions = {},
50
+ ): void {
51
+ const latest = useRef(close);
52
+ latest.current = close;
53
+ const onWindow = options.window ?? true;
54
+ const onBlurToo = options.blur ?? true;
55
+ useEffect(() => {
56
+ if (!open) return;
57
+ const onDown = (e: PointerEvent): void => {
58
+ if (!(anchor.current?.contains(e.target as Node) ?? false)) latest.current();
59
+ };
60
+ const onKey = (e: KeyboardEvent): void => {
61
+ if (e.key !== "Escape") return;
62
+ // Escape closes the menu and nothing else (not a deselect under it).
63
+ e.stopPropagation();
64
+ latest.current();
65
+ };
66
+ const frames: { w: Window; down: (e: PointerEvent) => void; key: (e: KeyboardEvent) => void }[] = [];
67
+ const listened = new Set<Element>();
68
+ for (const frame of document.querySelectorAll("iframe")) {
69
+ try {
70
+ const w = frame.contentWindow;
71
+ if (w === null) continue;
72
+ // The loop's `w`, not `e.view`: a synthetic event has no view.
73
+ const down = (e: PointerEvent): void => {
74
+ e.preventDefault();
75
+ e.stopPropagation();
76
+ latest.current(w);
77
+ };
78
+ const key = (e: KeyboardEvent): void => {
79
+ if (e.key !== "Escape") return;
80
+ e.stopPropagation();
81
+ latest.current(w);
82
+ };
83
+ w.addEventListener("pointerdown", down, true);
84
+ w.addEventListener("keydown", key, true);
85
+ w.document.documentElement.setAttribute(TOOL_MENU_ATTR, "");
86
+ frames.push({ w, down, key });
87
+ listened.add(frame);
88
+ } catch {
89
+ // A cross-origin frame: a press there reaches this window as a blur.
90
+ }
91
+ }
92
+ // Read after the blur, once focus has landed: browsers differ on whether
93
+ // `activeElement` is the frame yet while the event runs.
94
+ let blurTimer: ReturnType<typeof setTimeout> | undefined;
95
+ const onBlur = (): void => {
96
+ blurTimer = setTimeout(() => {
97
+ const into = document.activeElement;
98
+ if (into === null || !listened.has(into)) latest.current();
99
+ }, 0);
100
+ };
101
+ if (onWindow) {
102
+ window.addEventListener("pointerdown", onDown, true);
103
+ window.addEventListener("keydown", onKey, true);
104
+ }
105
+ if (onBlurToo) window.addEventListener("blur", onBlur);
106
+ return () => {
107
+ clearTimeout(blurTimer);
108
+ window.removeEventListener("pointerdown", onDown, true);
109
+ window.removeEventListener("keydown", onKey, true);
110
+ window.removeEventListener("blur", onBlur);
111
+ for (const { w, down, key } of frames) {
112
+ try {
113
+ w.removeEventListener("pointerdown", down, true);
114
+ w.removeEventListener("keydown", key, true);
115
+ w.document.documentElement.removeAttribute(TOOL_MENU_ATTR);
116
+ } catch {
117
+ // The frame navigated away and is cross-origin now.
118
+ }
119
+ }
120
+ };
121
+ }, [open, anchor, onWindow, onBlurToo]);
122
+ }