Files
hyperframes/packages/studio-server/src/helpers/sourceStyleMutation.ts
T
Miguel Ángel 23f67da5e2 fix(studio-server): child-scoped patch operations with batch abort (#1908)
* 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)
2026-07-03 17:53:17 -07:00

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);
}