@hyperframes/studio 0.8.50 → 0.8.52
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.
- package/dist/assets/{hyperframes-player-CxxDxHTW.js → hyperframes-player-I5YRzB02.js} +3 -3
- package/dist/assets/{index-Dt3qBI-9.js → index-BTAfgesM.js} +1 -1
- package/dist/assets/index-CxAu_Okg.css +1 -0
- package/dist/assets/index-DocJlW2v.js +376 -0
- package/dist/assets/{index-DUAIWxkI.js → index-Du5x7KUR.js} +1 -1
- package/dist/index.d.ts +419 -20
- package/dist/index.html +2 -2
- package/dist/index.js +7496 -8288
- package/dist/index.js.map +1 -1
- package/dist/styles/tailwind-preset.js.map +1 -1
- package/package.json +13 -12
- package/src/App.tsx +131 -143
- package/src/captions/components/CaptionAnimationPanel.tsx +4 -4
- package/src/captions/components/CaptionOverlay.sender.test.tsx +52 -0
- package/src/captions/components/CaptionOverlay.tsx +8 -4
- package/src/captions/components/CaptionOverlayUtils.ts +2 -3
- package/src/captions/components/CaptionPropertyPanel.tsx +3 -3
- package/src/captions/components/CaptionTimeline.tsx +1 -1
- package/src/captions/components/shared.tsx +2 -2
- package/src/components/AskAgentModal.tsx +5 -5
- package/src/components/EditorShell.tsx +8 -7
- package/src/components/ExternalFileConflictBanner.tsx +16 -16
- package/src/components/LintModal.tsx +6 -16
- package/src/components/MediaPreview.tsx +2 -2
- package/src/components/RightPanelTabs.test.tsx +151 -0
- package/src/components/RightPanelTabs.tsx +59 -0
- package/src/components/SaveQueuePausedBanner.tsx +2 -2
- package/src/components/StudioHeader.dom.test.tsx +181 -0
- package/src/components/StudioHeader.tsx +110 -210
- package/src/components/StudioLeftSidebar.tsx +7 -4
- package/src/components/StudioOverlays.tsx +1 -1
- package/src/components/StudioRightPanel.tsx +51 -39
- package/src/components/StudioToast.tsx +2 -2
- package/src/components/TimelineHistoryButtons.dom.test.tsx +117 -0
- package/src/components/TimelineHistoryButtons.tsx +54 -0
- package/src/components/TimelineToolbar.test.tsx +8 -0
- package/src/components/TimelineToolbar.tsx +8 -12
- package/src/components/dock/Dock.test.tsx +260 -0
- package/src/components/dock/Dock.tsx +281 -0
- package/src/components/dock/dock.css +28 -0
- package/src/components/dock/dockAccessibility.test.ts +166 -0
- package/src/components/dock/dockAccessibility.ts +98 -0
- package/src/components/dock/dockLayout.test.ts +134 -0
- package/src/components/dock/dockLayout.ts +105 -0
- package/src/components/dock/dockLayoutSchema.ts +66 -0
- package/src/components/dock/dockLayoutStore.ts +91 -0
- package/src/components/dock/dockTestHarness.ts +11 -0
- package/src/components/dock/panelRegistry.ts +67 -0
- package/src/components/editor/AnimationCard.tsx +4 -4
- package/src/components/editor/AnimationCardParts.tsx +6 -6
- package/src/components/editor/BlockParamsPanel.tsx +4 -4
- package/src/components/editor/BorderRadiusEditor.tsx +1 -1
- package/src/components/editor/CanvasContextMenu.tsx +1 -1
- package/src/components/editor/ComputedTweenNotice.tsx +1 -1
- package/src/components/editor/DomEditOverlay.tsx +1 -1
- package/src/components/editor/EaseCurveSection.tsx +1 -1
- package/src/components/editor/EaseParamFields.tsx +5 -5
- package/src/components/editor/FileTree.tsx +4 -4
- package/src/components/editor/FileTreeIcons.tsx +1 -1
- package/src/components/editor/FileTreeNodes.tsx +13 -13
- package/src/components/editor/InlineTextToolbar.tsx +1 -1
- package/src/components/editor/InspectorHeaderActions.tsx +3 -3
- package/src/components/editor/KeyframeDiamond.tsx +1 -1
- package/src/components/editor/KeyframeEaseList.tsx +1 -1
- package/src/components/editor/LayersPanel.test.ts +97 -1
- package/src/components/editor/LayersPanel.tsx +7 -5
- package/src/components/editor/MarqueeOverlay.tsx +1 -1
- package/src/components/editor/OffCanvasIndicators.tsx +1 -1
- package/src/components/editor/PreviewGuides.tsx +1 -1
- package/src/components/editor/PromotableControl.tsx +2 -2
- package/src/components/editor/PropertyPanelEmptyState.tsx +7 -7
- package/src/components/editor/PropertyPanelFlatHeader.tsx +2 -2
- package/src/components/editor/SnapToolbar.tsx +2 -2
- package/src/components/editor/TimelineFxPopover.tsx +1 -1
- package/src/components/editor/TopologyLens.test.tsx +28 -0
- package/src/components/editor/TopologyLens.tsx +7 -5
- package/src/components/editor/Transform3DCube.tsx +2 -2
- package/src/components/editor/audioFxRevealTarget.ts +1 -0
- package/src/components/editor/domEditing.test.ts +1 -1
- package/src/components/editor/domEditingAgentPrompt.test.ts +8 -6
- package/src/components/editor/domEditingAgentPrompt.ts +4 -3
- package/src/components/editor/propertyPanelColor.tsx +11 -11
- package/src/components/editor/propertyPanelColorCurveGraph.tsx +1 -1
- package/src/components/editor/propertyPanelColorGradingControls.tsx +7 -9
- package/src/components/editor/propertyPanelColorGradingSection.tsx +6 -6
- package/src/components/editor/propertyPanelColorGradingSlider.tsx +5 -5
- package/src/components/editor/propertyPanelColorWheels.tsx +1 -1
- package/src/components/editor/propertyPanelCommitField.tsx +1 -1
- package/src/components/editor/propertyPanelFill.tsx +4 -4
- package/src/components/editor/propertyPanelFlatColorGradingAccessory.tsx +3 -3
- package/src/components/editor/propertyPanelFlatColorGradingSection.tsx +7 -7
- package/src/components/editor/propertyPanelFlatEffectsSection.tsx +4 -4
- package/src/components/editor/propertyPanelFlatMediaSection.tsx +3 -3
- package/src/components/editor/propertyPanelFlatOverlaysSection.tsx +1 -1
- package/src/components/editor/propertyPanelFlatPrimitives.tsx +10 -10
- package/src/components/editor/propertyPanelFlatSelectRow.tsx +3 -3
- package/src/components/editor/propertyPanelFlatTextSection.test.tsx +8 -9
- package/src/components/editor/propertyPanelFlatTextSection.tsx +5 -5
- package/src/components/editor/propertyPanelFlatToggle.tsx +1 -1
- package/src/components/editor/propertyPanelFont.tsx +5 -5
- package/src/components/editor/propertyPanelFxCarveModule.tsx +1 -1
- package/src/components/editor/propertyPanelFxControls.test.tsx +1 -1
- package/src/components/editor/propertyPanelFxControls.tsx +6 -6
- package/src/components/editor/propertyPanelFxEqModule.tsx +2 -2
- package/src/components/editor/propertyPanelFxPresetStyle.ts +2 -2
- package/src/components/editor/propertyPanelGradingNumberField.tsx +1 -1
- package/src/components/editor/propertyPanelMediaSection.tsx +1 -1
- package/src/components/editor/propertyPanelPrimitives.tsx +7 -7
- package/src/components/editor/propertyPanelSections.tsx +8 -8
- package/src/components/editor/useColorGradingController.test.ts +38 -0
- package/src/components/editor/useColorGradingController.ts +9 -1
- package/src/components/editor/useColorGradingPreviews.test.ts +76 -0
- package/src/components/editor/useColorGradingPreviews.ts +11 -1
- package/src/components/editor/usePreviewCompositionRect.test.tsx +95 -0
- package/src/components/editor/usePreviewCompositionRect.ts +21 -0
- package/src/components/feedback/StudioFeedbackCard.tsx +7 -7
- package/src/components/feedback/feedbackTrigger.ts +0 -1
- package/src/components/nle/AssetPreviewOverlay.tsx +6 -2
- package/src/components/nle/CompositionBreadcrumb.tsx +3 -3
- package/src/components/nle/NLEPreview.tsx +3 -3
- package/src/components/nle/PreviewOverlays.tsx +6 -6
- package/src/components/nle/PreviewPane.tsx +3 -3
- package/src/components/nle/TimelinePane.tsx +3 -3
- package/src/components/nle/TimelineResizeDivider.tsx +2 -2
- package/src/components/panels/SlideshowPanel.tsx +2 -2
- package/src/components/panels/SlideshowSubPanels.tsx +20 -17
- package/src/components/panels/VariablesBindElement.tsx +2 -2
- package/src/components/panels/VariablesDeclarationForm.tsx +2 -2
- package/src/components/panels/VariablesOtherCompositions.tsx +1 -1
- package/src/components/panels/VariablesPanel.tsx +3 -3
- package/src/components/panels/VariablesValueControls.tsx +2 -2
- package/src/components/pointerTargetSize.test.tsx +8 -0
- package/src/components/renders/FfmpegRequiredNotice.tsx +5 -5
- package/src/components/renders/RenderQueue.test.tsx +7 -0
- package/src/components/renders/RenderQueue.tsx +7 -7
- package/src/components/renders/RenderQueueItem.tsx +6 -6
- package/src/components/sidebar/AssetCard.tsx +8 -8
- package/src/components/sidebar/AssetContextMenu.tsx +8 -8
- package/src/components/sidebar/AssetsTab.tsx +2 -2
- package/src/components/sidebar/AudioRow.tsx +6 -6
- package/src/components/sidebar/BlocksTab.tsx +6 -6
- package/src/components/sidebar/CompositionsTab.tsx +5 -5
- package/src/components/sidebar/GlobalAssetsView.tsx +1 -1
- package/src/components/sidebar/LeftSidebar.lintBadge.test.tsx +58 -0
- package/src/components/sidebar/LeftSidebar.tsx +19 -5
- package/src/components/sidebar/PromptPreviewModal.tsx +3 -3
- package/src/components/timelineToolbarStyles.ts +5 -0
- package/src/components/ui/Button.test.tsx +252 -0
- package/src/components/ui/Button.tsx +89 -126
- package/src/components/ui/IconButton.tsx +38 -0
- package/src/components/ui/Input.tsx +115 -0
- package/src/components/ui/Menu.test.tsx +374 -0
- package/src/components/ui/Menu.tsx +213 -0
- package/src/components/ui/NumberField.tsx +122 -0
- package/src/components/ui/Popover.tsx +74 -0
- package/src/components/ui/SearchInput.tsx +2 -2
- package/src/components/ui/Select.tsx +100 -0
- package/src/components/ui/Slider.tsx +153 -0
- package/src/components/ui/Tabs.test.tsx +98 -0
- package/src/components/ui/Tabs.tsx +71 -0
- package/src/components/ui/Toggle.tsx +63 -0
- package/src/components/ui/Tooltip.test.tsx +110 -0
- package/src/components/ui/Tooltip.tsx +38 -112
- package/src/components/ui/cn.test.ts +30 -0
- package/src/components/ui/cn.ts +25 -0
- package/src/components/ui/index.ts +29 -2
- package/src/components/ui/mountHost.testHelpers.ts +24 -0
- package/src/components/ui/valueControls.test.tsx +524 -0
- package/src/hooks/useAppHotkeys.ts +21 -80
- package/src/hooks/useCaptionDetection.sender.test.tsx +46 -0
- package/src/hooks/useCaptionDetection.ts +3 -1
- package/src/hooks/useEditHistoryActions.test.tsx +90 -0
- package/src/hooks/useEditHistoryActions.ts +106 -0
- package/src/hooks/useLintModal.test.tsx +65 -0
- package/src/hooks/useLintModal.ts +3 -0
- package/src/hooks/useSdkSession.lifecycle.test.tsx +49 -4
- package/src/hooks/useSdkSession.ts +43 -10
- package/src/hooks/useServerConnection.hashProject.test.tsx +94 -0
- package/src/hooks/useServerConnection.ts +43 -10
- package/src/hooks/useTimelineAssetDropOps.test.tsx +41 -1
- package/src/hooks/useTimelineAssetDropOps.ts +52 -11
- package/src/index.ts +51 -0
- package/src/main.tsx +9 -3
- package/src/player/components/AudioWaveform.tsx +1 -1
- package/src/player/components/AutomationSelectionMenu.tsx +3 -3
- package/src/player/components/ClipContextMenu.tsx +3 -3
- package/src/player/components/CompositionThumbnail.test.ts +0 -3
- package/src/player/components/CompositionThumbnail.tsx +3 -4
- package/src/player/components/EditModal.tsx +1 -1
- package/src/player/components/ImageThumbnail.tsx +1 -1
- package/src/player/components/KeyframeDiamondContextMenu.tsx +4 -4
- package/src/player/components/LayerDisclosureRow.tsx +2 -2
- package/src/player/components/PlayerControls.tsx +1 -1
- package/src/player/components/ShortcutsPanel.tsx +9 -9
- package/src/player/components/SpeedMenu.tsx +3 -5
- package/src/player/components/Timeline.tsx +2 -1
- package/src/player/components/TimelineCanvas.tsx +21 -14
- package/src/player/components/TimelineClip.tsx +0 -1
- package/src/player/components/TimelineDiamondConnectors.tsx +1 -1
- package/src/player/components/TimelineEmptyState.tsx +5 -5
- package/src/player/components/TimelineFxButton.tsx +6 -6
- package/src/player/components/TimelineGroupHeader.tsx +5 -5
- package/src/player/components/TimelineGroupLaneLabels.tsx +1 -1
- package/src/player/components/TimelineGroupRow.tsx +1 -1
- package/src/player/components/TimelineLanes.test.tsx +32 -0
- package/src/player/components/TimelineLanes.tsx +2 -5
- package/src/player/components/TimelineLayerGroupHeader.tsx +2 -2
- package/src/player/components/TimelineRuler.tsx +1 -1
- package/src/player/components/TimelineShortcutHint.tsx +1 -1
- package/src/player/components/TimelineTrackHeader.tsx +1 -1
- package/src/player/components/TimelineTrackPlainHeader.tsx +3 -3
- package/src/player/components/TimelineTypes.ts +2 -1
- package/src/player/components/TrackGapContextMenu.tsx +1 -1
- package/src/player/components/VideoThumbnail.tsx +4 -2
- package/src/player/components/VolumeControl.tsx +2 -2
- package/src/player/components/timelineCallbacks.ts +8 -8
- package/src/player/components/timelineClipDragPreview.ts +51 -27
- package/src/player/components/timelineClipDragTypes.ts +3 -0
- package/src/player/components/timelineDragDrop.test.tsx +61 -1
- package/src/player/components/timelineDragDrop.ts +57 -15
- package/src/player/components/timelineLaneProps.ts +3 -0
- package/src/player/components/timelineSnapping.test.ts +27 -0
- package/src/player/components/timelineSnapping.ts +14 -1
- package/src/player/components/trackHeaderLabelRows.tsx +4 -4
- package/src/player/components/useTimelineClipDrag.resize.test.tsx +136 -2
- package/src/player/components/useTimelineClipDrag.ts +30 -3
- package/src/player/lib/describeClips.ts +3 -54
- package/src/player/lib/playbackShortcuts.ts +2 -0
- package/src/player/store/previewIframeStore.notify.test.tsx +75 -0
- package/src/player/store/previewIframeStore.test.tsx +29 -0
- package/src/player/store/previewIframeStore.ts +25 -0
- package/src/renderQueueExports.test.tsx +58 -0
- package/src/styles/classCandidates.test.ts +105 -0
- package/src/styles/classCandidates.ts +281 -0
- package/src/styles/hex-baseline.json +84 -0
- package/src/styles/hexRatchet.test.ts +201 -0
- package/src/styles/iconTokens.ts +20 -0
- package/src/styles/palette-reset.css +6 -0
- package/src/styles/studio.css +24 -21
- package/src/styles/styleSources.test.ts +20 -0
- package/src/styles/styleSources.ts +47 -0
- package/src/styles/tailwind-preset.shared.js +3 -0
- package/src/styles/theme.css +243 -0
- package/src/styles/theme.test.ts +183 -0
- package/src/styles/tokenGate.test.ts +109 -0
- package/src/utils/sourcePatcher.ts +1 -1
- package/src/utils/studioHelpers.test.ts +18 -0
- package/src/utils/studioHelpers.ts +11 -1
- package/src/utils/studioUiPreferences.ts +7 -0
- package/src/utils/timelineDropTrackInsert.test.ts +134 -0
- package/src/utils/timelineDropTrackInsert.ts +83 -0
- package/src/utils/typingTarget.ts +4 -0
- package/dist/assets/index-n2ml4tUn.js +0 -442
- package/dist/assets/index-pO6p0H27.css +0 -1
- package/src/components/PanelTabButton.tsx +0 -31
- package/src/components/storyboard/AgentChatMessageButton.test.tsx +0 -47
- package/src/components/storyboard/AgentChatMessageButton.tsx +0 -45
- package/src/components/storyboard/FramePoster.test.tsx +0 -61
- package/src/components/storyboard/FramePoster.tsx +0 -86
- package/src/components/storyboard/StoryboardDirection.tsx +0 -45
- package/src/components/storyboard/StoryboardFrameFocus.tsx +0 -412
- package/src/components/storyboard/StoryboardFrameTile.tsx +0 -117
- package/src/components/storyboard/StoryboardGrid.tsx +0 -55
- package/src/components/storyboard/StoryboardLoaded.tsx +0 -307
- package/src/components/storyboard/StoryboardReviewGuide.tsx +0 -300
- package/src/components/storyboard/StoryboardScriptPanel.tsx +0 -26
- package/src/components/storyboard/StoryboardSourceEditor.tsx +0 -231
- package/src/components/storyboard/StoryboardStatusLegend.tsx +0 -21
- package/src/components/storyboard/StoryboardView.tsx +0 -169
- package/src/components/storyboard/StoryboardViewModeGuard.test.tsx +0 -187
- package/src/components/storyboard/frameComments.test.ts +0 -95
- package/src/components/storyboard/frameComments.ts +0 -115
- package/src/components/storyboard/frameStatus.ts +0 -36
- package/src/components/storyboard/storyboardReviewStage.test.ts +0 -40
- package/src/components/storyboard/storyboardReviewStage.ts +0 -45
- package/src/components/storyboard/useFrameComments.ts +0 -98
- package/src/contexts/ViewModeContext.tsx +0 -142
- package/src/hooks/useProjectSignaturePoll.test.tsx +0 -135
- package/src/hooks/useProjectSignaturePoll.ts +0 -68
- package/src/hooks/useStoryboard.ts +0 -99
|
@@ -93,7 +93,7 @@ export function EaseBezierField({
|
|
|
93
93
|
}
|
|
94
94
|
}}
|
|
95
95
|
onBlur={(event) => commit(event.currentTarget.value)}
|
|
96
|
-
className={`w-full rounded border bg-black/20 px-1.5 py-1 font-mono text-[10px] text-neutral-300 outline-
|
|
96
|
+
className={`w-full rounded border bg-black/20 px-1.5 py-1 font-mono text-[10px] text-neutral-300 outline-hidden ${
|
|
97
97
|
error
|
|
98
98
|
? "border-red-500/70 focus:border-red-400"
|
|
99
99
|
: "border-white/10 focus:border-panel-accent/50"
|
|
@@ -183,7 +183,7 @@ export function SpringBounceField({
|
|
|
183
183
|
const bounce = parseSpringBounce(`spring(${value})`);
|
|
184
184
|
if (bounce !== null) onCommit(`spring(${round2(bounce)})`);
|
|
185
185
|
}}
|
|
186
|
-
className="w-16 rounded border border-white/10 bg-black/20 px-1.5 py-1 font-mono text-[10px] text-neutral-300 outline-
|
|
186
|
+
className="w-16 rounded-sm border border-white/10 bg-black/20 px-1.5 py-1 font-mono text-[10px] text-neutral-300 outline-hidden focus:border-panel-accent/50"
|
|
187
187
|
/>
|
|
188
188
|
</div>
|
|
189
189
|
);
|
|
@@ -207,7 +207,7 @@ export function WiggleField({
|
|
|
207
207
|
min={1}
|
|
208
208
|
step={1}
|
|
209
209
|
onCommit={(value) => commitWiggle(onCommit, value, config.type, amplitude)}
|
|
210
|
-
className="w-14 rounded border border-white/10 bg-black/20 px-1.5 py-1 font-mono text-[10px] text-neutral-300 outline-
|
|
210
|
+
className="w-14 rounded-sm border border-white/10 bg-black/20 px-1.5 py-1 font-mono text-[10px] text-neutral-300 outline-hidden focus:border-panel-accent/50"
|
|
211
211
|
/>
|
|
212
212
|
</div>
|
|
213
213
|
<div className="flex items-center gap-1">
|
|
@@ -220,7 +220,7 @@ export function WiggleField({
|
|
|
220
220
|
commitWiggle(onCommit, config.wiggles, event.currentTarget.value, amplitude);
|
|
221
221
|
}
|
|
222
222
|
}}
|
|
223
|
-
className="rounded border border-white/10 bg-black/20 px-1.5 py-1 text-[10px] text-neutral-300 outline-
|
|
223
|
+
className="rounded-sm border border-white/10 bg-black/20 px-1.5 py-1 text-[10px] text-neutral-300 outline-hidden focus:border-panel-accent/50"
|
|
224
224
|
>
|
|
225
225
|
{WIGGLE_TYPES.map((type) => (
|
|
226
226
|
<option key={type} value={type}>
|
|
@@ -238,7 +238,7 @@ export function WiggleField({
|
|
|
238
238
|
max={1}
|
|
239
239
|
step={0.01}
|
|
240
240
|
onCommit={(value) => commitWiggle(onCommit, config.wiggles, config.type, value)}
|
|
241
|
-
className="w-16 rounded border border-white/10 bg-black/20 px-1.5 py-1 font-mono text-[10px] text-neutral-300 outline-
|
|
241
|
+
className="w-16 rounded-sm border border-white/10 bg-black/20 px-1.5 py-1 font-mono text-[10px] text-neutral-300 outline-hidden focus:border-panel-accent/50"
|
|
242
242
|
/>
|
|
243
243
|
</div>
|
|
244
244
|
</div>
|
|
@@ -235,14 +235,14 @@ export const FileTree = memo(function FileTree({
|
|
|
235
235
|
<div className="flex flex-col h-full min-h-0">
|
|
236
236
|
{/* FILES header with action buttons */}
|
|
237
237
|
{hasFileOps && (
|
|
238
|
-
<div className="flex items-center justify-between px-2.5 py-1.5 border-b border-neutral-800/50
|
|
238
|
+
<div className="flex items-center justify-between px-2.5 py-1.5 border-b border-neutral-800/50 shrink-0">
|
|
239
239
|
<span className="text-[10px] font-semibold tracking-wider text-neutral-600 uppercase">
|
|
240
240
|
Files
|
|
241
241
|
</span>
|
|
242
242
|
<div className="flex items-center gap-0.5">
|
|
243
243
|
<button
|
|
244
244
|
onClick={() => handleNewFile("")}
|
|
245
|
-
className="p-1.5 rounded hover:bg-neutral-800 text-neutral-600 hover:text-neutral-400 active:scale-[0.97] transition-colors"
|
|
245
|
+
className="p-1.5 rounded-sm hover:bg-neutral-800 text-neutral-600 hover:text-neutral-400 active:scale-[0.97] transition-colors"
|
|
246
246
|
title="New File"
|
|
247
247
|
aria-label="New File"
|
|
248
248
|
>
|
|
@@ -250,7 +250,7 @@ export const FileTree = memo(function FileTree({
|
|
|
250
250
|
</button>
|
|
251
251
|
<button
|
|
252
252
|
onClick={() => handleNewFolder("")}
|
|
253
|
-
className="p-1.5 rounded hover:bg-neutral-800 text-neutral-600 hover:text-neutral-400 active:scale-[0.97] transition-colors"
|
|
253
|
+
className="p-1.5 rounded-sm hover:bg-neutral-800 text-neutral-600 hover:text-neutral-400 active:scale-[0.97] transition-colors"
|
|
254
254
|
title="New Folder"
|
|
255
255
|
aria-label="New Folder"
|
|
256
256
|
>
|
|
@@ -263,7 +263,7 @@ export const FileTree = memo(function FileTree({
|
|
|
263
263
|
<div
|
|
264
264
|
className={`flex-1 overflow-y-auto py-1 transition-colors ${
|
|
265
265
|
dragOverFolder === ""
|
|
266
|
-
? "bg-[#3CE6AC]/5 outline outline-1 outline-[#3CE6AC]/30 -outline-offset-1"
|
|
266
|
+
? "bg-[#3CE6AC]/5 outline-solid outline-1 outline-[#3CE6AC]/30 -outline-offset-1"
|
|
267
267
|
: ""
|
|
268
268
|
}`}
|
|
269
269
|
onContextMenu={handleRootContextMenu}
|
|
@@ -23,7 +23,7 @@ const W = "duotone" as const;
|
|
|
23
23
|
|
|
24
24
|
export function FileIcon({ path }: { path: string }) {
|
|
25
25
|
const ext = path.split(".").pop()?.toLowerCase() ?? "";
|
|
26
|
-
const c = "
|
|
26
|
+
const c = "shrink-0";
|
|
27
27
|
if (ext === "html") return <FileHtml size={SZ} weight={W} color="#E44D26" className={c} />;
|
|
28
28
|
if (ext === "css") return <FileCss size={SZ} weight={W} color="#264DE4" className={c} />;
|
|
29
29
|
if (ext === "js" || ext === "mjs" || ext === "cjs")
|
|
@@ -107,7 +107,7 @@ export function ContextMenu({
|
|
|
107
107
|
<>
|
|
108
108
|
<button
|
|
109
109
|
role="menuitem"
|
|
110
|
-
className="w-full flex items-center gap-2 px-3 py-1.5 text-xs text-neutral-300 hover:bg-neutral-800 focus-visible:bg-neutral-800 active:bg-neutral-700 outline-
|
|
110
|
+
className="w-full flex items-center gap-2 px-3 py-1.5 text-xs text-neutral-300 hover:bg-neutral-800 focus-visible:bg-neutral-800 active:bg-neutral-700 outline-hidden cursor-pointer text-left"
|
|
111
111
|
onClick={() => {
|
|
112
112
|
onNewFile(state.targetPath);
|
|
113
113
|
onClose();
|
|
@@ -118,7 +118,7 @@ export function ContextMenu({
|
|
|
118
118
|
</button>
|
|
119
119
|
<button
|
|
120
120
|
role="menuitem"
|
|
121
|
-
className="w-full flex items-center gap-2 px-3 py-1.5 text-xs text-neutral-300 hover:bg-neutral-800 focus-visible:bg-neutral-800 active:bg-neutral-700 outline-
|
|
121
|
+
className="w-full flex items-center gap-2 px-3 py-1.5 text-xs text-neutral-300 hover:bg-neutral-800 focus-visible:bg-neutral-800 active:bg-neutral-700 outline-hidden cursor-pointer text-left"
|
|
122
122
|
onClick={() => {
|
|
123
123
|
onNewFolder(state.targetPath);
|
|
124
124
|
onClose();
|
|
@@ -134,7 +134,7 @@ export function ContextMenu({
|
|
|
134
134
|
<>
|
|
135
135
|
<button
|
|
136
136
|
role="menuitem"
|
|
137
|
-
className="w-full flex items-center gap-2 px-3 py-1.5 text-xs text-neutral-300 hover:bg-neutral-800 focus-visible:bg-neutral-800 active:bg-neutral-700 outline-
|
|
137
|
+
className="w-full flex items-center gap-2 px-3 py-1.5 text-xs text-neutral-300 hover:bg-neutral-800 focus-visible:bg-neutral-800 active:bg-neutral-700 outline-hidden cursor-pointer text-left"
|
|
138
138
|
onClick={() => {
|
|
139
139
|
onNewFile(parentPath);
|
|
140
140
|
onClose();
|
|
@@ -148,7 +148,7 @@ export function ContextMenu({
|
|
|
148
148
|
)}
|
|
149
149
|
<button
|
|
150
150
|
role="menuitem"
|
|
151
|
-
className="w-full flex items-center gap-2 px-3 py-1.5 text-xs text-neutral-300 hover:bg-neutral-800 focus-visible:bg-neutral-800 active:bg-neutral-700 outline-
|
|
151
|
+
className="w-full flex items-center gap-2 px-3 py-1.5 text-xs text-neutral-300 hover:bg-neutral-800 focus-visible:bg-neutral-800 active:bg-neutral-700 outline-hidden cursor-pointer text-left"
|
|
152
152
|
onClick={() => {
|
|
153
153
|
onRename(state.targetPath);
|
|
154
154
|
onClose();
|
|
@@ -160,7 +160,7 @@ export function ContextMenu({
|
|
|
160
160
|
{!state.targetIsFolder && (
|
|
161
161
|
<button
|
|
162
162
|
role="menuitem"
|
|
163
|
-
className="w-full flex items-center gap-2 px-3 py-1.5 text-xs text-neutral-300 hover:bg-neutral-800 focus-visible:bg-neutral-800 active:bg-neutral-700 outline-
|
|
163
|
+
className="w-full flex items-center gap-2 px-3 py-1.5 text-xs text-neutral-300 hover:bg-neutral-800 focus-visible:bg-neutral-800 active:bg-neutral-700 outline-hidden cursor-pointer text-left"
|
|
164
164
|
onClick={() => {
|
|
165
165
|
onDuplicate(state.targetPath);
|
|
166
166
|
onClose();
|
|
@@ -173,7 +173,7 @@ export function ContextMenu({
|
|
|
173
173
|
<div className="border-t border-neutral-700 my-1" />
|
|
174
174
|
<button
|
|
175
175
|
role="menuitem"
|
|
176
|
-
className="w-full flex items-center gap-2 px-3 py-1.5 text-xs text-red-400 hover:bg-red-900/30 focus-visible:bg-red-900/30 active:bg-red-900/50 outline-
|
|
176
|
+
className="w-full flex items-center gap-2 px-3 py-1.5 text-xs text-red-400 hover:bg-red-900/30 focus-visible:bg-red-900/30 active:bg-red-900/50 outline-hidden cursor-pointer text-left"
|
|
177
177
|
onClick={() => {
|
|
178
178
|
onDelete(state.targetPath);
|
|
179
179
|
onClose();
|
|
@@ -263,7 +263,7 @@ export function InlineInput({
|
|
|
263
263
|
style={{ paddingLeft: `${8 + depth * 12 + (isFolder ? 0 : 14)}px` }}
|
|
264
264
|
>
|
|
265
265
|
{isFolder ? (
|
|
266
|
-
<FolderSimple size={SZ_ICON} weight="duotone" color="#6B7280" className="
|
|
266
|
+
<FolderSimple size={SZ_ICON} weight="duotone" color="#6B7280" className="shrink-0" />
|
|
267
267
|
) : (
|
|
268
268
|
<FileIcon path={value} />
|
|
269
269
|
)}
|
|
@@ -278,7 +278,7 @@ export function InlineInput({
|
|
|
278
278
|
onKeyDown={handleKeyDown}
|
|
279
279
|
onBlur={handleBlur}
|
|
280
280
|
aria-invalid={error ? true : undefined}
|
|
281
|
-
className={`w-full min-w-0 bg-neutral-800 text-neutral-200 text-xs px-1.5 py-0.5 rounded border outline-
|
|
281
|
+
className={`w-full min-w-0 bg-neutral-800 text-neutral-200 text-xs px-1.5 py-0.5 rounded border outline-hidden ${
|
|
282
282
|
error ? "border-red-500/70" : "border-neutral-600 focus:border-[#3CE6AC]"
|
|
283
283
|
}`}
|
|
284
284
|
spellCheck={false}
|
|
@@ -344,13 +344,13 @@ export function DeleteConfirm({
|
|
|
344
344
|
<div className="flex gap-1.5">
|
|
345
345
|
<button
|
|
346
346
|
onClick={onCancel}
|
|
347
|
-
className="flex-1 px-2 py-1 rounded bg-neutral-700 text-neutral-300 hover:bg-neutral-600 transition-colors"
|
|
347
|
+
className="flex-1 px-2 py-1 rounded-sm bg-neutral-700 text-neutral-300 hover:bg-neutral-600 transition-colors"
|
|
348
348
|
>
|
|
349
349
|
Cancel
|
|
350
350
|
</button>
|
|
351
351
|
<button
|
|
352
352
|
onClick={onConfirm}
|
|
353
|
-
className="flex-1 px-2 py-1 rounded bg-red-900/60 text-red-300 hover:bg-red-800/60 transition-colors"
|
|
353
|
+
className="flex-1 px-2 py-1 rounded-sm bg-red-900/60 text-red-300 hover:bg-red-800/60 transition-colors"
|
|
354
354
|
>
|
|
355
355
|
Delete
|
|
356
356
|
</button>
|
|
@@ -419,7 +419,7 @@ export const TreeFile = memo(function TreeFile({
|
|
|
419
419
|
<span className="truncate flex-1">{node.name}</span>
|
|
420
420
|
{lintInfo && lintInfo.count > 0 && (
|
|
421
421
|
<span
|
|
422
|
-
className="
|
|
422
|
+
className="shrink-0 min-w-[16px] rounded-full bg-amber-500/20 px-1 text-[8px] font-bold text-amber-400 text-center mr-1"
|
|
423
423
|
title={lintInfo.messages.join("\n")}
|
|
424
424
|
>
|
|
425
425
|
{lintInfo.count}
|
|
@@ -505,11 +505,11 @@ export const TreeFolder = memo(function TreeFolder({
|
|
|
505
505
|
}}
|
|
506
506
|
onDragLeave={onDragLeave}
|
|
507
507
|
className={`w-full flex items-center gap-1.5 px-2.5 py-1 min-h-7 text-left text-xs text-neutral-400 hover:bg-neutral-800/30 hover:text-neutral-300 transition-colors ${
|
|
508
|
-
isDragOver ? "bg-[#3CE6AC]/10 outline outline-1 outline-[#3CE6AC]/40" : ""
|
|
508
|
+
isDragOver ? "bg-[#3CE6AC]/10 outline-solid outline-1 outline-[#3CE6AC]/40" : ""
|
|
509
509
|
}`}
|
|
510
510
|
style={{ paddingLeft: `${8 + depth * 12}px` }}
|
|
511
511
|
>
|
|
512
|
-
<Chevron size={10} className="
|
|
512
|
+
<Chevron size={10} className="shrink-0 text-neutral-600" />
|
|
513
513
|
<span className="truncate font-medium">{node.name}</span>
|
|
514
514
|
</button>
|
|
515
515
|
{isOpen && (
|
|
@@ -77,7 +77,7 @@ export function InlineTextToolbar({
|
|
|
77
77
|
data-inline-text-toolbar="true"
|
|
78
78
|
role="toolbar"
|
|
79
79
|
aria-label="Text formatting"
|
|
80
|
-
className="pointer-events-auto fixed z-
|
|
80
|
+
className="pointer-events-auto fixed z-200 flex items-center gap-1 rounded-lg border border-white/10 bg-[#15171c] p-1 shadow-[0_8px_24px_rgba(0,0,0,0.55)]"
|
|
81
81
|
style={{
|
|
82
82
|
left: placement.left,
|
|
83
83
|
top: placement.top,
|
|
@@ -37,7 +37,7 @@ export function InspectorHeaderActions({
|
|
|
37
37
|
track("toggle", "Element visibility");
|
|
38
38
|
void onToggleHidden(selectedElementId, !selectedElementHidden);
|
|
39
39
|
}}
|
|
40
|
-
className="flex h-6 w-6 items-center justify-center rounded text-neutral-500 transition-colors hover:bg-neutral-800 hover:text-neutral-300"
|
|
40
|
+
className="flex h-6 w-6 items-center justify-center rounded-sm text-neutral-500 transition-colors hover:bg-neutral-800 hover:text-neutral-300"
|
|
41
41
|
>
|
|
42
42
|
{selectedElementHidden ? (
|
|
43
43
|
<EyeSlash size={13} weight="bold" aria-hidden="true" />
|
|
@@ -54,7 +54,7 @@ export function InspectorHeaderActions({
|
|
|
54
54
|
onUngroup();
|
|
55
55
|
}}
|
|
56
56
|
title="Ungroup (⌘⇧G)"
|
|
57
|
-
className="flex h-6 items-center rounded px-2 text-[11px] font-medium text-neutral-400 transition-colors hover:bg-neutral-800 hover:text-neutral-200"
|
|
57
|
+
className="flex h-6 items-center rounded-sm px-2 text-[11px] font-medium text-neutral-400 transition-colors hover:bg-neutral-800 hover:text-neutral-200"
|
|
58
58
|
>
|
|
59
59
|
Ungroup
|
|
60
60
|
</button>
|
|
@@ -91,7 +91,7 @@ export function InspectorHeaderActions({
|
|
|
91
91
|
track("button", "Clear selection");
|
|
92
92
|
onClear();
|
|
93
93
|
}}
|
|
94
|
-
className="flex h-6 w-6 items-center justify-center rounded text-neutral-500 transition-colors hover:bg-neutral-800 hover:text-neutral-300"
|
|
94
|
+
className="flex h-6 w-6 items-center justify-center rounded-sm text-neutral-500 transition-colors hover:bg-neutral-800 hover:text-neutral-300"
|
|
95
95
|
>
|
|
96
96
|
<X size={13} />
|
|
97
97
|
</button>
|
|
@@ -29,7 +29,7 @@ export const KeyframeDiamond = memo(function KeyframeDiamond({
|
|
|
29
29
|
e.stopPropagation();
|
|
30
30
|
onClick();
|
|
31
31
|
}}
|
|
32
|
-
className="relative
|
|
32
|
+
className="relative shrink-0 p-0.5 transition-opacity hover:opacity-100 before:absolute before:-inset-1.5 before:content-['']"
|
|
33
33
|
style={{ color, opacity }}
|
|
34
34
|
title={title}
|
|
35
35
|
aria-label={title}
|
|
@@ -74,7 +74,7 @@ export function KeyframeEaseList({
|
|
|
74
74
|
const next = e.target.value;
|
|
75
75
|
if (next) onApplyAll(next);
|
|
76
76
|
}}
|
|
77
|
-
className="ml-auto cursor-pointer rounded bg-neutral-800 px-1.5 py-0.5 text-[9px] text-neutral-300 outline-
|
|
77
|
+
className="ml-auto cursor-pointer rounded-sm bg-neutral-800 px-1.5 py-0.5 text-[9px] text-neutral-300 outline-hidden hover:bg-neutral-700 focus:ring-1 focus:ring-panel-accent/40"
|
|
78
78
|
>
|
|
79
79
|
<option value="" disabled>
|
|
80
80
|
Set all…
|
|
@@ -1,12 +1,55 @@
|
|
|
1
1
|
// @vitest-environment happy-dom
|
|
2
2
|
|
|
3
|
+
import { createElement, act } from "react";
|
|
4
|
+
import { createRoot, type Root } from "react-dom/client";
|
|
3
5
|
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
|
|
4
6
|
import { Window } from "happy-dom";
|
|
7
|
+
import { usePreviewIframeStore } from "../../player/store/previewIframeStore";
|
|
5
8
|
import type { DomEditLayerItem } from "./domEditingTypes";
|
|
6
|
-
import { createRafThrottle, sortLayersByZIndex } from "./LayersPanel";
|
|
9
|
+
import { createRafThrottle, LayersPanel, sortLayersByZIndex } from "./LayersPanel";
|
|
7
10
|
import { isLayerDraggable } from "./useLayerDrag";
|
|
8
11
|
import { liveTime } from "../../player";
|
|
9
12
|
|
|
13
|
+
(globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
|
14
|
+
|
|
15
|
+
const mocks = vi.hoisted(() => ({
|
|
16
|
+
previewIframeRef: { current: null } as { current: unknown },
|
|
17
|
+
collect: vi.fn(),
|
|
18
|
+
setActiveGroupElement: vi.fn(),
|
|
19
|
+
showToast: vi.fn(),
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
vi.mock("../../contexts/StudioContext", () => ({
|
|
23
|
+
useStudioShellContext: () => ({
|
|
24
|
+
previewIframeRef: mocks.previewIframeRef,
|
|
25
|
+
activeCompPath: "index.html",
|
|
26
|
+
showToast: mocks.showToast,
|
|
27
|
+
}),
|
|
28
|
+
useStudioPlaybackContext: () => ({
|
|
29
|
+
refreshKey: 0,
|
|
30
|
+
compositionLoading: true,
|
|
31
|
+
timelineElements: [],
|
|
32
|
+
isPlaying: false,
|
|
33
|
+
}),
|
|
34
|
+
}));
|
|
35
|
+
vi.mock("../../contexts/DomEditContext", () => ({
|
|
36
|
+
useDomEditContext: () => ({
|
|
37
|
+
domEditSelection: null,
|
|
38
|
+
activeGroupElement: null,
|
|
39
|
+
applyDomSelection: vi.fn(),
|
|
40
|
+
updateDomEditHoverSelection: vi.fn(),
|
|
41
|
+
handleDomZIndexReorderCommit: vi.fn(),
|
|
42
|
+
setActiveGroupElement: mocks.setActiveGroupElement,
|
|
43
|
+
}),
|
|
44
|
+
}));
|
|
45
|
+
vi.mock("../nle/useCanvasZOrderTimelineMirror", () => ({
|
|
46
|
+
useLayerReorderTimelineMirror: () => vi.fn(),
|
|
47
|
+
}));
|
|
48
|
+
vi.mock("./domEditing", async (importOriginal) => ({
|
|
49
|
+
...(await importOriginal<typeof import("./domEditing")>()),
|
|
50
|
+
collectDomEditLayerItems: mocks.collect,
|
|
51
|
+
}));
|
|
52
|
+
|
|
10
53
|
function makeLayer(
|
|
11
54
|
overrides: Partial<DomEditLayerItem> & { zIndex?: string; locked?: boolean },
|
|
12
55
|
): DomEditLayerItem {
|
|
@@ -197,3 +240,56 @@ describe("liveTime subscribe / unsubscribe (LayersPanel scrub contract)", () =>
|
|
|
197
240
|
unsubscribe();
|
|
198
241
|
});
|
|
199
242
|
});
|
|
243
|
+
|
|
244
|
+
describe("LayersPanel preview promotion", () => {
|
|
245
|
+
let root: Root;
|
|
246
|
+
let host: HTMLDivElement;
|
|
247
|
+
|
|
248
|
+
function makeIframe(compId: string): HTMLIFrameElement {
|
|
249
|
+
const iframe = document.createElement("iframe");
|
|
250
|
+
const doc = new Window().document;
|
|
251
|
+
doc.body.innerHTML = `<div data-composition-id="${compId}"></div>`;
|
|
252
|
+
Object.defineProperty(iframe, "contentDocument", { value: doc });
|
|
253
|
+
return iframe;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const collectedRootIds = () =>
|
|
257
|
+
mocks.collect.mock.calls.map((call) => (call[0] as HTMLElement).dataset.compositionId);
|
|
258
|
+
|
|
259
|
+
beforeEach(() => {
|
|
260
|
+
mocks.collect.mockReset().mockReturnValue([]);
|
|
261
|
+
host = document.createElement("div");
|
|
262
|
+
document.body.append(host);
|
|
263
|
+
root = createRoot(host);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
afterEach(() => {
|
|
267
|
+
act(() => root.unmount());
|
|
268
|
+
host.remove();
|
|
269
|
+
usePreviewIframeStore.setState({ iframe: null });
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it("recollects layers from the promoted document's load, not the replaced one", () => {
|
|
273
|
+
const a = makeIframe("a");
|
|
274
|
+
const b = makeIframe("b");
|
|
275
|
+
mocks.previewIframeRef.current = a;
|
|
276
|
+
usePreviewIframeStore.setState({ iframe: a });
|
|
277
|
+
act(() => root.render(createElement(LayersPanel)));
|
|
278
|
+
|
|
279
|
+
act(() => {
|
|
280
|
+
mocks.previewIframeRef.current = b;
|
|
281
|
+
usePreviewIframeStore.getState().setIframe(b);
|
|
282
|
+
});
|
|
283
|
+
mocks.collect.mockClear();
|
|
284
|
+
|
|
285
|
+
act(() => {
|
|
286
|
+
a.dispatchEvent(new Event("load"));
|
|
287
|
+
});
|
|
288
|
+
expect(mocks.collect).not.toHaveBeenCalled();
|
|
289
|
+
|
|
290
|
+
act(() => {
|
|
291
|
+
b.dispatchEvent(new Event("load"));
|
|
292
|
+
});
|
|
293
|
+
expect(collectedRootIds()).toEqual(["b"]);
|
|
294
|
+
});
|
|
295
|
+
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useLivePreviewIframe } from "../../player/store/previewIframeStore";
|
|
1
2
|
import { memo, useState, useCallback, useEffect, useRef } from "react";
|
|
2
3
|
import {
|
|
3
4
|
collectDomEditLayerItems,
|
|
@@ -122,6 +123,7 @@ export const LayersPanel = memo(function LayersPanel() {
|
|
|
122
123
|
|
|
123
124
|
const isMasterView = !activeCompPath || activeCompPath === "index.html";
|
|
124
125
|
|
|
126
|
+
const livePreviewIframe = useLivePreviewIframe();
|
|
125
127
|
const collectLayers = useCallback(() => {
|
|
126
128
|
const iframe = previewIframeRef.current;
|
|
127
129
|
if (!iframe) return;
|
|
@@ -152,7 +154,7 @@ export const LayersPanel = memo(function LayersPanel() {
|
|
|
152
154
|
|
|
153
155
|
useEffect(() => {
|
|
154
156
|
collectLayers();
|
|
155
|
-
}, [collectLayers, refreshKey, zEditVersion]);
|
|
157
|
+
}, [collectLayers, refreshKey, zEditVersion, livePreviewIframe]);
|
|
156
158
|
|
|
157
159
|
useEffect(() => {
|
|
158
160
|
const iframe = previewIframeRef.current;
|
|
@@ -163,7 +165,7 @@ export const LayersPanel = memo(function LayersPanel() {
|
|
|
163
165
|
};
|
|
164
166
|
iframe.addEventListener("load", handleLoad);
|
|
165
167
|
return () => iframe.removeEventListener("load", handleLoad);
|
|
166
|
-
}, [previewIframeRef, collectLayers]);
|
|
168
|
+
}, [previewIframeRef, livePreviewIframe, collectLayers]);
|
|
167
169
|
|
|
168
170
|
useEffect(() => {
|
|
169
171
|
if (!compositionLoading) {
|
|
@@ -485,7 +487,7 @@ export const LayersPanel = memo(function LayersPanel() {
|
|
|
485
487
|
onClick={(e) => toggleCollapse(layer.key, e)}
|
|
486
488
|
aria-expanded={!isCollapsed}
|
|
487
489
|
aria-label={isCollapsed ? "Expand children" : "Collapse children"}
|
|
488
|
-
className="relative flex h-4 w-4
|
|
490
|
+
className="relative flex h-4 w-4 shrink-0 items-center justify-center rounded-sm text-neutral-500 hover:text-neutral-300 before:absolute before:-inset-1.5 before:content-['']"
|
|
489
491
|
>
|
|
490
492
|
<svg
|
|
491
493
|
width="8"
|
|
@@ -498,10 +500,10 @@ export const LayersPanel = memo(function LayersPanel() {
|
|
|
498
500
|
</svg>
|
|
499
501
|
</button>
|
|
500
502
|
) : (
|
|
501
|
-
<span className="w-4
|
|
503
|
+
<span className="w-4 shrink-0" />
|
|
502
504
|
)}
|
|
503
505
|
<span
|
|
504
|
-
className={`flex h-5 w-5
|
|
506
|
+
className={`flex h-5 w-5 shrink-0 items-center justify-center rounded text-[8px] font-bold uppercase ${
|
|
505
507
|
selected
|
|
506
508
|
? "bg-panel-accent/18 text-panel-accent"
|
|
507
509
|
: isCompHost
|
|
@@ -19,7 +19,7 @@ export function MarqueeOverlay({ candidateRects, marqueeRect }: MarqueeOverlayPr
|
|
|
19
19
|
<div
|
|
20
20
|
key={i}
|
|
21
21
|
aria-hidden="true"
|
|
22
|
-
className="pointer-events-none absolute rounded-
|
|
22
|
+
className="pointer-events-none absolute rounded-xs border border-studio-accent bg-studio-accent/5"
|
|
23
23
|
style={{ left: r.left, top: r.top, width: r.width, height: r.height }}
|
|
24
24
|
/>
|
|
25
25
|
))}
|
|
@@ -167,7 +167,7 @@ export function ChildRectOutlines({
|
|
|
167
167
|
{rects.map((rect, index) => (
|
|
168
168
|
<div
|
|
169
169
|
key={index}
|
|
170
|
-
className="pointer-events-none absolute border border-dashed border-white/20 rounded-
|
|
170
|
+
className="pointer-events-none absolute border border-dashed border-white/20 rounded-xs"
|
|
171
171
|
style={{
|
|
172
172
|
left: rect.left,
|
|
173
173
|
top: rect.top,
|
|
@@ -8,7 +8,7 @@ interface PreviewGuidesProps {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
const TICKS = Array.from({ length: 11 }, (_, i) => i * 10);
|
|
11
|
-
const INK = "
|
|
11
|
+
const INK = "color-mix(in srgb, white 70%, transparent)";
|
|
12
12
|
|
|
13
13
|
/** Ruler and safe-margin boxes drawn over the preview pane, never inside the composition. */
|
|
14
14
|
export const PreviewGuides = memo(function PreviewGuides({ iframeRef }: PreviewGuidesProps) {
|
|
@@ -83,7 +83,7 @@ export function PromotableControl({
|
|
|
83
83
|
<span
|
|
84
84
|
// Sits above the row (not on top of top-0) so it clears a value that
|
|
85
85
|
// renders flush to the row's right edge, e.g. flat Font/Color rows.
|
|
86
|
-
className="pointer-events-none absolute -top-2 right-1.5 z-10 inline-flex max-w-[60%] items-center gap-1 truncate rounded bg-studio-accent/20 px-1 py-px font-mono text-[8px] font-medium text-studio-accent"
|
|
86
|
+
className="pointer-events-none absolute -top-2 right-1.5 z-10 inline-flex max-w-[60%] items-center gap-1 truncate rounded-sm bg-studio-accent/20 px-1 py-px font-mono text-[8px] font-medium text-studio-accent"
|
|
87
87
|
title={`Bound to variable "${promote.boundId}"`}
|
|
88
88
|
>
|
|
89
89
|
◆ {promote.boundId}
|
|
@@ -97,7 +97,7 @@ export function PromotableControl({
|
|
|
97
97
|
e.stopPropagation();
|
|
98
98
|
promote.promote();
|
|
99
99
|
}}
|
|
100
|
-
className="absolute -top-2 right-1.5 z-10 inline-flex items-center gap-1 rounded bg-neutral-800/80 px-1 py-px font-mono text-[8px] font-medium text-neutral-400 opacity-70 transition-colors hover:bg-studio-accent/20 hover:text-studio-accent hover:opacity-100"
|
|
100
|
+
className="absolute -top-2 right-1.5 z-10 inline-flex items-center gap-1 rounded-sm bg-neutral-800/80 px-1 py-px font-mono text-[8px] font-medium text-neutral-400 opacity-70 transition-colors hover:bg-studio-accent/20 hover:text-studio-accent hover:opacity-100"
|
|
101
101
|
>
|
|
102
102
|
◇ var
|
|
103
103
|
</button>
|
|
@@ -18,7 +18,7 @@ function FlatEmptyState() {
|
|
|
18
18
|
</svg>
|
|
19
19
|
</span>
|
|
20
20
|
<div className="text-[13px] font-semibold text-panel-text-0">Nothing selected</div>
|
|
21
|
-
<div className="max-w-[250px] text-[11px] leading-
|
|
21
|
+
<div className="max-w-[250px] text-[11px] leading-normal text-panel-text-3">
|
|
22
22
|
Click any element on the canvas to edit it, or drag to select several.
|
|
23
23
|
</div>
|
|
24
24
|
<div className="mt-2 flex w-full flex-col gap-1.5">
|
|
@@ -27,7 +27,7 @@ function FlatEmptyState() {
|
|
|
27
27
|
<span className="text-panel-danger">●</span>
|
|
28
28
|
Record a gesture
|
|
29
29
|
</span>
|
|
30
|
-
<span className="rounded border border-panel-border-input px-[5px] py-px font-mono text-[9px] text-panel-text-5">
|
|
30
|
+
<span className="rounded-sm border border-panel-border-input px-[5px] py-px font-mono text-[9px] text-panel-text-5">
|
|
31
31
|
R
|
|
32
32
|
</span>
|
|
33
33
|
</span>
|
|
@@ -36,7 +36,7 @@ function FlatEmptyState() {
|
|
|
36
36
|
<span className="text-panel-accent">✦</span>
|
|
37
37
|
Describe a change to the agent
|
|
38
38
|
</span>
|
|
39
|
-
<span className="rounded border border-panel-border-input px-[5px] py-px font-mono text-[9px] text-panel-text-5">
|
|
39
|
+
<span className="rounded-sm border border-panel-border-input px-[5px] py-px font-mono text-[9px] text-panel-text-5">
|
|
40
40
|
⌘K
|
|
41
41
|
</span>
|
|
42
42
|
</span>
|
|
@@ -74,7 +74,7 @@ function FlatMultiSelectState({
|
|
|
74
74
|
return (
|
|
75
75
|
<div className="flex flex-col gap-3 px-4 py-3">
|
|
76
76
|
<div className="flex items-center gap-3 rounded-xl border border-panel-border bg-panel-surface p-3">
|
|
77
|
-
<span className="flex h-8 w-8
|
|
77
|
+
<span className="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-panel-accent/10 text-panel-accent">
|
|
78
78
|
<Layers size={16} />
|
|
79
79
|
</span>
|
|
80
80
|
<div className="min-w-0 flex-1">
|
|
@@ -90,7 +90,7 @@ function FlatMultiSelectState({
|
|
|
90
90
|
data-flat-multiselect-clear="true"
|
|
91
91
|
aria-label="Clear selection"
|
|
92
92
|
onClick={onClearSelection}
|
|
93
|
-
className="flex h-[26px] w-[26px]
|
|
93
|
+
className="flex h-[26px] w-[26px] shrink-0 items-center justify-center text-panel-text-3"
|
|
94
94
|
>
|
|
95
95
|
<svg
|
|
96
96
|
width="13"
|
|
@@ -113,14 +113,14 @@ function FlatMultiSelectState({
|
|
|
113
113
|
className="flex items-center gap-2 rounded-lg border border-panel-border bg-panel-bg px-2.5 py-[7px]"
|
|
114
114
|
>
|
|
115
115
|
<span
|
|
116
|
-
className={`flex h-[18px] w-[18px]
|
|
116
|
+
className={`flex h-[18px] w-[18px] shrink-0 items-center justify-center rounded-sm text-[9px] font-bold ${className}`}
|
|
117
117
|
>
|
|
118
118
|
{glyph}
|
|
119
119
|
</span>
|
|
120
120
|
<span className="min-w-0 flex-1 truncate text-[11px] text-panel-text-1">
|
|
121
121
|
{element.label}
|
|
122
122
|
</span>
|
|
123
|
-
<span className="
|
|
123
|
+
<span className="shrink-0 font-mono text-[9px] text-panel-text-4">
|
|
124
124
|
{element.id ? `#${element.id}` : element.selector}
|
|
125
125
|
</span>
|
|
126
126
|
</span>
|
|
@@ -41,13 +41,13 @@ export function PropertyPanelFlatHeader({
|
|
|
41
41
|
<Icon
|
|
42
42
|
size={15}
|
|
43
43
|
data-flat-header-icon="true"
|
|
44
|
-
className={`
|
|
44
|
+
className={`shrink-0 ${ICON_COLOR_BY_KIND[elementKind]}`}
|
|
45
45
|
/>
|
|
46
46
|
<div className="flex min-w-0 flex-1 items-baseline gap-2">
|
|
47
47
|
<span className="truncate text-[13px] font-semibold text-panel-text-0">{name}</span>
|
|
48
48
|
<span className="truncate font-mono text-[10px] text-panel-text-4">{meta}</span>
|
|
49
49
|
</div>
|
|
50
|
-
<div className="flex
|
|
50
|
+
<div className="flex shrink-0 items-center gap-2.5 text-panel-text-3">
|
|
51
51
|
{showUngroup && (
|
|
52
52
|
<button
|
|
53
53
|
type="button"
|
|
@@ -179,7 +179,7 @@ export const SnapToolbar = memo(function SnapToolbar({ onSnapChange }: SnapToolb
|
|
|
179
179
|
</button>
|
|
180
180
|
<button
|
|
181
181
|
type="button"
|
|
182
|
-
className="absolute -right-0.5 -bottom-0.5 rounded p-0.5 text-white/50 hover:text-white/90 bg-black/50"
|
|
182
|
+
className="absolute -right-0.5 -bottom-0.5 rounded-sm p-0.5 text-white/50 hover:text-white/90 bg-black/50"
|
|
183
183
|
onClick={() => setGridPopoverOpen((v) => !v)}
|
|
184
184
|
title="Grid options"
|
|
185
185
|
aria-label="Grid options"
|
|
@@ -209,7 +209,7 @@ export const SnapToolbar = memo(function SnapToolbar({ onSnapChange }: SnapToolb
|
|
|
209
209
|
updatePrefs({ gridSpacing: val });
|
|
210
210
|
}
|
|
211
211
|
}}
|
|
212
|
-
className="w-16 rounded bg-neutral-900 border border-neutral-600 px-1.5 py-0.5 text-xs text-white text-right tabular-nums outline-
|
|
212
|
+
className="w-16 rounded-sm bg-neutral-900 border border-neutral-600 px-1.5 py-0.5 text-xs text-white text-right tabular-nums outline-hidden focus:border-studio-accent"
|
|
213
213
|
/>
|
|
214
214
|
</label>
|
|
215
215
|
<label className="flex items-center gap-2 text-xs text-white/80 cursor-pointer">
|
|
@@ -139,7 +139,7 @@ export function TimelineFxPopover({
|
|
|
139
139
|
ref={rootRef}
|
|
140
140
|
role="dialog"
|
|
141
141
|
aria-label="Effects"
|
|
142
|
-
className="z-
|
|
142
|
+
className="z-200 flex flex-col overflow-hidden rounded-md border border-white/10 bg-[#1b1b1f] p-2 shadow-xl"
|
|
143
143
|
style={clampedStyle(anchorRect)}
|
|
144
144
|
onKeyDown={onKeyDown}
|
|
145
145
|
onPointerDown={(event) => event.stopPropagation()}
|
|
@@ -4,6 +4,7 @@ import React, { act } from "react";
|
|
|
4
4
|
import { createRoot, type Root } from "react-dom/client";
|
|
5
5
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
6
6
|
import { studioEditLifecycle, type StudioWriteResult } from "../../webmcp/writeCoordinator";
|
|
7
|
+
import { usePreviewIframeStore } from "../../player/store/previewIframeStore";
|
|
7
8
|
import { TopologyLens } from "./TopologyLens";
|
|
8
9
|
|
|
9
10
|
(globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
|
@@ -291,4 +292,31 @@ describe("TopologyLens", () => {
|
|
|
291
292
|
expect(vi.getTimerCount()).toBe(0);
|
|
292
293
|
expect(studioEditLifecycle.getSnapshot()).toEqual({ phase: "idle" });
|
|
293
294
|
});
|
|
295
|
+
|
|
296
|
+
it("dismisses on the promoted iframe's load, not the replaced one", () => {
|
|
297
|
+
const a = document.createElement("iframe");
|
|
298
|
+
const b = document.createElement("iframe");
|
|
299
|
+
document.body.append(a, b);
|
|
300
|
+
const ref = { current: a as HTMLIFrameElement | null };
|
|
301
|
+
usePreviewIframeStore.setState({ iframe: a });
|
|
302
|
+
window.matchMedia = matchMedia(false);
|
|
303
|
+
host = document.createElement("div");
|
|
304
|
+
document.body.append(host);
|
|
305
|
+
root = createRoot(host);
|
|
306
|
+
act(() => {
|
|
307
|
+
root?.render(<TopologyLens iframeRef={ref} activeCompositionPath="index.html" />);
|
|
308
|
+
});
|
|
309
|
+
begin();
|
|
310
|
+
|
|
311
|
+
act(() => {
|
|
312
|
+
ref.current = b;
|
|
313
|
+
usePreviewIframeStore.getState().setIframe(b);
|
|
314
|
+
});
|
|
315
|
+
act(() => a.dispatchEvent(new Event("load")));
|
|
316
|
+
expect(host.querySelector('[data-topology-lens="hidden"]')).toBeNull();
|
|
317
|
+
|
|
318
|
+
act(() => b.dispatchEvent(new Event("load")));
|
|
319
|
+
expect(host.querySelector('[data-topology-lens="hidden"]')).not.toBeNull();
|
|
320
|
+
usePreviewIframeStore.setState({ iframe: null });
|
|
321
|
+
});
|
|
294
322
|
});
|