mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-07 10:06:21 +00:00
* test(studio): add design-panel QA fixture and triage matrix Fixture project covering all panel-editable element archetypes, plus the QA findings matrix from the design-panel bug campaign. * fix(studio): make canvas selection hit intended elements - honor author pointer-events:none in hit-testing (was selecting invisible overlays) - pause playback before mousedown sampling; fall back to hover selection on null resolve - invalidate committed selection when the active composition changes - double-click keeps selection and defers to multi-candidate click cycling * fix(studio): close remaining selection-layer review findings - hoverSelection fallback now wired at all 3 mousedown call sites (box-click, blocked-drag, plain overlay click) instead of just the overlay path - pointer-events override detection reads computed style, not inline style, so a CSS-class opt-in (not just inline style=) on a descendant is honored - defensively remove the pointer-events override before the group-fallback check too, closing a theoretical gap in the no-elementsFromPoint branch - a click that resolves to nothing (dead-zone / deselect) no longer leaves playback paused if it was already playing * fix(studio-server): child-scoped patch operations with batch abort - PatchOperation gains optional childSelector/childIndex resolved under the matched parent - pre-pass resolves every op target; any miss aborts the batch with matched:false, no partial write - style-decl parsing extracted to sourceStyleMutation to stay under the file-size cap - new ./source-mutation subpath export (mirrors ./finite-mutation) * fix(studio): per-child patch op builders and persist-seam harness - buildTextFieldChildLocator indexes over the parent's full same-tag child list - buildTextFieldChildOperations emits per-field ops for same-shape multi-field edits - SDK cutover declines child-scoped batches (hfId mapping would hit the parent) - persist-seam integration harness drives real client ops through patchElementInHtml * fix(studio): fail closed on unresolved text-field child index buildTextFieldChildLocator guessed a synthetic field's position by counting same-tag "child" fields elsewhere in the array whenever sourceChildIndex was absent. That heuristic is unreachable today (the count-mismatch guard in buildTextFieldChildOperations already refuses add/remove edits before it's reached) but would silently locate the wrong element for a future caller that wires up synthetic-field support without also computing a real sourceChildIndex. Return null instead so the caller falls back to the unsupported-structure path.
64 lines
2.3 KiB
TypeScript
64 lines
2.3 KiB
TypeScript
import {
|
|
buildDomEditStylePatchOperation,
|
|
buildDomEditTextPatchOperation,
|
|
buildTextFieldChildLocator,
|
|
type DomEditTextField,
|
|
} from "../components/editor/domEditing";
|
|
import type { PatchOperation } from "../utils/sourcePatcher";
|
|
|
|
function hasSameKeysInSamePositions(
|
|
originalFields: DomEditTextField[],
|
|
nextFields: DomEditTextField[],
|
|
): boolean {
|
|
return originalFields.every((field, index) => nextFields[index]?.key === field.key);
|
|
}
|
|
|
|
function inlineStyleValue(styles: Record<string, string>, property: string): string | null {
|
|
return Object.prototype.hasOwnProperty.call(styles, property) ? styles[property] : null;
|
|
}
|
|
|
|
function inlineStyleProperties(
|
|
originalStyles: Record<string, string>,
|
|
nextStyles: Record<string, string>,
|
|
): string[] {
|
|
return Array.from(new Set([...Object.keys(originalStyles), ...Object.keys(nextStyles)]));
|
|
}
|
|
|
|
// fallow-ignore-next-line complexity
|
|
export function buildTextFieldChildOperations(
|
|
originalFields: DomEditTextField[],
|
|
nextFields: DomEditTextField[],
|
|
): PatchOperation[] | null {
|
|
if (originalFields.length !== nextFields.length) return null;
|
|
if (!hasSameKeysInSamePositions(originalFields, nextFields)) return null;
|
|
if (nextFields.some((field) => field.source === "text-node")) return null;
|
|
if (nextFields.some((field) => field.source !== "child")) return null;
|
|
if (originalFields.some((field) => field.source !== "child")) return null;
|
|
|
|
const originalByKey = new Map(originalFields.map((field) => [field.key, field]));
|
|
const operations: PatchOperation[] = [];
|
|
|
|
for (const nextField of nextFields) {
|
|
const originalField = originalByKey.get(nextField.key);
|
|
const locator = buildTextFieldChildLocator(originalFields, nextField.key);
|
|
if (!originalField || !locator) return null;
|
|
|
|
if (nextField.value !== originalField.value) {
|
|
operations.push(buildDomEditTextPatchOperation(nextField.value, locator));
|
|
}
|
|
|
|
for (const property of inlineStyleProperties(
|
|
originalField.inlineStyles,
|
|
nextField.inlineStyles,
|
|
)) {
|
|
const originalValue = inlineStyleValue(originalField.inlineStyles, property);
|
|
const nextValue = inlineStyleValue(nextField.inlineStyles, property);
|
|
if (nextValue !== originalValue) {
|
|
operations.push(buildDomEditStylePatchOperation(property, nextValue, locator));
|
|
}
|
|
}
|
|
}
|
|
|
|
return operations;
|
|
}
|