@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
|
@@ -70,23 +70,20 @@ function HdrBanner({ metadata }: { metadata: MediaMetadata | null }) {
|
|
|
70
70
|
return (
|
|
71
71
|
<div
|
|
72
72
|
data-flat-grade-hdr-banner="true"
|
|
73
|
-
className="rounded-md border border-amber-500/30 bg-amber-500/10 px-2.5 py-1.5 text-[10px] leading-4 text-
|
|
73
|
+
className="rounded-md border border-amber-500/30 bg-amber-500/10 px-2.5 py-1.5 text-[10px] leading-4 text-warning-ink"
|
|
74
74
|
>
|
|
75
75
|
<div className="mb-0.5 flex items-center justify-between gap-2">
|
|
76
76
|
<span className="font-semibold">{metadata.color.label} source</span>
|
|
77
|
-
<span className="rounded-sm bg-amber-
|
|
77
|
+
<span className="rounded-sm bg-amber-500/15 px-1.5 py-0.5 text-[9px] font-semibold uppercase tracking-wide text-warning-ink">
|
|
78
78
|
SDR preview
|
|
79
79
|
</span>
|
|
80
80
|
</div>
|
|
81
|
-
<p className="text-
|
|
81
|
+
<p className="text-text-2">
|
|
82
82
|
These controls use the current SDR shader preview path. Render may stay HDR-tagged, but this
|
|
83
83
|
is not true HDR color grading yet.
|
|
84
84
|
</p>
|
|
85
85
|
{details && (
|
|
86
|
-
<p
|
|
87
|
-
data-flat-grade-hdr-detail="true"
|
|
88
|
-
className="mt-0.5 truncate text-[9px] text-amber-100/55"
|
|
89
|
-
>
|
|
86
|
+
<p data-flat-grade-hdr-detail="true" className="mt-0.5 truncate text-[9px] text-text-muted">
|
|
90
87
|
{details}
|
|
91
88
|
</p>
|
|
92
89
|
)}
|
|
@@ -214,7 +211,7 @@ export function FlatColorGradingSection({
|
|
|
214
211
|
<button
|
|
215
212
|
type="button"
|
|
216
213
|
onClick={onRequestPresetPreviews}
|
|
217
|
-
className="text-[10px] font-medium text-
|
|
214
|
+
className="text-[10px] font-medium text-accent-ink hover:text-accent-ink/80"
|
|
218
215
|
>
|
|
219
216
|
Retry look previews
|
|
220
217
|
</button>
|
|
@@ -519,7 +516,7 @@ export function FlatColorGradingSection({
|
|
|
519
516
|
track("button", "Apply grade to scope");
|
|
520
517
|
onApplyToScope();
|
|
521
518
|
}}
|
|
522
|
-
className="text-[11px] font-medium text-
|
|
519
|
+
className="text-[11px] font-medium text-accent-ink hover:text-accent-ink/80 disabled:cursor-not-allowed disabled:opacity-50"
|
|
523
520
|
>
|
|
524
521
|
{applyBusy ? "Applying" : "Apply"}
|
|
525
522
|
</button>
|
|
@@ -186,7 +186,7 @@ export function FlatEffectsSection({
|
|
|
186
186
|
track("button", "Customize effect palette");
|
|
187
187
|
onCommitColorGrading({ ...grading, palette: fallback });
|
|
188
188
|
}}
|
|
189
|
-
className="flex min-h-[28px] items-center gap-1 text-[10px] font-medium text-
|
|
189
|
+
className="flex min-h-[28px] items-center gap-1 text-[10px] font-medium text-accent-ink hover:text-accent-ink/80"
|
|
190
190
|
>
|
|
191
191
|
<Plus size={11} /> Custom palette
|
|
192
192
|
</button>
|
|
@@ -307,7 +307,7 @@ export function FlatEffectsSection({
|
|
|
307
307
|
type="button"
|
|
308
308
|
title={`Remove ${selectedEffect.label}`}
|
|
309
309
|
onClick={() => removeEffect(selectedEffect)}
|
|
310
|
-
className="text-panel-text-4 hover:text-
|
|
310
|
+
className="text-panel-text-4 hover:text-danger-ink"
|
|
311
311
|
>
|
|
312
312
|
<X size={11} />
|
|
313
313
|
</button>
|
|
@@ -352,7 +352,7 @@ export function FlatEffectsSection({
|
|
|
352
352
|
data-flat-effects-add-toggle="true"
|
|
353
353
|
aria-expanded={catalogOpen}
|
|
354
354
|
onClick={() => setCatalogOpen((open) => !open)}
|
|
355
|
-
className="flex min-h-[30px] items-center gap-1 text-[10px] font-medium text-
|
|
355
|
+
className="flex min-h-[30px] items-center gap-1 text-[10px] font-medium text-accent-ink hover:text-accent-ink/80"
|
|
356
356
|
>
|
|
357
357
|
<Plus size={11} /> Add effect
|
|
358
358
|
</button>
|
|
@@ -374,7 +374,7 @@ export function FlatEffectsSection({
|
|
|
374
374
|
onClick={() => setCatalogGroup(group.label)}
|
|
375
375
|
className={`min-h-[25px] min-w-0 truncate bg-panel-bg px-2 text-[9px] ${
|
|
376
376
|
catalogGroup === group.label
|
|
377
|
-
? "font-medium text-
|
|
377
|
+
? "font-medium text-accent-ink"
|
|
378
378
|
: "text-panel-text-4 hover:text-panel-text-1"
|
|
379
379
|
}`}
|
|
380
380
|
>
|
|
@@ -200,7 +200,7 @@ export function FlatMediaSection({
|
|
|
200
200
|
data-flat-media-remove-bg="true"
|
|
201
201
|
disabled={!canRemoveBackground || removeBusy}
|
|
202
202
|
onClick={() => void runBackgroundRemoval()}
|
|
203
|
-
className="flex items-center gap-1 text-[10px] font-medium text-
|
|
203
|
+
className="flex items-center gap-1 text-[10px] font-medium text-accent-ink disabled:cursor-not-allowed disabled:opacity-50"
|
|
204
204
|
title={
|
|
205
205
|
canRemoveBackground
|
|
206
206
|
? "Remove background and save a transparent asset"
|
|
@@ -231,7 +231,7 @@ export function FlatMediaSection({
|
|
|
231
231
|
<div className="h-1 overflow-hidden rounded-full bg-panel-hover">
|
|
232
232
|
<div
|
|
233
233
|
className={`h-full rounded-full ${
|
|
234
|
-
removeProgress.status === "failed" ? "bg-
|
|
234
|
+
removeProgress.status === "failed" ? "bg-danger-ink" : "bg-panel-accent"
|
|
235
235
|
}`}
|
|
236
236
|
style={{ width: `${Math.max(0, Math.min(100, removeProgress.progress))}%` }}
|
|
237
237
|
/>
|
|
@@ -148,13 +148,13 @@ export function FlatMotionSection({
|
|
|
148
148
|
{showEffects && (
|
|
149
149
|
<>
|
|
150
150
|
{multipleTimelines && (
|
|
151
|
-
<p className="rounded-lg bg-amber-500/10 px-3 py-2 text-[11px] leading-relaxed text-
|
|
151
|
+
<p className="rounded-lg bg-amber-500/10 px-3 py-2 text-[11px] leading-relaxed text-warning-ink">
|
|
152
152
|
This file has multiple GSAP timelines. Animation editing is disabled to prevent data
|
|
153
153
|
loss — consolidate into a single timeline to enable editing.
|
|
154
154
|
</p>
|
|
155
155
|
)}
|
|
156
156
|
{unsupportedTimelinePattern && (
|
|
157
|
-
<p className="rounded-lg bg-amber-500/10 px-3 py-2 text-[11px] leading-relaxed text-
|
|
157
|
+
<p className="rounded-lg bg-amber-500/10 px-3 py-2 text-[11px] leading-relaxed text-warning-ink">
|
|
158
158
|
This timeline uses a computed key the editor can't resolve statically.
|
|
159
159
|
</p>
|
|
160
160
|
)}
|
|
@@ -45,7 +45,7 @@ export function FlatOverlaysSection({
|
|
|
45
45
|
return <div className="py-4 text-center text-[10px] text-panel-text-4">Loading overlays…</div>;
|
|
46
46
|
}
|
|
47
47
|
if (error) {
|
|
48
|
-
return <div className="py-4 text-center text-[10px] text-
|
|
48
|
+
return <div className="py-4 text-center text-[10px] text-danger-ink">{error}</div>;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
const busy = adding !== null;
|
|
@@ -95,7 +95,7 @@ export function FlatOverlaysSection({
|
|
|
95
95
|
)}
|
|
96
96
|
<Plus
|
|
97
97
|
size={12}
|
|
98
|
-
className="absolute right-1.5 top-1.5 text-white opacity-70 drop-shadow-sm group-hover:text-
|
|
98
|
+
className="absolute right-1.5 top-1.5 text-white opacity-70 drop-shadow-sm group-hover:text-accent-ink group-hover:opacity-100"
|
|
99
99
|
/>
|
|
100
100
|
</span>
|
|
101
101
|
<span className="block truncate px-2 py-1.5 text-[10px] text-panel-text-2">
|
|
@@ -60,7 +60,7 @@ describe("FlatRow", () => {
|
|
|
60
60
|
/>,
|
|
61
61
|
);
|
|
62
62
|
const value = host.querySelector('[data-flat-row-value="true"]');
|
|
63
|
-
expect(value?.className).toContain("text-
|
|
63
|
+
expect(value?.className).toContain("text-accent-ink");
|
|
64
64
|
const reset = host.querySelector<HTMLButtonElement>('[data-flat-row-reset="true"]');
|
|
65
65
|
expect(reset).not.toBeNull();
|
|
66
66
|
act(() => reset?.dispatchEvent(new MouseEvent("click", { bubbles: true })));
|
|
@@ -356,7 +356,7 @@ describe("FlatSlider", () => {
|
|
|
356
356
|
const fill = host.querySelector<HTMLElement>('[data-flat-slider-fill="true"]');
|
|
357
357
|
expect(fill?.style.width).toBe("100%");
|
|
358
358
|
const knob = host.querySelector<HTMLElement>('[data-flat-slider-knob="true"]');
|
|
359
|
-
expect(knob?.className).toContain("bg-
|
|
359
|
+
expect(knob?.className).toContain("bg-text-0");
|
|
360
360
|
act(() => root.unmount());
|
|
361
361
|
});
|
|
362
362
|
|
|
@@ -1172,7 +1172,7 @@ describe("FlatSelectRow", () => {
|
|
|
1172
1172
|
/>,
|
|
1173
1173
|
);
|
|
1174
1174
|
const select = host.querySelector<HTMLSelectElement>("select");
|
|
1175
|
-
expect(select?.className).toContain("text-
|
|
1175
|
+
expect(select?.className).toContain("text-accent-ink");
|
|
1176
1176
|
const reset = host.querySelector<HTMLButtonElement>('[data-flat-select-reset="true"]');
|
|
1177
1177
|
act(() => reset?.dispatchEvent(new MouseEvent("click", { bubbles: true })));
|
|
1178
1178
|
expect(onReset).toHaveBeenCalledTimes(1);
|
|
@@ -1364,6 +1364,7 @@ describe("FlatSlider — typed value", () => {
|
|
|
1364
1364
|
const still = host.querySelector<HTMLInputElement>('[data-flat-slider-input="true"]');
|
|
1365
1365
|
expect(still).not.toBeNull();
|
|
1366
1366
|
expect(still?.getAttribute("aria-invalid")).toBe("true");
|
|
1367
|
+
expect(still?.className).toContain("border-dashed");
|
|
1367
1368
|
act(() => still?.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape", bubbles: true })));
|
|
1368
1369
|
expect(host.querySelector('[data-flat-slider-input="true"]')).toBeNull();
|
|
1369
1370
|
expect(host.querySelector('[data-flat-slider-value="true"]')?.textContent).toBe("1x");
|
|
@@ -537,7 +537,7 @@ export function FlatSlider({
|
|
|
537
537
|
<div
|
|
538
538
|
data-flat-slider-knob="true"
|
|
539
539
|
className={`absolute top-1/2 -translate-x-1/2 -translate-y-1/2 rounded-full ${
|
|
540
|
-
tier === "explicitCustom" ? "h-2 w-2 bg-
|
|
540
|
+
tier === "explicitCustom" ? "h-2 w-2 bg-text-0" : "h-[7px] w-[7px] bg-panel-text-4"
|
|
541
541
|
}`}
|
|
542
542
|
style={{ left: `${clampedPct}%` }}
|
|
543
543
|
/>
|
|
@@ -76,7 +76,7 @@ export function FlatSliderReadout({
|
|
|
76
76
|
}}
|
|
77
77
|
onBlur={() => commit(false)}
|
|
78
78
|
className={`w-11 shrink-0 rounded-[3px] border bg-panel-surface px-1 text-right font-mono text-[10px] text-panel-text-0 outline-none ${
|
|
79
|
-
invalid ? "border-
|
|
79
|
+
invalid ? "border-dashed border-danger-ink" : "border-panel-accent"
|
|
80
80
|
}`}
|
|
81
81
|
/>
|
|
82
82
|
);
|
|
@@ -8,7 +8,11 @@ import { sortFontOptions } from "./propertyPanelHelpers";
|
|
|
8
8
|
|
|
9
9
|
vi.mock("./propertyPanelHelpers", async (importOriginal) => {
|
|
10
10
|
const actual = await importOriginal<typeof import("./propertyPanelHelpers")>();
|
|
11
|
-
return {
|
|
11
|
+
return {
|
|
12
|
+
...actual,
|
|
13
|
+
sortFontOptions: vi.fn(actual.sortFontOptions),
|
|
14
|
+
uniqueFontFamilies: vi.fn(actual.uniqueFontFamilies),
|
|
15
|
+
};
|
|
12
16
|
});
|
|
13
17
|
|
|
14
18
|
(globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
|
@@ -56,13 +60,14 @@ describe("FontFamilyField font list", () => {
|
|
|
56
60
|
root.render(<FontFamilyField flat value={value} importedFonts={[]} onCommit={vi.fn()} />);
|
|
57
61
|
try {
|
|
58
62
|
await act(async () => render("Arial"));
|
|
59
|
-
// The
|
|
63
|
+
// The value changes while the dropdown is closed, as during a drag.
|
|
60
64
|
await act(async () => render("Georgia"));
|
|
61
65
|
expect(sortFontOptions).not.toHaveBeenCalled();
|
|
62
66
|
|
|
63
67
|
const trigger = host.querySelector<HTMLButtonElement>('[data-flat-font-trigger="true"]');
|
|
64
68
|
await act(async () => trigger?.click());
|
|
65
|
-
|
|
69
|
+
await act(async () => new Promise((resolve) => setTimeout(resolve, 10)));
|
|
70
|
+
expect(sortFontOptions).toHaveBeenCalled();
|
|
66
71
|
expect(host.textContent).toContain("Roboto Slab");
|
|
67
72
|
expect(document.head.querySelector('link[href*="fonts.googleapis.com"]')).toBeNull();
|
|
68
73
|
} finally {
|
|
@@ -71,3 +76,75 @@ describe("FontFamilyField font list", () => {
|
|
|
71
76
|
}
|
|
72
77
|
});
|
|
73
78
|
});
|
|
79
|
+
|
|
80
|
+
describe("FontFamilyField session font lists", () => {
|
|
81
|
+
const fresh = async () => {
|
|
82
|
+
vi.resetModules();
|
|
83
|
+
const { FontFamilyField } = await import("./propertyPanelFont");
|
|
84
|
+
const { uniqueFontFamilies } = await import("./propertyPanelHelpers");
|
|
85
|
+
const loop = await import("./overlayFrameLoop");
|
|
86
|
+
vi.mocked(uniqueFontFamilies).mockClear();
|
|
87
|
+
const host = document.createElement("div");
|
|
88
|
+
document.body.append(host);
|
|
89
|
+
const root = createRoot(host);
|
|
90
|
+
const render = (value: string) =>
|
|
91
|
+
root.render(<FontFamilyField flat value={value} importedFonts={[]} onCommit={vi.fn()} />);
|
|
92
|
+
const toggle = () =>
|
|
93
|
+
host.querySelector<HTMLButtonElement>('[data-flat-font-trigger="true"]')?.click();
|
|
94
|
+
const done = () => {
|
|
95
|
+
act(() => root.unmount());
|
|
96
|
+
host.remove();
|
|
97
|
+
loop.resetOverlayFrameLoopForTests();
|
|
98
|
+
};
|
|
99
|
+
return { uniqueFontFamilies, loop, host, render, toggle, done };
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
it("does no list work in a drag's frames when the lists land mid-drag", async () => {
|
|
103
|
+
vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout", "performance", "Date"] });
|
|
104
|
+
const raf = window.requestAnimationFrame;
|
|
105
|
+
window.requestAnimationFrame = (() => 0) as typeof window.requestAnimationFrame;
|
|
106
|
+
const lists = vi.mocked(fetch).getMockImplementation()!;
|
|
107
|
+
vi.mocked(fetch).mockImplementation(async (url) => {
|
|
108
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
109
|
+
return lists(url);
|
|
110
|
+
});
|
|
111
|
+
const t = await fresh();
|
|
112
|
+
const stopLoop = t.loop.subscribeOverlayFrame(() => undefined);
|
|
113
|
+
try {
|
|
114
|
+
await act(async () => t.render("Georgia"));
|
|
115
|
+
for (let elapsed = 0; elapsed < 600; elapsed += 16) {
|
|
116
|
+
window.dispatchEvent(new Event("pointermove"));
|
|
117
|
+
await act(async () => vi.advanceTimersByTimeAsync(16));
|
|
118
|
+
}
|
|
119
|
+
expect(fetch).toHaveBeenCalledTimes(2);
|
|
120
|
+
expect(t.uniqueFontFamilies).not.toHaveBeenCalled();
|
|
121
|
+
|
|
122
|
+
await act(async () => vi.advanceTimersByTimeAsync(500));
|
|
123
|
+
expect(t.uniqueFontFamilies).toHaveBeenCalledTimes(1);
|
|
124
|
+
await act(async () => t.toggle());
|
|
125
|
+
expect(t.host.textContent).toContain("Roboto Slab");
|
|
126
|
+
} finally {
|
|
127
|
+
stopLoop();
|
|
128
|
+
t.done();
|
|
129
|
+
window.requestAnimationFrame = raf;
|
|
130
|
+
vi.useRealTimers();
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("retries a failed list fetch when the picker opens", async () => {
|
|
135
|
+
vi.mocked(fetch).mockRejectedValueOnce(new Error("offline"));
|
|
136
|
+
const t = await fresh();
|
|
137
|
+
try {
|
|
138
|
+
await act(async () => t.render("Georgia"));
|
|
139
|
+
await act(async () => new Promise((resolve) => setTimeout(resolve, 10)));
|
|
140
|
+
expect(t.uniqueFontFamilies).not.toHaveBeenCalled();
|
|
141
|
+
|
|
142
|
+
await act(async () => t.toggle());
|
|
143
|
+
await act(async () => new Promise((resolve) => setTimeout(resolve, 10)));
|
|
144
|
+
expect(fetch).toHaveBeenCalledTimes(4);
|
|
145
|
+
expect(t.host.textContent).toContain("Roboto Slab");
|
|
146
|
+
} finally {
|
|
147
|
+
t.done();
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect, useMemo, useRef, useState } from "react";
|
|
1
|
+
import { useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
2
2
|
import {
|
|
3
3
|
googleFontStylesheetUrl,
|
|
4
4
|
POPULAR_GOOGLE_FONT_FAMILIES,
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
type LocalFontData,
|
|
20
20
|
} from "./propertyPanelHelpers";
|
|
21
21
|
import { useTrackDesignInput } from "../../contexts/DesignPanelInputContext";
|
|
22
|
+
import { runWhenInputIdle } from "./overlayFrameLoop";
|
|
22
23
|
|
|
23
24
|
/* ------------------------------------------------------------------ */
|
|
24
25
|
/* Font helper functions */
|
|
@@ -117,6 +118,62 @@ function loadImportedFontStylesheet(asset: ImportedFontAsset): void {
|
|
|
117
118
|
document.head.appendChild(style);
|
|
118
119
|
}
|
|
119
120
|
|
|
121
|
+
type FontLists = {
|
|
122
|
+
loaded: boolean;
|
|
123
|
+
google: readonly string[];
|
|
124
|
+
googleKeys: ReadonlySet<string>;
|
|
125
|
+
installed: string[];
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
let fontLists: FontLists = {
|
|
129
|
+
loaded: false,
|
|
130
|
+
google: POPULAR_GOOGLE_FONT_FAMILIES,
|
|
131
|
+
googleKeys: new Set(POPULAR_GOOGLE_FONT_FAMILIES.map((f) => f.toLowerCase())),
|
|
132
|
+
installed: [],
|
|
133
|
+
};
|
|
134
|
+
let fontListsRequest: Promise<void> | null = null;
|
|
135
|
+
const fontListListeners = new Set<() => void>();
|
|
136
|
+
|
|
137
|
+
async function fetchFontList(url: string): Promise<string[]> {
|
|
138
|
+
const data = (await (await fetch(url)).json()) as { fonts?: unknown };
|
|
139
|
+
if (!Array.isArray(data.fonts)) throw new Error(`${url} returned no font list`);
|
|
140
|
+
return data.fonts as string[];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function loadFontLists(): void {
|
|
144
|
+
if (fontLists.loaded) return;
|
|
145
|
+
fontListsRequest ??= Promise.all([
|
|
146
|
+
fetchFontList("/api/fonts"),
|
|
147
|
+
fetchFontList("/api/fonts/google"),
|
|
148
|
+
])
|
|
149
|
+
.then(([installed, google]) => {
|
|
150
|
+
const names = google.concat(POPULAR_GOOGLE_FONT_FAMILIES);
|
|
151
|
+
const families: string[] = [];
|
|
152
|
+
const googleKeys = new Set<string>();
|
|
153
|
+
let next = 0;
|
|
154
|
+
runWhenInputIdle((timeLeft) => {
|
|
155
|
+
while (next < names.length && timeLeft() > 0) {
|
|
156
|
+
const batch = names.slice(next, next + 200);
|
|
157
|
+
next += batch.length;
|
|
158
|
+
families.push(...uniqueFontFamilies(batch, googleKeys));
|
|
159
|
+
}
|
|
160
|
+
if (next < names.length) return false;
|
|
161
|
+
fontLists = { loaded: true, google: families, googleKeys, installed };
|
|
162
|
+
for (const listener of fontListListeners) listener();
|
|
163
|
+
return true;
|
|
164
|
+
});
|
|
165
|
+
})
|
|
166
|
+
.catch(() => {
|
|
167
|
+
fontListsRequest = null;
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function subscribeFontLists(listener: () => void): () => void {
|
|
172
|
+
fontListListeners.add(listener);
|
|
173
|
+
loadFontLists();
|
|
174
|
+
return () => fontListListeners.delete(listener);
|
|
175
|
+
}
|
|
176
|
+
|
|
120
177
|
/* ------------------------------------------------------------------ */
|
|
121
178
|
/* FontFamilyField */
|
|
122
179
|
/* ------------------------------------------------------------------ */
|
|
@@ -146,9 +203,8 @@ export function FontFamilyField({
|
|
|
146
203
|
const [activeIndex, setActiveIndex] = useState(-1);
|
|
147
204
|
const [localFonts, setLocalFonts] = useState<string[]>([]);
|
|
148
205
|
const [localFontData, setLocalFontData] = useState<LocalFontData[]>([]);
|
|
149
|
-
const
|
|
206
|
+
const lists = useSyncExternalStore(subscribeFontLists, () => fontLists);
|
|
150
207
|
const [loadingLocalFonts, setLoadingLocalFonts] = useState(false);
|
|
151
|
-
const [loadingGoogleFonts, setLoadingGoogleFonts] = useState(false);
|
|
152
208
|
const [importingFonts, setImportingFonts] = useState(false);
|
|
153
209
|
const [fontNotice, setFontNotice] = useState<string | null>(null);
|
|
154
210
|
const canQueryLocalFonts =
|
|
@@ -175,46 +231,18 @@ export function FontFamilyField({
|
|
|
175
231
|
}, [open]);
|
|
176
232
|
|
|
177
233
|
useEffect(() => {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
.then((r) => (r.ok ? r.json() : null))
|
|
181
|
-
.then((data: { fonts?: string[] } | null) => {
|
|
182
|
-
if (cancelled || !Array.isArray(data?.fonts)) return;
|
|
183
|
-
setLocalFonts((cur) => uniqueFontFamilies([...cur, ...data.fonts!]));
|
|
184
|
-
})
|
|
185
|
-
.catch(() => undefined);
|
|
186
|
-
return () => {
|
|
187
|
-
cancelled = true;
|
|
188
|
-
};
|
|
189
|
-
}, []);
|
|
190
|
-
|
|
191
|
-
useEffect(() => {
|
|
192
|
-
let cancelled = false;
|
|
193
|
-
setLoadingGoogleFonts(true);
|
|
194
|
-
void fetch("/api/fonts/google")
|
|
195
|
-
.then((r) => (r.ok ? r.json() : null))
|
|
196
|
-
.then((data: { fonts?: string[] } | null) => {
|
|
197
|
-
if (cancelled || !Array.isArray(data?.fonts)) return;
|
|
198
|
-
setGoogleFonts(uniqueFontFamilies([...data.fonts!, ...POPULAR_GOOGLE_FONT_FAMILIES]));
|
|
199
|
-
})
|
|
200
|
-
.catch(() => undefined)
|
|
201
|
-
.finally(() => {
|
|
202
|
-
if (!cancelled) setLoadingGoogleFonts(false);
|
|
203
|
-
});
|
|
204
|
-
return () => {
|
|
205
|
-
cancelled = true;
|
|
206
|
-
};
|
|
207
|
-
}, []);
|
|
234
|
+
if (open) loadFontLists();
|
|
235
|
+
}, [open]);
|
|
208
236
|
|
|
209
237
|
useEffect(() => {
|
|
210
|
-
if (
|
|
238
|
+
if (lists.googleKeys.has(currentFamily.toLowerCase())) {
|
|
211
239
|
loadGoogleFontStylesheet(currentFamily);
|
|
212
240
|
}
|
|
213
241
|
const imported = importedFonts.find(
|
|
214
242
|
(f) => f.family.toLowerCase() === currentFamily.toLowerCase(),
|
|
215
243
|
);
|
|
216
244
|
if (imported) loadImportedFontStylesheet(imported);
|
|
217
|
-
}, [currentFamily,
|
|
245
|
+
}, [currentFamily, lists, importedFonts]);
|
|
218
246
|
|
|
219
247
|
const loadBrowserLocalFonts = async () => {
|
|
220
248
|
if (!canQueryLocalFonts || !window.queryLocalFonts) {
|
|
@@ -278,24 +306,19 @@ export function FontFamilyField({
|
|
|
278
306
|
const options = useMemo(() => {
|
|
279
307
|
if (!open) return [];
|
|
280
308
|
const documentFonts = collectDocumentFontFamilies();
|
|
281
|
-
const googleSet = new Set(googleFonts.map((f) => f.toLowerCase()));
|
|
282
|
-
const taggedLocal = localFonts.map(
|
|
283
|
-
(family): FontOption => ({
|
|
284
|
-
family,
|
|
285
|
-
source: googleSet.has(family.toLowerCase()) ? "Google" : "Local",
|
|
286
|
-
}),
|
|
287
|
-
);
|
|
288
309
|
return sortFontOptions(
|
|
289
310
|
uniqueFontOptions([
|
|
290
311
|
{ family: currentFamily, source: "Current" },
|
|
291
312
|
...documentFonts.map((f): FontOption => ({ family: f, source: "Document" })),
|
|
292
313
|
...projectFontAssets,
|
|
293
|
-
...
|
|
294
|
-
...
|
|
314
|
+
...lists.google.map((f): FontOption => ({ family: f, source: "Google" })),
|
|
315
|
+
...[...lists.installed, ...localFonts].map(
|
|
316
|
+
(f): FontOption => ({ family: f, source: "Local" }),
|
|
317
|
+
),
|
|
295
318
|
...DEFAULT_FONT_FAMILIES.map((f): FontOption => ({ family: f, source: "System" })),
|
|
296
319
|
]),
|
|
297
320
|
);
|
|
298
|
-
}, [open, currentFamily,
|
|
321
|
+
}, [open, currentFamily, lists, localFonts, projectFontAssets]);
|
|
299
322
|
|
|
300
323
|
const filteredOptions = useMemo(() => {
|
|
301
324
|
const matches = options.filter((o) => fontMatchesQuery(o.family, query));
|
|
@@ -392,7 +415,7 @@ export function FontFamilyField({
|
|
|
392
415
|
type="text"
|
|
393
416
|
value={query}
|
|
394
417
|
disabled={disabled}
|
|
395
|
-
placeholder={
|
|
418
|
+
placeholder={lists.loaded ? "Search fonts" : "Loading Google Fonts..."}
|
|
396
419
|
onChange={(e) => {
|
|
397
420
|
setQuery(e.target.value);
|
|
398
421
|
setActiveIndex(-1);
|
|
@@ -433,7 +456,7 @@ export function FontFamilyField({
|
|
|
433
456
|
type="button"
|
|
434
457
|
disabled={disabled || loadingLocalFonts}
|
|
435
458
|
onClick={loadBrowserLocalFonts}
|
|
436
|
-
className="rounded-lg border border-neutral-700 bg-neutral-900 px-2.5 text-[10px] font-medium text-neutral-400 transition-colors hover:border-neutral-600 hover:text-neutral-100 disabled:cursor-not-allowed disabled:text-
|
|
459
|
+
className="rounded-lg border border-neutral-700 bg-neutral-900 px-2.5 text-[10px] font-medium text-neutral-400 transition-colors hover:border-neutral-600 hover:text-neutral-100 disabled:cursor-not-allowed disabled:text-text-off"
|
|
437
460
|
>
|
|
438
461
|
{loadingLocalFonts ? "..." : "Local"}
|
|
439
462
|
</button>
|
|
@@ -442,7 +465,7 @@ export function FontFamilyField({
|
|
|
442
465
|
type="button"
|
|
443
466
|
disabled={disabled || importingFonts || !onImportFonts}
|
|
444
467
|
onClick={() => fontInputRef.current?.click()}
|
|
445
|
-
className="rounded-lg border border-neutral-700 bg-neutral-900 px-2.5 text-[10px] font-medium text-neutral-400 transition-colors hover:border-neutral-600 hover:text-neutral-100 disabled:cursor-not-allowed disabled:text-
|
|
468
|
+
className="rounded-lg border border-neutral-700 bg-neutral-900 px-2.5 text-[10px] font-medium text-neutral-400 transition-colors hover:border-neutral-600 hover:text-neutral-100 disabled:cursor-not-allowed disabled:text-text-off"
|
|
446
469
|
>
|
|
447
470
|
{importingFonts ? "..." : "Import"}
|
|
448
471
|
</button>
|
|
@@ -111,7 +111,7 @@ function FxCarveMember({
|
|
|
111
111
|
{/* The lane is where an automated value comes from, and where it is
|
|
112
112
|
edited — saying so is the difference between a stale readout and
|
|
113
113
|
a pointer to the thing that owns it. */}
|
|
114
|
-
{automated ? <span className="text-
|
|
114
|
+
{automated ? <span className="text-accent-ink">A</span> : null}
|
|
115
115
|
</span>
|
|
116
116
|
);
|
|
117
117
|
})}
|
|
@@ -90,7 +90,7 @@ export function AutomationToggle({
|
|
|
90
90
|
type="button"
|
|
91
91
|
className={`hf-fx-automate w-[16px] shrink-0 rounded-[3px] border font-mono text-[9px] leading-none ${
|
|
92
92
|
automated
|
|
93
|
-
? "border-panel-accent text-
|
|
93
|
+
? "border-panel-accent text-accent-ink"
|
|
94
94
|
: "border-panel-border-input text-panel-text-2 hover:text-panel-text-0"
|
|
95
95
|
}`}
|
|
96
96
|
aria-pressed={automated}
|
|
@@ -237,7 +237,7 @@ export function FxParamRow({
|
|
|
237
237
|
{/* See the enum row above for why the name wraps instead of truncating. */}
|
|
238
238
|
<span
|
|
239
239
|
className={`hf-fx-label w-[86px] shrink-0 wrap-break-word text-[10px] leading-tight ${
|
|
240
|
-
automated ? "text-
|
|
240
|
+
automated ? "text-accent-ink" : "text-panel-text-2"
|
|
241
241
|
}`}
|
|
242
242
|
>
|
|
243
243
|
{param.label}
|
|
@@ -115,7 +115,7 @@ function Fader({
|
|
|
115
115
|
</span>
|
|
116
116
|
<span
|
|
117
117
|
className={`hf-fx-eq-value font-mono text-[9px] tabular-nums ${
|
|
118
|
-
moved ? "text-
|
|
118
|
+
moved ? "text-accent-ink" : "text-panel-text-2"
|
|
119
119
|
}`}
|
|
120
120
|
>
|
|
121
121
|
{moved ? shown(value) : "0"}
|
|
@@ -160,7 +160,7 @@ export function FxEqModule({
|
|
|
160
160
|
<span className="font-mono text-[9px] text-panel-text-2">{bands.length}-band</span>
|
|
161
161
|
<button
|
|
162
162
|
type="button"
|
|
163
|
-
className="hf-fx-remove px-1 text-[11px] text-panel-text-2 hover:text-
|
|
163
|
+
className="hf-fx-remove px-1 text-[11px] text-panel-text-2 hover:text-danger-ink"
|
|
164
164
|
aria-label="Remove Tone"
|
|
165
165
|
disabled={disabled}
|
|
166
166
|
onClick={onRemove}
|
|
@@ -216,7 +216,7 @@ function FxNodeHeader({
|
|
|
216
216
|
/>
|
|
217
217
|
<button
|
|
218
218
|
type="button"
|
|
219
|
-
className="hf-fx-remove px-1 font-mono text-[11px] text-panel-text-2 hover:text-
|
|
219
|
+
className="hf-fx-remove px-1 font-mono text-[11px] text-panel-text-2 hover:text-danger-ink disabled:opacity-40"
|
|
220
220
|
title="Remove"
|
|
221
221
|
disabled={disabled}
|
|
222
222
|
onClick={onRemove}
|
|
@@ -189,7 +189,7 @@ export function FxPresetRun({
|
|
|
189
189
|
</button>
|
|
190
190
|
<button
|
|
191
191
|
type="button"
|
|
192
|
-
className="hf-fx-preset-run-remove px-1 font-mono text-[11px] text-panel-text-2 hover:text-
|
|
192
|
+
className="hf-fx-preset-run-remove px-1 font-mono text-[11px] text-panel-text-2 hover:text-danger-ink disabled:opacity-40"
|
|
193
193
|
title={`Remove ${preset.label}`}
|
|
194
194
|
disabled={disabled}
|
|
195
195
|
onClick={onRemoveRun}
|
|
@@ -125,8 +125,7 @@ export function localFontSortScore(font: LocalFontData): number {
|
|
|
125
125
|
return 3;
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
export function uniqueFontFamilies(values: string[]): string[] {
|
|
129
|
-
const seen = new Set<string>();
|
|
128
|
+
export function uniqueFontFamilies(values: string[], seen = new Set<string>()): string[] {
|
|
130
129
|
return values.reduce<string[]>((result, value) => {
|
|
131
130
|
const family = value.trim();
|
|
132
131
|
if (!family) return result;
|
|
@@ -231,7 +231,7 @@ export function MediaSection({
|
|
|
231
231
|
<div className="h-1 overflow-hidden rounded-full bg-panel-border">
|
|
232
232
|
<div
|
|
233
233
|
className={`h-full rounded-full ${
|
|
234
|
-
removeProgress.status === "failed" ? "bg-
|
|
234
|
+
removeProgress.status === "failed" ? "bg-danger-ink" : "bg-studio-accent"
|
|
235
235
|
}`}
|
|
236
236
|
style={{ width: `${Math.max(0, Math.min(100, removeProgress.progress))}%` }}
|
|
237
237
|
/>
|
|
@@ -234,7 +234,7 @@ export function SegmentedControl({
|
|
|
234
234
|
aria-pressed={option.value === value}
|
|
235
235
|
className={`min-w-0 truncate rounded px-2 py-[5px] text-[11px] font-medium transition-colors disabled:cursor-not-allowed ${
|
|
236
236
|
option.value === value
|
|
237
|
-
? "bg-panel-hover text-
|
|
237
|
+
? "bg-panel-hover text-text-0"
|
|
238
238
|
: "text-panel-text-4 hover:text-panel-text-2"
|
|
239
239
|
}`}
|
|
240
240
|
>
|
|
@@ -323,7 +323,7 @@ function TextFieldEditor({
|
|
|
323
323
|
track("button", "Remove text field");
|
|
324
324
|
onRemoveTextField(field.key);
|
|
325
325
|
}}
|
|
326
|
-
className="inline-flex h-7 shrink-0 items-center rounded-lg border border-neutral-700 bg-neutral-950 px-2.5 text-[11px] font-medium text-neutral-300 transition-colors hover:border-neutral-600 hover:text-
|
|
326
|
+
className="inline-flex h-7 shrink-0 items-center rounded-lg border border-neutral-700 bg-neutral-950 px-2.5 text-[11px] font-medium text-neutral-300 transition-colors hover:border-neutral-600 hover:text-text-0"
|
|
327
327
|
>
|
|
328
328
|
Remove
|
|
329
329
|
</button>
|
|
@@ -474,7 +474,7 @@ export function TextSection({
|
|
|
474
474
|
if (nextKey) setActiveTextFieldKey(nextKey);
|
|
475
475
|
});
|
|
476
476
|
}}
|
|
477
|
-
className="inline-flex h-7 max-w-full items-center gap-1.5 rounded-lg border border-neutral-700 bg-neutral-950 px-2.5 text-[11px] font-medium text-neutral-300 transition-colors hover:border-neutral-600 hover:text-
|
|
477
|
+
className="inline-flex h-7 max-w-full items-center gap-1.5 rounded-lg border border-neutral-700 bg-neutral-950 px-2.5 text-[11px] font-medium text-neutral-300 transition-colors hover:border-neutral-600 hover:text-text-0"
|
|
478
478
|
>
|
|
479
479
|
<Plus size={12} className="shrink-0" />
|
|
480
480
|
<span className="truncate">Add text</span>
|
|
@@ -27,6 +27,6 @@ describe("value tier class maps", () => {
|
|
|
27
27
|
expect(VALUE_TIER_LABEL_CLASS[tier]).toBeTruthy();
|
|
28
28
|
expect(VALUE_TIER_VALUE_CLASS[tier]).toBeTruthy();
|
|
29
29
|
}
|
|
30
|
-
expect(VALUE_TIER_VALUE_CLASS.explicitCustom).toBe("text-
|
|
30
|
+
expect(VALUE_TIER_VALUE_CLASS.explicitCustom).toBe("text-accent-ink");
|
|
31
31
|
});
|
|
32
32
|
});
|
|
@@ -24,5 +24,5 @@ export const VALUE_TIER_LABEL_CLASS: Record<PropertyValueTier, string> = {
|
|
|
24
24
|
export const VALUE_TIER_VALUE_CLASS: Record<PropertyValueTier, string> = {
|
|
25
25
|
default: "text-panel-text-3",
|
|
26
26
|
explicitDefault: "text-panel-text-0",
|
|
27
|
-
explicitCustom: "text-
|
|
27
|
+
explicitCustom: "text-accent-ink",
|
|
28
28
|
};
|