@hyperframes/studio 0.7.57 → 0.7.59
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--Z69cEkE.js → hyperframes-player-Bm07FLkl.js} +1 -1
- package/dist/assets/{index-KsfE1bUu.js → index-B995FG46.js} +1 -1
- package/dist/assets/index-CrkAdJkb.js +426 -0
- package/dist/assets/index-Dj5p8U_A.css +1 -0
- package/dist/assets/{index-Bf1x1y8H.js → index-FvzmPhfG.js} +1 -1
- package/dist/{chunk-5QSIMBEJ.js → chunk-OBAG3GWK.js} +33 -21
- package/dist/chunk-OBAG3GWK.js.map +1 -0
- package/dist/{domEditingLayers-6LQGKPOI.js → domEditingLayers-2CKWGEHS.js} +2 -2
- package/dist/index.d.ts +27 -7
- package/dist/index.html +2 -2
- package/dist/index.js +18029 -11563
- package/dist/index.js.map +1 -1
- package/dist/styles/tailwind-preset.d.ts +5 -0
- package/dist/styles/tailwind-preset.js +8 -1
- package/dist/styles/tailwind-preset.js.map +1 -1
- package/package.json +7 -7
- package/src/App.tsx +6 -6
- package/src/components/DesignPanelPromoteProvider.tsx +15 -2
- package/src/components/StudioFeedbackBar.tsx +2 -2
- package/src/components/StudioRightPanel.tsx +29 -51
- package/src/components/editor/AnimationCard.test.tsx +134 -0
- package/src/components/editor/AnimationCard.tsx +19 -4
- package/src/components/editor/ArcPathControls.tsx +1 -0
- package/src/components/editor/CanvasContextMenu.test.tsx +27 -14
- package/src/components/editor/CanvasContextMenu.tsx +71 -6
- package/src/components/editor/DomEditOverlay.tsx +5 -2
- package/src/components/editor/GestureRecordControl.tsx +7 -1
- package/src/components/editor/GsapAnimationSection.tsx +109 -14
- package/src/components/editor/InspectorHeaderActions.tsx +42 -4
- package/src/components/editor/LayersPanel.tsx +110 -99
- package/src/components/editor/PropertyPanel.test.tsx +948 -0
- package/src/components/editor/PropertyPanel.tsx +144 -148
- package/src/components/editor/PropertyPanelEmptyState.test.tsx +74 -0
- package/src/components/editor/PropertyPanelEmptyState.tsx +179 -1
- package/src/components/editor/PropertyPanelFlat.tsx +580 -0
- package/src/components/editor/PropertyPanelFlatFooter.test.tsx +83 -0
- package/src/components/editor/PropertyPanelFlatFooter.tsx +73 -0
- package/src/components/editor/PropertyPanelFlatHeader.test.tsx +79 -0
- package/src/components/editor/PropertyPanelFlatHeader.tsx +112 -0
- package/src/components/editor/canvasContextMenuZOrder.test.ts +57 -0
- package/src/components/editor/canvasContextMenuZOrder.ts +40 -1
- package/src/components/editor/domEditingDom.ts +5 -6
- package/src/components/editor/domEditingElement.ts +15 -32
- package/src/components/editor/gsapAnimationCallbacks.ts +27 -0
- package/src/components/editor/gsapLivePreview.ts +34 -0
- package/src/components/editor/layersPanelSort.ts +80 -0
- package/src/components/editor/manualEditingAvailability.test.ts +10 -0
- package/src/components/editor/manualEditingAvailability.ts +9 -0
- package/src/components/editor/propertyPanel3dTransform.tsx +5 -0
- package/src/components/editor/propertyPanelColor.test.tsx +28 -0
- package/src/components/editor/propertyPanelColor.tsx +31 -1
- package/src/components/editor/propertyPanelColorGradingControls.tsx +11 -2
- package/src/components/editor/propertyPanelColorGradingSection.tsx +45 -365
- package/src/components/editor/propertyPanelColorGradingSlider.tsx +19 -5
- package/src/components/editor/propertyPanelFill.tsx +22 -5
- package/src/components/editor/propertyPanelFlatColorGradingSection.test.tsx +622 -0
- package/src/components/editor/propertyPanelFlatColorGradingSection.tsx +570 -0
- package/src/components/editor/propertyPanelFlatLayoutSection.test.tsx +293 -0
- package/src/components/editor/propertyPanelFlatLayoutSection.tsx +383 -0
- package/src/components/editor/propertyPanelFlatMaskInsetRows.tsx +102 -0
- package/src/components/editor/propertyPanelFlatMediaSection.test.tsx +436 -0
- package/src/components/editor/propertyPanelFlatMediaSection.tsx +286 -0
- package/src/components/editor/propertyPanelFlatMotionSection.test.tsx +272 -0
- package/src/components/editor/propertyPanelFlatMotionSection.tsx +315 -0
- package/src/components/editor/propertyPanelFlatPrimitives.test.tsx +1174 -0
- package/src/components/editor/propertyPanelFlatPrimitives.tsx +563 -0
- package/src/components/editor/propertyPanelFlatSelectRow.tsx +101 -0
- package/src/components/editor/propertyPanelFlatStyleHelpers.test.ts +23 -0
- package/src/components/editor/propertyPanelFlatStyleHelpers.ts +27 -0
- package/src/components/editor/propertyPanelFlatStyleSections.test.tsx +598 -0
- package/src/components/editor/propertyPanelFlatStyleSections.tsx +516 -0
- package/src/components/editor/propertyPanelFlatTextSection.test.tsx +465 -0
- package/src/components/editor/propertyPanelFlatTextSection.tsx +407 -0
- package/src/components/editor/propertyPanelFlatTimingDerivation.test.ts +71 -0
- package/src/components/editor/propertyPanelFlatTimingDerivation.ts +59 -0
- package/src/components/editor/propertyPanelFlatToggle.test.tsx +63 -0
- package/src/components/editor/propertyPanelFlatToggle.tsx +53 -0
- package/src/components/editor/propertyPanelFont.test.tsx +30 -0
- package/src/components/editor/propertyPanelFont.tsx +136 -95
- package/src/components/editor/propertyPanelHelpers.ts +73 -0
- package/src/components/editor/propertyPanelInputCoverage.test.tsx +587 -0
- package/src/components/editor/propertyPanelMediaSection.tsx +10 -0
- package/src/components/editor/propertyPanelPrimitives.tsx +60 -29
- package/src/components/editor/propertyPanelSections.test.tsx +161 -0
- package/src/components/editor/propertyPanelSections.tsx +154 -90
- package/src/components/editor/propertyPanelStyleSections.tsx +12 -1
- package/src/components/editor/propertyPanelTimingSection.tsx +1 -1
- package/src/components/editor/propertyPanelTypes.ts +14 -1
- package/src/components/editor/propertyPanelValueTier.test.ts +32 -0
- package/src/components/editor/propertyPanelValueTier.ts +28 -0
- package/src/components/editor/useColorGradingController.test.ts +418 -0
- package/src/components/editor/useColorGradingController.ts +596 -0
- package/src/components/editor/useLayerRevealOverride.test.ts +169 -0
- package/src/components/editor/useLayerRevealOverride.ts +389 -0
- package/src/components/nle/PreviewOverlays.tsx +27 -2
- package/src/components/nle/TimelinePane.test.ts +3 -0
- package/src/components/nle/TimelinePane.tsx +11 -1
- package/src/components/nle/useCanvasZOrderTimelineMirror.test.tsx +440 -0
- package/src/components/nle/useCanvasZOrderTimelineMirror.ts +191 -0
- package/src/components/nle/useTimelineEditCallbacks.ts +1 -0
- package/src/components/nle/zLaneGesture.test.ts +176 -0
- package/src/components/nle/zLaneGesture.ts +77 -0
- package/src/components/panels/VariablesPanel.tsx +4 -0
- package/src/components/sidebar/CompositionsTab.test.ts +28 -2
- package/src/components/sidebar/CompositionsTab.tsx +3 -1
- package/src/components/storyboard/FramePoster.tsx +24 -4
- package/src/components/storyboard/StoryboardFrameFocus.tsx +4 -0
- package/src/components/storyboard/StoryboardFrameTile.tsx +30 -1
- package/src/components/storyboard/StoryboardGrid.tsx +23 -1
- package/src/components/storyboard/StoryboardLoaded.tsx +58 -2
- package/src/components/storyboard/StoryboardView.tsx +9 -2
- package/src/components/storyboard/frameComments.test.ts +95 -0
- package/src/components/storyboard/frameComments.ts +115 -0
- package/src/components/storyboard/useFrameComments.ts +82 -0
- package/src/contexts/DesignPanelInputContext.test.tsx +81 -0
- package/src/contexts/DesignPanelInputContext.tsx +48 -0
- package/src/contexts/DomEditContext.tsx +4 -0
- package/src/contexts/VariablePromoteContext.test.tsx +67 -0
- package/src/contexts/VariablePromoteContext.tsx +10 -5
- package/src/hooks/domEditCommitRunner.ts +11 -0
- package/src/hooks/domEditCommitTypes.ts +17 -1
- package/src/hooks/elementLifecycleOpsTestUtils.ts +25 -0
- package/src/hooks/fetchStubTestUtils.ts +16 -0
- package/src/hooks/gsapScriptCommitTypes.ts +6 -4
- package/src/hooks/timelineEditingHelpers.test.ts +92 -1
- package/src/hooks/timelineEditingHelpers.ts +32 -15
- package/src/hooks/timelineMoveAdapter.ts +2 -1
- package/src/hooks/timelineTimingSync.test.ts +703 -33
- package/src/hooks/timelineTimingSync.ts +282 -97
- package/src/hooks/timelineTrackVisibility.test.ts +55 -1
- package/src/hooks/timelineTrackVisibility.ts +31 -13
- package/src/hooks/useAppHotkeys.ts +9 -0
- package/src/hooks/useClipboard.ts +19 -17
- package/src/hooks/useDomEditAttributeCommits.ts +123 -1
- package/src/hooks/useDomEditCommits.test.tsx +316 -38
- package/src/hooks/useDomEditCommits.ts +81 -162
- package/src/hooks/useDomEditCommitsHelpers.ts +158 -0
- package/src/hooks/useDomEditSession.test.tsx +3 -1
- package/src/hooks/useDomEditSession.ts +18 -6
- package/src/hooks/useDomEditTextCommits.ts +14 -9
- package/src/hooks/useDomEditWiring.ts +10 -9
- package/src/hooks/useElementLifecycleOps.test.tsx +426 -60
- package/src/hooks/useElementLifecycleOps.ts +182 -84
- package/src/hooks/useFileManager.projectOwnership.test.tsx +98 -0
- package/src/hooks/useFileManager.ts +82 -60
- package/src/hooks/useGsapAnimationOps.ts +7 -6
- package/src/hooks/useGsapKeyframeOps.ts +6 -5
- package/src/hooks/useGsapPropertyDebounce.ts +43 -28
- package/src/hooks/useGsapPropertyDebounceFlush.test.ts +35 -0
- package/src/hooks/useGsapScriptCommits.test.tsx +48 -2
- package/src/hooks/useGsapScriptCommits.ts +150 -50
- package/src/hooks/useGsapSelectionHandlers.test.tsx +116 -0
- package/src/hooks/useGsapSelectionHandlers.ts +94 -31
- package/src/hooks/useInspectorSplitResize.ts +51 -0
- package/src/hooks/usePersistentEditHistory.projectOwnership.test.tsx +60 -0
- package/src/hooks/usePersistentEditHistory.test.ts +49 -1
- package/src/hooks/usePersistentEditHistory.ts +71 -31
- package/src/hooks/usePreviewPersistence.ts +2 -2
- package/src/hooks/useProjectCompositionVariables.ts +10 -10
- package/src/hooks/useProjectSignaturePoll.test.tsx +135 -0
- package/src/hooks/useProjectSignaturePoll.ts +68 -0
- package/src/hooks/useSdkSession.lifecycle.test.tsx +137 -0
- package/src/hooks/useSdkSession.ts +155 -14
- package/src/hooks/useSlideshowPersist.ts +7 -1
- package/src/hooks/useStoryboard.ts +23 -4
- package/src/hooks/useTimelineEditing.test.tsx +306 -155
- package/src/hooks/useTimelineEditing.ts +111 -101
- package/src/hooks/useTimelineEditingTypes.ts +4 -1
- package/src/hooks/useTimelineGroupEditing.ts +51 -5
- package/src/hooks/useVariablesPersist.ts +9 -7
- package/src/icons/SystemIcons.tsx +2 -0
- package/src/player/components/Timeline.test.ts +31 -21
- package/src/player/components/Timeline.tsx +62 -91
- package/src/player/components/TimelineCanvas.tsx +36 -5
- package/src/player/components/TimelineLanes.tsx +32 -10
- package/src/player/components/TimelineOverlays.tsx +36 -0
- package/src/player/components/TimelineRuler.tsx +18 -5
- package/src/player/components/TrackGapContextMenu.tsx +116 -0
- package/src/player/components/timelineCallbacks.ts +4 -1
- package/src/player/components/timelineClipDragCommit.test.ts +279 -3
- package/src/player/components/timelineClipDragCommit.ts +173 -71
- package/src/player/components/timelineGapCommit.test.ts +163 -0
- package/src/player/components/timelineGapCommit.ts +103 -0
- package/src/player/components/timelineGaps.test.ts +221 -0
- package/src/player/components/timelineGaps.ts +182 -0
- package/src/player/components/timelineLayout.test.ts +2 -1
- package/src/player/components/timelineLayout.ts +22 -6
- package/src/player/components/timelineMarquee.test.ts +20 -10
- package/src/player/components/timelineMarquee.ts +9 -2
- package/src/player/components/timelineStackingSync.test.ts +42 -1
- package/src/player/components/timelineStackingSync.ts +31 -16
- package/src/player/components/timelineZMirror.test.ts +667 -0
- package/src/player/components/timelineZMirror.ts +393 -0
- package/src/player/components/timelineZoom.test.ts +2 -2
- package/src/player/components/timelineZoom.ts +7 -3
- package/src/player/components/useTimelineClipDrag.ts +1 -1
- package/src/player/components/useTimelineGapHighlights.test.ts +119 -0
- package/src/player/components/useTimelineGapHighlights.ts +110 -0
- package/src/player/components/useTimelinePlayhead.ts +4 -3
- package/src/player/components/useTimelineRangeSelection.ts +6 -4
- package/src/player/components/useTimelineScrollViewport.ts +73 -0
- package/src/player/components/useTimelineStackingSync.test.tsx +0 -1
- package/src/player/components/useTimelineStackingSync.ts +5 -3
- package/src/player/components/useTimelineTrackDerivations.ts +38 -0
- package/src/player/components/useTrackGapMenu.ts +155 -0
- package/src/player/hooks/useExpandedTimelineElements.test.ts +67 -4
- package/src/player/hooks/useExpandedTimelineElements.ts +12 -1
- package/src/player/hooks/useTimelineSyncCallbacks.ts +2 -0
- package/src/player/lib/layerOrdering.test.ts +1 -15
- package/src/player/lib/layerOrdering.ts +5 -28
- package/src/player/lib/timelineDOM.test.ts +72 -0
- package/src/player/lib/timelineDOM.ts +3 -0
- package/src/player/lib/timelineElementHelpers.ts +35 -11
- package/src/player/store/playerStore.ts +12 -1
- package/src/styles/studio.css +22 -0
- package/src/styles/tailwind-preset.shared.js +7 -0
- package/src/telemetry/events.test.ts +10 -0
- package/src/telemetry/events.ts +1 -0
- package/src/utils/designInputTracking.test.ts +97 -0
- package/src/utils/designInputTracking.ts +80 -0
- package/src/utils/domEditSaveQueue.ts +4 -3
- package/src/utils/editHistory.test.ts +82 -0
- package/src/utils/editHistory.ts +14 -2
- package/src/utils/gsapSoftReload.test.ts +54 -115
- package/src/utils/gsapSoftReload.ts +42 -152
- package/src/utils/gsapUndoRestore.test.ts +269 -0
- package/src/utils/gsapUndoRestore.ts +269 -0
- package/src/utils/sdkCutover.gate.test.ts +7 -7
- package/src/utils/sdkCutover.test.ts +490 -57
- package/src/utils/sdkCutover.ts +181 -219
- package/src/utils/sdkEditTransaction.ts +292 -0
- package/src/utils/sdkResolverAttempts.ts +94 -0
- package/src/utils/sdkResolverShadow.test.ts +148 -0
- package/src/utils/sdkResolverShadow.ts +139 -106
- package/src/utils/setSlideshowManifest.test.ts +0 -9
- package/src/utils/setSlideshowManifest.ts +20 -27
- package/src/utils/sourceScopedSelectorIndex.ts +29 -0
- package/src/utils/studioFileHistory.test.ts +41 -0
- package/src/utils/studioFileHistory.ts +30 -27
- package/src/utils/studioFileMutationCoordinator.ts +45 -0
- package/src/utils/studioHelpers.test.ts +45 -0
- package/src/utils/studioHelpers.ts +45 -14
- package/src/utils/studioSaveDiagnostics.ts +17 -0
- package/dist/assets/index-DfmYkU44.js +0 -423
- package/dist/assets/index-_pqzyxB1.css +0 -1
- package/dist/chunk-5QSIMBEJ.js.map +0 -1
- /package/dist/{domEditingLayers-6LQGKPOI.js.map → domEditingLayers-2CKWGEHS.js.map} +0 -0
|
@@ -0,0 +1,587 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
|
|
3
|
+
import React, { act, type ReactElement } from "react";
|
|
4
|
+
import { createRoot, type Root } from "react-dom/client";
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
6
|
+
import { DesignPanelInputProvider } from "../../contexts/DesignPanelInputContext";
|
|
7
|
+
import { __resetDesignInputThrottle } from "../../utils/designInputTracking";
|
|
8
|
+
import type { PropertyPanelProps } from "./propertyPanelHelpers";
|
|
9
|
+
import { ColorField } from "./propertyPanelColor";
|
|
10
|
+
import { FontFamilyField } from "./propertyPanelFont";
|
|
11
|
+
import {
|
|
12
|
+
FlatRow,
|
|
13
|
+
FlatSegmentedRow,
|
|
14
|
+
FlatSelectRow,
|
|
15
|
+
FlatSlider,
|
|
16
|
+
} from "./propertyPanelFlatPrimitives";
|
|
17
|
+
import { FlatToggle } from "./propertyPanelFlatToggle";
|
|
18
|
+
import {
|
|
19
|
+
DetailField,
|
|
20
|
+
MetricField,
|
|
21
|
+
Section,
|
|
22
|
+
SegmentedControl,
|
|
23
|
+
SelectField,
|
|
24
|
+
SliderControl,
|
|
25
|
+
} from "./propertyPanelPrimitives";
|
|
26
|
+
import { TextAreaField } from "./propertyPanelSections";
|
|
27
|
+
|
|
28
|
+
const trackStudioEvent = vi.hoisted(() => vi.fn());
|
|
29
|
+
|
|
30
|
+
vi.mock("../../utils/studioTelemetry", () => ({
|
|
31
|
+
trackStudioEvent: (...args: unknown[]) => trackStudioEvent(...args),
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
vi.mock("../../contexts/StudioContext", async () => {
|
|
35
|
+
const actual = await vi.importActual<typeof import("../../contexts/StudioContext")>(
|
|
36
|
+
"../../contexts/StudioContext",
|
|
37
|
+
);
|
|
38
|
+
return { ...actual, useStudioShellContext: () => ({ showToast: vi.fn() }) };
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
(globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
|
42
|
+
|
|
43
|
+
let roots: Root[] = [];
|
|
44
|
+
|
|
45
|
+
beforeEach(() => {
|
|
46
|
+
trackStudioEvent.mockReset();
|
|
47
|
+
__resetDesignInputThrottle();
|
|
48
|
+
vi.stubGlobal(
|
|
49
|
+
"fetch",
|
|
50
|
+
vi.fn(async () => new Response(JSON.stringify([]), { status: 200 })),
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
afterEach(() => {
|
|
55
|
+
for (const root of roots) act(() => root.unmount());
|
|
56
|
+
roots = [];
|
|
57
|
+
document.body.innerHTML = "";
|
|
58
|
+
vi.useRealTimers();
|
|
59
|
+
vi.doUnmock("./manualEditingAvailability");
|
|
60
|
+
vi.resetModules();
|
|
61
|
+
vi.unstubAllGlobals();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
function render(ui: ReactElement): HTMLElement {
|
|
65
|
+
const host = document.createElement("div");
|
|
66
|
+
document.body.append(host);
|
|
67
|
+
const root = createRoot(host);
|
|
68
|
+
roots.push(root);
|
|
69
|
+
act(() => root.render(ui));
|
|
70
|
+
return host;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function changeInput(input: HTMLInputElement, value: string) {
|
|
74
|
+
const setter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")?.set;
|
|
75
|
+
if (!setter) throw new Error("expected native input value setter");
|
|
76
|
+
setter.call(input, value);
|
|
77
|
+
input.dispatchEvent(new Event("input", { bubbles: true }));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function changeTextarea(textarea: HTMLTextAreaElement, value: string) {
|
|
81
|
+
const setter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, "value")?.set;
|
|
82
|
+
if (!setter) throw new Error("expected native textarea value setter");
|
|
83
|
+
setter.call(textarea, value);
|
|
84
|
+
textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function blurInput(input: HTMLInputElement) {
|
|
88
|
+
input.focus();
|
|
89
|
+
input.blur();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function expectTracked(control: string, name: string, section = "style") {
|
|
93
|
+
expect(trackStudioEvent).toHaveBeenLastCalledWith("design_input", {
|
|
94
|
+
ui: "classic",
|
|
95
|
+
section,
|
|
96
|
+
control,
|
|
97
|
+
name,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function expectFlatTracked(control: string, name: string, section = "layout") {
|
|
102
|
+
expect(trackStudioEvent).toHaveBeenLastCalledWith("design_input", {
|
|
103
|
+
ui: "flat",
|
|
104
|
+
section,
|
|
105
|
+
control,
|
|
106
|
+
name,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function flatSection(children: ReactElement) {
|
|
111
|
+
return (
|
|
112
|
+
<DesignPanelInputProvider ui="flat" section="layout">
|
|
113
|
+
{children}
|
|
114
|
+
</DesignPanelInputProvider>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function classicSection(children: ReactElement) {
|
|
119
|
+
return (
|
|
120
|
+
<DesignPanelInputProvider ui="classic">
|
|
121
|
+
<Section title="Style" icon={null}>
|
|
122
|
+
{children}
|
|
123
|
+
</Section>
|
|
124
|
+
</DesignPanelInputProvider>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
describe("classic property-panel primitive telemetry", () => {
|
|
129
|
+
it("tracks MetricField only when a changed value commits", () => {
|
|
130
|
+
const onCommit = vi.fn();
|
|
131
|
+
const host = render(
|
|
132
|
+
classicSection(<MetricField label="Opacity" value="20" onCommit={onCommit} />),
|
|
133
|
+
);
|
|
134
|
+
const input = host.querySelector("input");
|
|
135
|
+
if (!input) throw new Error("expected metric input");
|
|
136
|
+
|
|
137
|
+
act(() => blurInput(input));
|
|
138
|
+
expect(trackStudioEvent).not.toHaveBeenCalled();
|
|
139
|
+
|
|
140
|
+
act(() => {
|
|
141
|
+
changeInput(input, "40");
|
|
142
|
+
});
|
|
143
|
+
act(() => blurInput(input));
|
|
144
|
+
|
|
145
|
+
expect(onCommit).toHaveBeenCalledWith("40");
|
|
146
|
+
expectTracked("metric", "opacity");
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("tracks SliderControl on settle, not on its scheduled commit tick", () => {
|
|
150
|
+
vi.useFakeTimers();
|
|
151
|
+
const onCommit = vi.fn();
|
|
152
|
+
const host = render(
|
|
153
|
+
classicSection(
|
|
154
|
+
<SliderControl
|
|
155
|
+
trackName="Opacity"
|
|
156
|
+
value={20}
|
|
157
|
+
min={0}
|
|
158
|
+
max={100}
|
|
159
|
+
step={1}
|
|
160
|
+
displayValue="20%"
|
|
161
|
+
onCommit={onCommit}
|
|
162
|
+
/>,
|
|
163
|
+
),
|
|
164
|
+
);
|
|
165
|
+
const input = host.querySelector<HTMLInputElement>('input[type="range"]');
|
|
166
|
+
if (!input) throw new Error("expected slider input");
|
|
167
|
+
|
|
168
|
+
act(() => {
|
|
169
|
+
changeInput(input, "40");
|
|
170
|
+
});
|
|
171
|
+
act(() => vi.advanceTimersByTime(40));
|
|
172
|
+
expect(trackStudioEvent).not.toHaveBeenCalled();
|
|
173
|
+
|
|
174
|
+
act(() => input.dispatchEvent(new MouseEvent("mouseup", { bubbles: true })));
|
|
175
|
+
expectTracked("slider", "opacity");
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("tracks SelectField with its label", () => {
|
|
179
|
+
const host = render(
|
|
180
|
+
classicSection(
|
|
181
|
+
<SelectField
|
|
182
|
+
label="Blend mode"
|
|
183
|
+
value="normal"
|
|
184
|
+
options={["normal", "multiply"]}
|
|
185
|
+
onChange={vi.fn()}
|
|
186
|
+
/>,
|
|
187
|
+
),
|
|
188
|
+
);
|
|
189
|
+
const select = host.querySelector("select");
|
|
190
|
+
if (!select) throw new Error("expected select");
|
|
191
|
+
act(() => {
|
|
192
|
+
select.value = "multiply";
|
|
193
|
+
select.dispatchEvent(new Event("change", { bubbles: true }));
|
|
194
|
+
});
|
|
195
|
+
expectTracked("select", "blend-mode");
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("tracks DetailField with its label", () => {
|
|
199
|
+
const host = render(
|
|
200
|
+
classicSection(<DetailField label="External URL" value="old.png" onCommit={vi.fn()} />),
|
|
201
|
+
);
|
|
202
|
+
const input = host.querySelector("input");
|
|
203
|
+
if (!input) throw new Error("expected detail input");
|
|
204
|
+
act(() => changeInput(input, "new.png"));
|
|
205
|
+
act(() => blurInput(input));
|
|
206
|
+
expectTracked("text", "external-url");
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it("tracks SegmentedControl with its explicit name", () => {
|
|
210
|
+
const host = render(
|
|
211
|
+
classicSection(
|
|
212
|
+
<SegmentedControl
|
|
213
|
+
trackName="Fill type"
|
|
214
|
+
value="solid"
|
|
215
|
+
options={[
|
|
216
|
+
{ label: "Solid", value: "solid" },
|
|
217
|
+
{ label: "Gradient", value: "gradient" },
|
|
218
|
+
]}
|
|
219
|
+
onChange={vi.fn()}
|
|
220
|
+
/>,
|
|
221
|
+
),
|
|
222
|
+
);
|
|
223
|
+
const gradient = Array.from(host.querySelectorAll("button")).find(
|
|
224
|
+
(button) => button.textContent === "Gradient",
|
|
225
|
+
);
|
|
226
|
+
if (!gradient) throw new Error("expected Gradient segment");
|
|
227
|
+
act(() => gradient.dispatchEvent(new MouseEvent("click", { bubbles: true })));
|
|
228
|
+
expectTracked("segmented", "fill-type");
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
describe("flat property-panel primitive telemetry", () => {
|
|
233
|
+
it("tracks FlatRow commits with its label", () => {
|
|
234
|
+
const host = render(
|
|
235
|
+
flatSection(<FlatRow label="Z-index" value="1" tier="default" onCommit={vi.fn()} />),
|
|
236
|
+
);
|
|
237
|
+
const input = host.querySelector("input");
|
|
238
|
+
if (!input) throw new Error("expected flat row input");
|
|
239
|
+
|
|
240
|
+
act(() => changeInput(input, "2"));
|
|
241
|
+
act(() => blurInput(input));
|
|
242
|
+
|
|
243
|
+
expectFlatTracked("metric", "z-index");
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it("tracks FlatSlider once on pointer settle, not during drag commits", () => {
|
|
247
|
+
const host = render(
|
|
248
|
+
flatSection(
|
|
249
|
+
<FlatSlider
|
|
250
|
+
label="Opacity"
|
|
251
|
+
value={10}
|
|
252
|
+
min={0}
|
|
253
|
+
max={100}
|
|
254
|
+
tier="explicitCustom"
|
|
255
|
+
displayValue="10%"
|
|
256
|
+
onCommit={vi.fn()}
|
|
257
|
+
/>,
|
|
258
|
+
),
|
|
259
|
+
);
|
|
260
|
+
const slider = host.querySelector<HTMLElement>('[data-flat-slider-track="true"]');
|
|
261
|
+
if (!slider) throw new Error("expected flat slider");
|
|
262
|
+
Object.defineProperty(slider, "getBoundingClientRect", {
|
|
263
|
+
value: () => ({ left: 0, width: 100, top: 0, height: 20, right: 100, bottom: 20 }),
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
act(() => {
|
|
267
|
+
slider.dispatchEvent(
|
|
268
|
+
new PointerEvent("pointerdown", { bubbles: true, clientX: 20, pointerId: 1 }),
|
|
269
|
+
);
|
|
270
|
+
slider.dispatchEvent(
|
|
271
|
+
new PointerEvent("pointermove", { bubbles: true, clientX: 80, pointerId: 1 }),
|
|
272
|
+
);
|
|
273
|
+
});
|
|
274
|
+
expect(trackStudioEvent).not.toHaveBeenCalled();
|
|
275
|
+
|
|
276
|
+
act(() => {
|
|
277
|
+
slider.dispatchEvent(
|
|
278
|
+
new PointerEvent("pointerup", { bubbles: true, clientX: 80, pointerId: 1 }),
|
|
279
|
+
);
|
|
280
|
+
});
|
|
281
|
+
expect(trackStudioEvent).toHaveBeenCalledTimes(1);
|
|
282
|
+
expectFlatTracked("slider", "opacity");
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it("tracks FlatSegmentedRow changes with its label", () => {
|
|
286
|
+
const host = render(
|
|
287
|
+
flatSection(
|
|
288
|
+
<FlatSegmentedRow
|
|
289
|
+
label="Direction"
|
|
290
|
+
options={[
|
|
291
|
+
{ key: "row", node: "Row", label: "Row", active: true },
|
|
292
|
+
{ key: "column", node: "Column", label: "Column", active: false },
|
|
293
|
+
]}
|
|
294
|
+
onChange={vi.fn()}
|
|
295
|
+
/>,
|
|
296
|
+
),
|
|
297
|
+
);
|
|
298
|
+
const column = host.querySelector<HTMLButtonElement>('[aria-label="Column"]');
|
|
299
|
+
act(() => column?.dispatchEvent(new MouseEvent("click", { bubbles: true })));
|
|
300
|
+
expectFlatTracked("segmented", "direction");
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it("tracks FlatToggle changes with its label", () => {
|
|
304
|
+
const host = render(
|
|
305
|
+
flatSection(<FlatToggle label="Loop" checked={false} onChange={vi.fn()} />),
|
|
306
|
+
);
|
|
307
|
+
const toggle = host.querySelector<HTMLButtonElement>('[data-flat-toggle="true"]');
|
|
308
|
+
act(() => toggle?.dispatchEvent(new MouseEvent("click", { bubbles: true })));
|
|
309
|
+
expectFlatTracked("toggle", "loop");
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it("tracks FlatSelectRow changes with its accessible label", () => {
|
|
313
|
+
const host = render(
|
|
314
|
+
flatSection(
|
|
315
|
+
<FlatSelectRow
|
|
316
|
+
label=""
|
|
317
|
+
ariaLabel="Preset"
|
|
318
|
+
value="neutral"
|
|
319
|
+
options={["neutral", "warm"]}
|
|
320
|
+
tier="default"
|
|
321
|
+
onChange={vi.fn()}
|
|
322
|
+
/>,
|
|
323
|
+
),
|
|
324
|
+
);
|
|
325
|
+
const select = host.querySelector("select");
|
|
326
|
+
if (!select) throw new Error("expected flat select");
|
|
327
|
+
act(() => {
|
|
328
|
+
select.value = "warm";
|
|
329
|
+
select.dispatchEvent(new Event("change", { bubbles: true }));
|
|
330
|
+
});
|
|
331
|
+
expectFlatTracked("select", "preset");
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
describe.each(["classic", "flat"] as const)("shared %s input telemetry", (ui) => {
|
|
336
|
+
const section = (children: ReactElement) => (
|
|
337
|
+
<DesignPanelInputProvider ui={ui} section="text">
|
|
338
|
+
{children}
|
|
339
|
+
</DesignPanelInputProvider>
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
it("tracks ColorField exactly once for a real color change", () => {
|
|
343
|
+
const host = render(
|
|
344
|
+
section(<ColorField flat={ui === "flat"} label="Color" value="#FF0000" onCommit={vi.fn()} />),
|
|
345
|
+
);
|
|
346
|
+
const trigger = host.querySelector<HTMLButtonElement>('[aria-label="Pick color color"]');
|
|
347
|
+
act(() => trigger?.dispatchEvent(new MouseEvent("click", { bubbles: true })));
|
|
348
|
+
const hex = Array.from(document.body.querySelectorAll<HTMLInputElement>("input")).find(
|
|
349
|
+
(input) => input.value === "#FF0000",
|
|
350
|
+
);
|
|
351
|
+
if (!hex) throw new Error("expected color hex input");
|
|
352
|
+
act(() => changeInput(hex, "#00FF00"));
|
|
353
|
+
|
|
354
|
+
expect(trackStudioEvent).toHaveBeenCalledTimes(1);
|
|
355
|
+
expect(trackStudioEvent).toHaveBeenLastCalledWith("design_input", {
|
|
356
|
+
ui,
|
|
357
|
+
section: "text",
|
|
358
|
+
control: "color",
|
|
359
|
+
name: "color",
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
it("tracks FontFamilyField exactly once for a real selection", () => {
|
|
364
|
+
const host = render(
|
|
365
|
+
section(
|
|
366
|
+
<FontFamilyField
|
|
367
|
+
flat={ui === "flat"}
|
|
368
|
+
value="Arial"
|
|
369
|
+
importedFonts={[]}
|
|
370
|
+
onCommit={vi.fn()}
|
|
371
|
+
/>,
|
|
372
|
+
),
|
|
373
|
+
);
|
|
374
|
+
const trigger = host.querySelector<HTMLButtonElement>(
|
|
375
|
+
ui === "flat" ? '[data-flat-font-trigger="true"]' : "button",
|
|
376
|
+
);
|
|
377
|
+
act(() => trigger?.dispatchEvent(new MouseEvent("click", { bubbles: true })));
|
|
378
|
+
const option = Array.from(host.querySelectorAll<HTMLButtonElement>("button")).find((button) =>
|
|
379
|
+
button.textContent?.includes("sans-serif"),
|
|
380
|
+
);
|
|
381
|
+
if (!option) throw new Error("expected sans-serif font option");
|
|
382
|
+
act(() => option.dispatchEvent(new MouseEvent("click", { bubbles: true })));
|
|
383
|
+
|
|
384
|
+
expect(trackStudioEvent).toHaveBeenCalledTimes(1);
|
|
385
|
+
expect(trackStudioEvent).toHaveBeenLastCalledWith("design_input", {
|
|
386
|
+
ui,
|
|
387
|
+
section: "text",
|
|
388
|
+
control: "select",
|
|
389
|
+
name: "font-family",
|
|
390
|
+
});
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
it("tracks TextAreaField exactly once across scheduled commit and blur", () => {
|
|
394
|
+
vi.useFakeTimers();
|
|
395
|
+
const host = render(
|
|
396
|
+
section(
|
|
397
|
+
<TextAreaField flat={ui === "flat"} label="Content" value="Before" onCommit={vi.fn()} />,
|
|
398
|
+
),
|
|
399
|
+
);
|
|
400
|
+
const textarea = host.querySelector("textarea");
|
|
401
|
+
if (!textarea) throw new Error("expected text area");
|
|
402
|
+
act(() => changeTextarea(textarea, "After"));
|
|
403
|
+
act(() => vi.advanceTimersByTime(120));
|
|
404
|
+
act(() => {
|
|
405
|
+
textarea.focus();
|
|
406
|
+
textarea.blur();
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
expect(trackStudioEvent).toHaveBeenCalledTimes(1);
|
|
410
|
+
expect(trackStudioEvent).toHaveBeenLastCalledWith("design_input", {
|
|
411
|
+
ui,
|
|
412
|
+
section: "text",
|
|
413
|
+
control: "text",
|
|
414
|
+
name: "content",
|
|
415
|
+
});
|
|
416
|
+
});
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
function representativeElement() {
|
|
420
|
+
return {
|
|
421
|
+
element: document.createElement("div"),
|
|
422
|
+
id: "panel-target",
|
|
423
|
+
selector: "#panel-target",
|
|
424
|
+
label: "Panel Target",
|
|
425
|
+
tagName: "div",
|
|
426
|
+
sourceFile: "index.html",
|
|
427
|
+
compositionPath: "index.html",
|
|
428
|
+
isCompositionHost: false,
|
|
429
|
+
isInsideLockedComposition: false,
|
|
430
|
+
boundingBox: { x: 0, y: 0, width: 320, height: 180 },
|
|
431
|
+
textContent: "",
|
|
432
|
+
dataAttributes: {},
|
|
433
|
+
inlineStyles: {},
|
|
434
|
+
computedStyles: {},
|
|
435
|
+
textFields: [],
|
|
436
|
+
capabilities: {
|
|
437
|
+
canSelect: true,
|
|
438
|
+
canEditStyles: false,
|
|
439
|
+
canCrop: true,
|
|
440
|
+
canMove: true,
|
|
441
|
+
canResize: true,
|
|
442
|
+
canApplyManualOffset: true,
|
|
443
|
+
canApplyManualSize: true,
|
|
444
|
+
canApplyManualRotation: true,
|
|
445
|
+
},
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
describe("classic PropertyPanel input coverage", () => {
|
|
450
|
+
it("emits only named, known-section events across body inputs and header/footer chrome", async () => {
|
|
451
|
+
const { PropertyPanel } = await import("./PropertyPanel");
|
|
452
|
+
const host = render(
|
|
453
|
+
<PropertyPanel
|
|
454
|
+
{...({
|
|
455
|
+
element: representativeElement(),
|
|
456
|
+
assets: [],
|
|
457
|
+
onSetStyle: vi.fn(),
|
|
458
|
+
onSetText: vi.fn(),
|
|
459
|
+
onSetAttributeLive: vi.fn(),
|
|
460
|
+
onSetManualOffset: vi.fn(),
|
|
461
|
+
onSetManualSize: vi.fn(),
|
|
462
|
+
onSetManualRotation: vi.fn(),
|
|
463
|
+
onClearSelection: vi.fn(),
|
|
464
|
+
onAskAgent: vi.fn(),
|
|
465
|
+
onToggleElementHidden: vi.fn(),
|
|
466
|
+
recordingState: "idle",
|
|
467
|
+
onToggleRecording: vi.fn(),
|
|
468
|
+
} as unknown as PropertyPanelProps)}
|
|
469
|
+
/>,
|
|
470
|
+
);
|
|
471
|
+
|
|
472
|
+
// Fire every body text input across the WHOLE panel (not just the layout
|
|
473
|
+
// section): a section rendered without a <DesignPanelInputProvider section="X">
|
|
474
|
+
// would surface here as section "unknown" and fail the invariant below.
|
|
475
|
+
const bodyInputs = Array.from(host.querySelectorAll<HTMLInputElement>('input[type="text"]'));
|
|
476
|
+
expect(bodyInputs.length).toBeGreaterThan(0);
|
|
477
|
+
for (const [index, input] of bodyInputs.entries()) {
|
|
478
|
+
act(() => changeInput(input, String(100 + index)));
|
|
479
|
+
act(() => blurInput(input));
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Header + footer chrome — the classic siblings of the flat header/footer.
|
|
483
|
+
// (Copy is skipped: its handler reaches for the clipboard, unavailable here.
|
|
484
|
+
// The visibility toggle is store-gated on a live selection this unit mock does
|
|
485
|
+
// not model; Clear selection already exercises the header section.)
|
|
486
|
+
const clear = host.querySelector<HTMLButtonElement>('[aria-label="Clear selection"]');
|
|
487
|
+
if (!clear) throw new Error("expected classic Clear selection control");
|
|
488
|
+
act(() => clear.dispatchEvent(new MouseEvent("click", { bubbles: true })));
|
|
489
|
+
const recordButton = Array.from(host.querySelectorAll("button")).find((b) =>
|
|
490
|
+
b.textContent?.includes("Record gesture"),
|
|
491
|
+
);
|
|
492
|
+
if (!recordButton) throw new Error("expected classic gesture record button");
|
|
493
|
+
act(() => recordButton.dispatchEvent(new MouseEvent("click", { bubbles: true })));
|
|
494
|
+
|
|
495
|
+
expect(trackStudioEvent).toHaveBeenCalled();
|
|
496
|
+
const sections = new Set<string>();
|
|
497
|
+
for (const [, payload] of trackStudioEvent.mock.calls) {
|
|
498
|
+
expect(payload.ui).toBe("classic");
|
|
499
|
+
expect(payload.name).not.toBe("");
|
|
500
|
+
expect(payload.name).not.toBe("unnamed");
|
|
501
|
+
expect(payload.section).not.toBe("");
|
|
502
|
+
expect(payload.section).not.toBe("unknown");
|
|
503
|
+
sections.add(payload.section as string);
|
|
504
|
+
}
|
|
505
|
+
// A body section plus both chrome regions: proves coverage beyond one section
|
|
506
|
+
// and that classic chrome is wired in parallel with the flat header/footer.
|
|
507
|
+
expect(sections.has("header")).toBe(true);
|
|
508
|
+
expect(sections.has("footer")).toBe(true);
|
|
509
|
+
expect(sections.size).toBeGreaterThan(2);
|
|
510
|
+
});
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
describe("flat PropertyPanel input coverage", () => {
|
|
514
|
+
it("emits only named flat events from known sections for every visible layout input", async () => {
|
|
515
|
+
vi.resetModules();
|
|
516
|
+
vi.doMock("./manualEditingAvailability", async () => {
|
|
517
|
+
const actual = await vi.importActual<typeof import("./manualEditingAvailability")>(
|
|
518
|
+
"./manualEditingAvailability",
|
|
519
|
+
);
|
|
520
|
+
return { ...actual, STUDIO_FLAT_INSPECTOR_ENABLED: true };
|
|
521
|
+
});
|
|
522
|
+
const { PropertyPanel } = await import("./PropertyPanel");
|
|
523
|
+
const host = render(
|
|
524
|
+
<PropertyPanel
|
|
525
|
+
{...({
|
|
526
|
+
element: representativeElement(),
|
|
527
|
+
assets: [],
|
|
528
|
+
onSetStyle: vi.fn(),
|
|
529
|
+
onSetText: vi.fn(),
|
|
530
|
+
onSetAttributeLive: vi.fn(),
|
|
531
|
+
onSetManualOffset: vi.fn(),
|
|
532
|
+
onSetManualSize: vi.fn(),
|
|
533
|
+
onSetManualRotation: vi.fn(),
|
|
534
|
+
// Header/footer controls render only when their callbacks are wired —
|
|
535
|
+
// supply them so the coverage guard exercises the header + footer sections.
|
|
536
|
+
selectedElementId: "el-1",
|
|
537
|
+
selectedElementHidden: false,
|
|
538
|
+
onToggleElementHidden: vi.fn(),
|
|
539
|
+
onCopyElementInfo: vi.fn(),
|
|
540
|
+
onClearSelection: vi.fn(),
|
|
541
|
+
onAskAgent: vi.fn(),
|
|
542
|
+
onToggleRecording: vi.fn(),
|
|
543
|
+
} as unknown as PropertyPanelProps)}
|
|
544
|
+
/>,
|
|
545
|
+
);
|
|
546
|
+
const layout = host.querySelector('[data-flat-group-open="true"]');
|
|
547
|
+
if (!layout || !layout.textContent?.includes("Layout")) {
|
|
548
|
+
throw new Error("expected open flat Layout group");
|
|
549
|
+
}
|
|
550
|
+
const inputs = Array.from(layout.querySelectorAll<HTMLInputElement>('input[type="text"]'));
|
|
551
|
+
expect(inputs.length).toBeGreaterThan(0);
|
|
552
|
+
|
|
553
|
+
for (const [index, input] of inputs.entries()) {
|
|
554
|
+
act(() => changeInput(input, String(200 + index)));
|
|
555
|
+
act(() => blurInput(input));
|
|
556
|
+
}
|
|
557
|
+
// Header (Clear selection) and footer (ask + record) controls — exercises the
|
|
558
|
+
// "header" and "footer" sections. The visibility toggle is intentionally omitted:
|
|
559
|
+
// it renders only when the dispatcher forwards a live selection handle, which this
|
|
560
|
+
// unit-level mock does not model. Clear selection already covers the header section.
|
|
561
|
+
for (const selector of [
|
|
562
|
+
'[aria-label="Clear selection"]',
|
|
563
|
+
'[data-flat-footer-ask="true"]',
|
|
564
|
+
'[data-flat-footer-record="true"]',
|
|
565
|
+
]) {
|
|
566
|
+
const button = host.querySelector<HTMLButtonElement>(selector);
|
|
567
|
+
if (!button) throw new Error(`expected flat panel control ${selector}`);
|
|
568
|
+
act(() => button.dispatchEvent(new MouseEvent("click", { bubbles: true })));
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
expect(trackStudioEvent).toHaveBeenCalled();
|
|
572
|
+
expect(new Set(trackStudioEvent.mock.calls.map(([, payload]) => payload.section))).toEqual(
|
|
573
|
+
new Set(["layout", "header", "footer"]),
|
|
574
|
+
);
|
|
575
|
+
for (const [, payload] of trackStudioEvent.mock.calls) {
|
|
576
|
+
expect(payload).toEqual(
|
|
577
|
+
expect.objectContaining({
|
|
578
|
+
ui: "flat",
|
|
579
|
+
}),
|
|
580
|
+
);
|
|
581
|
+
expect(payload.name).not.toBe("");
|
|
582
|
+
expect(payload.name).not.toBe("unnamed");
|
|
583
|
+
expect(payload.section).not.toBe("");
|
|
584
|
+
expect(payload.section).not.toBe("unknown");
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
});
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
stripQueryAndHash,
|
|
13
13
|
} from "./propertyPanelHelpers";
|
|
14
14
|
import { Section, SegmentedControl, SelectField, SliderControl } from "./propertyPanelPrimitives";
|
|
15
|
+
import { useTrackDesignInput } from "../../contexts/DesignPanelInputContext";
|
|
15
16
|
|
|
16
17
|
// fallow-ignore-next-line complexity
|
|
17
18
|
export function MediaSection({
|
|
@@ -38,6 +39,7 @@ export function MediaSection({
|
|
|
38
39
|
},
|
|
39
40
|
) => Promise<BackgroundRemovalResult>;
|
|
40
41
|
}) {
|
|
42
|
+
const track = useTrackDesignInput();
|
|
41
43
|
const isVideo = element.tagName === "video";
|
|
42
44
|
const isAudio = element.tagName === "audio";
|
|
43
45
|
const isImage = element.tagName === "img";
|
|
@@ -98,6 +100,7 @@ export function MediaSection({
|
|
|
98
100
|
|
|
99
101
|
const runBackgroundRemoval = async () => {
|
|
100
102
|
if (!onRemoveBackground || !projectSrc || removeBusy) return;
|
|
103
|
+
track("button", "Remove background");
|
|
101
104
|
setRemoveBusy(true);
|
|
102
105
|
setRemoveProgress({ status: "processing", progress: 0, stage: "Preparing" });
|
|
103
106
|
try {
|
|
@@ -194,6 +197,7 @@ export function MediaSection({
|
|
|
194
197
|
<div className="grid min-w-0 gap-1.5">
|
|
195
198
|
<span className={LABEL}>BG plate</span>
|
|
196
199
|
<SegmentedControl
|
|
200
|
+
trackName="BG plate"
|
|
197
201
|
value={createPlate ? "on" : "off"}
|
|
198
202
|
onChange={(next) => setCreatePlate(next === "on")}
|
|
199
203
|
options={[
|
|
@@ -245,6 +249,7 @@ export function MediaSection({
|
|
|
245
249
|
<div className="grid min-w-0 gap-1.5">
|
|
246
250
|
<span className={LABEL}>Volume</span>
|
|
247
251
|
<SliderControl
|
|
252
|
+
trackName="Volume"
|
|
248
253
|
value={volumePercent}
|
|
249
254
|
min={0}
|
|
250
255
|
max={100}
|
|
@@ -260,6 +265,7 @@ export function MediaSection({
|
|
|
260
265
|
<div className="grid min-w-0 gap-1.5">
|
|
261
266
|
<span className={LABEL}>Playback rate</span>
|
|
262
267
|
<SliderControl
|
|
268
|
+
trackName="Playback rate"
|
|
263
269
|
value={playbackRate * 100}
|
|
264
270
|
min={25}
|
|
265
271
|
max={300}
|
|
@@ -275,6 +281,7 @@ export function MediaSection({
|
|
|
275
281
|
<div className="grid min-w-0 gap-1.5">
|
|
276
282
|
<span className={LABEL}>Media start</span>
|
|
277
283
|
<SliderControl
|
|
284
|
+
trackName="Media start"
|
|
278
285
|
value={Math.round(mediaStart * 100)}
|
|
279
286
|
min={0}
|
|
280
287
|
max={mediaStartMax * 100}
|
|
@@ -291,6 +298,7 @@ export function MediaSection({
|
|
|
291
298
|
<div className="grid min-w-0 gap-1.5">
|
|
292
299
|
<span className={LABEL}>Loop</span>
|
|
293
300
|
<SegmentedControl
|
|
301
|
+
trackName="Loop"
|
|
294
302
|
value={hasLoop ? "on" : "off"}
|
|
295
303
|
onChange={(next) => {
|
|
296
304
|
void onSetHtmlAttribute("loop", next === "on" ? "true" : null);
|
|
@@ -304,6 +312,7 @@ export function MediaSection({
|
|
|
304
312
|
<div className="grid min-w-0 gap-1.5">
|
|
305
313
|
<span className={LABEL}>Muted</span>
|
|
306
314
|
<SegmentedControl
|
|
315
|
+
trackName="Muted"
|
|
307
316
|
value={hasMuted ? "on" : "off"}
|
|
308
317
|
onChange={(next) => {
|
|
309
318
|
void onSetHtmlAttribute("muted", next === "on" ? "true" : null);
|
|
@@ -320,6 +329,7 @@ export function MediaSection({
|
|
|
320
329
|
<div className="grid min-w-0 gap-1.5">
|
|
321
330
|
<span className={LABEL}>Has audio track</span>
|
|
322
331
|
<SegmentedControl
|
|
332
|
+
trackName="Has audio track"
|
|
323
333
|
value={hasAudio ? "yes" : "no"}
|
|
324
334
|
onChange={(next) => {
|
|
325
335
|
if (next === "yes") {
|