@hyperframes/studio 0.7.55 → 0.7.57
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 +459 -0
- package/dist/assets/{index-CPetwHFV.js → index-Bf1x1y8H.js} +1 -1
- package/dist/assets/index-DfmYkU44.js +423 -0
- package/dist/assets/{index-BXaqaVKt.js → index-KsfE1bUu.js} +1 -1
- package/dist/assets/index-_pqzyxB1.css +1 -0
- package/dist/{chunk-SOTCF4DF.js → chunk-5QSIMBEJ.js} +2 -1
- package/dist/chunk-5QSIMBEJ.js.map +1 -0
- package/dist/{domEditingLayers-2ECJK24D.js → domEditingLayers-6LQGKPOI.js} +2 -2
- package/dist/index.d.ts +172 -144
- package/dist/index.html +2 -2
- package/dist/index.js +43613 -40346
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/src/App.tsx +106 -96
- package/src/components/EditorShell.tsx +253 -0
- package/src/components/StudioGlobalDragOverlay.tsx +1 -3
- package/src/components/StudioLeftSidebar.tsx +13 -3
- package/src/components/StudioRightPanel.tsx +8 -3
- package/src/components/TimelineToolbar.test.tsx +2 -2
- package/src/components/TimelineToolbar.tsx +179 -121
- package/src/components/editor/CanvasContextMenu.test.tsx +152 -1
- package/src/components/editor/CanvasContextMenu.tsx +33 -18
- package/src/components/editor/DomEditCropHandles.test.tsx +74 -3
- package/src/components/editor/DomEditCropHandles.tsx +64 -29
- package/src/components/editor/DomEditOverlay.test.ts +245 -279
- package/src/components/editor/DomEditOverlay.tsx +126 -125
- package/src/components/editor/DomEditOverlayHover.test.tsx +90 -0
- package/src/components/editor/DomEditRotateHandle.tsx +38 -18
- package/src/components/editor/DomEditSelectionChrome.test.tsx +80 -0
- package/src/components/editor/DomEditSelectionChrome.tsx +257 -0
- package/src/components/editor/LayersPanel.test.ts +66 -2
- package/src/components/editor/LayersPanel.tsx +53 -2
- package/src/components/editor/OffCanvasIndicators.test.tsx +34 -0
- package/src/components/editor/OffCanvasIndicators.tsx +49 -10
- package/src/components/editor/SnapGuideOverlay.tsx +28 -23
- package/src/components/editor/SnapToolbar.test.tsx +0 -1
- package/src/components/editor/anchoredResizeCommitFeedsOffset.test.ts +214 -0
- package/src/components/editor/anchoredResizeReleaseShift.test.ts +106 -10
- package/src/components/editor/canvasContextMenuZOrder.test.ts +186 -2
- package/src/components/editor/canvasContextMenuZOrder.ts +144 -22
- package/src/components/editor/domEditNudge.test.ts +80 -0
- package/src/components/editor/domEditNudge.ts +44 -0
- package/src/components/editor/domEditOverlayGeometry.test.ts +167 -1
- package/src/components/editor/domEditOverlayGeometry.ts +280 -20
- package/src/components/editor/domEditOverlayGestures.ts +44 -41
- package/src/components/editor/domEditOverlayStartGesture.ts +62 -2
- package/src/components/editor/domEditResizeLocal.test.ts +131 -0
- package/src/components/editor/domEditResizeLocal.ts +125 -0
- package/src/components/editor/domEditingDom.ts +1 -1
- package/src/components/editor/manualEditsDomPatches.test.ts +47 -0
- package/src/components/editor/manualOffsetDrag.ts +55 -12
- package/src/components/editor/offCanvasIndicatorGeometry.ts +31 -10
- package/src/components/editor/offCanvasIndicatorRefresh.test.tsx +25 -1
- package/src/components/editor/propertyPanelColorGradingSection.tsx +15 -21
- package/src/components/editor/resizeDraft.ts +108 -0
- package/src/components/editor/snapEngine.test.ts +12 -78
- package/src/components/editor/snapEngine.ts +13 -53
- package/src/components/editor/useCanvasContextMenuState.ts +92 -0
- package/src/components/editor/useDomEditNudge.test.tsx +228 -0
- package/src/components/editor/useDomEditNudge.ts +254 -0
- package/src/components/editor/useDomEditOverlayGestures.ts +115 -116
- package/src/components/editor/useDomEditOverlayRects.ts +9 -2
- package/src/components/editor/useZOrderCrossedFlash.tsx +68 -0
- package/src/components/nle/AssetPreviewOverlay.test.tsx +106 -0
- package/src/components/nle/AssetPreviewOverlay.tsx +171 -0
- package/src/components/nle/NLEContext.test.ts +144 -0
- package/src/components/nle/NLEContext.tsx +328 -0
- package/src/components/nle/NLEPreview.tsx +1 -1
- package/src/components/nle/PreviewOverlays.tsx +260 -0
- package/src/components/nle/PreviewPane.tsx +163 -0
- package/src/components/nle/TimelinePane.test.ts +60 -0
- package/src/components/nle/TimelinePane.tsx +293 -0
- package/src/components/nle/TimelineResizeDivider.tsx +8 -2
- package/src/components/nle/useCompositionStack.test.tsx +44 -0
- package/src/components/nle/useCompositionStack.ts +17 -9
- package/src/components/nle/useTimelineEditCallbacks.ts +217 -0
- package/src/components/sidebar/AssetCard.test.tsx +106 -0
- package/src/components/sidebar/AssetCard.tsx +343 -0
- package/src/components/sidebar/AssetContextMenu.tsx +15 -30
- package/src/components/sidebar/AssetsTab.test.ts +96 -0
- package/src/components/sidebar/AssetsTab.tsx +71 -206
- package/src/components/sidebar/AudioRow.tsx +56 -2
- package/src/components/sidebar/BlocksTab.tsx +8 -1
- package/src/components/sidebar/LeftSidebar.tsx +4 -1
- package/src/components/sidebar/assetHelpers.ts +29 -0
- package/src/contexts/DomEditContext.tsx +9 -0
- package/src/contexts/StudioContext.tsx +9 -8
- package/src/contexts/TimelineEditContext.tsx +3 -7
- package/src/hooks/deleteSelectedKeyframes.ts +35 -0
- package/src/hooks/domEditCommitTypes.ts +23 -1
- package/src/hooks/domSelectionTestHarness.ts +14 -0
- package/src/hooks/gestureTransaction.test.ts +311 -0
- package/src/hooks/gestureTransaction.ts +199 -0
- package/src/hooks/gsapDragCommit.test.ts +141 -41
- package/src/hooks/gsapDragCommit.ts +64 -74
- package/src/hooks/gsapDragStaticSetHelpers.ts +9 -21
- package/src/hooks/gsapResizeIntercept.test.ts +69 -0
- package/src/hooks/gsapResizeIntercept.ts +38 -10
- package/src/hooks/gsapRuntimeBridge.test.ts +49 -13
- package/src/hooks/gsapRuntimeBridge.ts +4 -4
- package/src/hooks/gsapScriptCommitTypes.ts +16 -5
- package/src/hooks/gsapShared.test.ts +19 -1
- package/src/hooks/gsapShared.ts +14 -0
- package/src/hooks/patchDocumentRootDuration.test.ts +71 -0
- package/src/hooks/timelineEditingGsap.ts +14 -0
- package/src/hooks/timelineEditingHelpers.test.ts +125 -1
- package/src/hooks/timelineEditingHelpers.ts +53 -292
- package/src/hooks/timelineMoveAdapter.test.ts +81 -0
- package/src/hooks/timelineMoveAdapter.ts +39 -0
- package/src/hooks/timelineTimingSync.test.ts +150 -0
- package/src/hooks/timelineTimingSync.ts +412 -0
- package/src/hooks/useAnimatedPropertyCommit.test.tsx +36 -8
- package/src/hooks/useAnimatedPropertyCommit.ts +31 -26
- package/src/hooks/useAppHotkeys.ts +16 -25
- package/src/hooks/useBlockHandlers.ts +55 -27
- package/src/hooks/useCaptionDetection.ts +2 -0
- package/src/hooks/useCompositionDimensions.ts +32 -5
- package/src/hooks/useContextMenuDismiss.ts +31 -7
- package/src/hooks/useDomEditCommits.test.tsx +245 -1
- package/src/hooks/useDomEditCommits.ts +162 -3
- package/src/hooks/useDomEditPositionPatchCommit.ts +2 -0
- package/src/hooks/useDomEditSession.ts +2 -2
- package/src/hooks/useDomEditTextCommits.test.tsx +135 -0
- package/src/hooks/useDomEditTextCommits.ts +8 -3
- package/src/hooks/useDomEditWiring.ts +7 -9
- package/src/hooks/useDomGeometryCommits.ts +17 -2
- package/src/hooks/useDomSelection.test.ts +2 -36
- package/src/hooks/useDomSelection.ts +81 -51
- package/src/hooks/useDomSelectionSelectionGuards.test.ts +276 -0
- package/src/hooks/useElementLifecycleOps.test.tsx +423 -0
- package/src/hooks/useElementLifecycleOps.ts +53 -71
- package/src/hooks/useElementPicker.ts +7 -8
- package/src/hooks/useEnableKeyframes.test.ts +142 -1
- package/src/hooks/useEnableKeyframes.ts +73 -29
- package/src/hooks/useGestureCommit.test.tsx +130 -0
- package/src/hooks/useGestureCommit.ts +32 -8
- package/src/hooks/useGroupCommits.ts +7 -28
- package/src/hooks/useGsapAwareEditing.test.tsx +239 -0
- package/src/hooks/useGsapAwareEditing.ts +115 -20
- package/src/hooks/useGsapKeyframeOps.test.tsx +59 -0
- package/src/hooks/useGsapKeyframeOps.ts +38 -10
- package/src/hooks/useGsapScriptCommits.test.tsx +40 -1
- package/src/hooks/useGsapScriptCommits.ts +134 -50
- package/src/hooks/useGsapSelectionHandlers.ts +49 -16
- package/src/hooks/useMusicBeatAnalysis.ts +72 -36
- package/src/hooks/usePersistentEditHistory.test.ts +33 -0
- package/src/hooks/usePersistentEditHistory.ts +92 -58
- package/src/hooks/usePreviewPersistence.ts +25 -7
- package/src/hooks/useRazorSplit.history.test.tsx +148 -18
- package/src/hooks/useRazorSplit.test.ts +269 -0
- package/src/hooks/useRazorSplit.testHelpers.ts +64 -0
- package/src/hooks/useRazorSplit.ts +107 -36
- package/src/hooks/useRenderClipContent.ts +24 -6
- package/src/hooks/useStudioContextValue.ts +15 -5
- package/src/hooks/useStudioUrlState.ts +2 -4
- package/src/hooks/useTimelineAssetDropOps.ts +175 -0
- package/src/hooks/useTimelineEditing.test.tsx +398 -241
- package/src/hooks/useTimelineEditing.ts +181 -265
- package/src/hooks/useTimelineEditingTypes.ts +6 -0
- package/src/hooks/useTimelineGroupEditing.ts +167 -132
- package/src/index.ts +1 -1
- package/src/player/components/ImageThumbnail.test.tsx +173 -0
- package/src/player/components/ImageThumbnail.tsx +160 -0
- package/src/player/components/PlayerControls.tsx +5 -5
- package/src/player/components/PlayheadIndicator.tsx +55 -5
- package/src/player/components/Timeline.test.ts +183 -23
- package/src/player/components/Timeline.tsx +194 -194
- package/src/player/components/TimelineCanvas.tsx +218 -523
- package/src/player/components/TimelineClip.tsx +4 -1
- package/src/player/components/TimelineClipDiamonds.tsx +9 -40
- package/src/player/components/TimelineEmptyState.tsx +3 -1
- package/src/player/components/TimelineLanes.tsx +487 -0
- package/src/player/components/TimelineOverlays.tsx +122 -0
- package/src/player/components/TimelineRuler.tsx +58 -48
- package/src/player/components/VideoThumbnail.test.tsx +152 -0
- package/src/player/components/VideoThumbnail.tsx +28 -7
- package/src/player/components/thumbnailUtils.test.ts +127 -0
- package/src/player/components/thumbnailUtils.ts +54 -0
- package/src/player/components/timelineCallbacks.ts +18 -18
- package/src/player/components/timelineClipChildren.tsx +39 -0
- package/src/player/components/timelineClipDragCommit.test.ts +1026 -0
- package/src/player/components/timelineClipDragCommit.ts +498 -0
- package/src/player/components/timelineClipDragPreview.test.ts +144 -0
- package/src/player/components/timelineClipDragPreview.ts +323 -38
- package/src/player/components/timelineClipDragTypes.ts +65 -0
- package/src/player/components/timelineCollision.test.ts +42 -2
- package/src/player/components/timelineDragDrop.ts +69 -60
- package/src/player/components/timelineEditCapabilities.ts +52 -0
- package/src/player/components/timelineEditing.test.ts +1 -1
- package/src/player/components/timelineEditing.ts +76 -59
- package/src/player/components/timelineGroupEditing.test.ts +126 -0
- package/src/player/components/timelineGroupEditing.ts +133 -0
- package/src/player/components/timelineGroupResizeCommit.ts +54 -0
- package/src/player/components/timelineLayout.test.ts +95 -23
- package/src/player/components/timelineLayout.ts +207 -75
- package/src/player/components/timelineMarquee.test.ts +197 -0
- package/src/player/components/timelineMarquee.ts +112 -0
- package/src/player/components/timelineOptimisticRevision.ts +42 -0
- package/src/player/components/timelineRevealScroll.test.ts +133 -0
- package/src/player/components/timelineRevealScroll.ts +91 -0
- package/src/player/components/timelineStackingSync.test.ts +60 -0
- package/src/player/components/timelineStackingSync.ts +27 -2
- package/src/player/components/timelineTheme.ts +6 -1
- package/src/player/components/timelineTrackPersistPipeline.test.ts +168 -0
- package/src/player/components/timelineZones.test.ts +122 -310
- package/src/player/components/timelineZones.ts +82 -135
- package/src/player/components/useResolvedTimelineEditCallbacks.ts +25 -3
- package/src/player/components/useTimelineClipDrag.resize.test.tsx +241 -0
- package/src/player/components/useTimelineClipDrag.ts +380 -386
- package/src/player/components/useTimelineEditPinning.ts +121 -0
- package/src/player/components/useTimelineGeometry.ts +129 -0
- package/src/player/components/useTimelinePlayhead.ts +4 -16
- package/src/player/components/useTimelineRangeSelection.ts +341 -50
- package/src/player/components/useTimelineRevealClip.ts +56 -0
- package/src/player/components/useTimelineStackingSync.test.tsx +77 -0
- package/src/player/components/useTimelineStackingSync.ts +94 -0
- package/src/player/hooks/useExpandedTimelineElements.ts +7 -3
- package/src/player/hooks/usePlaybackKeyboard.ts +5 -0
- package/src/player/hooks/useTimelinePlayer.seek.test.ts +55 -0
- package/src/player/hooks/useTimelinePlayer.test.ts +36 -3
- package/src/player/hooks/useTimelinePlayer.ts +52 -53
- package/src/player/hooks/useTimelinePlayerLoop.ts +85 -0
- package/src/player/hooks/useTimelineSyncCallbacks.ts +9 -1
- package/src/player/index.ts +5 -2
- package/src/player/lib/runtimeProtocol.test.ts +48 -0
- package/src/player/lib/runtimeProtocol.ts +65 -0
- package/src/player/lib/time.test.ts +19 -19
- package/src/player/lib/time.ts +6 -2
- package/src/player/lib/timelineDOM.test.ts +25 -98
- package/src/player/lib/timelineDOM.ts +21 -81
- package/src/player/lib/timelineElementHelpers.ts +73 -5
- package/src/player/lib/timelineIframeHelpers.ts +4 -4
- package/src/player/store/playerStore.test.ts +38 -13
- package/src/player/store/playerStore.ts +99 -3
- package/src/styles/studio.css +27 -0
- package/src/utils/assetPreviewDismiss.test.ts +27 -0
- package/src/utils/assetPreviewDismiss.ts +29 -0
- package/src/utils/assetPreviewStore.ts +33 -0
- package/src/utils/blockInstaller.ts +10 -2
- package/src/utils/editHistory.test.ts +35 -0
- package/src/utils/gsapSoftReload.test.ts +119 -1
- package/src/utils/gsapSoftReload.ts +153 -0
- package/src/utils/mediaTypes.ts +3 -2
- package/src/utils/resizeDebug.ts +55 -0
- package/src/utils/rootDuration.test.ts +90 -1
- package/src/utils/rootDuration.ts +76 -13
- package/src/utils/sdkCutover.ts +4 -0
- package/src/utils/studioFileHistory.ts +37 -7
- package/src/utils/studioHelpers.ts +62 -0
- package/src/utils/studioPreviewHelpers.test.ts +65 -8
- package/src/utils/studioPreviewHelpers.ts +10 -0
- package/src/utils/studioTelemetry.ts +4 -1
- package/src/utils/studioUrlState.test.ts +0 -1
- package/src/utils/timelineAssetDrop.test.ts +144 -52
- package/src/utils/timelineAssetDrop.ts +67 -29
- package/src/utils/timelineDiscovery.ts +0 -17
- package/dist/assets/hyperframes-player-BBrKzTGd.js +0 -459
- package/dist/assets/index-BRwkMj0w.js +0 -423
- package/dist/assets/index-Dq7FEg0K.css +0 -1
- package/dist/chunk-SOTCF4DF.js.map +0 -1
- package/src/components/StudioPreviewArea.tsx +0 -500
- package/src/components/nle/NLELayout.test.ts +0 -12
- package/src/components/nle/NLELayout.tsx +0 -591
- package/src/player/components/TimelineLayerGutter.tsx +0 -61
- package/src/player/components/TimelineSelectionOverlays.tsx +0 -55
- package/src/player/components/timelineMarqueeSelection.test.ts +0 -87
- package/src/player/components/timelineSnapTargets.test.ts +0 -144
- package/src/player/components/timelineSnapTargets.ts +0 -164
- package/src/player/components/useTimelineClipDrag.test.tsx +0 -506
- package/src/player/components/useTimelineClipGroupDrag.ts +0 -417
- package/src/player/components/useTimelineMarqueeSelection.test.tsx +0 -231
- package/src/player/components/useTimelineMarqueeSelection.ts +0 -276
- package/src/utils/timelineDiscovery.test.ts +0 -90
- /package/dist/{domEditingLayers-2ECJK24D.js.map → domEditingLayers-6LQGKPOI.js.map} +0 -0
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { memo, useEffect, useMemo, useRef, useState, type RefObject } from "react";
|
|
2
|
-
import { getPreviewTargetFromPointer } from "../../utils/studioPreviewHelpers";
|
|
3
2
|
import { type DomEditSelection } from "./domEditing";
|
|
4
3
|
import type { PreviewMouseDownOptions } from "../../hooks/usePreviewInteraction";
|
|
5
4
|
import { useMarqueeGestures } from "./marqueeCommit";
|
|
6
5
|
import { MarqueeOverlay } from "./MarqueeOverlay";
|
|
7
6
|
import { resolveDomEditGroupOverlayRect } from "./domEditOverlayGeometry";
|
|
7
|
+
import { useZOrderCrossedFlash, ZOrderCrossedFlash } from "./useZOrderCrossedFlash";
|
|
8
|
+
import { useCanvasContextMenuState } from "./useCanvasContextMenuState";
|
|
8
9
|
import {
|
|
9
10
|
type BlockedMoveState,
|
|
10
11
|
type DomEditGroupPathOffsetCommit,
|
|
@@ -16,17 +17,20 @@ import {
|
|
|
16
17
|
import { useDomEditOverlayRects } from "./useDomEditOverlayRects";
|
|
17
18
|
import { OffCanvasIndicators, type OffCanvasRect } from "./OffCanvasIndicators";
|
|
18
19
|
import { createDomEditOverlayGestureHandlers } from "./useDomEditOverlayGestures";
|
|
20
|
+
import { useDomEditNudge } from "./useDomEditNudge";
|
|
19
21
|
import { SnapGuideOverlay, type SnapGuidesState } from "./SnapGuideOverlay";
|
|
20
22
|
import { GridOverlay } from "./GridOverlay";
|
|
21
23
|
import type { GestureRecordingState } from "./GestureRecordControl";
|
|
22
|
-
import {
|
|
23
|
-
import { DomEditRotateHandle } from "./DomEditRotateHandle";
|
|
24
|
+
import { DomEditGroupChrome, DomEditSelectionChrome } from "./DomEditSelectionChrome";
|
|
24
25
|
import { hugRectForElement } from "./domEditOverlayCrop";
|
|
25
26
|
import { useCropOverlay } from "../../hooks/useCropOverlay";
|
|
26
27
|
import { readDomEditSelectionShapeStyles, resolveBoxChromeClass } from "./domEditOverlayShape";
|
|
27
28
|
import { useDomEditCompositionRect } from "./useDomEditCompositionRect";
|
|
28
29
|
import { useMountEffect } from "../../hooks/useMountEffect";
|
|
29
30
|
import { startOffCanvasIndicatorRefresh } from "./offCanvasIndicatorRefresh";
|
|
31
|
+
import { CanvasContextMenu } from "./CanvasContextMenu";
|
|
32
|
+
import type { ZOrderAction, ZOrderPatch } from "./canvasContextMenuZOrder";
|
|
33
|
+
import { getPreviewTargetFromPointer } from "../../utils/studioPreviewHelpers";
|
|
30
34
|
|
|
31
35
|
// Re-exports for external consumers — preserving existing import paths.
|
|
32
36
|
export {
|
|
@@ -37,7 +41,6 @@ export {
|
|
|
37
41
|
export {
|
|
38
42
|
focusDomEditOverlayElement,
|
|
39
43
|
hasDomEditRotationChanged,
|
|
40
|
-
resolveDomEditResizeGesture,
|
|
41
44
|
resolveDomEditRotationGesture,
|
|
42
45
|
} from "./domEditOverlayGestures";
|
|
43
46
|
export type { DomEditGroupPathOffsetCommit } from "./domEditOverlayGestures";
|
|
@@ -73,6 +76,8 @@ interface DomEditOverlayProps {
|
|
|
73
76
|
onBoxSizeCommit: (
|
|
74
77
|
selection: DomEditSelection,
|
|
75
78
|
next: { width: number; height: number },
|
|
79
|
+
offset?: { x: number; y: number },
|
|
80
|
+
restore?: () => void,
|
|
76
81
|
) => Promise<void> | void;
|
|
77
82
|
onRotationCommit: (selection: DomEditSelection, next: { angle: number }) => Promise<void> | void;
|
|
78
83
|
onStyleCommit?: (property: string, value: string) => Promise<void> | void;
|
|
@@ -81,6 +86,24 @@ interface DomEditOverlayProps {
|
|
|
81
86
|
recordingState?: GestureRecordingState;
|
|
82
87
|
onToggleRecording?: () => void;
|
|
83
88
|
onMarqueeSelect?: (selections: DomEditSelection[], additive: boolean) => void;
|
|
89
|
+
/**
|
|
90
|
+
* Delete the selected canvas element.
|
|
91
|
+
* Wire to handleDomEditElementDelete from useDomEditActionsContext —
|
|
92
|
+
* same handler the Delete/Backspace hotkey uses.
|
|
93
|
+
*/
|
|
94
|
+
onDeleteSelection?: (selection: DomEditSelection) => void;
|
|
95
|
+
/**
|
|
96
|
+
* Called with the resolved z-order patch list and the menu action that
|
|
97
|
+
* produced it (feeds the undo coalesce key). The patch list is tie-aware and
|
|
98
|
+
* may include sibling elements (see canvasContextMenuZOrder); the live DOM is
|
|
99
|
+
* NOT yet mutated. Wire to handleDomZIndexReorderCommit from
|
|
100
|
+
* useDomEditActionsContext. See CanvasContextMenu.tsx module comment.
|
|
101
|
+
*/
|
|
102
|
+
onApplyZIndex?: (
|
|
103
|
+
selection: DomEditSelection,
|
|
104
|
+
patches: ZOrderPatch[],
|
|
105
|
+
action: ZOrderAction,
|
|
106
|
+
) => void;
|
|
84
107
|
}
|
|
85
108
|
|
|
86
109
|
// fallow-ignore-next-line complexity
|
|
@@ -105,6 +128,8 @@ export const DomEditOverlay = memo(function DomEditOverlay({
|
|
|
105
128
|
onRotationCommit,
|
|
106
129
|
onStyleCommit,
|
|
107
130
|
onMarqueeSelect,
|
|
131
|
+
onDeleteSelection,
|
|
132
|
+
onApplyZIndex,
|
|
108
133
|
}: DomEditOverlayProps) {
|
|
109
134
|
const overlayRef = useRef<HTMLDivElement | null>(null);
|
|
110
135
|
const boxRef = useRef<HTMLDivElement | null>(null);
|
|
@@ -123,6 +148,11 @@ export const DomEditOverlay = memo(function DomEditOverlay({
|
|
|
123
148
|
|
|
124
149
|
const selectionRef = useRef(selection);
|
|
125
150
|
selectionRef.current = selection;
|
|
151
|
+
|
|
152
|
+
// Brief highlight on the sibling a forward/backward z step crossed — drawn
|
|
153
|
+
// in this studio overlay, never in the iframe DOM (see useZOrderCrossedFlash).
|
|
154
|
+
const { zOrderFlashRect, handleZOrderCrossed } = useZOrderCrossedFlash({ overlayRef, iframeRef });
|
|
155
|
+
|
|
126
156
|
const activeCompositionPathRef = useRef(activeCompositionPath);
|
|
127
157
|
activeCompositionPathRef.current = activeCompositionPath;
|
|
128
158
|
const groupSelectionsRef = useRef(groupSelections);
|
|
@@ -241,6 +271,23 @@ export const DomEditOverlay = memo(function DomEditOverlay({
|
|
|
241
271
|
snapGuidesRef,
|
|
242
272
|
});
|
|
243
273
|
|
|
274
|
+
// Arrow-key nudge (1px, Shift = 10px) — commits through the same
|
|
275
|
+
// path-offset callbacks as a drag, one undo entry per key burst.
|
|
276
|
+
const { flushNudge } = useDomEditNudge({
|
|
277
|
+
selection,
|
|
278
|
+
groupSelections,
|
|
279
|
+
allowCanvasMovement,
|
|
280
|
+
selectionRef,
|
|
281
|
+
overlayRectRef,
|
|
282
|
+
groupOverlayItemsRef,
|
|
283
|
+
gestureRef,
|
|
284
|
+
groupGestureRef,
|
|
285
|
+
blockedMoveRef,
|
|
286
|
+
onManualDragStartRef,
|
|
287
|
+
onPathOffsetCommitRef,
|
|
288
|
+
onGroupPathOffsetCommitRef,
|
|
289
|
+
});
|
|
290
|
+
|
|
244
291
|
const marquee = useMarqueeGestures({
|
|
245
292
|
iframeRef,
|
|
246
293
|
overlayRef,
|
|
@@ -370,6 +417,16 @@ export const DomEditOverlay = memo(function DomEditOverlay({
|
|
|
370
417
|
e.stopPropagation();
|
|
371
418
|
};
|
|
372
419
|
|
|
420
|
+
// Right-click state + handler: select the element under the pointer (if
|
|
421
|
+
// needed), then open the menu; closes when the selection moves off-target.
|
|
422
|
+
const { contextMenu, closeContextMenu, handleContextMenu } = useCanvasContextMenuState({
|
|
423
|
+
selection,
|
|
424
|
+
selectionRef,
|
|
425
|
+
hoverSelectionRef,
|
|
426
|
+
onCanvasPointerMoveRef,
|
|
427
|
+
onSelectionChangeRef,
|
|
428
|
+
});
|
|
429
|
+
|
|
373
430
|
return (
|
|
374
431
|
<div
|
|
375
432
|
ref={overlayRef}
|
|
@@ -378,142 +435,59 @@ export const DomEditOverlay = memo(function DomEditOverlay({
|
|
|
378
435
|
aria-label="Composition canvas"
|
|
379
436
|
// Cursor follows marquee rect *state* (re-renders), not the mutable ref.
|
|
380
437
|
style={marquee.marqueeRect ? { cursor: "crosshair" } : undefined}
|
|
381
|
-
onPointerDownCapture={(event) =>
|
|
382
|
-
|
|
383
|
-
|
|
438
|
+
onPointerDownCapture={(event) => {
|
|
439
|
+
// A pointer gesture supersedes a pending nudge burst — commit it first
|
|
440
|
+
// so the gesture's member snapshot starts from the nudged position.
|
|
441
|
+
flushNudge();
|
|
442
|
+
focusDomEditOverlayElement(event.currentTarget as FocusableDomEditOverlay);
|
|
443
|
+
}}
|
|
384
444
|
onPointerDown={handleOverlayPointerDown}
|
|
385
445
|
onMouseDown={handleOverlayMouseDown}
|
|
386
446
|
onPointerMove={marquee.onPointerMove}
|
|
387
447
|
onPointerLeave={() => onCanvasPointerLeaveRef.current()}
|
|
388
448
|
onPointerUp={marquee.onPointerUp}
|
|
389
449
|
onPointerCancel={marquee.onPointerCancel}
|
|
450
|
+
onContextMenu={handleContextMenu}
|
|
390
451
|
>
|
|
391
452
|
{hoverSelection && hoverRect && compRect.width > 0 && (
|
|
392
453
|
<div
|
|
393
454
|
aria-hidden="true"
|
|
394
455
|
data-dom-edit-hover-box="true"
|
|
395
456
|
className="pointer-events-none absolute rounded-md border border-studio-accent/80 shadow-[0_0_0_1px_rgba(60,230,172,0.25)]"
|
|
396
|
-
style={
|
|
457
|
+
style={{
|
|
458
|
+
...hugRectForElement(hoverRect, hoverSelection.element),
|
|
459
|
+
transform: hoverRect.angle ? `rotate(${hoverRect.angle}deg)` : undefined,
|
|
460
|
+
}}
|
|
397
461
|
/>
|
|
398
462
|
)}
|
|
399
463
|
{hasGroupSelection && groupOverlayItems.length > 1 && groupBounds && compRect.width > 0 && (
|
|
400
|
-
|
|
401
|
-
{groupOverlayItems
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
width: item.rect.width,
|
|
410
|
-
height: item.rect.height,
|
|
411
|
-
}}
|
|
412
|
-
/>
|
|
413
|
-
))}
|
|
414
|
-
<div
|
|
415
|
-
data-dom-edit-selection-box="true"
|
|
416
|
-
className="pointer-events-auto absolute rounded-xl border border-studio-accent shadow-[0_0_0_1px_rgba(60,230,172,0.3)]"
|
|
417
|
-
style={{
|
|
418
|
-
left: groupBounds.left,
|
|
419
|
-
top: groupBounds.top,
|
|
420
|
-
width: groupBounds.width,
|
|
421
|
-
height: groupBounds.height,
|
|
422
|
-
cursor: allowCanvasMovement && groupCanMove ? "move" : "default",
|
|
423
|
-
}}
|
|
424
|
-
onPointerDown={(e) => {
|
|
425
|
-
if (!allowCanvasMovement || !groupCanMove || e.shiftKey) return;
|
|
426
|
-
gestures.startGroupDrag(e);
|
|
427
|
-
}}
|
|
428
|
-
onMouseDown={suppressBoxMouseDown}
|
|
429
|
-
onClick={handleBoxClick}
|
|
430
|
-
/>
|
|
431
|
-
</>
|
|
464
|
+
<DomEditGroupChrome
|
|
465
|
+
groupOverlayItems={groupOverlayItems}
|
|
466
|
+
groupBounds={groupBounds}
|
|
467
|
+
allowCanvasMovement={allowCanvasMovement}
|
|
468
|
+
groupCanMove={groupCanMove}
|
|
469
|
+
gestures={gestures}
|
|
470
|
+
onBoxMouseDown={suppressBoxMouseDown}
|
|
471
|
+
onBoxClick={handleBoxClick}
|
|
472
|
+
/>
|
|
432
473
|
)}
|
|
433
474
|
{!hasGroupSelection && selection && overlayRect && compRect.width > 0 && (
|
|
434
|
-
|
|
435
|
-
{
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
style={{
|
|
451
|
-
left: overlayRect.left,
|
|
452
|
-
top: overlayRect.top,
|
|
453
|
-
width: overlayRect.width,
|
|
454
|
-
height: overlayRect.height,
|
|
455
|
-
clipPath: boxClipPath,
|
|
456
|
-
cursor:
|
|
457
|
-
allowCanvasMovement && selection.capabilities.canApplyManualOffset
|
|
458
|
-
? "move"
|
|
459
|
-
: "default",
|
|
460
|
-
}}
|
|
461
|
-
onPointerDown={(e) => {
|
|
462
|
-
if (!allowCanvasMovement || e.shiftKey) return;
|
|
463
|
-
if (selection.capabilities.canApplyManualOffset) {
|
|
464
|
-
gestures.startGesture("drag", e);
|
|
465
|
-
return;
|
|
466
|
-
}
|
|
467
|
-
e.preventDefault();
|
|
468
|
-
e.stopPropagation();
|
|
469
|
-
e.currentTarget.setPointerCapture(e.pointerId);
|
|
470
|
-
blockedMoveRef.current = {
|
|
471
|
-
pointerId: e.pointerId,
|
|
472
|
-
startX: e.clientX,
|
|
473
|
-
startY: e.clientY,
|
|
474
|
-
notified: false,
|
|
475
|
-
};
|
|
476
|
-
}}
|
|
477
|
-
onMouseDown={suppressBoxMouseDown}
|
|
478
|
-
onClick={handleBoxClick}
|
|
479
|
-
>
|
|
480
|
-
{cropOutlineInsetPx && (
|
|
481
|
-
<div
|
|
482
|
-
className="pointer-events-none absolute rounded-md border border-studio-accent/80 shadow-[0_0_0_1px_rgba(60,230,172,0.25)]"
|
|
483
|
-
style={{
|
|
484
|
-
left: cropOutlineInsetPx.left,
|
|
485
|
-
top: cropOutlineInsetPx.top,
|
|
486
|
-
right: cropOutlineInsetPx.right,
|
|
487
|
-
bottom: cropOutlineInsetPx.bottom,
|
|
488
|
-
}}
|
|
489
|
-
/>
|
|
490
|
-
)}
|
|
491
|
-
{allowCanvasMovement && selection.capabilities.canApplyManualSize && (
|
|
492
|
-
<div
|
|
493
|
-
className="absolute -right-1.5 -bottom-1.5 w-3 h-3 rounded-sm bg-studio-accent border border-studio-accent/60"
|
|
494
|
-
style={{
|
|
495
|
-
cursor: "se-resize",
|
|
496
|
-
touchAction: "none",
|
|
497
|
-
...(cropOutlineInsetPx && {
|
|
498
|
-
right: cropOutlineInsetPx.right - 6,
|
|
499
|
-
bottom: cropOutlineInsetPx.bottom - 6,
|
|
500
|
-
}),
|
|
501
|
-
}}
|
|
502
|
-
onPointerDown={(e) => {
|
|
503
|
-
e.stopPropagation();
|
|
504
|
-
gestures.startGesture("resize", e);
|
|
505
|
-
}}
|
|
506
|
-
/>
|
|
507
|
-
)}
|
|
508
|
-
</div>
|
|
509
|
-
{selection.capabilities.canCrop && groupSelections.length <= 1 && (
|
|
510
|
-
<DomEditCropHandles
|
|
511
|
-
selection={selection}
|
|
512
|
-
overlayRect={overlayRect}
|
|
513
|
-
onStyleCommit={onStyleCommitRef.current}
|
|
514
|
-
/>
|
|
515
|
-
)}
|
|
516
|
-
</>
|
|
475
|
+
<DomEditSelectionChrome
|
|
476
|
+
selection={selection}
|
|
477
|
+
overlayRect={overlayRect}
|
|
478
|
+
allowCanvasMovement={allowCanvasMovement}
|
|
479
|
+
cropOutlineInsetPx={cropOutlineInsetPx ?? undefined}
|
|
480
|
+
boxRef={boxRef}
|
|
481
|
+
boxChromeClass={boxChromeClass}
|
|
482
|
+
boxClipPath={boxClipPath}
|
|
483
|
+
selectionKey={selectionKey}
|
|
484
|
+
groupSelectionCount={groupSelections.length}
|
|
485
|
+
blockedMoveRef={blockedMoveRef}
|
|
486
|
+
gestures={gestures}
|
|
487
|
+
onStyleCommit={onStyleCommitRef.current}
|
|
488
|
+
onBoxMouseDown={suppressBoxMouseDown}
|
|
489
|
+
onBoxClick={handleBoxClick}
|
|
490
|
+
/>
|
|
517
491
|
)}
|
|
518
492
|
{childRects.length > 0 &&
|
|
519
493
|
compRect.width > 0 &&
|
|
@@ -539,6 +513,31 @@ export const DomEditOverlay = memo(function DomEditOverlay({
|
|
|
539
513
|
onSelectionChangeRef={onSelectionChangeRef}
|
|
540
514
|
/>
|
|
541
515
|
<MarqueeOverlay candidateRects={marquee.candidateRects} marqueeRect={marquee.marqueeRect} />
|
|
516
|
+
{contextMenu && (
|
|
517
|
+
<CanvasContextMenu
|
|
518
|
+
x={contextMenu.x}
|
|
519
|
+
y={contextMenu.y}
|
|
520
|
+
selection={contextMenu.sel}
|
|
521
|
+
onClose={closeContextMenu}
|
|
522
|
+
onDelete={
|
|
523
|
+
onDeleteSelection
|
|
524
|
+
? (sel) => {
|
|
525
|
+
closeContextMenu();
|
|
526
|
+
onDeleteSelection(sel);
|
|
527
|
+
}
|
|
528
|
+
: undefined
|
|
529
|
+
}
|
|
530
|
+
onApplyZIndex={
|
|
531
|
+
onApplyZIndex
|
|
532
|
+
? (patches, action) => {
|
|
533
|
+
onApplyZIndex(contextMenu.sel, patches, action);
|
|
534
|
+
}
|
|
535
|
+
: undefined
|
|
536
|
+
}
|
|
537
|
+
onZOrderCrossed={handleZOrderCrossed}
|
|
538
|
+
/>
|
|
539
|
+
)}
|
|
540
|
+
<ZOrderCrossedFlash rect={zOrderFlashRect} />
|
|
542
541
|
<GridOverlay
|
|
543
542
|
visible={gridVisible}
|
|
544
543
|
spacing={gridSpacing}
|
|
@@ -551,8 +550,10 @@ export const DomEditOverlay = memo(function DomEditOverlay({
|
|
|
551
550
|
/>
|
|
552
551
|
<SnapGuideOverlay
|
|
553
552
|
snapGuidesRef={snapGuidesRef}
|
|
554
|
-
|
|
555
|
-
|
|
553
|
+
compositionLeft={compRect.left}
|
|
554
|
+
compositionTop={compRect.top}
|
|
555
|
+
compositionWidth={compRect.width}
|
|
556
|
+
compositionHeight={compRect.height}
|
|
556
557
|
/>
|
|
557
558
|
</div>
|
|
558
559
|
);
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
|
|
3
|
+
import React, { act } from "react";
|
|
4
|
+
import { createRoot } from "react-dom/client";
|
|
5
|
+
import { expect, it, vi } from "vitest";
|
|
6
|
+
import type { DomEditSelection } from "./domEditing";
|
|
7
|
+
import { DomEditOverlay } from "./DomEditOverlay";
|
|
8
|
+
|
|
9
|
+
(globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
|
10
|
+
|
|
11
|
+
const hoverState = vi.hoisted(() => ({ angle: 0 }));
|
|
12
|
+
|
|
13
|
+
vi.mock("./useDomEditOverlayRects", () => ({
|
|
14
|
+
useDomEditOverlayRects: ({ hoverSelectionRef }: { hoverSelectionRef: { current: unknown } }) => ({
|
|
15
|
+
overlayRect: null,
|
|
16
|
+
overlayRectRef: { current: null },
|
|
17
|
+
setOverlayRect: () => undefined,
|
|
18
|
+
hoverRect: hoverSelectionRef.current
|
|
19
|
+
? {
|
|
20
|
+
left: 20,
|
|
21
|
+
top: 30,
|
|
22
|
+
width: 100,
|
|
23
|
+
height: 40,
|
|
24
|
+
editScaleX: 1,
|
|
25
|
+
editScaleY: 1,
|
|
26
|
+
angle: hoverState.angle,
|
|
27
|
+
}
|
|
28
|
+
: null,
|
|
29
|
+
groupOverlayItems: [],
|
|
30
|
+
groupOverlayItemsRef: { current: [] },
|
|
31
|
+
setGroupOverlayItems: () => undefined,
|
|
32
|
+
childRects: [],
|
|
33
|
+
}),
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
vi.mock("./useDomEditCompositionRect", () => ({
|
|
37
|
+
useDomEditCompositionRect: () => ({
|
|
38
|
+
left: 0,
|
|
39
|
+
top: 0,
|
|
40
|
+
width: 800,
|
|
41
|
+
height: 450,
|
|
42
|
+
scaleX: 1,
|
|
43
|
+
scaleY: 1,
|
|
44
|
+
}),
|
|
45
|
+
}));
|
|
46
|
+
|
|
47
|
+
vi.mock("./offCanvasIndicatorRefresh", () => ({
|
|
48
|
+
startOffCanvasIndicatorRefresh: () => () => undefined,
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
function renderHover(angle: number): string {
|
|
52
|
+
hoverState.angle = angle;
|
|
53
|
+
const host = document.createElement("div");
|
|
54
|
+
document.body.append(host);
|
|
55
|
+
const root = createRoot(host);
|
|
56
|
+
const element = document.createElement("div");
|
|
57
|
+
const hoverSelection = { element } as unknown as DomEditSelection;
|
|
58
|
+
act(() => {
|
|
59
|
+
root.render(
|
|
60
|
+
<DomEditOverlay
|
|
61
|
+
iframeRef={{ current: document.createElement("iframe") }}
|
|
62
|
+
activeCompositionPath={null}
|
|
63
|
+
selection={null}
|
|
64
|
+
hoverSelection={hoverSelection}
|
|
65
|
+
onCanvasMouseDown={() => undefined}
|
|
66
|
+
onCanvasPointerMove={() => Promise.resolve(null)}
|
|
67
|
+
onCanvasPointerLeave={() => undefined}
|
|
68
|
+
onSelectionChange={() => undefined}
|
|
69
|
+
onBlockedMove={() => undefined}
|
|
70
|
+
onPathOffsetCommit={() => undefined}
|
|
71
|
+
onGroupPathOffsetCommit={() => undefined}
|
|
72
|
+
onBoxSizeCommit={() => undefined}
|
|
73
|
+
onRotationCommit={() => undefined}
|
|
74
|
+
/>,
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
const box = host.querySelector<HTMLElement>('[data-dom-edit-hover-box="true"]');
|
|
78
|
+
const transform = box?.style.transform ?? "";
|
|
79
|
+
act(() => root.unmount());
|
|
80
|
+
host.remove();
|
|
81
|
+
return transform;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
it("rotates the hover box with the element", () => {
|
|
85
|
+
expect(renderHover(30)).toBe("rotate(30deg)");
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("leaves the hover box untransformed at angle zero", () => {
|
|
89
|
+
expect(renderHover(0)).toBe("");
|
|
90
|
+
});
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import type { PointerEvent as ReactPointerEvent } from "react";
|
|
2
2
|
import type { OverlayRect } from "./domEditOverlayGeometry";
|
|
3
3
|
|
|
4
|
-
/** Rotate
|
|
5
|
-
*
|
|
4
|
+
/** Rotate handle below the selection: an attached circular-arrows icon chip
|
|
5
|
+
* (no connecting stem). Anchors to the crop outline when the element is
|
|
6
|
+
* cropped so it stays next to what's visible on screen. Presentation only —
|
|
7
|
+
* the rotation gesture measures pointer angles from the element CENTER
|
|
8
|
+
* (resolveDomEditRotationGesture), so the handle position doesn't affect the
|
|
9
|
+
* math. Sits 12px below the bbox, past the bottom crop handle's hit strip. */
|
|
6
10
|
export function DomEditRotateHandle({
|
|
7
11
|
overlayRect,
|
|
8
12
|
cropOutlineInsetPx,
|
|
@@ -15,27 +19,43 @@ export function DomEditRotateHandle({
|
|
|
15
19
|
const inset = cropOutlineInsetPx ?? { top: 0, right: 0, bottom: 0, left: 0 };
|
|
16
20
|
const visibleLeft = overlayRect.left + inset.left;
|
|
17
21
|
const visibleWidth = Math.max(0, overlayRect.width - inset.left - inset.right);
|
|
18
|
-
const
|
|
22
|
+
const visibleBottom = overlayRect.top + overlayRect.height - inset.bottom;
|
|
19
23
|
return (
|
|
20
|
-
<
|
|
21
|
-
|
|
24
|
+
<button
|
|
25
|
+
type="button"
|
|
26
|
+
className="pointer-events-auto absolute flex items-center justify-center border-0 bg-transparent p-0"
|
|
22
27
|
style={{
|
|
23
28
|
left: visibleLeft + visibleWidth / 2,
|
|
24
|
-
top:
|
|
25
|
-
width:
|
|
26
|
-
height:
|
|
29
|
+
top: visibleBottom + 12,
|
|
30
|
+
width: 22,
|
|
31
|
+
height: 22,
|
|
27
32
|
transform: "translateX(-50%)",
|
|
33
|
+
touchAction: "none",
|
|
34
|
+
// Closed-hand grab cursor: this handle is grabbed and dragged to rotate.
|
|
35
|
+
cursor: "grabbing",
|
|
28
36
|
}}
|
|
37
|
+
title="Rotate"
|
|
38
|
+
aria-label="Rotate selection"
|
|
39
|
+
onPointerDown={onStartRotate}
|
|
29
40
|
>
|
|
30
|
-
<
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
41
|
+
<span className="pointer-events-none flex h-[18px] w-[18px] items-center justify-center rounded-full border border-studio-accent/70 bg-studio-surface text-studio-accent shadow-[0_0_3px_rgba(0,0,0,0.45)]">
|
|
42
|
+
<svg
|
|
43
|
+
width="11"
|
|
44
|
+
height="11"
|
|
45
|
+
viewBox="0 0 24 24"
|
|
46
|
+
fill="none"
|
|
47
|
+
stroke="currentColor"
|
|
48
|
+
strokeWidth="2.5"
|
|
49
|
+
strokeLinecap="round"
|
|
50
|
+
strokeLinejoin="round"
|
|
51
|
+
aria-hidden="true"
|
|
52
|
+
>
|
|
53
|
+
<path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8" />
|
|
54
|
+
<path d="M21 3v5h-5" />
|
|
55
|
+
<path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16" />
|
|
56
|
+
<path d="M8 16H3v5" />
|
|
57
|
+
</svg>
|
|
58
|
+
</span>
|
|
59
|
+
</button>
|
|
40
60
|
);
|
|
41
61
|
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
|
|
3
|
+
import React, { act, createRef } from "react";
|
|
4
|
+
import { createRoot } from "react-dom/client";
|
|
5
|
+
import { describe, expect, it, vi } from "vitest";
|
|
6
|
+
import type { DomEditSelection } from "./domEditing";
|
|
7
|
+
import { DomEditSelectionChrome } from "./DomEditSelectionChrome";
|
|
8
|
+
|
|
9
|
+
(globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
|
10
|
+
|
|
11
|
+
describe("DomEditSelectionChrome crop composition", () => {
|
|
12
|
+
it("places rotated crop UI in exactly one oriented coordinate plane", () => {
|
|
13
|
+
const element = document.createElement("div");
|
|
14
|
+
element.id = "clip";
|
|
15
|
+
element.style.clipPath = "inset(10px)";
|
|
16
|
+
Object.defineProperties(element, {
|
|
17
|
+
offsetWidth: { value: 200 },
|
|
18
|
+
offsetHeight: { value: 100 },
|
|
19
|
+
});
|
|
20
|
+
document.body.append(element);
|
|
21
|
+
vi.spyOn(window, "getComputedStyle").mockReturnValue({
|
|
22
|
+
clipPath: "inset(10px)",
|
|
23
|
+
transform: "matrix(0.8660254, 0.5, -0.5, 0.8660254, 0, 0)",
|
|
24
|
+
} as CSSStyleDeclaration);
|
|
25
|
+
const selection = {
|
|
26
|
+
element,
|
|
27
|
+
id: "clip",
|
|
28
|
+
selector: "#clip",
|
|
29
|
+
capabilities: {
|
|
30
|
+
canCrop: true,
|
|
31
|
+
canApplyManualOffset: true,
|
|
32
|
+
canApplyManualSize: true,
|
|
33
|
+
canApplyManualRotation: true,
|
|
34
|
+
},
|
|
35
|
+
} as unknown as DomEditSelection;
|
|
36
|
+
const host = document.createElement("div");
|
|
37
|
+
document.body.append(host);
|
|
38
|
+
const root = createRoot(host);
|
|
39
|
+
act(() => {
|
|
40
|
+
root.render(
|
|
41
|
+
<DomEditSelectionChrome
|
|
42
|
+
selection={selection}
|
|
43
|
+
overlayRect={{
|
|
44
|
+
left: 100,
|
|
45
|
+
top: 50,
|
|
46
|
+
width: 220,
|
|
47
|
+
height: 130,
|
|
48
|
+
editScaleX: 1,
|
|
49
|
+
editScaleY: 1,
|
|
50
|
+
angle: 30,
|
|
51
|
+
}}
|
|
52
|
+
allowCanvasMovement={true}
|
|
53
|
+
boxRef={createRef()}
|
|
54
|
+
boxChromeClass=""
|
|
55
|
+
boxClipPath={undefined}
|
|
56
|
+
selectionKey="clip"
|
|
57
|
+
groupSelectionCount={0}
|
|
58
|
+
blockedMoveRef={createRef()}
|
|
59
|
+
gestures={{ startGesture: vi.fn() } as never}
|
|
60
|
+
onStyleCommit={vi.fn()}
|
|
61
|
+
onBoxMouseDown={vi.fn()}
|
|
62
|
+
onBoxClick={vi.fn()}
|
|
63
|
+
/>,
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const cropFrame = host.querySelector<HTMLElement>("[data-dom-edit-crop-frame]")!;
|
|
68
|
+
const rotations: string[] = [];
|
|
69
|
+
for (
|
|
70
|
+
let node: HTMLElement | null = cropFrame;
|
|
71
|
+
node && node !== host;
|
|
72
|
+
node = node.parentElement
|
|
73
|
+
) {
|
|
74
|
+
if (node.style.transform.includes("rotate(")) rotations.push(node.style.transform);
|
|
75
|
+
}
|
|
76
|
+
expect(rotations).toHaveLength(1);
|
|
77
|
+
expect(Number.parseFloat(rotations[0]!.slice("rotate(".length))).toBeCloseTo(30, 5);
|
|
78
|
+
act(() => root.unmount());
|
|
79
|
+
});
|
|
80
|
+
});
|