mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-11-15 13:58:20 +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)
59 lines
1.7 KiB
TypeScript
59 lines
1.7 KiB
TypeScript
// fallow-ignore-next-line complexity
|
|
export function parseStyleDecls(style: string): { props: Map<string, string>; order: string[] } {
|
|
const props = new Map<string, string>();
|
|
const order: string[] = [];
|
|
let i = 0;
|
|
while (i < style.length) {
|
|
let depth = 0;
|
|
let inSingle = false;
|
|
let inDouble = false;
|
|
const start = i;
|
|
while (i < style.length) {
|
|
const ch = style[i];
|
|
if (ch === "'" && !inDouble) inSingle = !inSingle;
|
|
else if (ch === '"' && !inSingle) inDouble = !inDouble;
|
|
else if (!inSingle && !inDouble) {
|
|
if (ch === "(") depth++;
|
|
else if (ch === ")") depth = Math.max(0, depth - 1);
|
|
else if (ch === ";" && depth === 0) break;
|
|
}
|
|
i++;
|
|
}
|
|
const decl = style.slice(start, i).trim();
|
|
i++;
|
|
if (!decl) continue;
|
|
const colon = decl.indexOf(":");
|
|
if (colon < 0) continue;
|
|
const key = decl.slice(0, colon).trim();
|
|
const val = decl.slice(colon + 1).trim();
|
|
if (!key) continue;
|
|
if (!props.has(key)) order.push(key);
|
|
props.set(key, val);
|
|
}
|
|
return { props, order };
|
|
}
|
|
|
|
function serializeStyleDecls(props: Map<string, string>, order: string[]): string {
|
|
return order
|
|
.map((k) => `${k}: ${props.get(k) ?? ""}`)
|
|
.filter((d) => d.trim())
|
|
.join("; ");
|
|
}
|
|
|
|
export function patchStyleAttrString(
|
|
style: string,
|
|
property: string,
|
|
value: string | null,
|
|
): string {
|
|
const { props, order } = parseStyleDecls(style);
|
|
if (value === null) {
|
|
props.delete(property);
|
|
const idx = order.indexOf(property);
|
|
if (idx >= 0) order.splice(idx, 1);
|
|
} else {
|
|
if (!props.has(property)) order.push(property);
|
|
props.set(property, value);
|
|
}
|
|
return serializeStyleDecls(props, order);
|
|
}
|