import type { PatchOperation } from "../../utils/sourcePatcher"; import { STUDIO_OFFSET_X_PROP, STUDIO_OFFSET_Y_PROP, STUDIO_WIDTH_PROP, STUDIO_HEIGHT_PROP, STUDIO_ROTATION_PROP, STUDIO_PATH_OFFSET_ATTR, STUDIO_BOX_SIZE_ATTR, STUDIO_ROTATION_ATTR, STUDIO_ROTATION_DRAFT_ATTR, STUDIO_ORIGINAL_TRANSLATE_ATTR, STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR, STUDIO_ORIGINAL_WIDTH_ATTR, STUDIO_ORIGINAL_HEIGHT_ATTR, STUDIO_ORIGINAL_MIN_WIDTH_ATTR, STUDIO_ORIGINAL_MIN_HEIGHT_ATTR, STUDIO_ORIGINAL_MAX_WIDTH_ATTR, STUDIO_ORIGINAL_MAX_HEIGHT_ATTR, STUDIO_ORIGINAL_FLEX_BASIS_ATTR, STUDIO_ORIGINAL_FLEX_GROW_ATTR, STUDIO_ORIGINAL_FLEX_SHRINK_ATTR, STUDIO_ORIGINAL_BOX_SIZING_ATTR, STUDIO_ORIGINAL_SCALE_ATTR, STUDIO_ORIGINAL_TRANSFORM_ORIGIN_ATTR, STUDIO_ORIGINAL_DISPLAY_ATTR, STUDIO_ORIGINAL_ROTATE_ATTR, STUDIO_ORIGINAL_INLINE_ROTATE_ATTR, STUDIO_ORIGINAL_ROTATION_TRANSFORM_ORIGIN_ATTR, STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR, } from "./manualEditsTypes"; import { STUDIO_MOTION_ATTR, STUDIO_MOTION_ORIGINAL_TRANSFORM_ATTR, STUDIO_MOTION_ORIGINAL_OPACITY_ATTR, STUDIO_MOTION_ORIGINAL_VISIBILITY_ATTR, } from "./studioMotionTypes"; /* ── Shared helpers ──────────────────────────────────────────────── */ function collectInlineStyleOps( element: HTMLElement, properties: readonly string[], ops: PatchOperation[], ): void { for (const prop of properties) { const val = element.style.getPropertyValue(prop); if (val) ops.push({ type: "inline-style", property: prop, value: val }); } } function collectAttributeOps( element: HTMLElement, attrNames: readonly string[], ops: PatchOperation[], ): void { for (const attr of attrNames) { const val = element.getAttribute(attr); if (val !== null) ops.push({ type: "attribute", property: attr, value: val }); } } function appendTransformDisplayOps(element: HTMLElement, ops: PatchOperation[]): void { const val = element.getAttribute(STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR); if (val !== null) { ops.push({ type: "inline-style", property: "display", value: val || null }); ops.push({ type: "attribute", property: STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR, value: null }); } } /* ── Path offset patches ─────────────────────────────────────────── */ export function buildPathOffsetPatches(element: HTMLElement): PatchOperation[] { const ops: PatchOperation[] = []; collectInlineStyleOps(element, [STUDIO_OFFSET_X_PROP, STUDIO_OFFSET_Y_PROP], ops); // When GSAP owns the element's transform, the live inline translate is kept // at "none" (the offset lives in GSAP's cache — see applyStudioPathOffset). // Persist the var() expression in that case, so a reload re-folds the offset. const inlineTranslate = element.style.getPropertyValue("translate"); const hasOffsetVars = element.style.getPropertyValue(STUDIO_OFFSET_X_PROP) || element.style.getPropertyValue(STUDIO_OFFSET_Y_PROP); const translateValue = inlineTranslate && inlineTranslate !== "none" ? inlineTranslate : hasOffsetVars ? `var(${STUDIO_OFFSET_X_PROP}, 0px) var(${STUDIO_OFFSET_Y_PROP}, 0px)` : null; if (translateValue) { ops.push({ type: "inline-style", property: "translate", value: translateValue }); } ops.push({ type: "attribute", property: STUDIO_PATH_OFFSET_ATTR, value: "true" }); collectAttributeOps( element, [STUDIO_ORIGINAL_TRANSLATE_ATTR, STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR], ops, ); collectInlineStyleOps(element, ["display"], ops); collectAttributeOps(element, [STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR], ops); return ops; } export function buildClearPathOffsetPatches(element: HTMLElement): PatchOperation[] { const originalInlineTranslate = element.getAttribute(STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR); const ops: PatchOperation[] = [ { type: "inline-style", property: STUDIO_OFFSET_X_PROP, value: null }, { type: "inline-style", property: STUDIO_OFFSET_Y_PROP, value: null }, { type: "inline-style", property: "translate", value: originalInlineTranslate || null }, { type: "attribute", property: STUDIO_PATH_OFFSET_ATTR, value: null }, { type: "attribute", property: STUDIO_ORIGINAL_TRANSLATE_ATTR, value: null }, { type: "attribute", property: STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR, value: null }, ]; appendTransformDisplayOps(element, ops); return ops; } /* ── Box size patches ────────────────────────────────────────────── */ const BOX_SIZE_STYLE_PROPS = [ "width", "height", "min-width", "min-height", "max-width", "max-height", "flex-basis", "flex-grow", "flex-shrink", "box-sizing", "scale", "transform-origin", "display", ] as const; const BOX_SIZE_ORIG_ATTRS: ReadonlyArray<[string, string]> = [ [STUDIO_ORIGINAL_WIDTH_ATTR, "width"], [STUDIO_ORIGINAL_HEIGHT_ATTR, "height"], [STUDIO_ORIGINAL_MIN_WIDTH_ATTR, "min-width"], [STUDIO_ORIGINAL_MIN_HEIGHT_ATTR, "min-height"], [STUDIO_ORIGINAL_MAX_WIDTH_ATTR, "max-width"], [STUDIO_ORIGINAL_MAX_HEIGHT_ATTR, "max-height"], [STUDIO_ORIGINAL_FLEX_BASIS_ATTR, "flex-basis"], [STUDIO_ORIGINAL_FLEX_GROW_ATTR, "flex-grow"], [STUDIO_ORIGINAL_FLEX_SHRINK_ATTR, "flex-shrink"], [STUDIO_ORIGINAL_BOX_SIZING_ATTR, "box-sizing"], [STUDIO_ORIGINAL_SCALE_ATTR, "scale"], [STUDIO_ORIGINAL_TRANSFORM_ORIGIN_ATTR, "transform-origin"], [STUDIO_ORIGINAL_DISPLAY_ATTR, "display"], [STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR, ""], ]; export function buildBoxSizePatches(element: HTMLElement): PatchOperation[] { const ops: PatchOperation[] = []; collectInlineStyleOps(element, [STUDIO_WIDTH_PROP, STUDIO_HEIGHT_PROP], ops); collectInlineStyleOps(element, BOX_SIZE_STYLE_PROPS, ops); ops.push({ type: "attribute", property: STUDIO_BOX_SIZE_ATTR, value: "true" }); collectAttributeOps( element, BOX_SIZE_ORIG_ATTRS.map(([attr]) => attr), ops, ); return ops; } export function buildClearBoxSizePatches(element: HTMLElement): PatchOperation[] { const ops: PatchOperation[] = [ { type: "inline-style", property: STUDIO_WIDTH_PROP, value: null }, { type: "inline-style", property: STUDIO_HEIGHT_PROP, value: null }, { type: "attribute", property: STUDIO_BOX_SIZE_ATTR, value: null }, ]; for (const [attrName, styleProp] of BOX_SIZE_ORIG_ATTRS) { const origVal = element.getAttribute(attrName); if (origVal !== null && styleProp) { ops.push({ type: "inline-style", property: styleProp, value: origVal || null }); } ops.push({ type: "attribute", property: attrName, value: null }); } return ops; } /* ── Rotation patches ────────────────────────────────────────────── */ const ROTATION_STYLE_PROPS = [ STUDIO_ROTATION_PROP, "rotate", "transform-origin", "display", ] as const; const ROTATION_ORIG_ATTRS = [ STUDIO_ORIGINAL_ROTATE_ATTR, STUDIO_ORIGINAL_INLINE_ROTATE_ATTR, STUDIO_ORIGINAL_ROTATION_TRANSFORM_ORIGIN_ATTR, STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR, ] as const; export function buildRotationPatches(element: HTMLElement): PatchOperation[] { const ops: PatchOperation[] = []; collectInlineStyleOps(element, ROTATION_STYLE_PROPS, ops); ops.push({ type: "attribute", property: STUDIO_ROTATION_ATTR, value: "true" }); collectAttributeOps(element, ROTATION_ORIG_ATTRS, ops); return ops; } export function buildClearRotationPatches(element: HTMLElement): PatchOperation[] { const origInlineRotate = element.getAttribute(STUDIO_ORIGINAL_INLINE_ROTATE_ATTR); const origRotationTransformOrigin = element.getAttribute( STUDIO_ORIGINAL_ROTATION_TRANSFORM_ORIGIN_ATTR, ); const ops: PatchOperation[] = [ { type: "inline-style", property: STUDIO_ROTATION_PROP, value: null }, { type: "inline-style", property: "rotate", value: origInlineRotate || null }, { type: "inline-style", property: "transform-origin", value: origRotationTransformOrigin !== null ? origRotationTransformOrigin || null : null, }, { type: "attribute", property: STUDIO_ROTATION_ATTR, value: null }, { type: "attribute", property: STUDIO_ROTATION_DRAFT_ATTR, value: null }, { type: "attribute", property: STUDIO_ORIGINAL_ROTATE_ATTR, value: null }, { type: "attribute", property: STUDIO_ORIGINAL_INLINE_ROTATE_ATTR, value: null }, { type: "attribute", property: STUDIO_ORIGINAL_ROTATION_TRANSFORM_ORIGIN_ATTR, value: null }, ]; appendTransformDisplayOps(element, ops); return ops; } /* ── Motion patches ──────────────────────────────────────────────── */ const MOTION_ORIG_ATTRS = [ STUDIO_MOTION_ORIGINAL_TRANSFORM_ATTR, STUDIO_MOTION_ORIGINAL_OPACITY_ATTR, STUDIO_MOTION_ORIGINAL_VISIBILITY_ATTR, ] as const; export function buildMotionPatches(element: HTMLElement): PatchOperation[] { const motionJson = element.getAttribute(STUDIO_MOTION_ATTR); if (!motionJson) return []; const ops: PatchOperation[] = [ { type: "attribute", property: STUDIO_MOTION_ATTR, value: motionJson }, ]; collectAttributeOps(element, MOTION_ORIG_ATTRS, ops); return ops; } export function buildClearMotionPatches(_element: HTMLElement): PatchOperation[] { return [ { type: "attribute", property: STUDIO_MOTION_ATTR, value: null }, { type: "attribute", property: STUDIO_MOTION_ORIGINAL_TRANSFORM_ATTR, value: null }, { type: "attribute", property: STUDIO_MOTION_ORIGINAL_OPACITY_ATTR, value: null }, { type: "attribute", property: STUDIO_MOTION_ORIGINAL_VISIBILITY_ATTR, value: null }, ]; }