chore: import upstream snapshot with attribution
regression / regression-shards (style-16-prod style-9-prod style-17-prod iframe-render-compat variables-prod mp4-h265-sdr, shard-4) (push) Has been cancelled
regression / regression-shards (style-4-prod style-11-prod style-2-prod animejs-adapter typegpu-adapter parallel-capture-regression, shard-5) (push) Has been cancelled
regression / regression-shards (style-7-prod style-8-prod style-10-prod css-spinner-render-compat webm-transparency mp4-h264-sdr webm-vp9, shard-3) (push) Has been cancelled
regression / regression-shards (sub-composition-video style-18-prod raf-ball-render-compat font-variant-numeric sub-comp-t0 sub-comp-id-selector, shard-7) (push) Has been cancelled
Windows render verification / Detect changes (push) Has been cancelled
Windows render verification / Preflight (lint + format) (push) Has been cancelled
Windows render verification / Render on windows-latest (push) Has been cancelled
Windows render verification / Tests on windows-latest (push) Has been cancelled
CI / Detect changes (push) Has been cancelled
CI / Build (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Fallow audit (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Producer: integration tests (push) Has been cancelled
CI / Producer: unit tests (push) Has been cancelled
CI / File size check (push) Has been cancelled
CI / Test: skills (push) Has been cancelled
CI / Skills: manifest in sync (push) Has been cancelled
CI / CLI: npx shim (macos-latest) (push) Has been cancelled
CI / CLI: npx shim (ubuntu-latest) (push) Has been cancelled
CI / CLI: npx shim (windows-latest) (push) Has been cancelled
CI / SDK: unit + contract + smoke (push) Has been cancelled
CI / Test: runtime contract (push) Has been cancelled
CI / Studio: load smoke (push) Has been cancelled
CI / Smoke: global install (push) Has been cancelled
CI / CLI smoke (required) (push) Has been cancelled
CI / Semantic PR title (push) Has been cancelled
Player perf / Detect changes (push) Has been cancelled
Player perf / Preflight (lint + format) (push) Has been cancelled
Player perf / player-perf (push) Has been cancelled
Player perf / Perf: drift (push) Has been cancelled
Player perf / Perf: fps (push) Has been cancelled
Player perf / Perf: parity (push) Has been cancelled
Player perf / Perf: scrub (push) Has been cancelled
Player perf / Perf: load (push) Has been cancelled
preview-regression / Detect changes (push) Has been cancelled
preview-regression / Preflight (lint + format) (push) Has been cancelled
preview-regression / Preview parity (push) Has been cancelled
preview-regression / preview-regression (push) Has been cancelled
regression / regression (push) Has been cancelled
regression / Detect changes (push) Has been cancelled
regression / Preflight (lint + format) (push) Has been cancelled
regression / regression-shards (hdr-regression style-5-prod style-3-prod mov-prores, shard-1) (push) Has been cancelled
regression / regression-shards (overlay-montage-prod style-12-prod chat missing-host-comp-id png-sequence portrait-edge-bleed, shard-6) (push) Has been cancelled
regression / regression-shards (style-13-prod style-6-prod vignelli-stacking gsap-letters-render-compat audio-mux-parity, shard-8) (push) Has been cancelled
regression / regression-shards (style-15-prod hdr-hlg-regression style-1-prod many-cuts vfr-screen-recording render-symlinked-assets, shard-2) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Docs / Validate docs (push) Has been cancelled
Sync skills to ClawHub / Publish changed skills (push) Has been cancelled
regression / regression-shards (style-16-prod style-9-prod style-17-prod iframe-render-compat variables-prod mp4-h265-sdr, shard-4) (push) Has been cancelled
regression / regression-shards (style-4-prod style-11-prod style-2-prod animejs-adapter typegpu-adapter parallel-capture-regression, shard-5) (push) Has been cancelled
regression / regression-shards (style-7-prod style-8-prod style-10-prod css-spinner-render-compat webm-transparency mp4-h264-sdr webm-vp9, shard-3) (push) Has been cancelled
regression / regression-shards (sub-composition-video style-18-prod raf-ball-render-compat font-variant-numeric sub-comp-t0 sub-comp-id-selector, shard-7) (push) Has been cancelled
Windows render verification / Detect changes (push) Has been cancelled
Windows render verification / Preflight (lint + format) (push) Has been cancelled
Windows render verification / Render on windows-latest (push) Has been cancelled
Windows render verification / Tests on windows-latest (push) Has been cancelled
CI / Detect changes (push) Has been cancelled
CI / Build (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Fallow audit (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Producer: integration tests (push) Has been cancelled
CI / Producer: unit tests (push) Has been cancelled
CI / File size check (push) Has been cancelled
CI / Test: skills (push) Has been cancelled
CI / Skills: manifest in sync (push) Has been cancelled
CI / CLI: npx shim (macos-latest) (push) Has been cancelled
CI / CLI: npx shim (ubuntu-latest) (push) Has been cancelled
CI / CLI: npx shim (windows-latest) (push) Has been cancelled
CI / SDK: unit + contract + smoke (push) Has been cancelled
CI / Test: runtime contract (push) Has been cancelled
CI / Studio: load smoke (push) Has been cancelled
CI / Smoke: global install (push) Has been cancelled
CI / CLI smoke (required) (push) Has been cancelled
CI / Semantic PR title (push) Has been cancelled
Player perf / Detect changes (push) Has been cancelled
Player perf / Preflight (lint + format) (push) Has been cancelled
Player perf / player-perf (push) Has been cancelled
Player perf / Perf: drift (push) Has been cancelled
Player perf / Perf: fps (push) Has been cancelled
Player perf / Perf: parity (push) Has been cancelled
Player perf / Perf: scrub (push) Has been cancelled
Player perf / Perf: load (push) Has been cancelled
preview-regression / Detect changes (push) Has been cancelled
preview-regression / Preflight (lint + format) (push) Has been cancelled
preview-regression / Preview parity (push) Has been cancelled
preview-regression / preview-regression (push) Has been cancelled
regression / regression (push) Has been cancelled
regression / Detect changes (push) Has been cancelled
regression / Preflight (lint + format) (push) Has been cancelled
regression / regression-shards (hdr-regression style-5-prod style-3-prod mov-prores, shard-1) (push) Has been cancelled
regression / regression-shards (overlay-montage-prod style-12-prod chat missing-host-comp-id png-sequence portrait-edge-bleed, shard-6) (push) Has been cancelled
regression / regression-shards (style-13-prod style-6-prod vignelli-stacking gsap-letters-render-compat audio-mux-parity, shard-8) (push) Has been cancelled
regression / regression-shards (style-15-prod hdr-hlg-regression style-1-prod many-cuts vfr-screen-recording render-symlinked-assets, shard-2) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Docs / Validate docs (push) Has been cancelled
Sync skills to ClawHub / Publish changed skills (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,224 @@
|
||||
/**
|
||||
* Gesture-begin functions: startGroupDrag and startGesture.
|
||||
* These are pure "start a new gesture" operations — no draft rect updates.
|
||||
*/
|
||||
import { readElementGsapNumber } from "../../utils/elementGsap";
|
||||
import { type DomEditSelection } from "./domEditing";
|
||||
import {
|
||||
createManualOffsetDragMember,
|
||||
readGsapRotation,
|
||||
restoreManualOffsetDragMembers,
|
||||
type ManualOffsetDragMember,
|
||||
} from "./manualOffsetDrag";
|
||||
import {
|
||||
beginStudioManualEditGesture,
|
||||
captureStudioBoxSize,
|
||||
captureStudioPathOffset,
|
||||
captureStudioRotation,
|
||||
readStudioBoxSize,
|
||||
readStudioRotation,
|
||||
} from "./manualEdits";
|
||||
import {
|
||||
type OverlayRect,
|
||||
filterNestedDomEditGroupItems,
|
||||
selectionCacheKey,
|
||||
} from "./domEditOverlayGeometry";
|
||||
import {
|
||||
type GestureKind,
|
||||
type GestureState,
|
||||
type UseDomEditOverlayGesturesOptions,
|
||||
} from "./domEditOverlayGestures";
|
||||
import { collectSnapContext, buildExcludeElements } from "./snapTargetCollection";
|
||||
|
||||
export function startGroupDrag(
|
||||
e: React.PointerEvent<HTMLElement>,
|
||||
opts: UseDomEditOverlayGesturesOptions,
|
||||
): boolean {
|
||||
const items = opts.groupOverlayItemsRef.current;
|
||||
if (items.length <= 1) return false;
|
||||
|
||||
const blockedSelection = items.find(
|
||||
(item) => !item.selection.capabilities.canApplyManualOffset,
|
||||
)?.selection;
|
||||
if (blockedSelection) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
opts.onBlockedMoveRef.current(blockedSelection);
|
||||
return false;
|
||||
}
|
||||
|
||||
opts.onManualDragStartRef.current?.();
|
||||
const dragItems = filterNestedDomEditGroupItems(items);
|
||||
const members: ManualOffsetDragMember[] = [];
|
||||
for (const item of dragItems) {
|
||||
const result = createManualOffsetDragMember({
|
||||
key: item.key,
|
||||
selection: item.selection,
|
||||
element: item.element,
|
||||
rect: item.rect,
|
||||
});
|
||||
if (!result.ok) {
|
||||
restoreManualOffsetDragMembers(members);
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
opts.onBlockedMoveRef.current(result.selection);
|
||||
return false;
|
||||
}
|
||||
members.push(result.member);
|
||||
}
|
||||
|
||||
const overlayEl = opts.overlayRef.current;
|
||||
const iframe = opts.iframeRef.current;
|
||||
const snapContext =
|
||||
overlayEl && iframe
|
||||
? collectSnapContext({
|
||||
overlayEl,
|
||||
iframe,
|
||||
excludeElements: buildExcludeElements({
|
||||
iframe,
|
||||
groupSelections: items.map((i) => i.selection),
|
||||
}),
|
||||
})
|
||||
: undefined;
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
e.currentTarget.setPointerCapture(e.pointerId);
|
||||
opts.rafPausedRef.current = true;
|
||||
opts.groupGestureRef.current = {
|
||||
startX: e.clientX,
|
||||
startY: e.clientY,
|
||||
originItems: items,
|
||||
members,
|
||||
snapContext,
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
// fallow-ignore-next-line complexity
|
||||
export function startGesture(
|
||||
kind: GestureKind,
|
||||
e: React.PointerEvent<HTMLElement>,
|
||||
opts: UseDomEditOverlayGesturesOptions,
|
||||
options?: { selection?: DomEditSelection; rect?: OverlayRect | null },
|
||||
): boolean {
|
||||
const sel = options?.selection ?? opts.selectionRef.current;
|
||||
const rect = options?.rect ?? opts.overlayRectRef.current;
|
||||
const box = opts.boxRef.current;
|
||||
const overlayEl = opts.overlayRef.current;
|
||||
if (!sel || !rect) return false;
|
||||
if (kind !== "drag" && !box) return false;
|
||||
const mode: GestureState["mode"] =
|
||||
kind === "rotate" ? "rotation" : kind === "drag" ? "path-offset" : "box-size";
|
||||
if (kind === "drag" && !sel.capabilities.canApplyManualOffset) return false;
|
||||
if (kind === "resize" && !sel.capabilities.canApplyManualSize) return false;
|
||||
if (kind === "rotate" && !sel.capabilities.canApplyManualRotation) return false;
|
||||
if (kind === "resize" && (!Number.isFinite(rect.width) || !Number.isFinite(rect.height)))
|
||||
return false;
|
||||
|
||||
const size = readStudioBoxSize(sel.element);
|
||||
// Single-source rotation base = the live GSAP transform rotation plus any legacy
|
||||
// `--hf-studio-rotation` CSS var (old projects), so a rotate gesture starts from the
|
||||
// element's actual visual angle and commits an absolute angle to the timeline.
|
||||
const rotation = { angle: readGsapRotation(sel.element) + readStudioRotation(sel.element).angle };
|
||||
// The draft writes CSS width/height, so the resize base must be the CSS
|
||||
// layout size. offsetWidth/Height are transform-free; the overlay-rect
|
||||
// fallback (rect / editScale) includes the element's own GSAP scale and
|
||||
// would make a rescaled element's draft grow from the RENDERED size.
|
||||
const layoutWidth = sel.element.offsetWidth;
|
||||
const layoutHeight = sel.element.offsetHeight;
|
||||
const actualWidth =
|
||||
size.width > 0 ? size.width : layoutWidth > 0 ? layoutWidth : rect.width / rect.editScaleX;
|
||||
const actualHeight =
|
||||
size.height > 0 ? size.height : layoutHeight > 0 ? layoutHeight : rect.height / rect.editScaleY;
|
||||
// overlay rect = cssSize x contentScale x editScale, so the element's own
|
||||
// render factor (its GSAP scale) falls out of the measured rect. 1 when
|
||||
// unscaled or unmeasurable.
|
||||
const rawContentScaleX = rect.width / (rect.editScaleX * actualWidth);
|
||||
const rawContentScaleY = rect.height / (rect.editScaleY * actualHeight);
|
||||
const contentScaleX =
|
||||
Number.isFinite(rawContentScaleX) && rawContentScaleX > 0 ? rawContentScaleX : 1;
|
||||
const contentScaleY =
|
||||
Number.isFinite(rawContentScaleY) && rawContentScaleY > 0 ? rawContentScaleY : 1;
|
||||
let resizeAnchor: GestureState["resizeAnchor"];
|
||||
if (kind === "resize") {
|
||||
const startBcr = sel.element.getBoundingClientRect();
|
||||
resizeAnchor = {
|
||||
anchorX: startBcr.x,
|
||||
anchorY: startBcr.y,
|
||||
baseGsapX: readElementGsapNumber(sel.element, "x") ?? 0,
|
||||
baseGsapY: readElementGsapNumber(sel.element, "y") ?? 0,
|
||||
pinX: 0,
|
||||
pinY: 0,
|
||||
};
|
||||
}
|
||||
let initialPathOffset = captureStudioPathOffset(sel.element);
|
||||
let manualEditDragToken: string | undefined;
|
||||
let pathOffsetMember: ManualOffsetDragMember | undefined;
|
||||
|
||||
if (kind === "drag") {
|
||||
opts.onManualDragStartRef.current?.();
|
||||
opts.rafPausedRef.current = true;
|
||||
const result = createManualOffsetDragMember({
|
||||
key: selectionCacheKey(sel),
|
||||
selection: sel,
|
||||
element: sel.element,
|
||||
rect,
|
||||
});
|
||||
if (!result.ok) {
|
||||
opts.onBlockedMoveRef.current(result.selection);
|
||||
return false;
|
||||
}
|
||||
pathOffsetMember = result.member;
|
||||
initialPathOffset = result.member.initialPathOffset;
|
||||
manualEditDragToken = result.member.gestureToken;
|
||||
} else {
|
||||
manualEditDragToken = beginStudioManualEditGesture(sel.element);
|
||||
}
|
||||
|
||||
const overlayBounds = overlayEl?.getBoundingClientRect();
|
||||
const centerX = (overlayBounds?.left ?? 0) + rect.left + rect.width / 2;
|
||||
const centerY = (overlayBounds?.top ?? 0) + rect.top + rect.height / 2;
|
||||
|
||||
const iframe = opts.iframeRef.current;
|
||||
const snapContext =
|
||||
(kind === "drag" || kind === "resize") && overlayEl && iframe
|
||||
? collectSnapContext({
|
||||
overlayEl,
|
||||
iframe,
|
||||
excludeElements: buildExcludeElements({ iframe, selection: sel }),
|
||||
})
|
||||
: undefined;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
e.currentTarget.setPointerCapture(e.pointerId);
|
||||
opts.rafPausedRef.current = true;
|
||||
opts.gestureRef.current = {
|
||||
kind,
|
||||
mode,
|
||||
selection: sel,
|
||||
startX: e.clientX,
|
||||
startY: e.clientY,
|
||||
centerX,
|
||||
centerY,
|
||||
initialPathOffset,
|
||||
initialRotation: captureStudioRotation(sel.element),
|
||||
initialBoxSize: captureStudioBoxSize(sel.element),
|
||||
pathOffsetMember,
|
||||
originLeft: rect.left,
|
||||
originTop: rect.top,
|
||||
originWidth: rect.width,
|
||||
originHeight: rect.height,
|
||||
actualWidth,
|
||||
actualHeight,
|
||||
actualRotation: rotation.angle,
|
||||
editScaleX: rect.editScaleX,
|
||||
editScaleY: rect.editScaleY,
|
||||
contentScaleX,
|
||||
contentScaleY,
|
||||
resizeAnchor,
|
||||
manualEditDragToken,
|
||||
snapContext,
|
||||
};
|
||||
return true;
|
||||
}
|
||||
Reference in New Issue
Block a user