@hyperframes/studio 0.8.50 → 0.8.53-desktop.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 (330) hide show
  1. package/dist/assets/{hyperframes-player-CxxDxHTW.js → hyperframes-player-PTEnAHhg.js} +3 -3
  2. package/dist/assets/{index-DUAIWxkI.js → index--myXp8ui.js} +1 -1
  3. package/dist/assets/{index-Dt3qBI-9.js → index-BjslOS9g.js} +1 -1
  4. package/dist/assets/index-BnXZuIJD.css +1 -0
  5. package/dist/assets/index-CNyviK_l.js +390 -0
  6. package/dist/index.css +29 -0
  7. package/dist/index.css.map +1 -0
  8. package/dist/index.d.ts +825 -24
  9. package/dist/index.html +2 -2
  10. package/dist/index.js +8911 -9853
  11. package/dist/index.js.map +1 -1
  12. package/dist/styles/tailwind-preset.js.map +1 -1
  13. package/package.json +13 -12
  14. package/src/App.tsx +145 -163
  15. package/src/captions/components/CaptionAnimationPanel.tsx +4 -4
  16. package/src/captions/components/CaptionOverlay.sender.test.tsx +52 -0
  17. package/src/captions/components/CaptionOverlay.tsx +8 -4
  18. package/src/captions/components/CaptionOverlayUtils.ts +2 -3
  19. package/src/captions/components/CaptionPropertyPanel.tsx +3 -3
  20. package/src/captions/components/CaptionTimeline.tsx +1 -1
  21. package/src/captions/components/shared.tsx +2 -2
  22. package/src/components/AskAgentModal.tsx +5 -5
  23. package/src/components/EditorShell.selectionSync.test.tsx +9 -4
  24. package/src/components/EditorShell.tsx +59 -62
  25. package/src/components/ExternalFileConflictBanner.tsx +16 -16
  26. package/src/components/LintModal.tsx +6 -16
  27. package/src/components/MediaPreview.tsx +2 -2
  28. package/src/components/RightPanelTabs.test.tsx +151 -0
  29. package/src/components/RightPanelTabs.tsx +59 -0
  30. package/src/components/SaveQueuePausedBanner.tsx +2 -2
  31. package/src/components/StudioHeader.dom.test.tsx +181 -0
  32. package/src/components/StudioHeader.test.ts +0 -12
  33. package/src/components/StudioHeader.tsx +118 -221
  34. package/src/components/StudioLeftPanels.tsx +202 -0
  35. package/src/components/StudioOverlays.tsx +1 -1
  36. package/src/components/{StudioRightPanel.tsx → StudioRightPanels.tsx} +57 -194
  37. package/src/components/{StudioRightPanel.types.ts → StudioRightPanels.types.ts} +4 -3
  38. package/src/components/StudioToast.tsx +2 -2
  39. package/src/components/TimelineHistoryButtons.dom.test.tsx +117 -0
  40. package/src/components/TimelineHistoryButtons.tsx +54 -0
  41. package/src/components/TimelineToolbar.test.tsx +8 -0
  42. package/src/components/TimelineToolbar.tsx +8 -12
  43. package/src/components/dock/Dock.test.tsx +274 -0
  44. package/src/components/dock/Dock.tsx +291 -0
  45. package/src/components/dock/dock.css +28 -0
  46. package/src/components/dock/dockAccessibility.test.ts +166 -0
  47. package/src/components/dock/dockAccessibility.ts +98 -0
  48. package/src/components/dock/dockLayout.test.ts +134 -0
  49. package/src/components/dock/dockLayout.ts +105 -0
  50. package/src/components/dock/dockLayoutSchema.ts +66 -0
  51. package/src/components/dock/dockLayoutStore.test.ts +28 -0
  52. package/src/components/dock/dockLayoutStore.ts +108 -0
  53. package/src/components/dock/dockTestHarness.ts +11 -0
  54. package/src/components/dock/panelRegistry.ts +67 -0
  55. package/src/components/editor/AnimationCard.tsx +4 -4
  56. package/src/components/editor/AnimationCardParts.tsx +6 -6
  57. package/src/components/editor/BlockParamsPanel.tsx +4 -4
  58. package/src/components/editor/BorderRadiusEditor.tsx +1 -1
  59. package/src/components/editor/CanvasContextMenu.tsx +1 -1
  60. package/src/components/editor/ComputedTweenNotice.tsx +1 -1
  61. package/src/components/editor/DomEditOverlay.tsx +1 -1
  62. package/src/components/editor/EaseCurveSection.tsx +1 -1
  63. package/src/components/editor/EaseParamFields.tsx +5 -5
  64. package/src/components/editor/FileTree.tsx +4 -4
  65. package/src/components/editor/FileTreeIcons.tsx +1 -1
  66. package/src/components/editor/FileTreeNodes.tsx +13 -13
  67. package/src/components/editor/InlineTextToolbar.tsx +1 -1
  68. package/src/components/editor/InspectorHeaderActions.tsx +3 -3
  69. package/src/components/editor/KeyframeDiamond.tsx +1 -1
  70. package/src/components/editor/KeyframeEaseList.tsx +1 -1
  71. package/src/components/editor/LayersPanel.test.ts +97 -1
  72. package/src/components/editor/LayersPanel.tsx +7 -5
  73. package/src/components/editor/MarqueeOverlay.tsx +1 -1
  74. package/src/components/editor/OffCanvasIndicators.tsx +1 -1
  75. package/src/components/editor/PreviewGuides.tsx +1 -1
  76. package/src/components/editor/PromotableControl.tsx +2 -2
  77. package/src/components/editor/PropertyPanelEmptyState.tsx +7 -7
  78. package/src/components/editor/PropertyPanelFlatHeader.tsx +2 -2
  79. package/src/components/editor/SnapToolbar.test.tsx +0 -3
  80. package/src/components/editor/SnapToolbar.tsx +2 -2
  81. package/src/components/editor/TimelineFxPopover.tsx +1 -1
  82. package/src/components/editor/TopologyLens.test.tsx +28 -0
  83. package/src/components/editor/TopologyLens.tsx +7 -5
  84. package/src/components/editor/Transform3DCube.tsx +2 -2
  85. package/src/components/editor/audioFxRevealTarget.ts +1 -0
  86. package/src/components/editor/domEditing.test.ts +1 -1
  87. package/src/components/editor/domEditingAgentPrompt.test.ts +8 -6
  88. package/src/components/editor/domEditingAgentPrompt.ts +4 -3
  89. package/src/components/editor/propertyPanelColor.tsx +11 -11
  90. package/src/components/editor/propertyPanelColorCurveGraph.tsx +1 -1
  91. package/src/components/editor/propertyPanelColorGradingControls.tsx +7 -9
  92. package/src/components/editor/propertyPanelColorGradingSection.tsx +6 -6
  93. package/src/components/editor/propertyPanelColorGradingSlider.tsx +5 -5
  94. package/src/components/editor/propertyPanelColorWheels.tsx +1 -1
  95. package/src/components/editor/propertyPanelCommitField.tsx +1 -1
  96. package/src/components/editor/propertyPanelFill.tsx +4 -4
  97. package/src/components/editor/propertyPanelFlatColorGradingAccessory.tsx +3 -3
  98. package/src/components/editor/propertyPanelFlatColorGradingSection.tsx +7 -7
  99. package/src/components/editor/propertyPanelFlatEffectsSection.tsx +4 -4
  100. package/src/components/editor/propertyPanelFlatMediaSection.tsx +3 -3
  101. package/src/components/editor/propertyPanelFlatOverlaysSection.tsx +1 -1
  102. package/src/components/editor/propertyPanelFlatPrimitives.tsx +10 -10
  103. package/src/components/editor/propertyPanelFlatSelectRow.tsx +3 -3
  104. package/src/components/editor/propertyPanelFlatTextSection.test.tsx +8 -9
  105. package/src/components/editor/propertyPanelFlatTextSection.tsx +5 -5
  106. package/src/components/editor/propertyPanelFlatToggle.tsx +1 -1
  107. package/src/components/editor/propertyPanelFont.tsx +5 -5
  108. package/src/components/editor/propertyPanelFxCarveModule.tsx +1 -1
  109. package/src/components/editor/propertyPanelFxControls.test.tsx +1 -1
  110. package/src/components/editor/propertyPanelFxControls.tsx +6 -6
  111. package/src/components/editor/propertyPanelFxEqModule.tsx +2 -2
  112. package/src/components/editor/propertyPanelFxPresetStyle.ts +2 -2
  113. package/src/components/editor/propertyPanelGradingNumberField.tsx +1 -1
  114. package/src/components/editor/propertyPanelMediaSection.tsx +1 -1
  115. package/src/components/editor/propertyPanelPrimitives.tsx +7 -7
  116. package/src/components/editor/propertyPanelSections.tsx +8 -8
  117. package/src/components/editor/useColorGradingController.test.ts +38 -0
  118. package/src/components/editor/useColorGradingController.ts +9 -1
  119. package/src/components/editor/useColorGradingPreviews.test.ts +76 -0
  120. package/src/components/editor/useColorGradingPreviews.ts +11 -1
  121. package/src/components/editor/usePreviewCompositionRect.test.tsx +95 -0
  122. package/src/components/editor/usePreviewCompositionRect.ts +21 -0
  123. package/src/components/feedback/StudioFeedbackCard.tsx +7 -7
  124. package/src/components/feedback/feedbackTrigger.ts +0 -1
  125. package/src/components/nle/AssetPreviewOverlay.tsx +6 -2
  126. package/src/components/nle/CompositionBreadcrumb.tsx +3 -3
  127. package/src/components/nle/NLEContext.tsx +0 -43
  128. package/src/components/nle/NLEPreview.tsx +3 -3
  129. package/src/components/nle/PreviewOverlays.tsx +6 -6
  130. package/src/components/nle/PreviewPane.tsx +3 -3
  131. package/src/components/nle/TimelinePane.tsx +53 -67
  132. package/src/components/panels/SlideshowPanel.tsx +3 -3
  133. package/src/components/panels/SlideshowSubPanels.tsx +20 -17
  134. package/src/components/panels/VariablesBindElement.tsx +2 -2
  135. package/src/components/panels/VariablesDeclarationForm.tsx +2 -2
  136. package/src/components/panels/VariablesOtherCompositions.tsx +1 -1
  137. package/src/components/panels/VariablesPanel.tsx +4 -4
  138. package/src/components/panels/VariablesValueControls.tsx +2 -2
  139. package/src/components/pointerTargetSize.test.tsx +8 -0
  140. package/src/components/renders/FfmpegRequiredNotice.tsx +5 -5
  141. package/src/components/renders/RenderQueue.test.tsx +7 -0
  142. package/src/components/renders/RenderQueue.tsx +7 -7
  143. package/src/components/renders/RenderQueueItem.tsx +6 -6
  144. package/src/components/renders/RenderQueuePanel.tsx +1 -1
  145. package/src/components/sidebar/AssetCard.tsx +8 -8
  146. package/src/components/sidebar/AssetContextMenu.tsx +8 -8
  147. package/src/components/sidebar/AssetsTab.tsx +2 -2
  148. package/src/components/sidebar/AudioRow.tsx +6 -6
  149. package/src/components/sidebar/BlocksTab.tsx +6 -6
  150. package/src/components/sidebar/{LeftSidebar.masterComposition.test.tsx → CompositionsPanel.masterComposition.test.tsx} +6 -9
  151. package/src/components/sidebar/CompositionsPanel.tsx +15 -0
  152. package/src/components/sidebar/CompositionsTab.tsx +5 -5
  153. package/src/components/sidebar/GlobalAssetsView.tsx +1 -1
  154. package/src/components/sidebar/PromptPreviewModal.tsx +3 -3
  155. package/src/components/sidebar/SidebarLintButton.test.tsx +53 -0
  156. package/src/components/sidebar/SidebarLintButton.tsx +50 -0
  157. package/src/components/timelineToolbarStyles.ts +5 -0
  158. package/src/components/ui/Button.test.tsx +252 -0
  159. package/src/components/ui/Button.tsx +89 -126
  160. package/src/components/ui/IconButton.tsx +38 -0
  161. package/src/components/ui/Input.tsx +115 -0
  162. package/src/components/ui/Menu.test.tsx +374 -0
  163. package/src/components/ui/Menu.tsx +213 -0
  164. package/src/components/ui/NumberField.tsx +122 -0
  165. package/src/components/ui/Popover.tsx +74 -0
  166. package/src/components/ui/SearchInput.tsx +2 -2
  167. package/src/components/ui/Select.tsx +100 -0
  168. package/src/components/ui/Slider.tsx +153 -0
  169. package/src/components/ui/Tabs.test.tsx +98 -0
  170. package/src/components/ui/Tabs.tsx +71 -0
  171. package/src/components/ui/Toggle.tsx +63 -0
  172. package/src/components/ui/Tooltip.test.tsx +110 -0
  173. package/src/components/ui/Tooltip.tsx +38 -112
  174. package/src/components/ui/cn.test.ts +30 -0
  175. package/src/components/ui/cn.ts +25 -0
  176. package/src/components/ui/index.ts +29 -2
  177. package/src/components/ui/mountHost.testHelpers.ts +24 -0
  178. package/src/components/ui/valueControls.test.tsx +524 -0
  179. package/src/contexts/PanelLayoutContext.tsx +3 -57
  180. package/src/hooks/appHotkeysDispatch.ts +3 -4
  181. package/src/hooks/timelineEditPermission.ts +31 -0
  182. package/src/hooks/useAppHotkeys.previewForwarding.test.tsx +0 -2
  183. package/src/hooks/useAppHotkeys.test.ts +0 -1
  184. package/src/hooks/useAppHotkeys.textEditing.test.tsx +0 -3
  185. package/src/hooks/useAppHotkeys.ts +21 -84
  186. package/src/hooks/useCaptionDetection.sender.test.tsx +46 -0
  187. package/src/hooks/useCaptionDetection.ts +3 -1
  188. package/src/hooks/useDomEditPreviewSync.ts +4 -6
  189. package/src/hooks/useDomEditSession.ts +0 -7
  190. package/src/hooks/useDomEditWiring.ts +4 -9
  191. package/src/hooks/useEditHistoryActions.test.tsx +90 -0
  192. package/src/hooks/useEditHistoryActions.ts +106 -0
  193. package/src/hooks/useFileManager.ts +21 -123
  194. package/src/hooks/useLintModal.test.tsx +65 -0
  195. package/src/hooks/useLintModal.ts +3 -0
  196. package/src/hooks/usePanelLayout.test.ts +108 -296
  197. package/src/hooks/usePanelLayout.ts +50 -255
  198. package/src/hooks/usePersistentEditHistory.ts +1 -1
  199. package/src/hooks/useProjectFileWriter.ts +150 -0
  200. package/src/hooks/useRightPanelIntents.test.ts +181 -0
  201. package/src/hooks/useRightPanelIntents.ts +63 -0
  202. package/src/hooks/useSdkSession.lifecycle.test.tsx +49 -4
  203. package/src/hooks/useSdkSession.ts +43 -10
  204. package/src/hooks/useServerConnection.hashProject.test.tsx +94 -0
  205. package/src/hooks/useServerConnection.ts +43 -10
  206. package/src/hooks/useSlideshowTabState.test.ts +9 -31
  207. package/src/hooks/useSlideshowTabState.ts +6 -19
  208. package/src/hooks/useStudioContextValue.test.ts +6 -18
  209. package/src/hooks/useStudioContextValue.ts +5 -26
  210. package/src/hooks/useTimelineAssetDropOps.test.tsx +41 -1
  211. package/src/hooks/useTimelineAssetDropOps.ts +52 -11
  212. package/src/hooks/useTimelineDeleteOps.test.tsx +111 -0
  213. package/src/hooks/useTimelineEditing.test.tsx +322 -3
  214. package/src/hooks/useTimelineEditing.ts +103 -16
  215. package/src/hooks/useTimelineEditingTypes.ts +3 -0
  216. package/src/hooks/useTrackPendingTimelineEdit.test.tsx +62 -0
  217. package/src/hooks/useTrackPendingTimelineEdit.ts +22 -0
  218. package/src/hostMount.test.tsx +222 -0
  219. package/src/index.ts +87 -0
  220. package/src/main.tsx +9 -3
  221. package/src/player/components/AudioWaveform.tsx +1 -1
  222. package/src/player/components/AutomationSelectionMenu.tsx +3 -3
  223. package/src/player/components/ClipContextMenu.tsx +3 -3
  224. package/src/player/components/CompositionThumbnail.test.ts +0 -3
  225. package/src/player/components/CompositionThumbnail.tsx +3 -4
  226. package/src/player/components/EditModal.tsx +1 -1
  227. package/src/player/components/ImageThumbnail.tsx +1 -1
  228. package/src/player/components/KeyframeDiamondContextMenu.tsx +4 -4
  229. package/src/player/components/LayerDisclosureRow.tsx +2 -2
  230. package/src/player/components/PlayerControls.tsx +1 -1
  231. package/src/player/components/ShortcutsPanel.tsx +9 -9
  232. package/src/player/components/SpeedMenu.tsx +3 -5
  233. package/src/player/components/Timeline.tsx +3 -2
  234. package/src/player/components/TimelineCanvas.tsx +21 -14
  235. package/src/player/components/TimelineClip.tsx +0 -1
  236. package/src/player/components/TimelineDiamondConnectors.tsx +1 -1
  237. package/src/player/components/TimelineEmptyState.tsx +5 -5
  238. package/src/player/components/TimelineFxButton.tsx +6 -6
  239. package/src/player/components/TimelineGroupHeader.tsx +5 -5
  240. package/src/player/components/TimelineGroupLaneLabels.tsx +1 -1
  241. package/src/player/components/TimelineGroupRow.tsx +1 -1
  242. package/src/player/components/TimelineLanes.test.tsx +32 -0
  243. package/src/player/components/TimelineLanes.tsx +2 -5
  244. package/src/player/components/TimelineLayerGroupHeader.tsx +2 -2
  245. package/src/player/components/TimelineRuler.tsx +1 -1
  246. package/src/player/components/TimelineShortcutHint.tsx +1 -1
  247. package/src/player/components/TimelineTrackHeader.test.tsx +26 -5
  248. package/src/player/components/TimelineTrackHeader.tsx +2 -2
  249. package/src/player/components/TimelineTrackPlainHeader.tsx +3 -3
  250. package/src/player/components/TimelineTypes.ts +2 -1
  251. package/src/player/components/TrackGapContextMenu.tsx +1 -1
  252. package/src/player/components/VideoThumbnail.tsx +4 -2
  253. package/src/player/components/VolumeControl.tsx +2 -2
  254. package/src/player/components/timelineCallbacks.ts +8 -8
  255. package/src/player/components/timelineClipDragPreview.ts +51 -27
  256. package/src/player/components/timelineClipDragTypes.ts +3 -0
  257. package/src/player/components/timelineDragDrop.test.tsx +61 -1
  258. package/src/player/components/timelineDragDrop.ts +57 -15
  259. package/src/player/components/timelineLaneProps.ts +3 -0
  260. package/src/player/components/timelineSnapping.test.ts +27 -0
  261. package/src/player/components/timelineSnapping.ts +14 -1
  262. package/src/player/components/timelineTheme.test.ts +4 -4
  263. package/src/player/components/timelineTheme.ts +30 -30
  264. package/src/player/components/trackHeaderLabelRows.tsx +4 -4
  265. package/src/player/components/useTimelineClipDrag.resize.test.tsx +136 -2
  266. package/src/player/components/useTimelineClipDrag.ts +30 -3
  267. package/src/player/lib/describeClips.ts +3 -54
  268. package/src/player/lib/playbackShortcuts.ts +2 -0
  269. package/src/player/lib/timelinePerformanceDiagnostics.test.ts +3 -2
  270. package/src/player/lib/timelinePerformanceDiagnostics.ts +1 -1
  271. package/src/player/store/previewIframeStore.notify.test.tsx +75 -0
  272. package/src/player/store/previewIframeStore.test.tsx +29 -0
  273. package/src/player/store/previewIframeStore.ts +25 -0
  274. package/src/renderQueueExports.test.tsx +58 -0
  275. package/src/styles/classCandidates.test.ts +105 -0
  276. package/src/styles/classCandidates.ts +281 -0
  277. package/src/styles/hex-baseline.json +84 -0
  278. package/src/styles/hexRatchet.test.ts +201 -0
  279. package/src/styles/iconTokens.ts +20 -0
  280. package/src/styles/palette-reset.css +6 -0
  281. package/src/styles/studio.css +24 -21
  282. package/src/styles/styleSources.test.ts +20 -0
  283. package/src/styles/styleSources.ts +47 -0
  284. package/src/styles/tailwind-preset.shared.js +3 -0
  285. package/src/styles/theme.css +270 -0
  286. package/src/styles/theme.test.ts +183 -0
  287. package/src/styles/tokenGate.test.ts +109 -0
  288. package/src/utils/sourcePatcher.ts +1 -1
  289. package/src/utils/studioHelpers.test.ts +18 -0
  290. package/src/utils/studioHelpers.ts +11 -8
  291. package/src/utils/studioUiPreferences.test.ts +10 -16
  292. package/src/utils/studioUiPreferences.ts +7 -16
  293. package/src/utils/timelineDropTrackInsert.test.ts +134 -0
  294. package/src/utils/timelineDropTrackInsert.ts +83 -0
  295. package/src/utils/typingTarget.ts +4 -0
  296. package/dist/assets/index-n2ml4tUn.js +0 -442
  297. package/dist/assets/index-pO6p0H27.css +0 -1
  298. package/src/components/PanelTabButton.tsx +0 -31
  299. package/src/components/StudioLeftSidebar.tsx +0 -204
  300. package/src/components/nle/TimelineResizeDivider.tsx +0 -101
  301. package/src/components/sidebar/LeftSidebar.storage.test.ts +0 -28
  302. package/src/components/sidebar/LeftSidebar.tsx +0 -334
  303. package/src/components/storyboard/AgentChatMessageButton.test.tsx +0 -47
  304. package/src/components/storyboard/AgentChatMessageButton.tsx +0 -45
  305. package/src/components/storyboard/FramePoster.test.tsx +0 -61
  306. package/src/components/storyboard/FramePoster.tsx +0 -86
  307. package/src/components/storyboard/StoryboardDirection.tsx +0 -45
  308. package/src/components/storyboard/StoryboardFrameFocus.tsx +0 -412
  309. package/src/components/storyboard/StoryboardFrameTile.tsx +0 -117
  310. package/src/components/storyboard/StoryboardGrid.tsx +0 -55
  311. package/src/components/storyboard/StoryboardLoaded.tsx +0 -307
  312. package/src/components/storyboard/StoryboardReviewGuide.tsx +0 -300
  313. package/src/components/storyboard/StoryboardScriptPanel.tsx +0 -26
  314. package/src/components/storyboard/StoryboardSourceEditor.tsx +0 -231
  315. package/src/components/storyboard/StoryboardStatusLegend.tsx +0 -21
  316. package/src/components/storyboard/StoryboardView.tsx +0 -169
  317. package/src/components/storyboard/StoryboardViewModeGuard.test.tsx +0 -187
  318. package/src/components/storyboard/frameComments.test.ts +0 -95
  319. package/src/components/storyboard/frameComments.ts +0 -115
  320. package/src/components/storyboard/frameStatus.ts +0 -36
  321. package/src/components/storyboard/storyboardReviewStage.test.ts +0 -40
  322. package/src/components/storyboard/storyboardReviewStage.ts +0 -45
  323. package/src/components/storyboard/useFrameComments.ts +0 -98
  324. package/src/contexts/ViewModeContext.tsx +0 -142
  325. package/src/hooks/useInspectorSplitResize.ts +0 -51
  326. package/src/hooks/useProjectSignaturePoll.test.tsx +0 -135
  327. package/src/hooks/useProjectSignaturePoll.ts +0 -68
  328. package/src/hooks/useStoryboard.ts +0 -99
  329. package/src/utils/fitPanels.test.ts +0 -164
  330. package/src/utils/fitPanels.ts +0 -151
@@ -0,0 +1,274 @@
1
+ // @vitest-environment happy-dom
2
+
3
+ import React, { act, type ComponentProps } from "react";
4
+ import type { DockviewApi } from "dockview-react";
5
+ import { createRoot, type Root } from "react-dom/client";
6
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
7
+ import * as dockLayout from "./dockLayout";
8
+ import { Dock } from "./Dock";
9
+ import { parseDockLayout } from "./dockLayoutSchema";
10
+ import { useDockLayoutStore } from "./dockLayoutStore";
11
+ import { PANEL_IDS } from "./panelRegistry";
12
+ import { readStudioUiPreferences } from "../../utils/studioUiPreferences";
13
+
14
+ (globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
15
+
16
+ let dockApi: DockviewApi | null = null;
17
+ vi.mock("dockview-react", async (importOriginal) => {
18
+ const actual = await importOriginal<typeof import("dockview-react")>();
19
+ return {
20
+ ...actual,
21
+ DockviewReact: (props: ComponentProps<typeof actual.DockviewReact>) =>
22
+ React.createElement(actual.DockviewReact, {
23
+ ...props,
24
+ onReady: (event) => {
25
+ dockApi = event.api;
26
+ props.onReady(event);
27
+ },
28
+ }),
29
+ };
30
+ });
31
+
32
+ const liveObservers = new Set<{ callback: () => void; target?: Element }>();
33
+ class ResizeObserverStub {
34
+ private readonly entry: { callback: () => void; target?: Element };
35
+ constructor(callback: () => void) {
36
+ this.entry = { callback };
37
+ }
38
+ observe(target: Element) {
39
+ this.entry.target = target;
40
+ liveObservers.add(this.entry);
41
+ }
42
+ unobserve() {}
43
+ disconnect() {
44
+ liveObservers.delete(this.entry);
45
+ }
46
+ }
47
+ (globalThis as unknown as { ResizeObserver: unknown }).ResizeObserver = ResizeObserverStub;
48
+
49
+ vi.mock("./dockLayout", async (importOriginal) => {
50
+ const actual = await importOriginal<typeof import("./dockLayout")>();
51
+ return { ...actual, applySideMinimums: vi.fn(actual.applySideMinimums) };
52
+ });
53
+ const applySideMinimums = vi.mocked(dockLayout.applySideMinimums);
54
+
55
+ let root: Root | null = null;
56
+
57
+ function mount(
58
+ projectId: string | null,
59
+ titles: Partial<Record<(typeof PANEL_IDS)[number], string>> = {},
60
+ ) {
61
+ const host = document.createElement("div");
62
+ document.body.append(host);
63
+ root = createRoot(host);
64
+ act(() => {
65
+ root?.render(
66
+ <Dock.Root projectId={projectId}>
67
+ {PANEL_IDS.map((id) => (
68
+ <Dock.Panel key={id} id={id} title={titles[id]}>
69
+ <div data-testid={`content-${id}`}>{id}</div>
70
+ </Dock.Panel>
71
+ ))}
72
+ </Dock.Root>,
73
+ );
74
+ });
75
+ return host;
76
+ }
77
+
78
+ /** The persisted views of the group holding `id`, after the debounced write lands. */
79
+ function persistedGroupOf(id: string): string | undefined {
80
+ act(() => {
81
+ vi.advanceTimersByTime(1000);
82
+ });
83
+ const grid = JSON.stringify(readStudioUiPreferences(undefined, "p1").dockLayout?.grid);
84
+ const groups = [...grid.matchAll(/"views":\[([^\]]*)\]/g)].map((m) => m[1]);
85
+ return groups.find((views) => views.includes(`"${id}"`));
86
+ }
87
+
88
+ beforeEach(() => {
89
+ localStorage.clear();
90
+ vi.useFakeTimers();
91
+ });
92
+
93
+ afterEach(() => {
94
+ act(() => root?.unmount());
95
+ root = null;
96
+ document.body.innerHTML = "";
97
+ vi.useRealTimers();
98
+ });
99
+
100
+ // The default Edit layout tabs [compositions|assets|code|catalog] into one
101
+ // group and [design|layers|renders|variables] into another; dockview shows
102
+ // only the active tab's content per group. `slideshow` is never part of the
103
+ // default build — StudioRightPanels opens it itself when the file is one.
104
+ const DEFAULT_OPEN = PANEL_IDS.filter((id) => id !== "slideshow");
105
+ const DEFAULT_VISIBLE = ["preview", "timeline", "compositions", "design"];
106
+
107
+ describe("Dock on React 19", () => {
108
+ it("mounts the default layout's ten panels, showing only each group's active tab", () => {
109
+ const host = mount("p1");
110
+ for (const id of DEFAULT_VISIBLE) {
111
+ expect(host.querySelector(`[data-testid="content-${id}"]`)).not.toBeNull();
112
+ }
113
+ for (const id of DEFAULT_OPEN.filter((id) => !DEFAULT_VISIBLE.includes(id))) {
114
+ expect(host.querySelector(`[data-testid="content-${id}"]`)).toBeNull();
115
+ }
116
+ expect(host.querySelector('[data-testid="content-slideshow"]')).toBeNull();
117
+ expect(useDockLayoutStore.getState().openPanels).toEqual(new Set(DEFAULT_OPEN));
118
+ });
119
+
120
+ it("persists a layout change per project and reads it back through the schema", () => {
121
+ mount("p1");
122
+ act(() => useDockLayoutStore.getState().closePanel("renders"));
123
+ act(() => {
124
+ vi.advanceTimersByTime(400);
125
+ });
126
+ const stored = readStudioUiPreferences(undefined, "p1").dockLayout;
127
+ const expected = DEFAULT_OPEN.filter((id) => id !== "renders");
128
+ expect(Object.keys(stored?.panels ?? {}).sort()).toEqual([...expected].sort());
129
+ expect(readStudioUiPreferences(undefined, "p2").dockLayout).toBeUndefined();
130
+ });
131
+
132
+ it("closes a panel and reopens it from the store, becoming its group's visible tab", async () => {
133
+ const host = mount("p1");
134
+ act(() => useDockLayoutStore.getState().closePanel("renders"));
135
+ expect(useDockLayoutStore.getState().openPanels.has("renders")).toBe(false);
136
+ expect(host.querySelector('[data-testid="content-renders"]')).toBeNull();
137
+ // dockview's own panel-active event dispatch resolves on a microtask, one
138
+ // tick after the synchronous store call returns; `act(async ...)` is what
139
+ // actually waits for it instead of asserting against pre-flush DOM.
140
+ await act(async () => {
141
+ useDockLayoutStore.getState().togglePanel("renders");
142
+ await Promise.resolve();
143
+ });
144
+ expect(useDockLayoutStore.getState().openPanels.has("renders")).toBe(true);
145
+ expect(host.querySelector('[data-testid="content-renders"]')).not.toBeNull();
146
+ });
147
+
148
+ it("keeps a panel's custom title when it is closed and reopened", async () => {
149
+ const host = mount("p1", { renders: "Renders (2)" });
150
+ expect(host.textContent).toContain("Renders (2)");
151
+ act(() => useDockLayoutStore.getState().closePanel("renders"));
152
+ await act(async () => {
153
+ useDockLayoutStore.getState().togglePanel("renders");
154
+ await Promise.resolve();
155
+ });
156
+ expect(host.textContent).toContain("Renders (2)");
157
+ });
158
+
159
+ it("reopens a closed panel as a tab of its zone's group, not a new group", () => {
160
+ mount("p1");
161
+ act(() => useDockLayoutStore.getState().closePanel("compositions"));
162
+ act(() => useDockLayoutStore.getState().togglePanel("compositions"));
163
+ expect(persistedGroupOf("compositions")).toContain('"assets"');
164
+ });
165
+
166
+ it("reopens a side panel next to the preview when its whole column was closed", () => {
167
+ mount("p1");
168
+ const { closePanel, togglePanel } = useDockLayoutStore.getState();
169
+ for (const id of ["compositions", "assets", "code", "catalog"] as const)
170
+ act(() => closePanel(id));
171
+ act(() => togglePanel("compositions"));
172
+ expect(useDockLayoutStore.getState().openPanels.has("compositions")).toBe(true);
173
+ expect(useDockLayoutStore.getState().visiblePanels.has("compositions")).toBe(true);
174
+ });
175
+
176
+ it("reopens the timeline as its own group, never as a tab of the preview", () => {
177
+ mount("p1");
178
+ act(() => useDockLayoutStore.getState().closePanel("timeline"));
179
+ act(() => useDockLayoutStore.getState().togglePanel("timeline"));
180
+ expect(persistedGroupOf("timeline")).not.toContain('"preview"');
181
+ });
182
+
183
+ it("restores the stored layout on the next mount instead of rebuilding the default", () => {
184
+ mount("p1");
185
+ act(() => useDockLayoutStore.getState().closePanel("renders"));
186
+ act(() => {
187
+ vi.advanceTimersByTime(1000);
188
+ });
189
+ act(() => root?.unmount());
190
+ root = null;
191
+ document.body.innerHTML = "";
192
+
193
+ mount("p1");
194
+ expect(useDockLayoutStore.getState().openPanels.has("renders")).toBe(false);
195
+ });
196
+
197
+ it("falls back to the default layout when the stored one names an unknown panel", () => {
198
+ localStorage.setItem(
199
+ "hf-studio-ui-preferences:p1",
200
+ JSON.stringify({ dockLayout: { grid: {}, panels: { nope: {} } } }),
201
+ );
202
+ const host = mount("p1");
203
+ expect(host.querySelector('[data-testid="content-preview"]')).not.toBeNull();
204
+ });
205
+ });
206
+
207
+ describe("Dock wiring", () => {
208
+ it("re-applies the side minimums when a panel is dragged to another group", () => {
209
+ mount(null);
210
+ applySideMinimums.mockClear();
211
+ const preview = dockApi?.getPanel("preview");
212
+ const design = dockApi?.getPanel("design");
213
+ if (!preview || !design) throw new Error("default layout is missing panels");
214
+ act(() => design.api.moveTo({ group: preview.group }));
215
+ expect(applySideMinimums).toHaveBeenCalled();
216
+ });
217
+
218
+ it("makes the sashes keyboard-focusable separators", () => {
219
+ const host = mount(null);
220
+ const sashes = host.querySelectorAll<HTMLElement>('.dv-sash[role="separator"]');
221
+ expect(sashes.length).toBeGreaterThan(0);
222
+ for (const sash of sashes) expect(sash.tabIndex).toBe(0);
223
+ });
224
+
225
+ it("re-fits the side columns to the window width when it resizes", () => {
226
+ mount(null);
227
+ const innerWidth = window.innerWidth;
228
+ Object.defineProperty(window, "innerWidth", { value: 560, configurable: true });
229
+ try {
230
+ applySideMinimums.mockClear();
231
+ const dockObservers = [...liveObservers].filter(({ target }) =>
232
+ target?.classList.contains("hf-dock"),
233
+ );
234
+ act(() => dockObservers.forEach(({ callback }) => callback()));
235
+ expect(applySideMinimums).toHaveBeenCalledWith(expect.anything(), 560);
236
+ } finally {
237
+ Object.defineProperty(window, "innerWidth", { value: innerWidth, configurable: true });
238
+ }
239
+ });
240
+ });
241
+
242
+ describe("parseDockLayout", () => {
243
+ it("rejects shapes that are not a dock layout", () => {
244
+ expect(parseDockLayout(null)).toBeNull();
245
+ expect(parseDockLayout({ grid: {}, panels: {} })).toBeNull();
246
+ expect(
247
+ parseDockLayout({
248
+ grid: {
249
+ width: 1,
250
+ height: 1,
251
+ orientation: "HORIZONTAL",
252
+ root: { type: "leaf", data: { views: ["ghost"] } },
253
+ },
254
+ panels: { ghost: { id: "ghost", contentComponent: "panel" } },
255
+ }),
256
+ ).toBeNull();
257
+ });
258
+
259
+ const placed = (views: string[], panelIds: string[]) => ({
260
+ grid: {
261
+ width: 1,
262
+ height: 1,
263
+ orientation: "HORIZONTAL",
264
+ root: { type: "leaf", data: { views } },
265
+ },
266
+ panels: Object.fromEntries(panelIds.map((id) => [id, { id, contentComponent: "panel" }])),
267
+ });
268
+
269
+ it("rejects a view that has no panel entry and a panel that no view places", () => {
270
+ expect(parseDockLayout(placed(["preview", "design"], ["preview"]))).toBeNull();
271
+ expect(parseDockLayout(placed(["preview"], ["preview", "design"]))).toBeNull();
272
+ expect(parseDockLayout(placed(["preview", "design"], ["preview", "design"]))).not.toBeNull();
273
+ });
274
+ });
@@ -0,0 +1,291 @@
1
+ import {
2
+ createContext,
3
+ useCallback,
4
+ useContext,
5
+ useEffect,
6
+ useRef,
7
+ useState,
8
+ type ReactNode,
9
+ } from "react";
10
+ import { createPortal } from "react-dom";
11
+ import {
12
+ DockviewReact,
13
+ type DockviewApi,
14
+ type DockviewReadyEvent,
15
+ type IDockviewPanelProps,
16
+ } from "dockview-react";
17
+ import { readStudioUiPreferences, writeStudioUiPreferences } from "../../utils/studioUiPreferences";
18
+ import { installDockAccessibility } from "./dockAccessibility";
19
+ import { addRegisteredPanel, applySideMinimums, buildEditLayout } from "./dockLayout";
20
+ import { DOCK_PANEL_COMPONENT } from "./dockLayoutSchema";
21
+ import { useDockLayoutStore, type DockController, type DockSnapshot } from "./dockLayoutStore";
22
+ import {
23
+ PANEL_DEFINITIONS,
24
+ PANEL_IDS,
25
+ isPanelId,
26
+ panelsInZone,
27
+ type PanelDefinition,
28
+ type PanelId,
29
+ } from "./panelRegistry";
30
+ import "./dock.css";
31
+
32
+ const PERSIST_DEBOUNCE_MS = 250;
33
+
34
+ type Slots = Partial<Record<PanelId, HTMLElement>>;
35
+
36
+ interface SlotsContextValue {
37
+ slots: Slots;
38
+ registerSlot: (id: PanelId, element: HTMLElement | null) => void;
39
+ }
40
+
41
+ const SlotsContext = createContext<SlotsContextValue | null>(null);
42
+
43
+ function useSlots(): SlotsContextValue {
44
+ const value = useContext(SlotsContext);
45
+ if (!value) throw new Error("Dock.Panel must be rendered inside Dock.Root");
46
+ return value;
47
+ }
48
+
49
+ /** The element dockview mounts for a panel; Dock.Panel portals the real content into it. */
50
+ function PanelSlot({ api }: IDockviewPanelProps) {
51
+ const { registerSlot } = useSlots();
52
+ const id = api.id;
53
+ const ref = useCallback(
54
+ (element: HTMLDivElement | null) => {
55
+ if (isPanelId(id)) registerSlot(id, element);
56
+ },
57
+ [id, registerSlot],
58
+ );
59
+ return <div ref={ref} className="h-full w-full min-h-0 min-w-0 overflow-hidden" />;
60
+ }
61
+
62
+ const COMPONENTS = { [DOCK_PANEL_COMPONENT]: PanelSlot };
63
+
64
+ function snapshot(api: DockviewApi): DockSnapshot {
65
+ const openPanels = new Set<PanelId>();
66
+ const visiblePanels = new Set<PanelId>();
67
+ for (const panel of api.panels) {
68
+ if (!isPanelId(panel.id)) continue;
69
+ openPanels.add(panel.id);
70
+ if (panel.api.isVisible && panel.group.api.isVisible) visiblePanels.add(panel.id);
71
+ }
72
+ const groupActivePanels = api.groups.flatMap((group) => {
73
+ const id = group.activePanel?.id;
74
+ return isPanelId(id) ? [id] : [];
75
+ });
76
+ const active = api.activePanel?.id;
77
+ return {
78
+ openPanels,
79
+ visiblePanels,
80
+ activePanel: isPanelId(active) ? active : null,
81
+ groupActivePanels,
82
+ };
83
+ }
84
+
85
+ function createController(api: DockviewApi): DockController {
86
+ const open = (id: PanelId) => {
87
+ if (api.getPanel(id)) return;
88
+ const { zone, reopen } = PANEL_DEFINITIONS[id];
89
+ // Side columns are tab groups; preview and timeline are separate groups in the centre.
90
+ const sibling =
91
+ zone === "center"
92
+ ? undefined
93
+ : panelsInZone(zone).find((other) => other !== id && api.getPanel(other));
94
+ if (sibling) {
95
+ addRegisteredPanel(api, id, { referencePanel: sibling, direction: "within" });
96
+ return;
97
+ }
98
+ const hasAnchor = api.getPanel(reopen.near) !== undefined;
99
+ const position = { referencePanel: reopen.near, direction: reopen.direction };
100
+ addRegisteredPanel(api, id, hasAnchor ? position : undefined);
101
+ };
102
+ return {
103
+ open,
104
+ activate: (id) => {
105
+ open(id);
106
+ api.getPanel(id)?.api.setActive();
107
+ },
108
+ close: (id) => {
109
+ const panel = api.getPanel(id);
110
+ if (panel) api.removePanel(panel);
111
+ },
112
+ setTitle: (id, title) => api.getPanel(id)?.api.setTitle(title),
113
+ setGroupVisible: (id, visible) => api.getPanel(id)?.group.api.setVisible(visible),
114
+ reset: () => buildEditLayout(api, window.innerWidth),
115
+ };
116
+ }
117
+
118
+ function restoreOrBuild(api: DockviewApi, projectId: string | null) {
119
+ const stored = readStudioUiPreferences(undefined, projectId).dockLayout;
120
+ if (stored) {
121
+ try {
122
+ api.fromJSON(stored);
123
+ return;
124
+ } catch {
125
+ /* a layout the schema accepted but dockview cannot load: start over */
126
+ }
127
+ }
128
+ buildEditLayout(api, window.innerWidth);
129
+ }
130
+
131
+ function Root({ projectId, children }: { projectId: string | null; children: ReactNode }) {
132
+ const [slots, setSlots] = useState<Slots>({});
133
+ const registerSlot = useCallback((id: PanelId, element: HTMLElement | null) => {
134
+ setSlots((prev) => {
135
+ if (prev[id] === (element ?? undefined)) return prev;
136
+ const next = { ...prev };
137
+ if (element) next[id] = element;
138
+ else delete next[id];
139
+ return next;
140
+ });
141
+ }, []);
142
+
143
+ const disposeRef = useRef<() => void>(() => {});
144
+ useEffect(() => () => disposeRef.current(), []);
145
+
146
+ const onReady = useCallback(
147
+ ({ api }: DockviewReadyEvent) => {
148
+ disposeRef.current();
149
+ restoreOrBuild(api, projectId);
150
+ applySideMinimums(api);
151
+ const root = api.groups[0]?.element.closest<HTMLElement>(".hf-dock");
152
+ const disposeAccessibility = root ? installDockAccessibility(api, root) : () => {};
153
+ // The dock spans the window (buildEditLayout sizes against it too); its own box lags a resize.
154
+ const resizeObserver = new ResizeObserver(() => applySideMinimums(api, window.innerWidth));
155
+ if (root) resizeObserver.observe(root);
156
+ const store = useDockLayoutStore.getState();
157
+ store.attach(createController(api));
158
+ store.sync(snapshot(api));
159
+ const pending = store.takePendingActivation();
160
+ if (pending) api.getPanel(pending)?.api.setActive();
161
+
162
+ let timer: ReturnType<typeof setTimeout> | undefined;
163
+ const persist = () => {
164
+ clearTimeout(timer);
165
+ timer = setTimeout(() => {
166
+ writeStudioUiPreferences({ dockLayout: api.toJSON() }, undefined, projectId);
167
+ }, PERSIST_DEBOUNCE_MS);
168
+ };
169
+ // dockview does not fire onDidLayoutChange for add/remove/activate,
170
+ // so every event class is wired to the same sync+persist pair.
171
+ const onDockChange = () => {
172
+ useDockLayoutStore.getState().sync(snapshot(api));
173
+ persist();
174
+ };
175
+ const subscriptions = [
176
+ api.onDidAddPanel(() => {
177
+ applySideMinimums(api);
178
+ onDockChange();
179
+ }),
180
+ api.onDidMovePanel(() => {
181
+ applySideMinimums(api);
182
+ onDockChange();
183
+ }),
184
+ api.onDidRemovePanel(onDockChange),
185
+ api.onDidActivePanelChange(onDockChange),
186
+ api.onDidLayoutChange(onDockChange),
187
+ ];
188
+ disposeRef.current = () => {
189
+ clearTimeout(timer);
190
+ for (const subscription of subscriptions) subscription.dispose();
191
+ disposeAccessibility();
192
+ resizeObserver.disconnect();
193
+ useDockLayoutStore.getState().detach();
194
+ };
195
+ },
196
+ [projectId],
197
+ );
198
+
199
+ return (
200
+ <SlotsContext.Provider value={{ slots, registerSlot }}>
201
+ <DockviewReact
202
+ key={projectId ?? ""}
203
+ className="hf-dock flex-1 min-h-0"
204
+ components={COMPONENTS}
205
+ defaultRenderer="always"
206
+ disableFloatingGroups
207
+ onReady={onReady}
208
+ />
209
+ {children}
210
+ </SlotsContext.Provider>
211
+ );
212
+ }
213
+
214
+ function Panel({ id, title, children }: { id: PanelId; title?: string; children: ReactNode }) {
215
+ const element = useSlots().slots[id];
216
+ const visible = useDockLayoutStore((state) => state.visiblePanels.has(id));
217
+ const controller = useDockLayoutStore((state) => state.controller);
218
+ const open = useDockLayoutStore((state) => state.openPanels.has(id));
219
+ const label = title ?? PANEL_DEFINITIONS[id].title;
220
+ useEffect(() => {
221
+ if (open) controller?.setTitle(id, label);
222
+ }, [controller, id, label, open]);
223
+ const definition: PanelDefinition = PANEL_DEFINITIONS[id];
224
+ const shown = visible || definition.keepMounted;
225
+ return element && shown ? createPortal(children, element) : null;
226
+ }
227
+
228
+ function WindowMenu() {
229
+ const openPanels = useDockLayoutStore((state) => state.openPanels);
230
+ const togglePanel = useDockLayoutStore((state) => state.togglePanel);
231
+ const resetLayout = useDockLayoutStore((state) => state.resetLayout);
232
+ const [open, setOpen] = useState(false);
233
+ const rootRef = useRef<HTMLDivElement>(null);
234
+
235
+ useEffect(() => {
236
+ if (!open) return;
237
+ const close = (event: PointerEvent) => {
238
+ if (!rootRef.current?.contains(event.target as Node)) setOpen(false);
239
+ };
240
+ document.addEventListener("pointerdown", close);
241
+ return () => document.removeEventListener("pointerdown", close);
242
+ }, [open]);
243
+
244
+ return (
245
+ <div ref={rootRef} className="relative">
246
+ <button
247
+ type="button"
248
+ aria-haspopup="menu"
249
+ aria-expanded={open}
250
+ onClick={() => setOpen((value) => !value)}
251
+ className="h-7 px-2.5 rounded-md text-[11px] font-medium text-neutral-400 hover:text-neutral-200 hover:bg-neutral-800 transition-colors"
252
+ >
253
+ Window
254
+ </button>
255
+ {open && (
256
+ <div
257
+ role="menu"
258
+ className="absolute right-0 top-8 z-50 w-44 rounded-md border border-neutral-800 bg-neutral-900 py-1 shadow-lg"
259
+ >
260
+ {PANEL_IDS.map((id) => (
261
+ <button
262
+ key={id}
263
+ type="button"
264
+ role="menuitemcheckbox"
265
+ aria-checked={openPanels.has(id)}
266
+ onClick={() => togglePanel(id)}
267
+ className="flex w-full items-center gap-2 px-3 py-1.5 text-left text-[11px] text-neutral-300 hover:bg-neutral-800"
268
+ >
269
+ <span className="w-3 text-panel-accent">{openPanels.has(id) ? "✓" : ""}</span>
270
+ {PANEL_DEFINITIONS[id].title}
271
+ </button>
272
+ ))}
273
+ <div className="my-1 border-t border-neutral-800" />
274
+ <button
275
+ type="button"
276
+ role="menuitem"
277
+ onClick={() => {
278
+ resetLayout();
279
+ setOpen(false);
280
+ }}
281
+ className="w-full px-3 py-1.5 text-left text-[11px] text-neutral-300 hover:bg-neutral-800"
282
+ >
283
+ Reset layout
284
+ </button>
285
+ </div>
286
+ )}
287
+ </div>
288
+ );
289
+ }
290
+
291
+ export const Dock = { Root, Panel, WindowMenu };
@@ -0,0 +1,28 @@
1
+ @import "dockview-react/dist/styles/dockview.css";
2
+
3
+ .hf-dock {
4
+ --dv-group-view-background-color: var(--color-panel-bg);
5
+ --dv-tabs-and-actions-container-background-color: var(--color-panel-surface);
6
+ --dv-activegroup-visiblepanel-tab-background-color: var(--color-panel-bg);
7
+ --dv-activegroup-hiddenpanel-tab-background-color: var(--color-panel-surface);
8
+ --dv-inactivegroup-visiblepanel-tab-background-color: var(--color-panel-bg);
9
+ --dv-inactivegroup-hiddenpanel-tab-background-color: var(--color-panel-surface);
10
+ --dv-activegroup-visiblepanel-tab-color: var(--color-panel-text-1);
11
+ --dv-activegroup-hiddenpanel-tab-color: var(--color-panel-text-3);
12
+ --dv-inactivegroup-visiblepanel-tab-color: var(--color-panel-text-2);
13
+ --dv-inactivegroup-hiddenpanel-tab-color: var(--color-panel-text-4);
14
+ --dv-tab-divider-color: var(--color-panel-border);
15
+ --dv-separator-border: var(--color-panel-border);
16
+ --dv-paneview-header-border-color: var(--color-panel-border);
17
+ --dv-sash-color: var(--color-panel-hover);
18
+ --dv-active-sash-color: var(--color-panel-accent);
19
+ --dv-drag-over-background-color: color-mix(in srgb, var(--color-panel-accent) 12%, transparent);
20
+ --dv-drag-over-border-color: var(--color-panel-accent);
21
+ --dv-tab-font-size: 11px;
22
+ --dv-tabs-and-actions-container-font-size: 11px;
23
+ }
24
+
25
+ .hf-dock .dv-sash:focus-visible {
26
+ background-color: var(--dv-active-sash-color);
27
+ outline: none;
28
+ }