mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-11 14:50:02 +00:00
refactor(studio): split oversized files to pass 600-line check (#1313)
This commit is contained in:
@@ -7,11 +7,14 @@ import {
|
||||
formatPxMetricValue,
|
||||
parsePxMetricValue,
|
||||
RESPONSIVE_GRID,
|
||||
readGsapRuntimeValuesForPanel,
|
||||
readGsapBorderRadiusForPanel,
|
||||
} from "./propertyPanelHelpers";
|
||||
import { MetricField, Section } from "./propertyPanelPrimitives";
|
||||
import { isMediaElement, MediaSection } from "./propertyPanelMediaSection";
|
||||
import { TextSection, StyleSections } from "./propertyPanelSections";
|
||||
import { GsapAnimationSection } from "./GsapAnimationSection";
|
||||
import { PropertyPanel3dTransform } from "./propertyPanel3dTransform";
|
||||
import { KeyframeNavigation } from "./KeyframeNavigation";
|
||||
import { STUDIO_GSAP_PANEL_ENABLED, STUDIO_KEYFRAMES_ENABLED } from "./manualEditingAvailability";
|
||||
import { usePlayerStore } from "../../player";
|
||||
@@ -188,69 +191,19 @@ export const PropertyPanel = memo(function PropertyPanel({
|
||||
gsapAnimations?.find((a) => a.keyframes)?.id ?? gsapAnimations?.[0]?.id ?? null;
|
||||
|
||||
// Read ALL GSAP-interpolated values at the current seek time.
|
||||
// Discovers animated properties from the animation's keyframes/tween vars.
|
||||
const gsapRuntimeValues: Record<string, number> | null = (() => {
|
||||
if (!gsapAnimId || gsapAnimations.length === 0) return null;
|
||||
const iframe = previewIframeRef?.current;
|
||||
if (!iframe?.contentWindow) return null;
|
||||
const selector = element.id ? `#${element.id}` : element.selector;
|
||||
if (!selector) return null;
|
||||
try {
|
||||
const gsap = (
|
||||
iframe.contentWindow as unknown as {
|
||||
gsap?: { getProperty: (el: Element, prop: string) => number | string };
|
||||
}
|
||||
).gsap;
|
||||
if (!gsap?.getProperty) return null;
|
||||
const el = iframe.contentDocument?.querySelector(selector);
|
||||
if (!el) return null;
|
||||
const propKeys = new Set<string>();
|
||||
for (const anim of gsapAnimations) {
|
||||
if (anim.keyframes) {
|
||||
for (const kf of anim.keyframes.keyframes) {
|
||||
for (const p of Object.keys(kf.properties)) propKeys.add(p);
|
||||
}
|
||||
}
|
||||
for (const p of Object.keys(anim.properties)) propKeys.add(p);
|
||||
}
|
||||
const result: Record<string, number> = {};
|
||||
for (const prop of propKeys) {
|
||||
const v = Number(gsap.getProperty(el, prop));
|
||||
if (Number.isFinite(v)) result[prop] = Math.round(v * 100) / 100;
|
||||
}
|
||||
return Object.keys(result).length > 0 ? result : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
})();
|
||||
const gsapRuntimeValues = readGsapRuntimeValuesForPanel(
|
||||
gsapAnimId,
|
||||
gsapAnimations,
|
||||
element,
|
||||
previewIframeRef ?? { current: null },
|
||||
);
|
||||
|
||||
const gsapBorderRadius: { tl: number; tr: number; br: number; bl: number } | null = (() => {
|
||||
if (!gsapRuntimeValues || !("borderRadius" in gsapRuntimeValues)) {
|
||||
const hasBRProp = gsapAnimations.some(
|
||||
(a) =>
|
||||
"borderRadius" in a.properties ||
|
||||
a.keyframes?.keyframes.some((kf) => "borderRadius" in kf.properties),
|
||||
);
|
||||
if (!hasBRProp) return null;
|
||||
}
|
||||
const iframe = previewIframeRef?.current;
|
||||
const selector = element.id ? `#${element.id}` : element.selector;
|
||||
if (!iframe?.contentDocument || !selector) return null;
|
||||
try {
|
||||
const el = iframe.contentDocument.querySelector(selector);
|
||||
if (!el) return null;
|
||||
const cs = iframe.contentWindow!.getComputedStyle(el);
|
||||
const parse = (v: string) => Number.parseFloat(v) || 0;
|
||||
return {
|
||||
tl: parse(cs.borderTopLeftRadius),
|
||||
tr: parse(cs.borderTopRightRadius),
|
||||
br: parse(cs.borderBottomRightRadius),
|
||||
bl: parse(cs.borderBottomLeftRadius),
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
})();
|
||||
const gsapBorderRadius = readGsapBorderRadiusForPanel(
|
||||
gsapRuntimeValues,
|
||||
gsapAnimations,
|
||||
element,
|
||||
previewIframeRef ?? { current: null },
|
||||
);
|
||||
|
||||
const displayX = gsapRuntimeValues?.x ?? manualOffset.x;
|
||||
const displayY = gsapRuntimeValues?.y ?? manualOffset.y;
|
||||
@@ -510,97 +463,19 @@ export const PropertyPanel = memo(function PropertyPanel({
|
||||
</div>
|
||||
</div>
|
||||
{gsapRuntimeValues && (
|
||||
<div className="mt-3 border-t border-neutral-800/40 pt-3">
|
||||
<div className="mb-2 text-[10px] font-medium uppercase tracking-wider text-neutral-600">
|
||||
3D Transform
|
||||
</div>
|
||||
<div className={RESPONSIVE_GRID}>
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="flex-1">
|
||||
<MetricField
|
||||
label="Z"
|
||||
value={formatPxMetricValue(gsapRuntimeValues.z ?? 0)}
|
||||
scrub
|
||||
onCommit={(next) => {
|
||||
const v = parsePxMetricValue(next);
|
||||
if (v != null && onCommitAnimatedProperty) {
|
||||
void onCommitAnimatedProperty(element, "z", v);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{STUDIO_KEYFRAMES_ENABLED && (gsapAnimId || onCommitAnimatedProperty) && (
|
||||
<KeyframeNavigation
|
||||
property="z"
|
||||
keyframes={gsapKeyframes}
|
||||
currentPercentage={currentPct}
|
||||
onSeek={(pct) => onSeekToTime?.(elStart + (pct / 100) * elDuration)}
|
||||
onAddKeyframe={() => {
|
||||
if (onCommitAnimatedProperty) {
|
||||
void onCommitAnimatedProperty(element, "z", gsapRuntimeValues?.z ?? 0);
|
||||
}
|
||||
}}
|
||||
onRemoveKeyframe={(pct) => gsapAnimId && onRemoveKeyframe?.(gsapAnimId, pct)}
|
||||
onConvertToKeyframes={() => gsapAnimId && onConvertToKeyframes?.(gsapAnimId)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="flex-1">
|
||||
<MetricField
|
||||
label="Scale"
|
||||
value={String(gsapRuntimeValues.scale ?? 1)}
|
||||
scrub
|
||||
onCommit={(next) => {
|
||||
const v = Number.parseFloat(next);
|
||||
if (Number.isFinite(v) && onCommitAnimatedProperty) {
|
||||
void onCommitAnimatedProperty(element, "scale", v);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{STUDIO_KEYFRAMES_ENABLED && (gsapAnimId || onCommitAnimatedProperty) && (
|
||||
<KeyframeNavigation
|
||||
property="scale"
|
||||
keyframes={gsapKeyframes}
|
||||
currentPercentage={currentPct}
|
||||
onSeek={(pct) => onSeekToTime?.(elStart + (pct / 100) * elDuration)}
|
||||
onAddKeyframe={() => {
|
||||
if (onCommitAnimatedProperty) {
|
||||
void onCommitAnimatedProperty(
|
||||
element,
|
||||
"scale",
|
||||
gsapRuntimeValues?.scale ?? 1,
|
||||
);
|
||||
}
|
||||
}}
|
||||
onRemoveKeyframe={(pct) => gsapAnimId && onRemoveKeyframe?.(gsapAnimId, pct)}
|
||||
onConvertToKeyframes={() => gsapAnimId && onConvertToKeyframes?.(gsapAnimId)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<MetricField
|
||||
label="RotX"
|
||||
value={`${gsapRuntimeValues.rotationX ?? 0}°`}
|
||||
onCommit={(next) => {
|
||||
const v = Number.parseFloat(next.replace("°", ""));
|
||||
if (Number.isFinite(v) && onCommitAnimatedProperty) {
|
||||
void onCommitAnimatedProperty(element, "rotationX", v);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<MetricField
|
||||
label="RotY"
|
||||
value={`${gsapRuntimeValues.rotationY ?? 0}°`}
|
||||
onCommit={(next) => {
|
||||
const v = Number.parseFloat(next.replace("°", ""));
|
||||
if (Number.isFinite(v) && onCommitAnimatedProperty) {
|
||||
void onCommitAnimatedProperty(element, "rotationY", v);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<PropertyPanel3dTransform
|
||||
gsapRuntimeValues={gsapRuntimeValues}
|
||||
gsapAnimId={gsapAnimId}
|
||||
gsapKeyframes={gsapKeyframes}
|
||||
currentPct={currentPct}
|
||||
elStart={elStart}
|
||||
elDuration={elDuration}
|
||||
element={element}
|
||||
onCommitAnimatedProperty={onCommitAnimatedProperty}
|
||||
onSeekToTime={onSeekToTime}
|
||||
onRemoveKeyframe={onRemoveKeyframe}
|
||||
onConvertToKeyframes={onConvertToKeyframes}
|
||||
/>
|
||||
)}
|
||||
<div className="mt-3">
|
||||
<div className="mb-2 text-[10px] font-medium uppercase tracking-wider text-neutral-600">
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Checks whether GSAP actively animates one or more CSS/GSAP properties on
|
||||
* the given element by inspecting all registered `__timelines`.
|
||||
*/
|
||||
export function gsapAnimatesProperty(el: HTMLElement, ...props: string[]): boolean {
|
||||
const win = el.ownerDocument.defaultView as
|
||||
| (Window & {
|
||||
__timelines?: Record<
|
||||
string,
|
||||
{
|
||||
getChildren?: (
|
||||
deep: boolean,
|
||||
) => Array<{ targets?: () => Element[]; vars?: Record<string, unknown> }>;
|
||||
}
|
||||
>;
|
||||
})
|
||||
| null;
|
||||
if (!win?.__timelines) return false;
|
||||
const propSet = new Set(props);
|
||||
for (const tl of Object.values(win.__timelines)) {
|
||||
if (!tl?.getChildren) continue;
|
||||
try {
|
||||
for (const child of tl.getChildren(true)) {
|
||||
if (!child.targets || !child.vars) continue;
|
||||
let targetsEl = false;
|
||||
for (const t of child.targets()) {
|
||||
if (t === el || (el.id && t.id === el.id)) {
|
||||
targetsEl = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!targetsEl) continue;
|
||||
const vars = child.vars;
|
||||
for (const p of propSet) {
|
||||
if (p in vars) return true;
|
||||
}
|
||||
if (vars.keyframes && typeof vars.keyframes === "object") {
|
||||
for (const kfVal of Object.values(vars.keyframes as Record<string, unknown>)) {
|
||||
if (kfVal && typeof kfVal === "object") {
|
||||
for (const p of propSet) {
|
||||
if (p in (kfVal as Record<string, unknown>)) return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
/* */
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
} from "./manualEditsTypes";
|
||||
import { roundRotationAngle } from "./manualEditsParsing";
|
||||
import { applyStudioMotionFromDom } from "./studioMotion";
|
||||
import { gsapAnimatesProperty } from "./gsapAnimatesProperty";
|
||||
|
||||
/* ── Gesture tracking ─────────────────────────────────────────────── */
|
||||
let studioManualEditGestureId = 0;
|
||||
@@ -534,55 +535,6 @@ function reapplyPathOffsets(doc: Document): void {
|
||||
}
|
||||
}
|
||||
|
||||
function gsapAnimatesProperty(el: HTMLElement, ...props: string[]): boolean {
|
||||
const win = el.ownerDocument.defaultView as
|
||||
| (Window & {
|
||||
__timelines?: Record<
|
||||
string,
|
||||
{
|
||||
getChildren?: (
|
||||
deep: boolean,
|
||||
) => Array<{ targets?: () => Element[]; vars?: Record<string, unknown> }>;
|
||||
}
|
||||
>;
|
||||
})
|
||||
| null;
|
||||
if (!win?.__timelines) return false;
|
||||
const propSet = new Set(props);
|
||||
for (const tl of Object.values(win.__timelines)) {
|
||||
if (!tl?.getChildren) continue;
|
||||
try {
|
||||
for (const child of tl.getChildren(true)) {
|
||||
if (!child.targets || !child.vars) continue;
|
||||
let targetsEl = false;
|
||||
for (const t of child.targets()) {
|
||||
if (t === el || (el.id && t.id === el.id)) {
|
||||
targetsEl = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!targetsEl) continue;
|
||||
const vars = child.vars;
|
||||
for (const p of propSet) {
|
||||
if (p in vars) return true;
|
||||
}
|
||||
if (vars.keyframes && typeof vars.keyframes === "object") {
|
||||
for (const kfVal of Object.values(vars.keyframes as Record<string, unknown>)) {
|
||||
if (kfVal && typeof kfVal === "object") {
|
||||
for (const p of propSet) {
|
||||
if (p in (kfVal as Record<string, unknown>)) return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
/* */
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function reapplyBoxSizes(doc: Document): void {
|
||||
for (const el of queryStudioElements(doc, STUDIO_BOX_SIZE_ATTR)) {
|
||||
if (gsapAnimatesProperty(el, "width", "height")) continue;
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
import type { DomEditSelection } from "./domEditingTypes";
|
||||
import { STUDIO_KEYFRAMES_ENABLED } from "./manualEditingAvailability";
|
||||
import { MetricField } from "./propertyPanelPrimitives";
|
||||
import { KeyframeNavigation } from "./KeyframeNavigation";
|
||||
import { formatPxMetricValue, parsePxMetricValue, RESPONSIVE_GRID } from "./propertyPanelHelpers";
|
||||
|
||||
type KeyframeEntry = Array<{
|
||||
percentage: number;
|
||||
properties: Record<string, number | string>;
|
||||
ease?: string;
|
||||
}> | null;
|
||||
|
||||
interface PropertyPanel3dTransformProps {
|
||||
gsapRuntimeValues: Record<string, number>;
|
||||
gsapAnimId: string | null;
|
||||
gsapKeyframes: KeyframeEntry;
|
||||
currentPct: number;
|
||||
elStart: number;
|
||||
elDuration: number;
|
||||
element: DomEditSelection;
|
||||
onCommitAnimatedProperty?: (
|
||||
element: DomEditSelection,
|
||||
property: string,
|
||||
value: number,
|
||||
) => Promise<void>;
|
||||
onSeekToTime?: (time: number) => void;
|
||||
onRemoveKeyframe?: (animId: string, pct: number) => void;
|
||||
onConvertToKeyframes?: (animId: string) => void;
|
||||
}
|
||||
|
||||
export function PropertyPanel3dTransform({
|
||||
gsapRuntimeValues,
|
||||
gsapAnimId,
|
||||
gsapKeyframes,
|
||||
currentPct,
|
||||
elStart,
|
||||
elDuration,
|
||||
element,
|
||||
onCommitAnimatedProperty,
|
||||
onSeekToTime,
|
||||
onRemoveKeyframe,
|
||||
onConvertToKeyframes,
|
||||
}: PropertyPanel3dTransformProps) {
|
||||
return (
|
||||
<div className="mt-3 border-t border-neutral-800/40 pt-3">
|
||||
<div className="mb-2 text-[10px] font-medium uppercase tracking-wider text-neutral-600">
|
||||
3D Transform
|
||||
</div>
|
||||
<div className={RESPONSIVE_GRID}>
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="flex-1">
|
||||
<MetricField
|
||||
label="Z"
|
||||
value={formatPxMetricValue(gsapRuntimeValues.z ?? 0)}
|
||||
scrub
|
||||
onCommit={(next) => {
|
||||
const v = parsePxMetricValue(next);
|
||||
if (v != null && onCommitAnimatedProperty) {
|
||||
void onCommitAnimatedProperty(element, "z", v);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{STUDIO_KEYFRAMES_ENABLED && (gsapAnimId || onCommitAnimatedProperty) && (
|
||||
<KeyframeNavigation
|
||||
property="z"
|
||||
keyframes={gsapKeyframes}
|
||||
currentPercentage={currentPct}
|
||||
onSeek={(pct) => onSeekToTime?.(elStart + (pct / 100) * elDuration)}
|
||||
onAddKeyframe={() => {
|
||||
if (onCommitAnimatedProperty) {
|
||||
void onCommitAnimatedProperty(element, "z", gsapRuntimeValues?.z ?? 0);
|
||||
}
|
||||
}}
|
||||
onRemoveKeyframe={(pct) => gsapAnimId && onRemoveKeyframe?.(gsapAnimId, pct)}
|
||||
onConvertToKeyframes={() => gsapAnimId && onConvertToKeyframes?.(gsapAnimId)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="flex-1">
|
||||
<MetricField
|
||||
label="Scale"
|
||||
value={String(gsapRuntimeValues.scale ?? 1)}
|
||||
scrub
|
||||
onCommit={(next) => {
|
||||
const v = Number.parseFloat(next);
|
||||
if (Number.isFinite(v) && onCommitAnimatedProperty) {
|
||||
void onCommitAnimatedProperty(element, "scale", v);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{STUDIO_KEYFRAMES_ENABLED && (gsapAnimId || onCommitAnimatedProperty) && (
|
||||
<KeyframeNavigation
|
||||
property="scale"
|
||||
keyframes={gsapKeyframes}
|
||||
currentPercentage={currentPct}
|
||||
onSeek={(pct) => onSeekToTime?.(elStart + (pct / 100) * elDuration)}
|
||||
onAddKeyframe={() => {
|
||||
if (onCommitAnimatedProperty) {
|
||||
void onCommitAnimatedProperty(element, "scale", gsapRuntimeValues?.scale ?? 1);
|
||||
}
|
||||
}}
|
||||
onRemoveKeyframe={(pct) => gsapAnimId && onRemoveKeyframe?.(gsapAnimId, pct)}
|
||||
onConvertToKeyframes={() => gsapAnimId && onConvertToKeyframes?.(gsapAnimId)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<MetricField
|
||||
label="RotX"
|
||||
value={`${gsapRuntimeValues.rotationX ?? 0}°`}
|
||||
onCommit={(next) => {
|
||||
const v = Number.parseFloat(next.replace("°", ""));
|
||||
if (Number.isFinite(v) && onCommitAnimatedProperty) {
|
||||
void onCommitAnimatedProperty(element, "rotationX", v);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<MetricField
|
||||
label="RotY"
|
||||
value={`${gsapRuntimeValues.rotationY ?? 0}°`}
|
||||
onCommit={(next) => {
|
||||
const v = Number.parseFloat(next.replace("°", ""));
|
||||
if (Number.isFinite(v) && onCommitAnimatedProperty) {
|
||||
void onCommitAnimatedProperty(element, "rotationY", v);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { parseCssColor, type ParsedColor } from "./colorValue";
|
||||
import { COMMON_LOCAL_FONT_FAMILIES } from "./fontCatalog";
|
||||
import type { DomEditSelection } from "./domEditing";
|
||||
import type { ImportedFontAsset } from "./fontAssets";
|
||||
import type { GsapAnimation } from "@hyperframes/core/gsap-parser";
|
||||
|
||||
export interface PropertyPanelProps {
|
||||
projectId: string;
|
||||
@@ -505,3 +506,78 @@ export function computeFitToChildrenSize(
|
||||
const height = Math.round((maxY - minY) * scaleY);
|
||||
return width > 0 && height > 0 ? { width, height } : null;
|
||||
}
|
||||
|
||||
// ── GSAP runtime value readers (used by PropertyPanel) ────────────────────
|
||||
|
||||
export function readGsapRuntimeValuesForPanel(
|
||||
gsapAnimId: string | null,
|
||||
gsapAnimations: GsapAnimation[],
|
||||
element: DomEditSelection,
|
||||
previewIframeRef: React.RefObject<HTMLIFrameElement | null>,
|
||||
): Record<string, number> | null {
|
||||
if (!gsapAnimId || gsapAnimations.length === 0) return null;
|
||||
const iframe = previewIframeRef?.current;
|
||||
if (!iframe?.contentWindow) return null;
|
||||
const selector = element.id ? `#${element.id}` : element.selector;
|
||||
if (!selector) return null;
|
||||
try {
|
||||
const gsap = (
|
||||
iframe.contentWindow as unknown as {
|
||||
gsap?: { getProperty: (el: Element, prop: string) => number | string };
|
||||
}
|
||||
).gsap;
|
||||
if (!gsap?.getProperty) return null;
|
||||
const el = iframe.contentDocument?.querySelector(selector);
|
||||
if (!el) return null;
|
||||
const propKeys = new Set<string>();
|
||||
for (const anim of gsapAnimations) {
|
||||
if (anim.keyframes) {
|
||||
for (const kf of anim.keyframes.keyframes) {
|
||||
for (const p of Object.keys(kf.properties)) propKeys.add(p);
|
||||
}
|
||||
}
|
||||
for (const p of Object.keys(anim.properties)) propKeys.add(p);
|
||||
}
|
||||
const result: Record<string, number> = {};
|
||||
for (const prop of propKeys) {
|
||||
const v = Number(gsap.getProperty(el, prop));
|
||||
if (Number.isFinite(v)) result[prop] = Math.round(v * 100) / 100;
|
||||
}
|
||||
return Object.keys(result).length > 0 ? result : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function readGsapBorderRadiusForPanel(
|
||||
gsapRuntimeValues: Record<string, number> | null,
|
||||
gsapAnimations: GsapAnimation[],
|
||||
element: DomEditSelection,
|
||||
previewIframeRef: React.RefObject<HTMLIFrameElement | null>,
|
||||
): { tl: number; tr: number; br: number; bl: number } | null {
|
||||
if (!gsapRuntimeValues || !("borderRadius" in gsapRuntimeValues)) {
|
||||
const hasBRProp = gsapAnimations.some(
|
||||
(a) =>
|
||||
"borderRadius" in a.properties ||
|
||||
a.keyframes?.keyframes.some((kf) => "borderRadius" in kf.properties),
|
||||
);
|
||||
if (!hasBRProp) return null;
|
||||
}
|
||||
const iframe = previewIframeRef?.current;
|
||||
const selector = element.id ? `#${element.id}` : element.selector;
|
||||
if (!iframe?.contentDocument || !selector) return null;
|
||||
try {
|
||||
const el = iframe.contentDocument.querySelector(selector);
|
||||
if (!el) return null;
|
||||
const cs = iframe.contentWindow!.getComputedStyle(el);
|
||||
const parse = (v: string) => Number.parseFloat(v) || 0;
|
||||
return {
|
||||
tl: parse(cs.borderTopLeftRadius),
|
||||
tr: parse(cs.borderTopRightRadius),
|
||||
br: parse(cs.borderBottomRightRadius),
|
||||
bl: parse(cs.borderBottomLeftRadius),
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user