@hyperframes/studio 0.8.104 → 0.8.106
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-DAoaajuM.js → hyperframes-player-dl6K6ABp.js} +1 -1
- package/dist/assets/index-CMiYVGv5.js +399 -0
- package/dist/assets/{index-WV9LroAD.js → index-DKT5NF1Y.js} +1 -1
- package/dist/assets/index-lrT-gfHa.css +1 -0
- package/dist/assets/{index-BDK4rTMb.js → index-ooaZZhKf.js} +1 -1
- package/dist/{chunk-62DDWOSH.js → chunk-EEXSYY5F.js} +19 -18
- package/dist/chunk-EEXSYY5F.js.map +1 -0
- package/dist/{chunk-HIGHVWYD.js → chunk-ICTZJPQQ.js} +3 -2
- package/dist/chunk-ICTZJPQQ.js.map +1 -0
- package/dist/{chunk-6GAO75IU.js → chunk-NCUBAVGK.js} +10 -8
- package/dist/chunk-NCUBAVGK.js.map +1 -0
- package/dist/index.css +6 -1
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +15 -4
- package/dist/index.html +11 -2
- package/dist/index.js +10391 -9836
- package/dist/index.js.map +1 -1
- package/dist/player.js +2 -2
- package/dist/styles/tailwind-preset.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/ui.js +2 -2
- package/package.json +9 -9
- package/src/captions/components/CaptionAnimationPanel.tsx +2 -2
- package/src/captions/components/CaptionPropertyPanel.tsx +2 -2
- package/src/captions/components/CaptionTimeline.tsx +3 -3
- package/src/captions/keyboard.test.ts +6 -0
- package/src/captions/keyboard.ts +2 -1
- package/src/components/AskAgentModal.tsx +1 -1
- package/src/components/LintModal.tsx +35 -35
- package/src/components/StudioErrorBoundary.tsx +1 -1
- package/src/components/StudioHeader.dom.test.tsx +35 -13
- package/src/components/StudioHeader.tsx +6 -2
- package/src/components/StudioToast.tsx +7 -16
- package/src/components/ThemeToggle.tsx +99 -0
- package/src/components/TimelineToolPicker.tsx +1 -1
- package/src/components/TimelineToolbar.tsx +11 -11
- package/src/components/dock/Dock.tsx +1 -1
- package/src/components/dock/dock.css +9 -1
- package/src/components/editor/AnimationCard.tsx +5 -5
- package/src/components/editor/AnimationCardParts.tsx +1 -1
- package/src/components/editor/BlockParamsPanel.tsx +3 -3
- package/src/components/editor/BorderRadiusEditor.tsx +8 -15
- package/src/components/editor/CanvasContextMenu.tsx +1 -1
- package/src/components/editor/ComputedTweenNotice.tsx +1 -1
- package/src/components/editor/DomEditOverlay.tsx +37 -5
- package/src/components/editor/DomEditRotateHandle.tsx +1 -1
- package/src/components/editor/EaseCurveSection.tsx +14 -19
- package/src/components/editor/EaseModeControls.tsx +2 -2
- package/src/components/editor/EaseParamFields.tsx +8 -8
- package/src/components/editor/FileTree.tsx +1 -1
- package/src/components/editor/FileTreeIcons.tsx +47 -26
- package/src/components/editor/FileTreeNodes.tsx +11 -8
- package/src/components/editor/GestureRecordControl.tsx +3 -3
- package/src/components/editor/GsapAddAnimationControl.tsx +1 -1
- package/src/components/editor/GsapAnimationSection.tsx +2 -2
- package/src/components/editor/InlineTextToolbar.tsx +4 -4
- package/src/components/editor/InspectorHeaderActions.tsx +1 -1
- package/src/components/editor/KeyframeDiamond.tsx +6 -4
- package/src/components/editor/KeyframeNavigation.tsx +4 -4
- package/src/components/editor/LayersPanel.tsx +3 -3
- package/src/components/editor/PromotableControl.tsx +2 -2
- package/src/components/editor/PropertyPanelEmptyState.tsx +4 -4
- package/src/components/editor/PropertyPanelFlatFooter.tsx +2 -2
- package/src/components/editor/PropertyPanelFlatHeader.test.tsx +1 -1
- package/src/components/editor/PropertyPanelFlatHeader.tsx +2 -2
- package/src/components/editor/SnapToolbar.tsx +5 -5
- package/src/components/editor/SourceEditor.tsx +2 -6
- package/src/components/editor/TimelineFxPopover.tsx +4 -4
- package/src/components/editor/Transform3DCube.tsx +1 -1
- package/src/components/editor/anchoredResizeReleaseShift.test.ts +0 -84
- package/src/components/editor/domEditOverlayStartGesture.ts +127 -115
- package/src/components/editor/easeCurveSvg.tsx +1 -1
- package/src/components/editor/gestureHoldsPreviewReload.test.tsx +275 -0
- package/src/components/editor/manualEditsDom.ts +36 -3
- package/src/components/editor/manualEditsDomPatches.test.ts +2 -86
- package/src/components/editor/manualEditsDomPatches.ts +0 -30
- package/src/components/editor/manualEditsSnapshot.ts +1 -0
- package/src/components/editor/marqueeCommit.test.tsx +69 -1
- package/src/components/editor/marqueeCommit.ts +6 -1
- package/src/components/editor/overlayFrameLoop.test.ts +27 -0
- package/src/components/editor/overlayFrameLoop.ts +21 -0
- package/src/components/editor/persistSeam.integration.test.ts +0 -18
- package/src/components/editor/plainTranslate.ts +34 -3
- package/src/components/editor/propertyPanelColorCurveGraph.tsx +2 -2
- package/src/components/editor/propertyPanelColorGradingControls.tsx +1 -1
- package/src/components/editor/propertyPanelColorGradingSection.tsx +8 -8
- package/src/components/editor/propertyPanelColorGradingSlider.tsx +1 -1
- package/src/components/editor/propertyPanelColorSecondary.tsx +4 -4
- package/src/components/editor/propertyPanelFill.tsx +6 -6
- package/src/components/editor/propertyPanelFlatColorGradingAccessory.tsx +3 -3
- package/src/components/editor/propertyPanelFlatColorGradingSection.test.tsx +1 -1
- package/src/components/editor/propertyPanelFlatColorGradingSection.tsx +6 -9
- package/src/components/editor/propertyPanelFlatEffectsSection.tsx +4 -4
- package/src/components/editor/propertyPanelFlatMediaSection.tsx +2 -2
- package/src/components/editor/propertyPanelFlatMotionSection.tsx +2 -2
- package/src/components/editor/propertyPanelFlatOverlaysSection.tsx +2 -2
- package/src/components/editor/propertyPanelFlatPrimitives.test.tsx +4 -3
- package/src/components/editor/propertyPanelFlatPrimitives.tsx +1 -1
- package/src/components/editor/propertyPanelFlatSliderReadout.tsx +1 -1
- package/src/components/editor/propertyPanelFont.test.tsx +80 -3
- package/src/components/editor/propertyPanelFont.tsx +71 -48
- package/src/components/editor/propertyPanelFxCarveModule.tsx +1 -1
- package/src/components/editor/propertyPanelFxControls.tsx +2 -2
- package/src/components/editor/propertyPanelFxEqModule.tsx +2 -2
- package/src/components/editor/propertyPanelFxNodeRow.tsx +1 -1
- package/src/components/editor/propertyPanelFxPresetRun.tsx +1 -1
- package/src/components/editor/propertyPanelHelpers.ts +1 -2
- package/src/components/editor/propertyPanelMediaSection.tsx +1 -1
- package/src/components/editor/propertyPanelPrimitives.tsx +1 -1
- package/src/components/editor/propertyPanelSections.tsx +2 -2
- package/src/components/editor/propertyPanelValueTier.test.ts +1 -1
- package/src/components/editor/propertyPanelValueTier.ts +1 -1
- package/src/components/editor/studioCodeTheme.test.ts +20 -0
- package/src/components/editor/studioCodeTheme.ts +99 -0
- package/src/components/editor/useDomEditNudge.test.tsx +31 -0
- package/src/components/editor/useDomEditNudge.ts +2 -2
- package/src/components/editor/useInlineTextEditing.tsx +1 -1
- package/src/components/editor/useMotionPathData.test.tsx +64 -0
- package/src/components/editor/useMotionPathData.ts +5 -1
- package/src/components/feedback/StudioFeedbackCard.tsx +11 -23
- package/src/components/nle/AssetPreviewOverlay.tsx +1 -1
- package/src/components/nle/AudioMeterStrip.tsx +2 -2
- package/src/components/nle/NLEPreview.test.ts +8 -4
- package/src/components/nle/PreviewOverlays.tsx +1 -1
- package/src/components/nle/PreviewPane.tsx +3 -3
- package/src/components/nle/PreviewPoster.tsx +29 -1
- package/src/components/nle/PreviewZoomOverlay.tsx +1 -1
- package/src/components/panels/SlideshowPanel.tsx +4 -4
- package/src/components/panels/SlideshowSubPanels.tsx +19 -21
- package/src/components/panels/VariablesBindElement.tsx +1 -1
- package/src/components/panels/VariablesDeclarationForm.tsx +1 -1
- package/src/components/panels/VariablesPanel.tsx +4 -6
- package/src/components/panels/VariablesRowAction.tsx +1 -1
- package/src/components/renders/FfmpegRequiredNotice.tsx +4 -4
- package/src/components/renders/RenderQueue.tsx +2 -2
- package/src/components/renders/RenderQueueItem.tsx +3 -3
- package/src/components/sidebar/AssetCard.tsx +3 -3
- package/src/components/sidebar/AssetContextMenu.tsx +7 -7
- package/src/components/sidebar/AssetsTab.tsx +6 -6
- package/src/components/sidebar/AudioRow.tsx +5 -3
- package/src/components/sidebar/BlocksTab.tsx +6 -6
- package/src/components/sidebar/CompositionsTab.drag.test.tsx +3 -69
- package/src/components/sidebar/CompositionsTab.rootBadge.test.tsx +4 -50
- package/src/components/sidebar/CompositionsTab.thumbnails.test.tsx +256 -0
- package/src/components/sidebar/CompositionsTab.tsx +65 -12
- package/src/components/sidebar/PromptPreviewModal.tsx +3 -3
- package/src/components/sidebar/SidebarLintButton.tsx +3 -3
- package/src/components/sidebar/compositionsTabTestUtils.tsx +39 -0
- package/src/components/timelineToolbarStyles.ts +3 -3
- package/src/components/ui/Button.tsx +4 -4
- package/src/components/ui/HyperframesLogo.tsx +19 -18
- package/src/components/ui/Input.tsx +1 -1
- package/src/components/ui/Menu.tsx +4 -2
- package/src/hooks/appHotkeysDispatch.ts +2 -1
- package/src/hooks/elementOffsetStager.ts +8 -7
- package/src/hooks/previewPromotionSelection.test.tsx +180 -0
- package/src/hooks/useAppHotkeys.test.ts +32 -0
- package/src/hooks/useAppHotkeys.ts +1 -2
- package/src/hooks/useClipboard.source.test.ts +141 -23
- package/src/hooks/useClipboard.ts +154 -123
- package/src/hooks/useDomEditCommits.ts +0 -3
- package/src/hooks/useDomEditSession.test.tsx +76 -72
- package/src/hooks/useDomEditSession.ts +7 -43
- package/src/hooks/useDomGeometryCommit.test.tsx +4 -1
- package/src/hooks/useDomGeometryCommit.ts +0 -1
- package/src/hooks/useDomGeometryCommits.test.tsx +177 -32
- package/src/hooks/useDomGeometryCommits.ts +4 -49
- package/src/hooks/useGsapTweenCache.ts +1 -2
- package/src/hooks/useInlineTextEdit.ts +16 -2
- package/src/hooks/useLivePreviewIframe.ts +18 -0
- package/src/hooks/usePreviewPersistence.history.test.tsx +47 -0
- package/src/hooks/usePreviewPersistence.ts +2 -5
- package/src/hooks/useRenderClipContent.ts +3 -3
- package/src/index.ts +2 -0
- package/src/livePreviewIframeExport.test.tsx +33 -0
- package/src/main.tsx +4 -1
- package/src/player/components/AudioWaveform.test.tsx +36 -15
- package/src/player/components/AudioWaveform.tsx +24 -7
- package/src/player/components/ClipContextMenu.tsx +1 -1
- package/src/player/components/CompositionThumbnail.tsx +23 -11
- package/src/player/components/EditModal.tsx +6 -6
- package/src/player/components/KeyframeDiamondContextMenu.tsx +2 -2
- package/src/player/components/PlayerControls.tsx +3 -3
- package/src/player/components/ShortcutsPanel.tsx +8 -20
- package/src/player/components/TimelineCanvas.tsx +1 -1
- package/src/player/components/TimelineClip.test.tsx +38 -0
- package/src/player/components/TimelineClip.tsx +16 -8
- package/src/player/components/TimelineClipFades.test.tsx +376 -74
- package/src/player/components/TimelineClipFades.tsx +368 -137
- package/src/player/components/TimelineEmptyState.tsx +2 -2
- package/src/player/components/TimelineLanes.tsx +0 -1
- package/src/player/components/TrackClipCount.tsx +1 -1
- package/src/player/components/VolumeControl.tsx +1 -1
- package/src/player/components/timelineClipChildren.tsx +1 -1
- package/src/player/components/timelineMotionStyles.test.ts +6 -22
- package/src/player/components/trackHeaderLabelRows.tsx +16 -16
- package/src/player/components/useTimelineKeyboardActor.ts +1 -1
- package/src/player/hooks/timelinePlayerTestHarness.ts +27 -0
- package/src/player/hooks/useShadowPreviewReload.ts +72 -5
- package/src/player/lib/automationStoreSync.test.ts +18 -1
- package/src/player/lib/automationStoreSync.ts +29 -17
- package/src/player/lib/thumbnailScheduler.ts +4 -5
- package/src/player/lib/timelineViewportBudgets.test.ts +1 -1
- package/src/player/lib/timelineViewportBudgets.ts +1 -1
- package/src/player/sceneSwap.ts +18 -0
- package/src/styles/components.css +26 -0
- package/src/styles/hex-baseline.json +7 -19
- package/src/styles/studio.css +4 -7
- package/src/styles/tailwind-preset.shared.js +2 -1
- package/src/styles/theme.css +214 -147
- package/src/styles/theme.test.ts +13 -9
- package/src/utils/blockCategories.ts +41 -9
- package/src/utils/clipboardPayload.ts +12 -1
- package/src/utils/studioPendingEdits.ts +21 -0
- package/src/utils/studioTheme.test.tsx +141 -0
- package/src/utils/studioTheme.ts +34 -0
- package/src/utils/studioUiPreferences.ts +3 -0
- package/src/utils/typingTarget.test.ts +8 -1
- package/src/utils/typingTarget.ts +5 -0
- package/dist/assets/index-CQN3M56N.css +0 -1
- package/dist/assets/index-De2aO4XM.js +0 -399
- package/dist/chunk-62DDWOSH.js.map +0 -1
- package/dist/chunk-6GAO75IU.js.map +0 -1
- package/dist/chunk-HIGHVWYD.js.map +0 -1
- package/src/hooks/gsapTargetCache.ts +0 -62
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
@import "dockview-react/dist/styles/dockview.css";
|
|
2
2
|
|
|
3
|
+
.dv-shell:has(.hf-dock) {
|
|
4
|
+
/* dockview's runtime stylesheet pins `color-scheme: dark` on its theme class, which sits on
|
|
5
|
+
* the shell around the dock. */
|
|
6
|
+
color-scheme: inherit;
|
|
7
|
+
}
|
|
8
|
+
|
|
3
9
|
.hf-dock {
|
|
4
10
|
--dv-group-view-background-color: var(--panel-card-bg);
|
|
5
11
|
--dv-tabs-and-actions-container-background-color: var(--panel-strip-bg);
|
|
6
12
|
--dv-tabs-and-actions-container-height: 36px;
|
|
13
|
+
--dv-icon-hover-background-color: var(--color-hover);
|
|
14
|
+
--dv-tabs-container-scrollbar-color: var(--color-text-muted);
|
|
7
15
|
/* The fill paints the shown tab, so every tab state is transparent. */
|
|
8
16
|
--dv-activegroup-visiblepanel-tab-background-color: transparent;
|
|
9
17
|
--dv-activegroup-hiddenpanel-tab-background-color: transparent;
|
|
@@ -178,7 +186,7 @@
|
|
|
178
186
|
}
|
|
179
187
|
|
|
180
188
|
.dv-groupview:hover .hf-dock-strip-actions {
|
|
181
|
-
opacity: 0.
|
|
189
|
+
opacity: 0.8;
|
|
182
190
|
}
|
|
183
191
|
|
|
184
192
|
.hf-dock-strip-actions:hover,
|
|
@@ -201,7 +201,7 @@ export const AnimationCard = memo(function AnimationCard({
|
|
|
201
201
|
className="flex w-full items-center gap-2 py-1.5 active:scale-[0.99]"
|
|
202
202
|
>
|
|
203
203
|
<span
|
|
204
|
-
className="rounded-sm bg-panel-accent/10 px-1.5 py-0.5 text-[10px] font-semibold text-
|
|
204
|
+
className="rounded-sm bg-panel-accent/10 px-1.5 py-0.5 text-[10px] font-semibold text-accent-ink"
|
|
205
205
|
title={METHOD_TOOLTIPS[animation.method]}
|
|
206
206
|
>
|
|
207
207
|
{methodLabel}
|
|
@@ -354,7 +354,7 @@ export const AnimationCard = memo(function AnimationCard({
|
|
|
354
354
|
|
|
355
355
|
{animation.method === "fromTo" && (
|
|
356
356
|
<div className="space-y-1">
|
|
357
|
-
<p className="text-[9px] font-semibold uppercase tracking-wider text-
|
|
357
|
+
<p className="text-[9px] font-semibold uppercase tracking-wider text-warning-ink">
|
|
358
358
|
From
|
|
359
359
|
</p>
|
|
360
360
|
<div className="space-y-1.5">
|
|
@@ -378,14 +378,14 @@ export const AnimationCard = memo(function AnimationCard({
|
|
|
378
378
|
onAdd={(prop) => onAddFromProperty?.(animation.id, prop)}
|
|
379
379
|
onOpen={() => setAddingFromProp(true)}
|
|
380
380
|
onClose={() => setAddingFromProp(false)}
|
|
381
|
-
buttonClassName="text-[11px] font-medium text-
|
|
381
|
+
buttonClassName="text-[11px] font-medium text-warning-ink transition-colors hover:text-text-0"
|
|
382
382
|
/>
|
|
383
383
|
</div>
|
|
384
384
|
</div>
|
|
385
385
|
)}
|
|
386
386
|
|
|
387
387
|
{animation.method === "fromTo" && Object.keys(animation.properties).length > 0 && (
|
|
388
|
-
<p className="text-[9px] font-semibold uppercase tracking-wider text-
|
|
388
|
+
<p className="text-[9px] font-semibold uppercase tracking-wider text-accent-ink">
|
|
389
389
|
To
|
|
390
390
|
</p>
|
|
391
391
|
)}
|
|
@@ -455,7 +455,7 @@ export const AnimationCard = memo(function AnimationCard({
|
|
|
455
455
|
<button
|
|
456
456
|
type="button"
|
|
457
457
|
onClick={() => onDeleteAnimation(animation.id)}
|
|
458
|
-
className="ml-auto text-[11px] font-medium text-
|
|
458
|
+
className="ml-auto text-[11px] font-medium rounded-sm text-danger-ink transition-colors hover:bg-danger/15"
|
|
459
459
|
title="Remove this animation"
|
|
460
460
|
>
|
|
461
461
|
Remove
|
|
@@ -46,7 +46,7 @@ function RemoveButton({ onClick, title }: { onClick: () => void; title: string }
|
|
|
46
46
|
<button
|
|
47
47
|
type="button"
|
|
48
48
|
onClick={onClick}
|
|
49
|
-
className="relative shrink-0 rounded-sm p-1.5 text-neutral-600 transition-colors hover:bg-neutral-800 hover:text-
|
|
49
|
+
className="relative shrink-0 rounded-sm p-1.5 text-neutral-600 transition-colors hover:bg-neutral-800 hover:text-danger-ink active:scale-[0.95]"
|
|
50
50
|
title={title}
|
|
51
51
|
aria-label={title}
|
|
52
52
|
>
|
|
@@ -170,7 +170,7 @@ export const BlockParamsPanel = memo(function BlockParamsPanel({
|
|
|
170
170
|
<div className="text-[10px] text-neutral-500">This block has no editable parameters.</div>
|
|
171
171
|
)}
|
|
172
172
|
{!fileManager && params.length > 0 && (
|
|
173
|
-
<div className="text-[10px] text-
|
|
173
|
+
<div className="text-[10px] text-warning-ink">
|
|
174
174
|
Block params can't be edited here — no project file access.
|
|
175
175
|
</div>
|
|
176
176
|
)}
|
|
@@ -189,12 +189,12 @@ export const BlockParamsPanel = memo(function BlockParamsPanel({
|
|
|
189
189
|
</div>
|
|
190
190
|
)}
|
|
191
191
|
{commitState.tone === "saved" && (
|
|
192
|
-
<div className="text-[10px] text-
|
|
192
|
+
<div className="text-[10px] text-accent-ink" role="status">
|
|
193
193
|
Saved to {compositionPath}
|
|
194
194
|
</div>
|
|
195
195
|
)}
|
|
196
196
|
{commitState.tone === "error" && (
|
|
197
|
-
<div className="text-[10px] text-
|
|
197
|
+
<div className="text-[10px] text-danger-ink" role="alert">
|
|
198
198
|
{commitState.message}
|
|
199
199
|
</div>
|
|
200
200
|
)}
|
|
@@ -63,6 +63,9 @@ export function BorderRadiusEditor({
|
|
|
63
63
|
}, [linked, uniform, tl, onCommit]);
|
|
64
64
|
|
|
65
65
|
const path = buildRoundedRectPath(PREVIEW_W, PREVIEW_H, sTL, sTR, sBR, sBL);
|
|
66
|
+
const dotFill = linked
|
|
67
|
+
? "fill-[light-dark(oklch(0.5_0.2_260),#3b82f6)]"
|
|
68
|
+
: "fill-[light-dark(oklch(0.55_0.2_294),#a78bfa)]";
|
|
66
69
|
|
|
67
70
|
return (
|
|
68
71
|
<div className="space-y-3">
|
|
@@ -73,21 +76,11 @@ export function BorderRadiusEditor({
|
|
|
73
76
|
viewBox={`0 0 ${PREVIEW_W} ${PREVIEW_H}`}
|
|
74
77
|
className="shrink-0"
|
|
75
78
|
>
|
|
76
|
-
<path
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
/>
|
|
82
|
-
<circle cx={sTL} cy={sTL} r={3} fill={linked ? "#3b82f6" : "#a78bfa"} />
|
|
83
|
-
<circle cx={PREVIEW_W - sTR} cy={sTR} r={3} fill={linked ? "#3b82f6" : "#a78bfa"} />
|
|
84
|
-
<circle
|
|
85
|
-
cx={PREVIEW_W - sBR}
|
|
86
|
-
cy={PREVIEW_H - sBR}
|
|
87
|
-
r={3}
|
|
88
|
-
fill={linked ? "#3b82f6" : "#a78bfa"}
|
|
89
|
-
/>
|
|
90
|
-
<circle cx={sBL} cy={PREVIEW_H - sBL} r={3} fill={linked ? "#3b82f6" : "#a78bfa"} />
|
|
79
|
+
<path d={path} className="fill-text-0/[0.06] stroke-text-0/25" strokeWidth={1.5} />
|
|
80
|
+
<circle cx={sTL} cy={sTL} r={3} className={dotFill} />
|
|
81
|
+
<circle cx={PREVIEW_W - sTR} cy={sTR} r={3} className={dotFill} />
|
|
82
|
+
<circle cx={PREVIEW_W - sBR} cy={PREVIEW_H - sBR} r={3} className={dotFill} />
|
|
83
|
+
<circle cx={sBL} cy={PREVIEW_H - sBL} r={3} className={dotFill} />
|
|
91
84
|
</svg>
|
|
92
85
|
|
|
93
86
|
<button
|
|
@@ -257,7 +257,7 @@ export const CanvasContextMenu = memo(function CanvasContextMenu({
|
|
|
257
257
|
{hasDelete && (
|
|
258
258
|
<button
|
|
259
259
|
type="button"
|
|
260
|
-
className="w-full flex items-center justify-between px-3 py-1.5 text-xs text-
|
|
260
|
+
className="w-full flex items-center justify-between px-3 py-1.5 text-xs text-danger-ink hover:bg-neutral-800 cursor-pointer text-left"
|
|
261
261
|
onPointerDown={(e) => {
|
|
262
262
|
if (e.button !== 0) return;
|
|
263
263
|
e.preventDefault();
|
|
@@ -29,7 +29,7 @@ export function ComputedTweenNotice({
|
|
|
29
29
|
<button
|
|
30
30
|
type="button"
|
|
31
31
|
onClick={onUnroll}
|
|
32
|
-
className="shrink-0 rounded-sm px-1.5 py-0.5 text-[9px] font-medium text-
|
|
32
|
+
className="shrink-0 rounded-sm px-1.5 py-0.5 text-[9px] font-medium text-accent-ink hover:bg-neutral-800"
|
|
33
33
|
title="Rewrite the helper/loop into explicit tweens so this keyframe edits directly"
|
|
34
34
|
>
|
|
35
35
|
Unroll to edit
|
|
@@ -31,6 +31,8 @@ import { useDomEditCompositionRect } from "./useDomEditCompositionRect";
|
|
|
31
31
|
import { CanvasContextMenu } from "./CanvasContextMenu";
|
|
32
32
|
import { useInlineTextEditing } from "./useInlineTextEditing";
|
|
33
33
|
import { usePreviewReadOnly } from "./previewReadOnlyContext";
|
|
34
|
+
import { useMountEffect } from "../../hooks/useMountEffect";
|
|
35
|
+
import { noteStudioManualEditSave as noteSave } from "./manualEditsDom";
|
|
34
36
|
import type { ZOrderAction, ZOrderPatch } from "./canvasContextMenuZOrder";
|
|
35
37
|
import { getPreviewTargetFromPointer } from "../../utils/studioPreviewHelpers";
|
|
36
38
|
import { logSelect } from "../../utils/selectDebug";
|
|
@@ -193,16 +195,34 @@ export const DomEditOverlay = memo(function DomEditOverlay({
|
|
|
193
195
|
onTextEditingChangeRef.current?.(true);
|
|
194
196
|
return () => onTextEditingChangeRef.current?.(false);
|
|
195
197
|
}, [inlineText.editing]);
|
|
198
|
+
// Each canvas save is counted before it starts, so a reload requested earlier loads again.
|
|
196
199
|
const onPathOffsetCommitRef = useRef(onPathOffsetCommit);
|
|
197
|
-
onPathOffsetCommitRef.current =
|
|
200
|
+
onPathOffsetCommitRef.current = (sel, ...rest) => {
|
|
201
|
+
noteSave(sel.element);
|
|
202
|
+
return onPathOffsetCommit(sel, ...rest);
|
|
203
|
+
};
|
|
198
204
|
const onGroupPathOffsetCommitRef = useRef(onGroupPathOffsetCommit);
|
|
199
|
-
onGroupPathOffsetCommitRef.current =
|
|
205
|
+
onGroupPathOffsetCommitRef.current = (updates) => {
|
|
206
|
+
if (updates[0]) noteSave(updates[0].selection.element);
|
|
207
|
+
return onGroupPathOffsetCommit(updates);
|
|
208
|
+
};
|
|
200
209
|
const onBoxSizeCommitRef = useRef(onBoxSizeCommit);
|
|
201
|
-
onBoxSizeCommitRef.current =
|
|
210
|
+
onBoxSizeCommitRef.current = (sel, ...rest) => {
|
|
211
|
+
noteSave(sel.element);
|
|
212
|
+
return onBoxSizeCommit(sel, ...rest);
|
|
213
|
+
};
|
|
202
214
|
const onRotationCommitRef = useRef(onRotationCommit);
|
|
203
|
-
onRotationCommitRef.current =
|
|
215
|
+
onRotationCommitRef.current = (sel, next) => {
|
|
216
|
+
noteSave(sel.element);
|
|
217
|
+
return onRotationCommit(sel, next);
|
|
218
|
+
};
|
|
204
219
|
const onStyleCommitRef = useRef(onStyleCommit);
|
|
205
|
-
onStyleCommitRef.current =
|
|
220
|
+
onStyleCommitRef.current =
|
|
221
|
+
onStyleCommit &&
|
|
222
|
+
((property, value) => {
|
|
223
|
+
if (selectionRef.current) noteSave(selectionRef.current.element);
|
|
224
|
+
return onStyleCommit(property, value);
|
|
225
|
+
});
|
|
206
226
|
const onBlockedMoveRef = useRef(onBlockedMove);
|
|
207
227
|
onBlockedMoveRef.current = onBlockedMove;
|
|
208
228
|
const onManualDragStartRef = useRef(onManualDragStart);
|
|
@@ -282,6 +302,17 @@ export const DomEditOverlay = memo(function DomEditOverlay({
|
|
|
282
302
|
useEffect(() => {
|
|
283
303
|
if (readOnly) gestures.clearPointerState(selectionRef);
|
|
284
304
|
}, [gestures, readOnly, selectionRef]);
|
|
305
|
+
// A gesture that loses its pointer, the window or the overlay is cancelled, so its mark goes too.
|
|
306
|
+
const cancelGestureRef = useRef(() => {});
|
|
307
|
+
cancelGestureRef.current = () => gestures.clearPointerState(selectionRef);
|
|
308
|
+
useMountEffect(() => {
|
|
309
|
+
const cancel = () => cancelGestureRef.current();
|
|
310
|
+
window.addEventListener("blur", cancel);
|
|
311
|
+
return () => {
|
|
312
|
+
window.removeEventListener("blur", cancel);
|
|
313
|
+
cancel();
|
|
314
|
+
};
|
|
315
|
+
});
|
|
285
316
|
|
|
286
317
|
// Arrow-key nudge (1px, Shift = 10px) — commits through the same
|
|
287
318
|
// path-offset callbacks as a drag, one undo entry per key burst.
|
|
@@ -463,6 +494,7 @@ export const DomEditOverlay = memo(function DomEditOverlay({
|
|
|
463
494
|
onPointerLeave={() => onCanvasPointerLeaveRef.current()}
|
|
464
495
|
onPointerUp={marquee.onPointerUp}
|
|
465
496
|
onPointerCancel={marquee.onPointerCancel}
|
|
497
|
+
onLostPointerCapture={() => cancelGestureRef.current()}
|
|
466
498
|
onContextMenu={hostInput ? undefined : handleContextMenu}
|
|
467
499
|
>
|
|
468
500
|
{!hostInput && hoverSelection && hoverRect && compRect.width > 0 && (
|
|
@@ -38,7 +38,7 @@ export function DomEditRotateHandle({
|
|
|
38
38
|
aria-label="Rotate selection"
|
|
39
39
|
onPointerDown={onStartRotate}
|
|
40
40
|
>
|
|
41
|
-
<span className="pointer-events-none flex h-[18px] w-[18px] items-center justify-center rounded-full border border-studio-accent/70 bg-studio-surface text-
|
|
41
|
+
<span className="pointer-events-none flex h-[18px] w-[18px] items-center justify-center rounded-full border border-studio-accent/70 bg-studio-surface text-accent-ink shadow-[0_0_3px_rgba(0,0,0,0.45)]">
|
|
42
42
|
<svg
|
|
43
43
|
width="11"
|
|
44
44
|
height="11"
|
|
@@ -42,7 +42,6 @@ const VMIN = -HR / S; // bottom of visible view (undershoot headroom)
|
|
|
42
42
|
// of snapping to the view edge; the handle DOT is still clampView'd into view.
|
|
43
43
|
const DRAG_VMAX = 2;
|
|
44
44
|
const DRAG_VMIN = -1;
|
|
45
|
-
const ACCENT = "#3CE6AC";
|
|
46
45
|
|
|
47
46
|
// Figma-style ease-type dropdown: the current ease (glyph + name) as a button
|
|
48
47
|
// that opens the preset grid in a popover. This is where a preset is selected —
|
|
@@ -130,7 +129,7 @@ function EaseTypeDropdown({
|
|
|
130
129
|
aria-expanded={open}
|
|
131
130
|
aria-controls={open ? menuId : undefined}
|
|
132
131
|
onClick={() => setOpen((prev) => !prev)}
|
|
133
|
-
className="flex w-full items-center gap-2 rounded-md border border-
|
|
132
|
+
className="flex w-full items-center gap-2 rounded-md border border-border bg-input px-2 py-1.5 text-left transition-colors hover:border-border-strong"
|
|
134
133
|
>
|
|
135
134
|
<MiniCurveSvg ease={ease} active size={16} />
|
|
136
135
|
<span className="text-[11px] text-neutral-200">{label}</span>
|
|
@@ -151,7 +150,7 @@ function EaseTypeDropdown({
|
|
|
151
150
|
role="menu"
|
|
152
151
|
aria-label={`${MODE_LABELS[kind]} ease presets`}
|
|
153
152
|
onKeyDown={handleMenuKeyDown}
|
|
154
|
-
className="absolute inset-x-0 top-full z-20 mt-1 rounded-md border border-
|
|
153
|
+
className="absolute inset-x-0 top-full z-20 mt-1 rounded-md border border-border bg-raised p-2 shadow-xl"
|
|
155
154
|
>
|
|
156
155
|
<EasePresetGrid
|
|
157
156
|
kind={kind}
|
|
@@ -407,7 +406,7 @@ export function EaseCurveSection({
|
|
|
407
406
|
{showGraph ? (
|
|
408
407
|
<>
|
|
409
408
|
<div
|
|
410
|
-
className="mx-auto overflow-hidden rounded-md border border-
|
|
409
|
+
className="mx-auto overflow-hidden rounded-md border border-border bg-input"
|
|
411
410
|
style={{ aspectRatio: `${SVGW} / ${SVGH}`, width: "100%", maxWidth: 230 }}
|
|
412
411
|
>
|
|
413
412
|
<svg
|
|
@@ -429,7 +428,7 @@ export function EaseCurveSection({
|
|
|
429
428
|
y1={top}
|
|
430
429
|
x2={xToSvg(q)}
|
|
431
430
|
y2={bottom}
|
|
432
|
-
|
|
431
|
+
className="stroke-text-0"
|
|
433
432
|
strokeOpacity="0.05"
|
|
434
433
|
strokeWidth="1"
|
|
435
434
|
/>
|
|
@@ -441,7 +440,7 @@ export function EaseCurveSection({
|
|
|
441
440
|
y1={yToSvg(q)}
|
|
442
441
|
x2={right}
|
|
443
442
|
y2={yToSvg(q)}
|
|
444
|
-
|
|
443
|
+
className="stroke-text-0"
|
|
445
444
|
strokeOpacity="0.05"
|
|
446
445
|
strokeWidth="1"
|
|
447
446
|
/>
|
|
@@ -453,7 +452,7 @@ export function EaseCurveSection({
|
|
|
453
452
|
width={S}
|
|
454
453
|
height={bottom - top}
|
|
455
454
|
fill="none"
|
|
456
|
-
|
|
455
|
+
className="stroke-text-0"
|
|
457
456
|
strokeOpacity="0.1"
|
|
458
457
|
strokeWidth="1"
|
|
459
458
|
/>
|
|
@@ -463,7 +462,7 @@ export function EaseCurveSection({
|
|
|
463
462
|
y1={a0.y}
|
|
464
463
|
x2={a1.x}
|
|
465
464
|
y2={a1.y}
|
|
466
|
-
|
|
465
|
+
className="stroke-text-0"
|
|
467
466
|
strokeOpacity="0.08"
|
|
468
467
|
strokeWidth="1"
|
|
469
468
|
strokeDasharray="3 4"
|
|
@@ -476,8 +475,7 @@ export function EaseCurveSection({
|
|
|
476
475
|
y1={a0.y}
|
|
477
476
|
x2={p1.x}
|
|
478
477
|
y2={p1.y}
|
|
479
|
-
stroke
|
|
480
|
-
strokeOpacity="0.5"
|
|
478
|
+
className="stroke-accent-ink"
|
|
481
479
|
strokeWidth="1.5"
|
|
482
480
|
/>
|
|
483
481
|
<line
|
|
@@ -485,8 +483,7 @@ export function EaseCurveSection({
|
|
|
485
483
|
y1={a1.y}
|
|
486
484
|
x2={p2.x}
|
|
487
485
|
y2={p2.y}
|
|
488
|
-
stroke
|
|
489
|
-
strokeOpacity="0.5"
|
|
486
|
+
className="stroke-accent-ink"
|
|
490
487
|
strokeWidth="1.5"
|
|
491
488
|
/>
|
|
492
489
|
</>
|
|
@@ -495,13 +492,13 @@ export function EaseCurveSection({
|
|
|
495
492
|
<path
|
|
496
493
|
d={curvePath}
|
|
497
494
|
fill="none"
|
|
498
|
-
stroke
|
|
495
|
+
className="stroke-accent-ink"
|
|
499
496
|
strokeWidth="2.5"
|
|
500
497
|
strokeLinecap="round"
|
|
501
498
|
/>
|
|
502
499
|
{/* Anchors at (0,0) and (1,1) */}
|
|
503
|
-
<circle cx={a0.x} cy={a0.y} r="3" fill
|
|
504
|
-
<circle cx={a1.x} cy={a1.y} r="3" fill
|
|
500
|
+
<circle cx={a0.x} cy={a0.y} r="3" className="fill-accent-ink" />
|
|
501
|
+
<circle cx={a1.x} cy={a1.y} r="3" className="fill-accent-ink" />
|
|
505
502
|
{/* Draggable control handles (large transparent hit area + visible dot) */}
|
|
506
503
|
{showHandles &&
|
|
507
504
|
[["p1", p1] as const, ["p2", p2] as const].map(([key, pt]) => (
|
|
@@ -518,7 +515,7 @@ export function EaseCurveSection({
|
|
|
518
515
|
aria-valuemax={1}
|
|
519
516
|
aria-valuenow={key === "p1" ? x1 : x2}
|
|
520
517
|
aria-valuetext={`x ${key === "p1" ? x1 : x2}, y ${key === "p1" ? y1 : y2}`}
|
|
521
|
-
className="cursor-grab stroke-transparent outline-hidden active:cursor-grabbing focus-visible:stroke-
|
|
518
|
+
className="cursor-grab stroke-transparent outline-hidden active:cursor-grabbing focus-visible:stroke-text-0 focus-visible:stroke-[2px]"
|
|
522
519
|
onPointerDown={(e) => handlePointerDown(key, e)}
|
|
523
520
|
onKeyDown={(event) => handleKeyDown(key, event)}
|
|
524
521
|
onPointerEnter={() => setHover(key)}
|
|
@@ -528,10 +525,8 @@ export function EaseCurveSection({
|
|
|
528
525
|
cx={pt.x}
|
|
529
526
|
cy={pt.y}
|
|
530
527
|
r={hover === key || draggingRef.current === key ? 7 : 5.5}
|
|
531
|
-
fill="#0a0a1a"
|
|
532
|
-
stroke={ACCENT}
|
|
533
528
|
strokeWidth="2.5"
|
|
534
|
-
className="pointer-events-none transition-[r]"
|
|
529
|
+
className="pointer-events-none fill-input stroke-accent-ink transition-[r]"
|
|
535
530
|
/>
|
|
536
531
|
</g>
|
|
537
532
|
))}
|
|
@@ -59,7 +59,7 @@ export const EasePresetGrid = function EasePresetGrid({
|
|
|
59
59
|
<MiniCurveSvg ease={preset.ease} active={isActive} />
|
|
60
60
|
<span
|
|
61
61
|
className={`text-center text-[8px] leading-none ${
|
|
62
|
-
isActive ? "text-
|
|
62
|
+
isActive ? "text-accent-ink" : "text-neutral-500"
|
|
63
63
|
}`}
|
|
64
64
|
>
|
|
65
65
|
{preset.label}
|
|
@@ -80,7 +80,7 @@ export function EaseModeToggle({
|
|
|
80
80
|
}) {
|
|
81
81
|
return (
|
|
82
82
|
<div
|
|
83
|
-
className="mb-2 grid grid-cols-3 rounded-md bg-
|
|
83
|
+
className="mb-2 grid grid-cols-3 rounded-md bg-press p-0.5"
|
|
84
84
|
role="radiogroup"
|
|
85
85
|
aria-label="Ease editor mode"
|
|
86
86
|
>
|
|
@@ -93,17 +93,17 @@ export function EaseBezierField({
|
|
|
93
93
|
}
|
|
94
94
|
}}
|
|
95
95
|
onBlur={(event) => commit(event.currentTarget.value)}
|
|
96
|
-
className={`w-full rounded border bg-
|
|
96
|
+
className={`w-full rounded border bg-input px-1.5 py-1 font-mono text-[10px] text-neutral-300 outline-hidden ${
|
|
97
97
|
error
|
|
98
|
-
? "border-
|
|
99
|
-
: "border-
|
|
98
|
+
? "border-danger-ink/70 focus:border-danger-ink"
|
|
99
|
+
: "border-border focus:border-panel-accent/50"
|
|
100
100
|
}`}
|
|
101
101
|
/>
|
|
102
102
|
</div>
|
|
103
103
|
<p
|
|
104
104
|
id={errorId}
|
|
105
105
|
aria-live="polite"
|
|
106
|
-
className={`mt-1 text-[9px] text-
|
|
106
|
+
className={`mt-1 text-[9px] text-danger-ink ${error ? "" : "sr-only"}`}
|
|
107
107
|
>
|
|
108
108
|
{error ?? "Valid bezier values"}
|
|
109
109
|
</p>
|
|
@@ -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-sm border border-
|
|
186
|
+
className="w-16 rounded-sm border border-border bg-input 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-sm border border-
|
|
210
|
+
className="w-14 rounded-sm border border-border bg-input 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-sm border border-
|
|
223
|
+
className="rounded-sm border border-border bg-input 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-sm border border-
|
|
241
|
+
className="w-16 rounded-sm border border-border bg-input 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>
|
|
@@ -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-
|
|
266
|
+
? "bg-accent/5 outline-solid outline-1 outline-accent-ink -outline-offset-1"
|
|
267
267
|
: ""
|
|
268
268
|
}`}
|
|
269
269
|
onContextMenu={handleRootContextMenu}
|
|
@@ -21,34 +21,55 @@ import {
|
|
|
21
21
|
const SZ = 14;
|
|
22
22
|
const W = "duotone" as const;
|
|
23
23
|
|
|
24
|
+
type Icon = typeof File;
|
|
25
|
+
// Brand hues; each light half is darkened to hold 3:1 on a hovered or selected row.
|
|
26
|
+
const RED = "text-[light-dark(oklch(0.59_0.194_35),oklch(0.71_0.194_35))]";
|
|
27
|
+
const BLUE = "text-[light-dark(oklch(0.56_0.14_253),oklch(0.69_0.14_253))]";
|
|
28
|
+
const GREEN = "text-[light-dark(oklch(0.53_0.192_150),#22C55E)]";
|
|
29
|
+
const GRAY = "text-[light-dark(oklch(0.56_0.019_261),#9CA3AF)]";
|
|
30
|
+
export const DIM = "text-[light-dark(oklch(0.55_0.023_264),oklch(0.69_0.023_264))]";
|
|
31
|
+
const ICONS = new Map<string, [Icon, string]>([
|
|
32
|
+
["html", [FileHtml, RED]],
|
|
33
|
+
["css", [FileCss, "text-[light-dark(oklch(0.5_0.2_266),oklch(0.71_0.2_266))]"]],
|
|
34
|
+
["js", [FileJs, "text-[light-dark(oklch(0.56_0.156_101),#F0DB4F)]"]],
|
|
35
|
+
["jsx", [FileJsx, "text-[light-dark(oklch(0.55_0.117_219),#61DAFB)]"]],
|
|
36
|
+
["ts", [FileTs, BLUE]],
|
|
37
|
+
["tsx", [FileTsx, BLUE]],
|
|
38
|
+
["json", [FileCode, "text-[light-dark(oklch(0.54_0.182_152),#4ADE80)]"]],
|
|
39
|
+
["svg", [FileSvg, "text-[light-dark(oklch(0.58_0.187_48),#F97316)]"]],
|
|
40
|
+
["md", [FileMd, GRAY]],
|
|
41
|
+
["txt", [FileTxt, GRAY]],
|
|
42
|
+
["png", [FilePng, GREEN]],
|
|
43
|
+
["jpg", [FileJpg, GREEN]],
|
|
44
|
+
["webp", [PhImage, GREEN]],
|
|
45
|
+
["mp4", [FileVideo, "text-[light-dark(oklch(0.59_0.2_304),oklch(0.71_0.2_304))]"]],
|
|
46
|
+
["mp3", [Waveform, "text-[light-dark(oklch(0.53_0.159_165),#3CE6AC)]"]],
|
|
47
|
+
["woff", [TextAa, DIM]],
|
|
48
|
+
]);
|
|
49
|
+
const ALIAS = new Map(
|
|
50
|
+
Object.entries({
|
|
51
|
+
mjs: "js",
|
|
52
|
+
cjs: "js",
|
|
53
|
+
mts: "ts",
|
|
54
|
+
mdx: "md",
|
|
55
|
+
jpeg: "jpg",
|
|
56
|
+
gif: "webp",
|
|
57
|
+
ico: "webp",
|
|
58
|
+
webm: "mp4",
|
|
59
|
+
mov: "mp4",
|
|
60
|
+
wav: "mp3",
|
|
61
|
+
ogg: "mp3",
|
|
62
|
+
m4a: "mp3",
|
|
63
|
+
woff2: "woff",
|
|
64
|
+
ttf: "woff",
|
|
65
|
+
otf: "woff",
|
|
66
|
+
}),
|
|
67
|
+
);
|
|
68
|
+
|
|
24
69
|
export function FileIcon({ path }: { path: string }) {
|
|
25
70
|
const ext = path.split(".").pop()?.toLowerCase() ?? "";
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
if (ext === "css") return <FileCss size={SZ} weight={W} color="#264DE4" className={c} />;
|
|
29
|
-
if (ext === "js" || ext === "mjs" || ext === "cjs")
|
|
30
|
-
return <FileJs size={SZ} weight={W} color="#F0DB4F" className={c} />;
|
|
31
|
-
if (ext === "jsx") return <FileJsx size={SZ} weight={W} color="#61DAFB" className={c} />;
|
|
32
|
-
if (ext === "ts" || ext === "mts")
|
|
33
|
-
return <FileTs size={SZ} weight={W} color="#3178C6" className={c} />;
|
|
34
|
-
if (ext === "tsx") return <FileTsx size={SZ} weight={W} color="#3178C6" className={c} />;
|
|
35
|
-
if (ext === "json") return <FileCode size={SZ} weight={W} color="#4ADE80" className={c} />;
|
|
36
|
-
if (ext === "svg") return <FileSvg size={SZ} weight={W} color="#F97316" className={c} />;
|
|
37
|
-
if (ext === "md" || ext === "mdx")
|
|
38
|
-
return <FileMd size={SZ} weight={W} color="#9CA3AF" className={c} />;
|
|
39
|
-
if (ext === "txt") return <FileTxt size={SZ} weight={W} color="#9CA3AF" className={c} />;
|
|
40
|
-
if (ext === "png") return <FilePng size={SZ} weight={W} color="#22C55E" className={c} />;
|
|
41
|
-
if (ext === "jpg" || ext === "jpeg")
|
|
42
|
-
return <FileJpg size={SZ} weight={W} color="#22C55E" className={c} />;
|
|
43
|
-
if (ext === "webp" || ext === "gif" || ext === "ico")
|
|
44
|
-
return <PhImage size={SZ} weight={W} color="#22C55E" className={c} />;
|
|
45
|
-
if (ext === "mp4" || ext === "webm" || ext === "mov")
|
|
46
|
-
return <FileVideo size={SZ} weight={W} color="#A855F7" className={c} />;
|
|
47
|
-
if (ext === "mp3" || ext === "wav" || ext === "ogg" || ext === "m4a")
|
|
48
|
-
return <Waveform size={SZ} weight={W} color="#3CE6AC" className={c} />;
|
|
49
|
-
if (ext === "woff" || ext === "woff2" || ext === "ttf" || ext === "otf")
|
|
50
|
-
return <TextAa size={SZ} weight={W} color="#6B7280" className={c} />;
|
|
51
|
-
return <File size={SZ} weight={W} color="#6B7280" className={c} />;
|
|
71
|
+
const [Glyph, color] = ICONS.get(ALIAS.get(ext) ?? ext) ?? [File, DIM];
|
|
72
|
+
return <Glyph size={SZ} weight={W} className={`shrink-0 ${color}`} />;
|
|
52
73
|
}
|
|
53
74
|
|
|
54
75
|
// ── Tree Types ──
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
type TreeNode,
|
|
17
17
|
type ContextMenuState,
|
|
18
18
|
type InlineInputState,
|
|
19
|
+
DIM,
|
|
19
20
|
} from "./FileTreeIcons";
|
|
20
21
|
|
|
21
22
|
export type { ContextMenuState, InlineInputState };
|
|
@@ -173,7 +174,7 @@ export function ContextMenu({
|
|
|
173
174
|
<div className="border-t border-neutral-700 my-1" />
|
|
174
175
|
<button
|
|
175
176
|
role="menuitem"
|
|
176
|
-
className="w-full flex items-center gap-2 px-3 py-1.5 text-xs text-
|
|
177
|
+
className="w-full flex items-center gap-2 px-3 py-1.5 text-xs text-danger-ink hover:bg-danger/25 focus-visible:bg-danger/25 active:bg-danger/25 outline-hidden cursor-pointer text-left"
|
|
177
178
|
onClick={() => {
|
|
178
179
|
onDelete(state.targetPath);
|
|
179
180
|
onClose();
|
|
@@ -263,7 +264,7 @@ export function InlineInput({
|
|
|
263
264
|
style={{ paddingLeft: `${8 + depth * 12 + (isFolder ? 0 : 14)}px` }}
|
|
264
265
|
>
|
|
265
266
|
{isFolder ? (
|
|
266
|
-
<FolderSimple size={SZ_ICON} weight="duotone"
|
|
267
|
+
<FolderSimple size={SZ_ICON} weight="duotone" className={`shrink-0 ${DIM}`} />
|
|
267
268
|
) : (
|
|
268
269
|
<FileIcon path={value} />
|
|
269
270
|
)}
|
|
@@ -279,12 +280,12 @@ export function InlineInput({
|
|
|
279
280
|
onBlur={handleBlur}
|
|
280
281
|
aria-invalid={error ? true : undefined}
|
|
281
282
|
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
|
-
error ? "border-
|
|
283
|
+
error ? "border-danger-ink" : "border-neutral-600 focus:border-accent-ink"
|
|
283
284
|
}`}
|
|
284
285
|
spellCheck={false}
|
|
285
286
|
/>
|
|
286
287
|
{error && (
|
|
287
|
-
<div className="mt-0.5 text-[10px] text-
|
|
288
|
+
<div className="mt-0.5 text-[10px] text-danger-ink" role="alert">
|
|
288
289
|
{error}
|
|
289
290
|
</div>
|
|
290
291
|
)}
|
|
@@ -344,13 +345,13 @@ export function DeleteConfirm({
|
|
|
344
345
|
<div className="flex gap-1.5">
|
|
345
346
|
<button
|
|
346
347
|
onClick={onCancel}
|
|
347
|
-
className="flex-1 px-2 py-1 rounded-sm bg-
|
|
348
|
+
className="flex-1 px-2 py-1 rounded-sm bg-hover text-neutral-300 hover:bg-press transition-colors"
|
|
348
349
|
>
|
|
349
350
|
Cancel
|
|
350
351
|
</button>
|
|
351
352
|
<button
|
|
352
353
|
onClick={onConfirm}
|
|
353
|
-
className="flex-1 px-2 py-1 rounded-sm bg-
|
|
354
|
+
className="flex-1 px-2 py-1 rounded-sm bg-danger/15 text-danger-ink hover:bg-danger/25 transition-colors"
|
|
354
355
|
>
|
|
355
356
|
Delete
|
|
356
357
|
</button>
|
|
@@ -419,7 +420,7 @@ export const TreeFile = memo(function TreeFile({
|
|
|
419
420
|
<span className="truncate flex-1">{node.name}</span>
|
|
420
421
|
{lintInfo && lintInfo.count > 0 && (
|
|
421
422
|
<span
|
|
422
|
-
className="shrink-0 min-w-[16px] rounded-full bg-amber-500/20 px-1 text-[8px] font-bold text-
|
|
423
|
+
className="shrink-0 min-w-[16px] rounded-full bg-amber-500/20 px-1 text-[8px] font-bold text-warning-ink text-center mr-1"
|
|
423
424
|
title={lintInfo.messages.join("\n")}
|
|
424
425
|
>
|
|
425
426
|
{lintInfo.count}
|
|
@@ -505,7 +506,9 @@ export const TreeFolder = memo(function TreeFolder({
|
|
|
505
506
|
}}
|
|
506
507
|
onDragLeave={onDragLeave}
|
|
507
508
|
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
|
|
509
|
+
isDragOver
|
|
510
|
+
? "bg-accent/10 outline-solid outline-1 outline-accent-ink -outline-offset-1"
|
|
511
|
+
: ""
|
|
509
512
|
}`}
|
|
510
513
|
style={{ paddingLeft: `${8 + depth * 12}px` }}
|
|
511
514
|
>
|
|
@@ -43,7 +43,7 @@ export function GestureRecordPanelButton({
|
|
|
43
43
|
}}
|
|
44
44
|
className={`w-full flex items-center justify-center gap-2 rounded-lg py-2 text-[11px] font-medium transition-colors ${
|
|
45
45
|
recording
|
|
46
|
-
? "bg-red-500/15 text-
|
|
46
|
+
? "bg-red-500/15 text-danger-ink border border-red-500/30 animate-pulse"
|
|
47
47
|
: "bg-panel-input text-panel-text-2 hover:bg-panel-hover border border-panel-border"
|
|
48
48
|
}`}
|
|
49
49
|
>
|
|
@@ -77,8 +77,8 @@ export function GestureRecordBadge({
|
|
|
77
77
|
title={label}
|
|
78
78
|
className={`pointer-events-auto absolute z-20 flex h-7 w-7 items-center justify-center rounded-full border shadow-lg transition-colors ${
|
|
79
79
|
recording
|
|
80
|
-
? "border-
|
|
81
|
-
: "border-studio-accent/60 bg-neutral-950 text-
|
|
80
|
+
? "border-danger/60 bg-danger text-on-danger animate-pulse"
|
|
81
|
+
: "border-studio-accent/60 bg-neutral-950 text-accent-ink hover:bg-neutral-900"
|
|
82
82
|
}`}
|
|
83
83
|
style={{
|
|
84
84
|
left: Math.max(0, rect.left + rect.width + 8),
|