mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-11 06:30:03 +00:00
feat(editing): shared resolveEditingAffordances (core) + studio re-point + SDK adapter (#1814)
* feat(core): add pure resolveEditingAffordances (edit capabilities + section applicability) * fix(core): replace prohibited as-cast and !-assertions in isIdentityTransform * refactor(studio): consume core resolveEditingAffordances; drop duplicated capability + section logic - affordances.ts: add matrix3d identity-transform branch (was missing, caused test regression) - domEditingLayers: add domEditSelectionToFacts mapper; resolveDomEditCapabilities is now a thin wrapper over core (kept for backward-compat — tests + barrel import it); isTextEditableSelection delegates to core sections.text; drop parsePx + isIdentityTransform imports (now in core) - PropertyPanel: import resolveEditingAffordances + domEditSelectionToFacts; compute sections once; replace isMediaElement/isColorGradingCapableElement/timing inline check with sections.* - propertyPanelMediaSection: delete isMediaElement (no remaining callers) - propertyPanelColorGradingSection: delete isColorGradingCapableElement (no remaining callers) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(sdk): add browser-only resolveElementAffordances adapter over core * fix(sdk): add position to inlineStyles, replace ! assertion with guard in test - Add missing 'position' key to inlineStyles in affordances.ts to match computedStyles - Replace non-null assertion (doc.defaultView!) with proper null guard in test Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com> * fix(editing): resolve code-review findings on affordances feature Max-effort review (8 verified findings) fixes: Correctness regressions (studio behavior): - SVG selection crash: dropped `classNames` from EditableElementFacts entirely (it was never read by the resolver), which removes the `.className.split()` calls that throw on SVGElement (className is an SVGAnimatedString, not a string). Masked in tests by happy-dom. - Timing panel hidden for GSAP-only layers: domEditSelectionToFacts now takes animationCount from the caller; PropertyPanel feeds the live gsapAnimations prop (selection.gsapAnimations is never populated). Cleanups: - Removed dead inline `position` key from SDK adapter (core reads position only from computedStyles). - Added sections-only `resolveEditingSections` export; PropertyPanel uses it so panel re-renders no longer re-run the capability geometry parse. - Declared happy-dom in packages/sdk devDependencies (was root-hoist only). - Deduped the two capability fact-construction sites behind a shared capabilityFacts() helper. - parsePx now has a single source of truth in core; studio domEditingDom re-exports it so the copies can't drift. isIdentityTransform is now core-internal (studio's only consumer moved to core in the prior task). bun.lock also reconciles stale 0.7.17->0.7.21 package versions. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
856bb0980f
commit
5915590b06
@@ -19,39 +19,10 @@ export function isHtmlElement(value: unknown): value is HTMLElement {
|
||||
|
||||
// ─── Style parsing ────────────────────────────────────────────────────────────
|
||||
|
||||
export function parsePx(value: string | undefined): number | null {
|
||||
if (!value) return null;
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed.endsWith("px")) return null;
|
||||
const parsed = parseFloat(trimmed);
|
||||
return Number.isFinite(parsed) ? parsed : null;
|
||||
}
|
||||
|
||||
export function isIdentityTransform(value: string | undefined): boolean {
|
||||
const transform = (value ?? "none").trim();
|
||||
if (!transform || transform === "none") return true;
|
||||
|
||||
const matrix = transform.match(/^matrix\(([^)]+)\)$/i);
|
||||
if (matrix) {
|
||||
const values = matrix[1].split(",").map((part) => Number.parseFloat(part.trim()));
|
||||
if (values.length !== 6 || values.some((part) => !Number.isFinite(part))) return false;
|
||||
return (
|
||||
Math.abs(values[0] - 1) < 0.0001 &&
|
||||
Math.abs(values[1]) < 0.0001 &&
|
||||
Math.abs(values[2]) < 0.0001 &&
|
||||
Math.abs(values[3] - 1) < 0.0001 &&
|
||||
Math.abs(values[4]) < 0.0001 &&
|
||||
Math.abs(values[5]) < 0.0001
|
||||
);
|
||||
}
|
||||
|
||||
const matrix3d = transform.match(/^matrix3d\(([^)]+)\)$/i);
|
||||
if (!matrix3d) return false;
|
||||
const values = matrix3d[1].split(",").map((part) => Number.parseFloat(part.trim()));
|
||||
if (values.length !== 16 || values.some((part) => !Number.isFinite(part))) return false;
|
||||
const identity = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
|
||||
return values.every((part, index) => Math.abs(part - identity[index]) < 0.0001);
|
||||
}
|
||||
// Single source of truth lives in @hyperframes/core/editing so the studio
|
||||
// callers and the core resolver can't drift. Re-exported here to keep this
|
||||
// module's public surface (6 studio callers import parsePx from it).
|
||||
export { parsePx } from "@hyperframes/core/editing";
|
||||
|
||||
export function isTextBearingTag(tagName: string): boolean {
|
||||
return ["div", "span", "p", "strong", "h1", "h2", "h3", "h4", "h5", "h6"].includes(tagName);
|
||||
|
||||
Reference in New Issue
Block a user