@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
|
@@ -42,7 +42,6 @@ import {
|
|
|
42
42
|
STUDIO_MOTION_ORIGINAL_VISIBILITY_ATTR,
|
|
43
43
|
} from "./studioMotionTypes";
|
|
44
44
|
import {
|
|
45
|
-
buildPathOffsetPatches,
|
|
46
45
|
buildClearPathOffsetPatches,
|
|
47
46
|
buildBoxSizePatches,
|
|
48
47
|
buildClearBoxSizePatches,
|
|
@@ -50,7 +49,7 @@ import {
|
|
|
50
49
|
buildMotionPatches,
|
|
51
50
|
buildClearMotionPatches,
|
|
52
51
|
} from "./manualEditsDomPatches";
|
|
53
|
-
import { applyStudioBoxSize
|
|
52
|
+
import { applyStudioBoxSize } from "./manualEditsDom";
|
|
54
53
|
|
|
55
54
|
/* ── helpers ── */
|
|
56
55
|
|
|
@@ -71,39 +70,7 @@ function assertClearCoversKeys(buildOps: PatchOperation[], clearOps: PatchOperat
|
|
|
71
70
|
|
|
72
71
|
/* ── Path offset ─────────────────────────────────────────────────────────── */
|
|
73
72
|
|
|
74
|
-
describe("
|
|
75
|
-
function populatedPathEl(): HTMLElement {
|
|
76
|
-
const e = div();
|
|
77
|
-
e.style.setProperty(STUDIO_OFFSET_X_PROP, "10px");
|
|
78
|
-
e.style.setProperty(STUDIO_OFFSET_Y_PROP, "20px");
|
|
79
|
-
e.style.setProperty("translate", "10px 20px");
|
|
80
|
-
e.setAttribute(STUDIO_ORIGINAL_TRANSLATE_ATTR, "5px 10px");
|
|
81
|
-
e.setAttribute(STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR, "3px");
|
|
82
|
-
e.style.setProperty("display", "flex");
|
|
83
|
-
e.setAttribute(STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR, "block");
|
|
84
|
-
return e;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
it("populated: captures offset styles, attrs, display, and transform-display marker in declaration order", () => {
|
|
88
|
-
const ops = buildPathOffsetPatches(populatedPathEl());
|
|
89
|
-
expect(ops).toEqual([
|
|
90
|
-
{ type: "inline-style", property: STUDIO_OFFSET_X_PROP, value: "10px" },
|
|
91
|
-
{ type: "inline-style", property: STUDIO_OFFSET_Y_PROP, value: "20px" },
|
|
92
|
-
{ type: "inline-style", property: "translate", value: "10px 20px" },
|
|
93
|
-
{ type: "attribute", property: STUDIO_PATH_OFFSET_ATTR, value: "true" },
|
|
94
|
-
{ type: "attribute", property: STUDIO_ORIGINAL_TRANSLATE_ATTR, value: "5px 10px" },
|
|
95
|
-
{ type: "attribute", property: STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR, value: "3px" },
|
|
96
|
-
{ type: "inline-style", property: "display", value: "flex" },
|
|
97
|
-
{ type: "attribute", property: STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR, value: "block" },
|
|
98
|
-
]);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it("empty: bare element yields only the path-offset marker", () => {
|
|
102
|
-
expect(buildPathOffsetPatches(div())).toEqual([
|
|
103
|
-
{ type: "attribute", property: STUDIO_PATH_OFFSET_ATTR, value: "true" },
|
|
104
|
-
]);
|
|
105
|
-
});
|
|
106
|
-
|
|
73
|
+
describe("buildClearPathOffsetPatches", () => {
|
|
107
74
|
it("clear: restores translate from STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR and display from STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR", () => {
|
|
108
75
|
const e = div();
|
|
109
76
|
e.setAttribute(STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR, "5px");
|
|
@@ -127,11 +94,6 @@ describe("buildPathOffsetPatches / buildClearPathOffsetPatches", () => {
|
|
|
127
94
|
const ops = buildClearPathOffsetPatches(e);
|
|
128
95
|
expect(ops.find((o) => o.property === "translate")?.value).toBeNull();
|
|
129
96
|
});
|
|
130
|
-
|
|
131
|
-
it("build/clear symmetry: clear addresses every {type,property} key that build emits", () => {
|
|
132
|
-
const e = populatedPathEl();
|
|
133
|
-
assertClearCoversKeys(buildPathOffsetPatches(e), buildClearPathOffsetPatches(e));
|
|
134
|
-
});
|
|
135
97
|
});
|
|
136
98
|
|
|
137
99
|
/* ── Box size ────────────────────────────────────────────────────────────── */
|
|
@@ -286,52 +248,6 @@ describe("buildBoxSizePatches / buildClearBoxSizePatches", () => {
|
|
|
286
248
|
});
|
|
287
249
|
});
|
|
288
250
|
|
|
289
|
-
/* ── Combined box-size + path-offset (anchored-corner resize) ──────────────── */
|
|
290
|
-
|
|
291
|
-
describe("anchored-corner combined patch: [...buildBoxSizePatches, ...buildPathOffsetPatches]", () => {
|
|
292
|
-
// NW/NE/SW resize commits size AND anchor offset in ONE persist. The two
|
|
293
|
-
// builders read the same already-mutated element and are concatenated; this
|
|
294
|
-
// is only safe if their {type,property} keys are disjoint (no builder
|
|
295
|
-
// overwrites the other's op when the source patcher applies them in order).
|
|
296
|
-
it("concatenation of both builders emits disjoint {type,property} keys (no collision)", () => {
|
|
297
|
-
const e = div();
|
|
298
|
-
applyStudioBoxSize(e, { width: 300, height: 200 });
|
|
299
|
-
applyStudioPathOffset(e, { x: 10, y: 20 });
|
|
300
|
-
|
|
301
|
-
const combined = [...buildBoxSizePatches(e), ...buildPathOffsetPatches(e)];
|
|
302
|
-
const keys = combined.map(opKey);
|
|
303
|
-
expect(new Set(keys).size, `duplicate {type,property} key in combined patch: ${keys}`).toBe(
|
|
304
|
-
keys.length,
|
|
305
|
-
);
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
it("combined patch carries BOTH markers so a soft-reload re-hydrates size and offset together", () => {
|
|
309
|
-
const e = div();
|
|
310
|
-
applyStudioBoxSize(e, { width: 300, height: 200 });
|
|
311
|
-
applyStudioPathOffset(e, { x: 10, y: 20 });
|
|
312
|
-
|
|
313
|
-
const combined = [...buildBoxSizePatches(e), ...buildPathOffsetPatches(e)];
|
|
314
|
-
const has = (property: string) =>
|
|
315
|
-
combined.some((op) => op.type === "attribute" && op.property === property);
|
|
316
|
-
expect(has(STUDIO_BOX_SIZE_ATTR)).toBe(true);
|
|
317
|
-
expect(has(STUDIO_PATH_OFFSET_ATTR)).toBe(true);
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
it("order is size-first: every box-size op precedes every path-offset op", () => {
|
|
321
|
-
const e = div();
|
|
322
|
-
applyStudioBoxSize(e, { width: 300, height: 200 });
|
|
323
|
-
applyStudioPathOffset(e, { x: 10, y: 20 });
|
|
324
|
-
|
|
325
|
-
const boxKeys = new Set(buildBoxSizePatches(e).map(opKey));
|
|
326
|
-
const combined = [...buildBoxSizePatches(e), ...buildPathOffsetPatches(e)];
|
|
327
|
-
const lastBoxIdx = combined.reduce((acc, op, i) => (boxKeys.has(opKey(op)) ? i : acc), -1);
|
|
328
|
-
const firstOffsetIdx = combined.findIndex(
|
|
329
|
-
(op) => op.type === "attribute" && op.property === STUDIO_PATH_OFFSET_ATTR,
|
|
330
|
-
);
|
|
331
|
-
expect(firstOffsetIdx).toBeGreaterThan(lastBoxIdx);
|
|
332
|
-
});
|
|
333
|
-
});
|
|
334
|
-
|
|
335
251
|
/* ── Rotation ────────────────────────────────────────────────────────────── */
|
|
336
252
|
|
|
337
253
|
describe("buildClearRotationPatches", () => {
|
|
@@ -72,36 +72,6 @@ function appendTransformDisplayOps(element: HTMLElement, ops: PatchOperation[]):
|
|
|
72
72
|
|
|
73
73
|
/* ── Path offset patches ─────────────────────────────────────────── */
|
|
74
74
|
|
|
75
|
-
export function buildPathOffsetPatches(element: HTMLElement): PatchOperation[] {
|
|
76
|
-
const ops: PatchOperation[] = [];
|
|
77
|
-
collectInlineStyleOps(element, [STUDIO_OFFSET_X_PROP, STUDIO_OFFSET_Y_PROP], ops);
|
|
78
|
-
// When GSAP owns the element's transform, the live inline translate is kept
|
|
79
|
-
// at "none" (the offset lives in GSAP's cache — see applyStudioPathOffset).
|
|
80
|
-
// Persist the var() expression in that case, so a reload re-folds the offset.
|
|
81
|
-
const inlineTranslate = element.style.getPropertyValue("translate");
|
|
82
|
-
const hasOffsetVars =
|
|
83
|
-
element.style.getPropertyValue(STUDIO_OFFSET_X_PROP) ||
|
|
84
|
-
element.style.getPropertyValue(STUDIO_OFFSET_Y_PROP);
|
|
85
|
-
const translateValue =
|
|
86
|
-
inlineTranslate && inlineTranslate !== "none"
|
|
87
|
-
? inlineTranslate
|
|
88
|
-
: hasOffsetVars
|
|
89
|
-
? `var(${STUDIO_OFFSET_X_PROP}, 0px) var(${STUDIO_OFFSET_Y_PROP}, 0px)`
|
|
90
|
-
: null;
|
|
91
|
-
if (translateValue) {
|
|
92
|
-
ops.push({ type: "inline-style", property: "translate", value: translateValue });
|
|
93
|
-
}
|
|
94
|
-
ops.push({ type: "attribute", property: STUDIO_PATH_OFFSET_ATTR, value: "true" });
|
|
95
|
-
collectAttributeOps(
|
|
96
|
-
element,
|
|
97
|
-
[STUDIO_ORIGINAL_TRANSLATE_ATTR, STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR],
|
|
98
|
-
ops,
|
|
99
|
-
);
|
|
100
|
-
collectInlineStyleOps(element, ["display"], ops);
|
|
101
|
-
collectAttributeOps(element, [STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR], ops);
|
|
102
|
-
return ops;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
75
|
export function buildClearPathOffsetPatches(element: HTMLElement): PatchOperation[] {
|
|
106
76
|
const originalInlineTranslate = element.getAttribute(STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR);
|
|
107
77
|
const ops: PatchOperation[] = [
|
|
@@ -265,6 +265,7 @@ function restoreOriginalTranslateProperty(element: HTMLElement): void {
|
|
|
265
265
|
export function clearStudioPathOffset(element: HTMLElement): void {
|
|
266
266
|
if (
|
|
267
267
|
element.hasAttribute(STUDIO_PATH_OFFSET_ATTR) ||
|
|
268
|
+
element.hasAttribute(STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR) ||
|
|
268
269
|
styleUsesStudioOffset(element.style.getPropertyValue("translate"))
|
|
269
270
|
) {
|
|
270
271
|
restoreOriginalTranslateProperty(element);
|
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
// @vitest-environment happy-dom
|
|
2
2
|
import { act, useRef } from "react";
|
|
3
3
|
import type { Root } from "react-dom/client";
|
|
4
|
-
import { afterEach, beforeEach, expect, it } from "vitest";
|
|
4
|
+
import { afterEach, beforeEach, expect, it, vi } from "vitest";
|
|
5
5
|
import { installReactActEnvironment, mountReactHarness } from "../../hooks/domSelectionTestHarness";
|
|
6
6
|
import { useMarqueeGestures } from "./marqueeCommit";
|
|
7
7
|
|
|
8
|
+
// Layout stands in for a real preview: every element is visible and sits at the band's start.
|
|
9
|
+
vi.mock("./domEditingElement", async (importOriginal) => ({
|
|
10
|
+
...(await importOriginal<typeof import("./domEditingElement")>()),
|
|
11
|
+
isElementComputedVisible: () => true,
|
|
12
|
+
}));
|
|
13
|
+
vi.mock("./domEditOverlayGeometry", async (importOriginal) => ({
|
|
14
|
+
...(await importOriginal<typeof import("./domEditOverlayGeometry")>()),
|
|
15
|
+
toVisibleOverlayRect: () => ({ left: 10, top: 10, width: 40, height: 40 }),
|
|
16
|
+
}));
|
|
17
|
+
|
|
8
18
|
installReactActEnvironment();
|
|
9
19
|
HTMLElement.prototype.setPointerCapture ??= () => {};
|
|
10
20
|
HTMLElement.prototype.releasePointerCapture ??= () => {};
|
|
@@ -82,3 +92,61 @@ it("an escape with no band in flight still reaches the host", () => {
|
|
|
82
92
|
expect(event.defaultPrevented).toBe(false);
|
|
83
93
|
expect(hostEscapes).toHaveLength(1);
|
|
84
94
|
});
|
|
95
|
+
|
|
96
|
+
it("a reload promoted mid-band selects from the preview on screen, not the retired one", async () => {
|
|
97
|
+
const preview = () => {
|
|
98
|
+
const iframe = document.createElement("iframe");
|
|
99
|
+
const doc = document.implementation.createHTMLDocument("preview");
|
|
100
|
+
doc.body.innerHTML = '<div data-composition-id="main"><h1 id="title">Title</h1></div>';
|
|
101
|
+
Object.defineProperty(iframe, "contentDocument", { value: doc });
|
|
102
|
+
return iframe;
|
|
103
|
+
};
|
|
104
|
+
const [retired, live] = [preview(), preview()];
|
|
105
|
+
const picked: HTMLElement[][] = [];
|
|
106
|
+
const iframeRef = { current: retired as HTMLIFrameElement | null };
|
|
107
|
+
function Band() {
|
|
108
|
+
const overlayRef = useRef<HTMLDivElement>(null);
|
|
109
|
+
const marquee = useMarqueeGestures({
|
|
110
|
+
iframeRef,
|
|
111
|
+
overlayRef,
|
|
112
|
+
activeCompositionPathRef: useRef<string | null>("index.html"),
|
|
113
|
+
onMarqueeSelectRef: useRef(() => undefined),
|
|
114
|
+
resolveHits: async (elements: HTMLElement[]) => {
|
|
115
|
+
picked.push(elements);
|
|
116
|
+
return [];
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
return (
|
|
120
|
+
<div
|
|
121
|
+
ref={overlayRef}
|
|
122
|
+
data-band-overlay
|
|
123
|
+
onPointerDown={marquee.begin}
|
|
124
|
+
onPointerMove={marquee.onPointerMove}
|
|
125
|
+
onPointerUp={marquee.onPointerUp}
|
|
126
|
+
/>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
const band = mountReactHarness(<Band />);
|
|
130
|
+
const fire = (type: string, x: number, y: number) =>
|
|
131
|
+
act(() => {
|
|
132
|
+
document.querySelector("[data-band-overlay]")!.dispatchEvent(
|
|
133
|
+
new PointerEvent(type, {
|
|
134
|
+
bubbles: true,
|
|
135
|
+
button: 0,
|
|
136
|
+
pointerId: 1,
|
|
137
|
+
clientX: x,
|
|
138
|
+
clientY: y,
|
|
139
|
+
}),
|
|
140
|
+
);
|
|
141
|
+
});
|
|
142
|
+
try {
|
|
143
|
+
fire("pointerdown", 0, 0);
|
|
144
|
+
fire("pointermove", 120, 90);
|
|
145
|
+
iframeRef.current = live;
|
|
146
|
+
await act(async () => fire("pointerup", 120, 90));
|
|
147
|
+
expect(picked.at(-1)?.length).toBeGreaterThan(0);
|
|
148
|
+
for (const element of picked.at(-1)!) expect(element.ownerDocument).toBe(live.contentDocument);
|
|
149
|
+
} finally {
|
|
150
|
+
act(() => band.unmount());
|
|
151
|
+
}
|
|
152
|
+
});
|
|
@@ -159,7 +159,12 @@ export function useMarqueeGestures<T>(deps: MarqueeGesturesDeps<T>): MarqueeGest
|
|
|
159
159
|
const overlay = deps.overlayRef.current;
|
|
160
160
|
if (!iframe || !overlay || !deps.onMarqueeSelectRef.current) return;
|
|
161
161
|
const acp = deps.activeCompositionPathRef.current ?? "index.html";
|
|
162
|
-
const
|
|
162
|
+
const cached = candidatesRef.current;
|
|
163
|
+
const measuredOnScreen = cached?.every(
|
|
164
|
+
(hit) => hit.element.ownerDocument === iframe.contentDocument,
|
|
165
|
+
);
|
|
166
|
+
const candidates =
|
|
167
|
+
cached && measuredOnScreen ? cached : collectMarqueeCandidates(iframe, overlay, acp);
|
|
163
168
|
const elements = hitsWithin(rect, candidates).map((hit) => hit.element);
|
|
164
169
|
const resolveHits = deps.resolveHits;
|
|
165
170
|
const picks = resolveHits
|
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
4
|
import {
|
|
5
5
|
IDLE_POLL_MS,
|
|
6
|
+
IDLE_SLICE_MS,
|
|
6
7
|
requestOverlayFrames,
|
|
7
8
|
resetOverlayFrameLoopForTests,
|
|
9
|
+
runWhenInputIdle,
|
|
8
10
|
subscribeOverlayFrame,
|
|
9
11
|
} from "./overlayFrameLoop";
|
|
10
12
|
import { usePlayerStore } from "../../player/store/playerStore";
|
|
@@ -71,6 +73,31 @@ describe("overlay frame loop", () => {
|
|
|
71
73
|
expect(runs).toBeGreaterThan(0);
|
|
72
74
|
});
|
|
73
75
|
|
|
76
|
+
it("runs idle work in capped slices, only while no input arrives", () => {
|
|
77
|
+
const budgets: number[] = [];
|
|
78
|
+
requestOverlayFrames();
|
|
79
|
+
runWhenInputIdle((timeLeft) => {
|
|
80
|
+
budgets.push(timeLeft());
|
|
81
|
+
return budgets.length === 3;
|
|
82
|
+
});
|
|
83
|
+
vi.advanceTimersByTime(300);
|
|
84
|
+
expect(budgets).toEqual([]);
|
|
85
|
+
vi.advanceTimersByTime(200);
|
|
86
|
+
expect(budgets).toHaveLength(3);
|
|
87
|
+
expect(Math.max(...budgets)).toBeLessThanOrEqual(IDLE_SLICE_MS);
|
|
88
|
+
|
|
89
|
+
// Input during a slice holds the next one until the loop has been idle again.
|
|
90
|
+
const slices: number[] = [];
|
|
91
|
+
runWhenInputIdle(() => {
|
|
92
|
+
slices.push(performance.now());
|
|
93
|
+
if (slices.length === 1) requestOverlayFrames();
|
|
94
|
+
return slices.length === 2;
|
|
95
|
+
});
|
|
96
|
+
vi.advanceTimersByTime(1000);
|
|
97
|
+
expect(slices).toHaveLength(2);
|
|
98
|
+
expect(slices[1] - slices[0]).toBeGreaterThanOrEqual(400);
|
|
99
|
+
});
|
|
100
|
+
|
|
74
101
|
it("runs every subscriber on one frame, not one frame each", () => {
|
|
75
102
|
const calls: string[] = [];
|
|
76
103
|
subscribeOverlayFrame(() => calls.push("a"));
|
|
@@ -143,6 +143,27 @@ function schedule(): void {
|
|
|
143
143
|
}, IDLE_POLL_MS);
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
+
/** Longest idle slice, so input that arrives during one waits at most this long for its frame. */
|
|
147
|
+
export const IDLE_SLICE_MS = 3;
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Runs `step` in idle slices once no input has woken the loop for AWAKE_MS, so it never shares a gesture's
|
|
151
|
+
* frames. `step` works while `timeLeft()` is positive and returns true when it has finished.
|
|
152
|
+
*/
|
|
153
|
+
export function runWhenInputIdle(step: (timeLeft: () => number) => boolean): void {
|
|
154
|
+
const wait = awakeUntil - performance.now();
|
|
155
|
+
if (wait > 0) {
|
|
156
|
+
setTimeout(() => runWhenInputIdle(step), wait);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const idle = window.requestIdleCallback ?? ((callback: () => void) => setTimeout(callback, 0));
|
|
160
|
+
idle(() => {
|
|
161
|
+
const end = performance.now() + IDLE_SLICE_MS;
|
|
162
|
+
if (performance.now() < awakeUntil || !step(() => end - performance.now()))
|
|
163
|
+
runWhenInputIdle(step);
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
146
167
|
/** Something moved, or might have. Run frames at full rate for a moment. */
|
|
147
168
|
export function requestOverlayFrames(): void {
|
|
148
169
|
awakeUntil = performance.now() + AWAKE_MS;
|
|
@@ -14,8 +14,6 @@ import {
|
|
|
14
14
|
buildDomEditStylePatchOperation,
|
|
15
15
|
buildDomEditTextPatchOperation,
|
|
16
16
|
} from "./domEditingLayers";
|
|
17
|
-
import { buildPathOffsetPatches } from "./manualEditsDomPatches";
|
|
18
|
-
import { STUDIO_OFFSET_X_PROP, STUDIO_PATH_OFFSET_ATTR } from "./manualEditsTypes";
|
|
19
17
|
import { makeSelection } from "../../hooks/domSelectionTestHarness";
|
|
20
18
|
import { buildTextFieldChildOperations } from "../../hooks/domEditTextFieldCommitOps";
|
|
21
19
|
|
|
@@ -126,22 +124,6 @@ describe("persist seam source mutation", () => {
|
|
|
126
124
|
expect(findByHfId(html, "qa-image").getAttribute("style")).toContain("opacity: 0.4");
|
|
127
125
|
});
|
|
128
126
|
|
|
129
|
-
it("persists detached jsdom path offset operations", () => {
|
|
130
|
-
const element = document.createElement("div");
|
|
131
|
-
element.style.setProperty(STUDIO_OFFSET_X_PROP, "24px");
|
|
132
|
-
|
|
133
|
-
const html = patchAndExpectChange(
|
|
134
|
-
indexHtml,
|
|
135
|
-
clientTarget({ id: "qa-shape", hfId: "qa-shape", tagName: "div" }),
|
|
136
|
-
buildPathOffsetPatches(element),
|
|
137
|
-
);
|
|
138
|
-
const shape = findByHfId(html, "qa-shape");
|
|
139
|
-
|
|
140
|
-
expect(shape.getAttribute("style")).toContain(`${STUDIO_OFFSET_X_PROP}: 24px`);
|
|
141
|
-
expect(shape.getAttribute("style")).toContain("translate: var(--hf-studio-offset-x, 0px)");
|
|
142
|
-
expect(shape.getAttribute(STUDIO_PATH_OFFSET_ATTR)).toBe("true");
|
|
143
|
-
});
|
|
144
|
-
|
|
145
127
|
it("persists timeline data-start attribute operation", () => {
|
|
146
128
|
const html = patchAndExpectChange(
|
|
147
129
|
indexHtml,
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { roundTo3 } from "../../utils/rounding";
|
|
2
2
|
import { gsapWritesPosition, gsapWritesRotation } from "../../hooks/gsapRuntimeKeyframes";
|
|
3
3
|
import { readCssRotation } from "../../hooks/draggedGsapPosition";
|
|
4
|
-
import { readStudioPathOffset, readStudioRotation } from "./manualEditsDom";
|
|
4
|
+
import { readStudioPathOffset, readStudioRotation, styleUsesStudioOffset } from "./manualEditsDom";
|
|
5
5
|
import { splitTopLevelWhitespace } from "./manualEditsStyleHelpers";
|
|
6
|
+
import {
|
|
7
|
+
STUDIO_OFFSET_X_PROP,
|
|
8
|
+
STUDIO_OFFSET_Y_PROP,
|
|
9
|
+
STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR,
|
|
10
|
+
STUDIO_PATH_OFFSET_ATTR,
|
|
11
|
+
} from "./manualEditsTypes";
|
|
6
12
|
import type { PatchOperation } from "../../utils/sourcePatcher";
|
|
7
13
|
|
|
8
14
|
type Point = { x: number; y: number };
|
|
@@ -102,11 +108,36 @@ function formatTranslatePx(p: Point): string {
|
|
|
102
108
|
return `${roundTo3(p.x)}px ${roundTo3(p.y)}px`;
|
|
103
109
|
}
|
|
104
110
|
|
|
105
|
-
|
|
106
|
-
|
|
111
|
+
const LEGACY_OFFSET_VARS = [STUDIO_OFFSET_X_PROP, STUDIO_OFFSET_Y_PROP];
|
|
112
|
+
|
|
113
|
+
/** Writes a move as the element's own translate; ends a legacy offset's vars and mark. */
|
|
114
|
+
export function writePlainMove(el: HTMLElement, p: Point): PatchOperation[] {
|
|
115
|
+
writeTranslatePx(el, p);
|
|
116
|
+
const patches: PatchOperation[] = [
|
|
117
|
+
{ type: "inline-style", property: "translate", value: formatTranslatePx(p) },
|
|
118
|
+
{
|
|
119
|
+
type: "attribute",
|
|
120
|
+
property: STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR,
|
|
121
|
+
value: el.getAttribute(STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR),
|
|
122
|
+
},
|
|
123
|
+
];
|
|
124
|
+
for (const prop of LEGACY_OFFSET_VARS.filter((v) => el.style.getPropertyValue(v))) {
|
|
125
|
+
el.style.removeProperty(prop);
|
|
126
|
+
patches.push({ type: "inline-style", property: prop, value: null });
|
|
127
|
+
}
|
|
128
|
+
if (el.hasAttribute(STUDIO_PATH_OFFSET_ATTR)) {
|
|
129
|
+
el.removeAttribute(STUDIO_PATH_OFFSET_ATTR);
|
|
130
|
+
patches.push({ type: "attribute", property: STUDIO_PATH_OFFSET_ATTR, value: null });
|
|
131
|
+
}
|
|
132
|
+
return patches;
|
|
107
133
|
}
|
|
108
134
|
|
|
135
|
+
/** The first write records the author's own translate ("" for none), which Reset puts back. */
|
|
109
136
|
export function writeTranslatePx(el: HTMLElement, p: Point): void {
|
|
137
|
+
if (!el.hasAttribute(STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR)) {
|
|
138
|
+
const own = el.style.getPropertyValue("translate");
|
|
139
|
+
el.setAttribute(STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR, styleUsesStudioOffset(own) ? "" : own);
|
|
140
|
+
}
|
|
110
141
|
el.style.setProperty("translate", formatTranslatePx(p));
|
|
111
142
|
}
|
|
112
143
|
|
|
@@ -477,7 +477,7 @@ export function CurveGraph({
|
|
|
477
477
|
onSettle();
|
|
478
478
|
}
|
|
479
479
|
}}
|
|
480
|
-
className="mx-auto aspect-square w-full max-w-[200px] touch-none rounded-sm border border-panel-border-input bg-
|
|
480
|
+
className="mx-auto aspect-square w-full max-w-[200px] touch-none rounded-sm scheme-dark border border-panel-border-input bg-bg-0 outline-hidden focus:ring-1 focus:ring-inset focus:ring-panel-accent"
|
|
481
481
|
>
|
|
482
482
|
<defs>
|
|
483
483
|
<linearGradient id={`hf-hue-axis-${tab.key}`}>
|
|
@@ -491,7 +491,7 @@ export function CurveGraph({
|
|
|
491
491
|
</linearGradient>
|
|
492
492
|
</defs>
|
|
493
493
|
{[0.25, 0.5, 0.75].map((ratio) => (
|
|
494
|
-
<g key={ratio}
|
|
494
|
+
<g key={ratio} className="stroke-text-0/10" strokeWidth="0.5">
|
|
495
495
|
<line
|
|
496
496
|
x1={GRAPH_PADDING}
|
|
497
497
|
y1={GRAPH_PADDING + ratio * (GRAPH_SIZE - GRAPH_PADDING * 2)}
|
|
@@ -14,11 +14,11 @@ import { useTrackDesignInput } from "../../contexts/DesignPanelInputContext";
|
|
|
14
14
|
function StatusPill({ status }: { status: RuntimeColorGradingStatus }) {
|
|
15
15
|
const dotClass =
|
|
16
16
|
status.state === "active"
|
|
17
|
-
? "bg-
|
|
17
|
+
? "bg-accent"
|
|
18
18
|
: status.state === "pending"
|
|
19
|
-
? "bg-
|
|
19
|
+
? "bg-warning-ink"
|
|
20
20
|
: status.state === "unavailable"
|
|
21
|
-
? "bg-
|
|
21
|
+
? "bg-danger-ink"
|
|
22
22
|
: "bg-panel-text-5";
|
|
23
23
|
return (
|
|
24
24
|
<div
|
|
@@ -44,18 +44,18 @@ function HdrMediaWarning({ metadata }: { metadata: MediaMetadata | null }) {
|
|
|
44
44
|
.join(" · ");
|
|
45
45
|
|
|
46
46
|
return (
|
|
47
|
-
<div className="mb-3 rounded-md border border-amber-500/30 bg-amber-500/10 px-3 py-2 text-[11px] leading-4 text-
|
|
47
|
+
<div className="mb-3 rounded-md border border-amber-500/30 bg-amber-500/10 px-3 py-2 text-[11px] leading-4 text-warning-ink">
|
|
48
48
|
<div className="mb-1 flex min-w-0 items-center justify-between gap-2">
|
|
49
49
|
<span className="font-semibold">{metadata.color.label} source</span>
|
|
50
|
-
<span className="rounded-sm bg-amber-
|
|
50
|
+
<span className="rounded-sm bg-amber-500/15 px-1.5 py-0.5 text-[9px] font-semibold uppercase tracking-wide text-warning-ink">
|
|
51
51
|
SDR preview
|
|
52
52
|
</span>
|
|
53
53
|
</div>
|
|
54
|
-
<p className="text-
|
|
54
|
+
<p className="text-text-2">
|
|
55
55
|
These controls use the current SDR shader preview path. Render may stay HDR-tagged, but this
|
|
56
56
|
is not true HDR color grading yet.
|
|
57
57
|
</p>
|
|
58
|
-
{details && <p className="mt-1 truncate text-[10px] text-
|
|
58
|
+
{details && <p className="mt-1 truncate text-[10px] text-text-muted">{details}</p>}
|
|
59
59
|
</div>
|
|
60
60
|
);
|
|
61
61
|
}
|
|
@@ -120,7 +120,7 @@ function HoldBeforeButton({
|
|
|
120
120
|
}}
|
|
121
121
|
className={`flex h-6 w-6 shrink-0 items-center justify-center rounded transition-colors ${
|
|
122
122
|
active
|
|
123
|
-
? "bg-studio-accent text-
|
|
123
|
+
? "bg-studio-accent text-on-accent"
|
|
124
124
|
: "text-panel-text-4 hover:bg-panel-hover hover:text-panel-text-1"
|
|
125
125
|
} disabled:cursor-not-allowed disabled:opacity-40`}
|
|
126
126
|
title="Hold to show original"
|
|
@@ -158,7 +158,7 @@ export function ColorGradingSliderControl({
|
|
|
158
158
|
settings.onClick();
|
|
159
159
|
}}
|
|
160
160
|
className={`relative flex h-5 w-5 shrink-0 items-center justify-center rounded transition-colors hover:bg-panel-hover hover:text-panel-text-1 disabled:cursor-not-allowed disabled:opacity-40 ${
|
|
161
|
-
settings.active ? "text-
|
|
161
|
+
settings.active ? "text-accent-ink" : "text-panel-text-5"
|
|
162
162
|
}`}
|
|
163
163
|
title={settings.label}
|
|
164
164
|
>
|
|
@@ -204,13 +204,13 @@ export function PropertyPanelColorSecondary({
|
|
|
204
204
|
setSampling(false);
|
|
205
205
|
}
|
|
206
206
|
}}
|
|
207
|
-
className="flex min-h-7 items-center gap-1.5 text-[10px] font-medium text-
|
|
207
|
+
className="flex min-h-7 items-center gap-1.5 text-[10px] font-medium text-accent-ink hover:text-accent-ink/80 disabled:opacity-50"
|
|
208
208
|
>
|
|
209
209
|
<Eyedropper size={12} />
|
|
210
210
|
{sampling ? "Capturing frame" : "Sample color from frame"}
|
|
211
211
|
</button>
|
|
212
212
|
{captureError && (
|
|
213
|
-
<p role="alert" className="text-[9px] leading-4 text-
|
|
213
|
+
<p role="alert" className="text-[9px] leading-4 text-danger-ink">
|
|
214
214
|
{captureError}
|
|
215
215
|
</p>
|
|
216
216
|
)}
|
|
@@ -221,7 +221,7 @@ export function PropertyPanelColorSecondary({
|
|
|
221
221
|
type="button"
|
|
222
222
|
aria-pressed={!showMatte}
|
|
223
223
|
onClick={() => setShowMatte(false)}
|
|
224
|
-
className={!showMatte ? "text-
|
|
224
|
+
className={!showMatte ? "text-accent-ink" : "text-panel-text-4"}
|
|
225
225
|
>
|
|
226
226
|
Source
|
|
227
227
|
</button>
|
|
@@ -229,7 +229,7 @@ export function PropertyPanelColorSecondary({
|
|
|
229
229
|
type="button"
|
|
230
230
|
aria-pressed={showMatte}
|
|
231
231
|
onClick={() => setShowMatte(true)}
|
|
232
|
-
className={showMatte ? "text-
|
|
232
|
+
className={showMatte ? "text-accent-ink" : "text-panel-text-4"}
|
|
233
233
|
>
|
|
234
234
|
Selection matte
|
|
235
235
|
</button>
|
|
@@ -131,7 +131,7 @@ export function ImageFillField({
|
|
|
131
131
|
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 ${
|
|
132
132
|
disabled || uploading
|
|
133
133
|
? "cursor-not-allowed text-neutral-600"
|
|
134
|
-
: "cursor-pointer hover:border-neutral-600 hover:text-
|
|
134
|
+
: "cursor-pointer hover:border-neutral-600 hover:text-text-0"
|
|
135
135
|
}`}
|
|
136
136
|
>
|
|
137
137
|
<Plus size={12} className="shrink-0" />
|
|
@@ -151,7 +151,7 @@ export function ImageFillField({
|
|
|
151
151
|
/>
|
|
152
152
|
</div>
|
|
153
153
|
{uploadError && (
|
|
154
|
-
<div className="text-[10px] text-
|
|
154
|
+
<div className="text-[10px] text-danger-ink" role="alert">
|
|
155
155
|
{uploadError}
|
|
156
156
|
</div>
|
|
157
157
|
)}
|
|
@@ -340,7 +340,7 @@ export function GradientField({
|
|
|
340
340
|
track("toggle", "Repeat gradient");
|
|
341
341
|
patch({ repeating: e.target.checked });
|
|
342
342
|
}}
|
|
343
|
-
className="h-4 w-4 rounded-sm border-neutral-700 bg-neutral-950 text-
|
|
343
|
+
className="h-4 w-4 rounded-sm border-neutral-700 bg-neutral-950 text-accent-ink focus:ring-panel-accent"
|
|
344
344
|
/>
|
|
345
345
|
Repeat
|
|
346
346
|
</label>
|
|
@@ -357,7 +357,7 @@ export function GradientField({
|
|
|
357
357
|
})),
|
|
358
358
|
});
|
|
359
359
|
}}
|
|
360
|
-
className="inline-flex h-7 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-
|
|
360
|
+
className="inline-flex h-7 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 disabled:cursor-not-allowed disabled:text-neutral-600"
|
|
361
361
|
>
|
|
362
362
|
<ReverseGradientIcon size={16} />
|
|
363
363
|
Reverse
|
|
@@ -442,7 +442,7 @@ export function GradientField({
|
|
|
442
442
|
disabled={disabled || parsed.stops.length >= 6}
|
|
443
443
|
onClick={() => addStop()}
|
|
444
444
|
title={parsed.stops.length >= 6 ? "Maximum 6 stops" : "Add a gradient stop"}
|
|
445
|
-
className="inline-flex h-7 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-
|
|
445
|
+
className="inline-flex h-7 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 active:scale-[0.98] disabled:cursor-not-allowed disabled:text-neutral-600"
|
|
446
446
|
>
|
|
447
447
|
<Plus size={12} />
|
|
448
448
|
Add stop
|
|
@@ -474,7 +474,7 @@ export function GradientField({
|
|
|
474
474
|
type="button"
|
|
475
475
|
disabled={disabled || parsed.stops.length <= 2}
|
|
476
476
|
onClick={() => removeStop(index)}
|
|
477
|
-
className="mt-[22px] flex h-10 items-center justify-center rounded-lg border border-neutral-700 bg-neutral-950 text-neutral-400 transition-colors hover:border-neutral-600 hover:text-
|
|
477
|
+
className="mt-[22px] flex h-10 items-center justify-center rounded-lg border border-neutral-700 bg-neutral-950 text-neutral-400 transition-colors hover:border-neutral-600 hover:text-text-0 disabled:cursor-not-allowed disabled:text-text-off"
|
|
478
478
|
aria-label={`Remove stop ${index + 1}`}
|
|
479
479
|
>
|
|
480
480
|
<X size={12} />
|
|
@@ -5,9 +5,9 @@ import { Compare, RotateCcw } from "../../icons/SystemIcons";
|
|
|
5
5
|
import type { ColorGradingControllerState } from "./useColorGradingController";
|
|
6
6
|
|
|
7
7
|
const STATUS_DOT_CLASS: Record<ColorGradingControllerState["runtimeStatus"]["state"], string> = {
|
|
8
|
-
active: "bg-
|
|
9
|
-
pending: "bg-
|
|
10
|
-
unavailable: "bg-
|
|
8
|
+
active: "bg-accent",
|
|
9
|
+
pending: "bg-warning-ink",
|
|
10
|
+
unavailable: "bg-danger-ink",
|
|
11
11
|
missing: "bg-panel-text-5",
|
|
12
12
|
inactive: "bg-panel-text-5",
|
|
13
13
|
};
|
|
@@ -81,7 +81,7 @@ describe("FlatColorGradingAccessory", () => {
|
|
|
81
81
|
const dot = host.querySelector('[data-flat-grade-status-dot="true"]');
|
|
82
82
|
expect(dot).not.toBeNull();
|
|
83
83
|
expect(dot?.getAttribute("title")).toBe("Shader active");
|
|
84
|
-
expect(dot?.className).toContain("bg-
|
|
84
|
+
expect(dot?.className).toContain("bg-accent");
|
|
85
85
|
act(() => root.unmount());
|
|
86
86
|
});
|
|
87
87
|
|