@hyperframes/studio-server 0.7.27 → 0.7.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
interface SourceMutationTarget {
|
|
2
|
+
id?: string | null;
|
|
3
|
+
hfId?: string;
|
|
4
|
+
selector?: string;
|
|
5
|
+
selectorIndex?: number;
|
|
6
|
+
}
|
|
7
|
+
declare function removeElementFromHtml(source: string, target: SourceMutationTarget): string;
|
|
8
|
+
declare function isHTMLElement(el: Node): el is HTMLElement;
|
|
9
|
+
interface PatchOperation {
|
|
10
|
+
type: "inline-style" | "attribute" | "html-attribute" | "text-content";
|
|
11
|
+
property: string;
|
|
12
|
+
value: string | null;
|
|
13
|
+
childSelector?: string;
|
|
14
|
+
childIndex?: number;
|
|
15
|
+
}
|
|
16
|
+
declare function patchElementInHtml(source: string, target: SourceMutationTarget, operations: PatchOperation[]): {
|
|
17
|
+
html: string;
|
|
18
|
+
matched: boolean;
|
|
19
|
+
};
|
|
20
|
+
declare function probeElementInSource(source: string, target: SourceMutationTarget): boolean;
|
|
21
|
+
interface SplitElementResult {
|
|
22
|
+
html: string;
|
|
23
|
+
matched: boolean;
|
|
24
|
+
newId: string | null;
|
|
25
|
+
}
|
|
26
|
+
declare function splitElementInHtml(source: string, target: SourceMutationTarget, splitTime: number, newId: string, fallbackTiming?: {
|
|
27
|
+
start: number;
|
|
28
|
+
duration: number;
|
|
29
|
+
}): SplitElementResult;
|
|
30
|
+
interface WrapElementsResult {
|
|
31
|
+
html: string;
|
|
32
|
+
matched: boolean;
|
|
33
|
+
groupId: string | null;
|
|
34
|
+
error?: string;
|
|
35
|
+
}
|
|
36
|
+
interface UnwrapElementsResult {
|
|
37
|
+
html: string;
|
|
38
|
+
unwrapped: boolean;
|
|
39
|
+
/** The unwrapped wrapper's id, so callers can strip GSAP that targeted it
|
|
40
|
+
* (the wrapper is gone; a leftover `gsap.set("#id")` would throw at runtime). */
|
|
41
|
+
unwrappedGroupId?: string;
|
|
42
|
+
/** Members (id'd children) with their absolute layout centres (post un-rebase),
|
|
43
|
+
* so the caller can BAKE the group's GSAP transform into each member before
|
|
44
|
+
* stripping it — otherwise the group's moves are lost on ungroup. */
|
|
45
|
+
members?: Array<{
|
|
46
|
+
id: string;
|
|
47
|
+
cx: number;
|
|
48
|
+
cy: number;
|
|
49
|
+
}>;
|
|
50
|
+
/** The wrapper's layout centre — the pivot for baking the group's rotation/scale. */
|
|
51
|
+
groupCenter?: {
|
|
52
|
+
cx: number;
|
|
53
|
+
cy: number;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
interface ElementRebase {
|
|
57
|
+
target: SourceMutationTarget;
|
|
58
|
+
left: number;
|
|
59
|
+
top: number;
|
|
60
|
+
}
|
|
61
|
+
declare function wrapElementsInHtml(source: string, targets: SourceMutationTarget[], groupId: string, bbox: {
|
|
62
|
+
left: number;
|
|
63
|
+
top: number;
|
|
64
|
+
width: number;
|
|
65
|
+
height: number;
|
|
66
|
+
}, rebases: ElementRebase[]): WrapElementsResult;
|
|
67
|
+
declare function unwrapElementsFromHtml(source: string, groupTarget: SourceMutationTarget): UnwrapElementsResult;
|
|
68
|
+
|
|
69
|
+
export { type ElementRebase, type PatchOperation, type SourceMutationTarget, type SplitElementResult, type UnwrapElementsResult, type WrapElementsResult, isHTMLElement, patchElementInHtml, probeElementInSource, removeElementFromHtml, splitElementInHtml, unwrapElementsFromHtml, wrapElementsInHtml };
|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
// src/helpers/sourceMutation.ts
|
|
2
|
+
import { parseHTML } from "linkedom";
|
|
3
|
+
import postcss from "postcss";
|
|
4
|
+
import selectorParser from "postcss-selector-parser";
|
|
5
|
+
import { isAllowedHtmlAttribute, isSafeAttributeValue } from "@hyperframes/core/html-attr-safety";
|
|
6
|
+
|
|
7
|
+
// src/helpers/sourceStyleMutation.ts
|
|
8
|
+
function parseStyleDecls(style) {
|
|
9
|
+
const props = /* @__PURE__ */ new Map();
|
|
10
|
+
const order = [];
|
|
11
|
+
let i = 0;
|
|
12
|
+
while (i < style.length) {
|
|
13
|
+
let depth = 0;
|
|
14
|
+
let inSingle = false;
|
|
15
|
+
let inDouble = false;
|
|
16
|
+
const start = i;
|
|
17
|
+
while (i < style.length) {
|
|
18
|
+
const ch = style[i];
|
|
19
|
+
if (ch === "'" && !inDouble) inSingle = !inSingle;
|
|
20
|
+
else if (ch === '"' && !inSingle) inDouble = !inDouble;
|
|
21
|
+
else if (!inSingle && !inDouble) {
|
|
22
|
+
if (ch === "(") depth++;
|
|
23
|
+
else if (ch === ")") depth = Math.max(0, depth - 1);
|
|
24
|
+
else if (ch === ";" && depth === 0) break;
|
|
25
|
+
}
|
|
26
|
+
i++;
|
|
27
|
+
}
|
|
28
|
+
const decl = style.slice(start, i).trim();
|
|
29
|
+
i++;
|
|
30
|
+
if (!decl) continue;
|
|
31
|
+
const colon = decl.indexOf(":");
|
|
32
|
+
if (colon < 0) continue;
|
|
33
|
+
const key = decl.slice(0, colon).trim();
|
|
34
|
+
const val = decl.slice(colon + 1).trim();
|
|
35
|
+
if (!key) continue;
|
|
36
|
+
if (!props.has(key)) order.push(key);
|
|
37
|
+
props.set(key, val);
|
|
38
|
+
}
|
|
39
|
+
return { props, order };
|
|
40
|
+
}
|
|
41
|
+
function serializeStyleDecls(props, order) {
|
|
42
|
+
return order.map((k) => `${k}: ${props.get(k) ?? ""}`).filter((d) => d.trim()).join("; ");
|
|
43
|
+
}
|
|
44
|
+
function patchStyleAttrString(style, property, value) {
|
|
45
|
+
const { props, order } = parseStyleDecls(style);
|
|
46
|
+
if (value === null) {
|
|
47
|
+
props.delete(property);
|
|
48
|
+
const idx = order.indexOf(property);
|
|
49
|
+
if (idx >= 0) order.splice(idx, 1);
|
|
50
|
+
} else {
|
|
51
|
+
if (!props.has(property)) order.push(property);
|
|
52
|
+
props.set(property, value);
|
|
53
|
+
}
|
|
54
|
+
return serializeStyleDecls(props, order);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// src/helpers/sourceMutation.ts
|
|
58
|
+
function parseSourceDocument(source) {
|
|
59
|
+
const hasDocumentShell = /<!doctype|<html[\s>]/i.test(source);
|
|
60
|
+
if (hasDocumentShell) {
|
|
61
|
+
return { document: parseHTML(source).document, wrappedFragment: false };
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
document: parseHTML(`<!DOCTYPE html><html><head></head><body>${source}</body></html>`).document,
|
|
65
|
+
wrappedFragment: true
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function duplicateCssRulesForId(document, originalId, newId) {
|
|
69
|
+
const idToken = `#${originalId}`;
|
|
70
|
+
const transform = selectorParser((selectors) => {
|
|
71
|
+
selectors.walkIds((node) => {
|
|
72
|
+
if (node.value === originalId) node.value = newId;
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
for (const styleEl of document.querySelectorAll("style")) {
|
|
76
|
+
const css = styleEl.textContent ?? "";
|
|
77
|
+
let root;
|
|
78
|
+
try {
|
|
79
|
+
root = postcss.parse(css);
|
|
80
|
+
} catch {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const clones = [];
|
|
84
|
+
root.walkRules((rule) => {
|
|
85
|
+
if (!rule.selector.includes(idToken)) return;
|
|
86
|
+
const newSelector = transform.processSync(rule.selector);
|
|
87
|
+
if (newSelector === rule.selector) return;
|
|
88
|
+
const clone = rule.clone({ selector: newSelector });
|
|
89
|
+
clones.push(clone);
|
|
90
|
+
});
|
|
91
|
+
if (clones.length > 0) {
|
|
92
|
+
for (const c of clones) root.append(c);
|
|
93
|
+
styleEl.textContent = root.toString();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function querySelectorAllWithTemplates(root, selector) {
|
|
98
|
+
const matches = Array.from(root.querySelectorAll(selector));
|
|
99
|
+
if (matches.length > 0) return matches;
|
|
100
|
+
const templates = Array.from(root.querySelectorAll("template"));
|
|
101
|
+
for (const tmpl of templates) {
|
|
102
|
+
const inner = tmpl.querySelectorAll(selector);
|
|
103
|
+
if (inner.length > 0) return Array.from(inner);
|
|
104
|
+
}
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
function escapeCssAttrValue(value) {
|
|
108
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
109
|
+
}
|
|
110
|
+
function findByHfId(document, hfId) {
|
|
111
|
+
try {
|
|
112
|
+
const matches = querySelectorAllWithTemplates(
|
|
113
|
+
document,
|
|
114
|
+
`[data-hf-id="${escapeCssAttrValue(hfId)}"]`
|
|
115
|
+
);
|
|
116
|
+
if (matches.length > 1) {
|
|
117
|
+
console.warn(
|
|
118
|
+
`sourceMutation: data-hf-id "${hfId}" matched ${matches.length} elements; using the first. ids must be unique per document.`
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
return matches[0] ?? null;
|
|
122
|
+
} catch {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function findTargetElement(document, target) {
|
|
127
|
+
if (target.hfId) {
|
|
128
|
+
const el = findByHfId(document, target.hfId);
|
|
129
|
+
if (el) return el;
|
|
130
|
+
}
|
|
131
|
+
if (target.id) {
|
|
132
|
+
const byId = document.getElementById(target.id);
|
|
133
|
+
if (byId) return byId;
|
|
134
|
+
}
|
|
135
|
+
if (!target.selector) return null;
|
|
136
|
+
try {
|
|
137
|
+
const matches = querySelectorAllWithTemplates(document, target.selector);
|
|
138
|
+
return matches[target.selectorIndex ?? 0] ?? null;
|
|
139
|
+
} catch {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function removeElementFromHtml(source, target) {
|
|
144
|
+
const { document, wrappedFragment } = parseSourceDocument(source);
|
|
145
|
+
const element = findTargetElement(document, target);
|
|
146
|
+
if (!element) return source;
|
|
147
|
+
element.remove();
|
|
148
|
+
return wrappedFragment ? document.body.innerHTML || "" : document.toString();
|
|
149
|
+
}
|
|
150
|
+
function isHTMLElement(el) {
|
|
151
|
+
const HTMLEl = el.ownerDocument?.defaultView?.HTMLElement;
|
|
152
|
+
return HTMLEl ? el instanceof HTMLEl : el.nodeType === 1 && "style" in el;
|
|
153
|
+
}
|
|
154
|
+
function resolveOperationTarget(parent, op) {
|
|
155
|
+
if (op.childSelector === void 0) return parent;
|
|
156
|
+
try {
|
|
157
|
+
const child = parent.querySelectorAll(op.childSelector)[op.childIndex ?? 0] ?? null;
|
|
158
|
+
return child && isHTMLElement(child) ? child : null;
|
|
159
|
+
} catch {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function patchElementInHtml(source, target, operations) {
|
|
164
|
+
const { document, wrappedFragment } = parseSourceDocument(source);
|
|
165
|
+
const el = findTargetElement(document, target);
|
|
166
|
+
if (!el || !isHTMLElement(el)) return { html: source, matched: false };
|
|
167
|
+
const htmlEl = el;
|
|
168
|
+
const resolved = [];
|
|
169
|
+
for (const op of operations) {
|
|
170
|
+
const opTarget = resolveOperationTarget(htmlEl, op);
|
|
171
|
+
if (!opTarget) return { html: source, matched: false };
|
|
172
|
+
resolved.push({ op, target: opTarget });
|
|
173
|
+
}
|
|
174
|
+
for (const { op, target: opTarget } of resolved) {
|
|
175
|
+
switch (op.type) {
|
|
176
|
+
case "inline-style":
|
|
177
|
+
{
|
|
178
|
+
const raw = opTarget.getAttribute("style") ?? "";
|
|
179
|
+
const patched = patchStyleAttrString(raw, op.property, op.value);
|
|
180
|
+
opTarget.setAttribute("style", patched);
|
|
181
|
+
}
|
|
182
|
+
break;
|
|
183
|
+
case "attribute":
|
|
184
|
+
{
|
|
185
|
+
const fullAttr = op.property.startsWith("data-") ? op.property : `data-${op.property}`;
|
|
186
|
+
if (op.value != null) {
|
|
187
|
+
opTarget.setAttribute(fullAttr, op.value);
|
|
188
|
+
} else {
|
|
189
|
+
opTarget.removeAttribute(fullAttr);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
break;
|
|
193
|
+
case "html-attribute":
|
|
194
|
+
if (!isAllowedHtmlAttribute(op.property)) break;
|
|
195
|
+
if (op.value != null) {
|
|
196
|
+
if (!isSafeAttributeValue(op.property, op.value)) break;
|
|
197
|
+
opTarget.setAttribute(op.property, op.value);
|
|
198
|
+
} else {
|
|
199
|
+
opTarget.removeAttribute(op.property);
|
|
200
|
+
}
|
|
201
|
+
break;
|
|
202
|
+
case "text-content":
|
|
203
|
+
if (op.value != null) {
|
|
204
|
+
const inner = opTarget.children.length === 1 ? opTarget.firstElementChild : null;
|
|
205
|
+
const textTarget = inner && isHTMLElement(inner) ? inner : opTarget;
|
|
206
|
+
textTarget.textContent = op.value;
|
|
207
|
+
}
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return {
|
|
212
|
+
html: wrappedFragment ? document.body.innerHTML || "" : document.toString(),
|
|
213
|
+
matched: true
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
function probeElementInSource(source, target) {
|
|
217
|
+
if (!target.id && !target.hfId && !target.selector) return false;
|
|
218
|
+
const { document } = parseSourceDocument(source);
|
|
219
|
+
const el = findTargetElement(document, target);
|
|
220
|
+
return el != null && isHTMLElement(el);
|
|
221
|
+
}
|
|
222
|
+
function resolveElementTiming(el) {
|
|
223
|
+
const start = parseFloat(el.getAttribute("data-start") ?? "0") || 0;
|
|
224
|
+
const usesDataEnd = el.hasAttribute("data-end");
|
|
225
|
+
const duration = usesDataEnd ? parseFloat(el.getAttribute("data-end") ?? "") - start || 0 : parseFloat(el.getAttribute("data-duration") ?? "0") || 0;
|
|
226
|
+
return { start, duration, usesDataEnd };
|
|
227
|
+
}
|
|
228
|
+
function setElementDuration(el, start, duration, usesDataEnd) {
|
|
229
|
+
if (usesDataEnd) {
|
|
230
|
+
const endTime = String(Math.round((start + duration) * 1e3) / 1e3);
|
|
231
|
+
el.setAttribute("data-end", endTime);
|
|
232
|
+
el.removeAttribute("data-duration");
|
|
233
|
+
} else {
|
|
234
|
+
el.setAttribute("data-duration", String(Math.round(duration * 1e3) / 1e3));
|
|
235
|
+
el.removeAttribute("data-end");
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
function splitElementInHtml(source, target, splitTime, newId, fallbackTiming) {
|
|
239
|
+
const { document, wrappedFragment } = parseSourceDocument(source);
|
|
240
|
+
const el = findTargetElement(document, target);
|
|
241
|
+
if (!el || !isHTMLElement(el)) return { html: source, matched: false, newId: null };
|
|
242
|
+
const timing = resolveElementTiming(el);
|
|
243
|
+
const { usesDataEnd } = timing;
|
|
244
|
+
let { start, duration } = timing;
|
|
245
|
+
if (duration <= 0 && fallbackTiming && fallbackTiming.duration > 0) {
|
|
246
|
+
start = fallbackTiming.start;
|
|
247
|
+
duration = fallbackTiming.duration;
|
|
248
|
+
}
|
|
249
|
+
if (duration <= 0 || splitTime <= start || splitTime >= start + duration) {
|
|
250
|
+
return { html: source, matched: false, newId: null };
|
|
251
|
+
}
|
|
252
|
+
if (document.getElementById(newId)) {
|
|
253
|
+
let suffix = 2;
|
|
254
|
+
const base = newId;
|
|
255
|
+
while (document.getElementById(newId)) {
|
|
256
|
+
newId = `${base}-${suffix++}`;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
const firstDuration = splitTime - start;
|
|
260
|
+
const secondDuration = duration - firstDuration;
|
|
261
|
+
const clone = el.cloneNode(true);
|
|
262
|
+
if (!isHTMLElement(clone)) return { html: source, matched: false, newId: null };
|
|
263
|
+
clone.setAttribute("id", newId);
|
|
264
|
+
clone.removeAttribute("data-hf-id");
|
|
265
|
+
for (const node of clone.querySelectorAll("[data-hf-id]")) node.removeAttribute("data-hf-id");
|
|
266
|
+
clone.setAttribute("data-start", String(Math.round(splitTime * 1e3) / 1e3));
|
|
267
|
+
setElementDuration(clone, splitTime, secondDuration, usesDataEnd);
|
|
268
|
+
const playbackStartAttr = el.hasAttribute("data-playback-start") ? "data-playback-start" : el.hasAttribute("data-media-start") ? "data-media-start" : null;
|
|
269
|
+
if (playbackStartAttr) {
|
|
270
|
+
const currentTrim = parseFloat(el.getAttribute(playbackStartAttr) ?? "0") || 0;
|
|
271
|
+
const rateRaw = parseFloat(el.getAttribute("data-playback-rate") ?? "");
|
|
272
|
+
const rate = Number.isFinite(rateRaw) ? rateRaw : 1;
|
|
273
|
+
clone.setAttribute(
|
|
274
|
+
playbackStartAttr,
|
|
275
|
+
String(Math.round((currentTrim + firstDuration * rate) * 1e3) / 1e3)
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
const originalId = el.getAttribute("id");
|
|
279
|
+
if (originalId) {
|
|
280
|
+
duplicateCssRulesForId(document, originalId, newId);
|
|
281
|
+
}
|
|
282
|
+
el.setAttribute("data-start", String(Math.round(start * 1e3) / 1e3));
|
|
283
|
+
setElementDuration(el, start, firstDuration, usesDataEnd);
|
|
284
|
+
if (el.nextSibling) {
|
|
285
|
+
el.parentElement.insertBefore(clone, el.nextSibling);
|
|
286
|
+
} else {
|
|
287
|
+
el.parentElement.appendChild(clone);
|
|
288
|
+
}
|
|
289
|
+
return {
|
|
290
|
+
html: wrappedFragment ? document.body.innerHTML || "" : document.toString(),
|
|
291
|
+
matched: true,
|
|
292
|
+
newId
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
function getInlineStylePx(el, property) {
|
|
296
|
+
const style = (isHTMLElement(el) ? el.getAttribute("style") : null) ?? "";
|
|
297
|
+
const { props } = parseStyleDecls(style);
|
|
298
|
+
const raw = props.get(property);
|
|
299
|
+
if (!raw) return 0;
|
|
300
|
+
const n = parseFloat(raw);
|
|
301
|
+
return Number.isFinite(n) ? n : 0;
|
|
302
|
+
}
|
|
303
|
+
function setInlineLeftTop(el, left, top) {
|
|
304
|
+
let style = el.getAttribute("style") ?? "";
|
|
305
|
+
style = patchStyleAttrString(style, "left", `${left}px`);
|
|
306
|
+
style = patchStyleAttrString(style, "top", `${top}px`);
|
|
307
|
+
el.setAttribute("style", style);
|
|
308
|
+
}
|
|
309
|
+
function uniqueGroupDomId(document, groupId) {
|
|
310
|
+
const base = groupId.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "group";
|
|
311
|
+
let id = base;
|
|
312
|
+
let n = 2;
|
|
313
|
+
while (document.getElementById(id)) {
|
|
314
|
+
id = `${base}-${n}`;
|
|
315
|
+
n += 1;
|
|
316
|
+
}
|
|
317
|
+
return id;
|
|
318
|
+
}
|
|
319
|
+
function wrapElementsInHtml(source, targets, groupId, bbox, rebases) {
|
|
320
|
+
const { document, wrappedFragment } = parseSourceDocument(source);
|
|
321
|
+
if (targets.length === 0) {
|
|
322
|
+
return { html: source, matched: false, groupId: null, error: "no targets" };
|
|
323
|
+
}
|
|
324
|
+
const els = [];
|
|
325
|
+
const seen = /* @__PURE__ */ new Set();
|
|
326
|
+
for (const target of targets) {
|
|
327
|
+
const el = findTargetElement(document, target);
|
|
328
|
+
if (!el || !isHTMLElement(el) || seen.has(el)) continue;
|
|
329
|
+
seen.add(el);
|
|
330
|
+
els.push(el);
|
|
331
|
+
}
|
|
332
|
+
if (els.length === 0) {
|
|
333
|
+
return { html: source, matched: false, groupId: null, error: "no targets matched" };
|
|
334
|
+
}
|
|
335
|
+
const parent = els[0]?.parentElement;
|
|
336
|
+
if (!parent || els.some((el) => el.parentElement !== parent)) {
|
|
337
|
+
return {
|
|
338
|
+
html: source,
|
|
339
|
+
matched: false,
|
|
340
|
+
groupId: null,
|
|
341
|
+
error: "grouped elements must share a single parent"
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
const memberSet = new Set(els);
|
|
345
|
+
const ordered = Array.from(parent.children).filter((c) => memberSet.has(c));
|
|
346
|
+
const rebaseByEl = /* @__PURE__ */ new Map();
|
|
347
|
+
for (const rebase of rebases) {
|
|
348
|
+
const el = findTargetElement(document, rebase.target);
|
|
349
|
+
if (el) rebaseByEl.set(el, { left: rebase.left, top: rebase.top });
|
|
350
|
+
}
|
|
351
|
+
const wrapper = document.createElement("div");
|
|
352
|
+
wrapper.setAttribute("data-hf-group", groupId);
|
|
353
|
+
wrapper.setAttribute("id", uniqueGroupDomId(document, groupId));
|
|
354
|
+
const memberZIndexes = ordered.map(
|
|
355
|
+
(el) => Number.parseInt(
|
|
356
|
+
parseStyleDecls(el.getAttribute("style") ?? "").props.get("z-index") ?? "",
|
|
357
|
+
10
|
|
358
|
+
)
|
|
359
|
+
).filter((z) => Number.isFinite(z));
|
|
360
|
+
const maxZ = memberZIndexes.length > 0 ? Math.max(...memberZIndexes) : null;
|
|
361
|
+
wrapper.setAttribute(
|
|
362
|
+
"style",
|
|
363
|
+
`position: absolute; left: ${bbox.left}px; top: ${bbox.top}px; width: ${bbox.width}px; height: ${bbox.height}px` + (maxZ !== null ? `; z-index: ${maxZ}` : "")
|
|
364
|
+
);
|
|
365
|
+
parent.insertBefore(wrapper, ordered[ordered.length - 1] ?? null);
|
|
366
|
+
for (const el of ordered) {
|
|
367
|
+
const rebase = rebaseByEl.get(el);
|
|
368
|
+
if (rebase) setInlineLeftTop(el, rebase.left, rebase.top);
|
|
369
|
+
wrapper.appendChild(el);
|
|
370
|
+
}
|
|
371
|
+
return {
|
|
372
|
+
html: wrappedFragment ? document.body.innerHTML || "" : document.toString(),
|
|
373
|
+
matched: true,
|
|
374
|
+
groupId
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
function unwrapElementsFromHtml(source, groupTarget) {
|
|
378
|
+
const { document, wrappedFragment } = parseSourceDocument(source);
|
|
379
|
+
const group = findTargetElement(document, groupTarget);
|
|
380
|
+
if (!group || !isHTMLElement(group)) return { html: source, unwrapped: false };
|
|
381
|
+
if (!group.hasAttribute("data-hf-group")) return { html: source, unwrapped: false };
|
|
382
|
+
const parent = group.parentElement;
|
|
383
|
+
if (!parent) return { html: source, unwrapped: false };
|
|
384
|
+
const wLeft = getInlineStylePx(group, "left");
|
|
385
|
+
const wTop = getInlineStylePx(group, "top");
|
|
386
|
+
const groupCenter = {
|
|
387
|
+
cx: wLeft + getInlineStylePx(group, "width") / 2,
|
|
388
|
+
cy: wTop + getInlineStylePx(group, "height") / 2
|
|
389
|
+
};
|
|
390
|
+
const members = [];
|
|
391
|
+
for (const child of Array.from(group.children)) {
|
|
392
|
+
if (isHTMLElement(child)) {
|
|
393
|
+
const newLeft = getInlineStylePx(child, "left") + wLeft;
|
|
394
|
+
const newTop = getInlineStylePx(child, "top") + wTop;
|
|
395
|
+
setInlineLeftTop(child, newLeft, newTop);
|
|
396
|
+
if (child.id) {
|
|
397
|
+
members.push({
|
|
398
|
+
id: child.id,
|
|
399
|
+
cx: newLeft + getInlineStylePx(child, "width") / 2,
|
|
400
|
+
cy: newTop + getInlineStylePx(child, "height") / 2
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
parent.insertBefore(child, group);
|
|
405
|
+
}
|
|
406
|
+
const groupId = group.id || void 0;
|
|
407
|
+
group.remove();
|
|
408
|
+
return {
|
|
409
|
+
html: wrappedFragment ? document.body.innerHTML || "" : document.toString(),
|
|
410
|
+
unwrapped: true,
|
|
411
|
+
unwrappedGroupId: groupId,
|
|
412
|
+
members,
|
|
413
|
+
groupCenter
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
export {
|
|
417
|
+
isHTMLElement,
|
|
418
|
+
patchElementInHtml,
|
|
419
|
+
probeElementInSource,
|
|
420
|
+
removeElementFromHtml,
|
|
421
|
+
splitElementInHtml,
|
|
422
|
+
unwrapElementsFromHtml,
|
|
423
|
+
wrapElementsInHtml
|
|
424
|
+
};
|
|
425
|
+
//# sourceMappingURL=sourceMutation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/helpers/sourceMutation.ts","../../src/helpers/sourceStyleMutation.ts"],"sourcesContent":["import { parseHTML } from \"linkedom\";\nimport postcss from \"postcss\";\nimport selectorParser from \"postcss-selector-parser\";\nimport { isAllowedHtmlAttribute, isSafeAttributeValue } from \"@hyperframes/core/html-attr-safety\";\nimport { parseStyleDecls, patchStyleAttrString } from \"./sourceStyleMutation.js\";\n\nexport interface SourceMutationTarget {\n id?: string | null;\n hfId?: string;\n selector?: string;\n selectorIndex?: number;\n}\n\nfunction parseSourceDocument(source: string): { document: Document; wrappedFragment: boolean } {\n const hasDocumentShell = /<!doctype|<html[\\s>]/i.test(source);\n if (hasDocumentShell) {\n return { document: parseHTML(source).document, wrappedFragment: false };\n }\n return {\n document: parseHTML(`<!DOCTYPE html><html><head></head><body>${source}</body></html>`).document,\n wrappedFragment: true,\n };\n}\n\nfunction duplicateCssRulesForId(document: Document, originalId: string, newId: string): void {\n const idToken = `#${originalId}`;\n const transform = selectorParser((selectors) => {\n selectors.walkIds((node) => {\n if (node.value === originalId) node.value = newId;\n });\n });\n for (const styleEl of document.querySelectorAll(\"style\")) {\n const css = styleEl.textContent ?? \"\";\n let root: postcss.Root;\n try {\n root = postcss.parse(css);\n } catch {\n continue;\n }\n const clones: postcss.Rule[] = [];\n root.walkRules((rule) => {\n if (!rule.selector.includes(idToken)) return;\n const newSelector = transform.processSync(rule.selector);\n if (newSelector === rule.selector) return;\n const clone = rule.clone({ selector: newSelector });\n clones.push(clone);\n });\n if (clones.length > 0) {\n for (const c of clones) root.append(c);\n styleEl.textContent = root.toString();\n }\n }\n}\n\nfunction querySelectorAllWithTemplates(root: Document | Element, selector: string): Element[] {\n const matches = Array.from(root.querySelectorAll(selector));\n if (matches.length > 0) return matches;\n // querySelectorAll doesn't traverse <template> content in linkedom.\n // Search directly on each template element (NOT .content — removing from\n // .content's DocumentFragment doesn't update the serialized output).\n const templates = Array.from(root.querySelectorAll(\"template\"));\n for (const tmpl of templates) {\n const inner = tmpl.querySelectorAll(selector);\n if (inner.length > 0) return Array.from(inner);\n }\n return [];\n}\n\n// Prevent CSS attribute-selector injection via a crafted hfId: escape\n// backslashes first, then double-quotes. Keeps a malformed/hostile value from\n// breaking out of the `[data-hf-id=\"…\"]` selector once callers beyond the\n// internal mint contract (R2+ user flows) pass values here.\nfunction escapeCssAttrValue(value: string): string {\n return value.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"');\n}\n\nfunction findByHfId(document: Document, hfId: string): Element | null {\n try {\n const matches = querySelectorAllWithTemplates(\n document,\n `[data-hf-id=\"${escapeCssAttrValue(hfId)}\"]`,\n );\n if (matches.length > 1) {\n // The mint contract guarantees uniqueness; a duplicate means upstream\n // id drift. Don't silently patch an arbitrary one — surface it.\n // eslint-disable-next-line no-console\n console.warn(\n `sourceMutation: data-hf-id \"${hfId}\" matched ${matches.length} elements; using the first. ids must be unique per document.`,\n );\n }\n return matches[0] ?? null;\n } catch {\n // Malformed selector despite escaping — let the caller fall back.\n return null;\n }\n}\n\nfunction findTargetElement(document: Document, target: SourceMutationTarget): Element | null {\n if (target.hfId) {\n const el = findByHfId(document, target.hfId);\n if (el) return el;\n }\n\n if (target.id) {\n const byId = document.getElementById(target.id);\n if (byId) return byId;\n }\n\n if (!target.selector) return null;\n try {\n const matches = querySelectorAllWithTemplates(document, target.selector);\n return matches[target.selectorIndex ?? 0] ?? null;\n } catch {\n return null;\n }\n}\n\nexport function removeElementFromHtml(source: string, target: SourceMutationTarget): string {\n const { document, wrappedFragment } = parseSourceDocument(source);\n const element = findTargetElement(document, target);\n if (!element) return source;\n\n element.remove();\n return wrappedFragment ? document.body.innerHTML || \"\" : document.toString();\n}\n\nexport function isHTMLElement(el: Node): el is HTMLElement {\n const HTMLEl = el.ownerDocument?.defaultView?.HTMLElement;\n return HTMLEl ? el instanceof HTMLEl : el.nodeType === 1 && \"style\" in el;\n}\n\nexport interface PatchOperation {\n type: \"inline-style\" | \"attribute\" | \"html-attribute\" | \"text-content\";\n property: string;\n value: string | null;\n childSelector?: string;\n childIndex?: number;\n}\n\ninterface ResolvedPatchOperation {\n op: PatchOperation;\n target: HTMLElement;\n}\n\nfunction resolveOperationTarget(parent: HTMLElement, op: PatchOperation): HTMLElement | null {\n if (op.childSelector === undefined) return parent;\n try {\n const child = parent.querySelectorAll(op.childSelector)[op.childIndex ?? 0] ?? null;\n return child && isHTMLElement(child) ? child : null;\n } catch {\n return null;\n }\n}\n\n// fallow-ignore-next-line complexity\nexport function patchElementInHtml(\n source: string,\n target: SourceMutationTarget,\n operations: PatchOperation[],\n): { html: string; matched: boolean } {\n const { document, wrappedFragment } = parseSourceDocument(source);\n const el = findTargetElement(document, target);\n if (!el || !isHTMLElement(el)) return { html: source, matched: false };\n const htmlEl = el;\n\n const resolved: ResolvedPatchOperation[] = [];\n for (const op of operations) {\n const opTarget = resolveOperationTarget(htmlEl, op);\n if (!opTarget) return { html: source, matched: false };\n resolved.push({ op, target: opTarget });\n }\n\n for (const { op, target: opTarget } of resolved) {\n switch (op.type) {\n case \"inline-style\":\n // linkedom's CSSStyleDeclaration does not support CSS custom properties\n // (--foo) or newer individual transform properties (translate, rotate,\n // scale) via style.setProperty(). Manipulate the style attribute string\n // directly so all property names survive the round-trip.\n {\n const raw = opTarget.getAttribute(\"style\") ?? \"\";\n const patched = patchStyleAttrString(raw, op.property, op.value);\n opTarget.setAttribute(\"style\", patched);\n }\n break;\n case \"attribute\":\n {\n const fullAttr = op.property.startsWith(\"data-\") ? op.property : `data-${op.property}`;\n if (op.value != null) {\n opTarget.setAttribute(fullAttr, op.value);\n } else {\n opTarget.removeAttribute(fullAttr);\n }\n }\n break;\n case \"html-attribute\":\n if (!isAllowedHtmlAttribute(op.property)) break;\n if (op.value != null) {\n if (!isSafeAttributeValue(op.property, op.value)) break;\n opTarget.setAttribute(op.property, op.value);\n } else {\n opTarget.removeAttribute(op.property);\n }\n break;\n case \"text-content\":\n if (op.value != null) {\n const inner = opTarget.children.length === 1 ? opTarget.firstElementChild : null;\n const textTarget = inner && isHTMLElement(inner) ? inner : opTarget;\n textTarget.textContent = op.value;\n }\n break;\n }\n }\n\n return {\n html: wrappedFragment ? document.body.innerHTML || \"\" : document.toString(),\n matched: true,\n };\n}\n\nexport function probeElementInSource(source: string, target: SourceMutationTarget): boolean {\n if (!target.id && !target.hfId && !target.selector) return false;\n const { document } = parseSourceDocument(source);\n const el = findTargetElement(document, target);\n return el != null && isHTMLElement(el);\n}\n\nexport interface SplitElementResult {\n html: string;\n matched: boolean;\n newId: string | null;\n}\n\nfunction resolveElementTiming(el: Element): {\n start: number;\n duration: number;\n usesDataEnd: boolean;\n} {\n const start = parseFloat(el.getAttribute(\"data-start\") ?? \"0\") || 0;\n const usesDataEnd = el.hasAttribute(\"data-end\");\n const duration = usesDataEnd\n ? parseFloat(el.getAttribute(\"data-end\") ?? \"\") - start || 0\n : parseFloat(el.getAttribute(\"data-duration\") ?? \"0\") || 0;\n return { start, duration, usesDataEnd };\n}\n\nfunction setElementDuration(\n el: Element,\n start: number,\n duration: number,\n usesDataEnd: boolean,\n): void {\n if (usesDataEnd) {\n const endTime = String(Math.round((start + duration) * 1000) / 1000);\n el.setAttribute(\"data-end\", endTime);\n el.removeAttribute(\"data-duration\");\n } else {\n el.setAttribute(\"data-duration\", String(Math.round(duration * 1000) / 1000));\n el.removeAttribute(\"data-end\");\n }\n}\n\n// fallow-ignore-next-line complexity\nexport function splitElementInHtml(\n source: string,\n target: SourceMutationTarget,\n splitTime: number,\n newId: string,\n fallbackTiming?: { start: number; duration: number },\n): SplitElementResult {\n const { document, wrappedFragment } = parseSourceDocument(source);\n const el = findTargetElement(document, target);\n if (!el || !isHTMLElement(el)) return { html: source, matched: false, newId: null };\n\n const timing = resolveElementTiming(el);\n const { usesDataEnd } = timing;\n let { start, duration } = timing;\n // GSAP-animated elements carry their timing in the script, not in data-* attrs,\n // so the source has no authored duration. Fall back to the store's (GSAP-derived)\n // range — the runtime windows visibility off data-start/data-duration regardless\n // of class, so stamping both halves below makes each half show only in its window.\n if (duration <= 0 && fallbackTiming && fallbackTiming.duration > 0) {\n start = fallbackTiming.start;\n duration = fallbackTiming.duration;\n }\n if (duration <= 0 || splitTime <= start || splitTime >= start + duration) {\n return { html: source, matched: false, newId: null };\n }\n\n if (document.getElementById(newId)) {\n let suffix = 2;\n const base = newId;\n while (document.getElementById(newId)) {\n newId = `${base}-${suffix++}`;\n }\n }\n\n const firstDuration = splitTime - start;\n const secondDuration = duration - firstDuration;\n\n const clone = el.cloneNode(true);\n if (!isHTMLElement(clone)) return { html: source, matched: false, newId: null };\n clone.setAttribute(\"id\", newId);\n clone.removeAttribute(\"data-hf-id\");\n // Descendants carry their own data-hf-id; leaving them duplicates the id of\n // every nested node (e.g. an inner <span>), so strip them on the clone too.\n for (const node of clone.querySelectorAll(\"[data-hf-id]\")) node.removeAttribute(\"data-hf-id\");\n clone.setAttribute(\"data-start\", String(Math.round(splitTime * 1000) / 1000));\n setElementDuration(clone, splitTime, secondDuration, usesDataEnd);\n\n // Keep the \"clip\" class — the runtime uses it to control visibility\n // based on data-start/data-duration timing.\n\n // Adjust media trim offset for the second half\n const playbackStartAttr = el.hasAttribute(\"data-playback-start\")\n ? \"data-playback-start\"\n : el.hasAttribute(\"data-media-start\")\n ? \"data-media-start\"\n : null;\n if (playbackStartAttr) {\n const currentTrim = parseFloat(el.getAttribute(playbackStartAttr) ?? \"0\") || 0;\n const rateRaw = parseFloat(el.getAttribute(\"data-playback-rate\") ?? \"\");\n const rate = Number.isFinite(rateRaw) ? rateRaw : 1;\n clone.setAttribute(\n playbackStartAttr,\n String(Math.round((currentTrim + firstDuration * rate) * 1000) / 1000),\n );\n }\n\n // Duplicate CSS rules targeting the original ID so the clone inherits the same styles.\n const originalId = el.getAttribute(\"id\");\n if (originalId) {\n duplicateCssRulesForId(document, originalId, newId);\n }\n\n // Trim the original element's duration. A GSAP element had no data-start; stamp\n // it so the runtime windows the first half (visibility selects on [data-start]).\n el.setAttribute(\"data-start\", String(Math.round(start * 1000) / 1000));\n setElementDuration(el, start, firstDuration, usesDataEnd);\n\n // Insert clone after original\n if (el.nextSibling) {\n el.parentElement!.insertBefore(clone, el.nextSibling);\n } else {\n el.parentElement!.appendChild(clone);\n }\n\n return {\n html: wrappedFragment ? document.body.innerHTML || \"\" : document.toString(),\n matched: true,\n newId,\n };\n}\n\n// --- Element grouping -------------------------------------------------------\n// A group is a real `<div data-hf-group=\"…\">` wrapping its members in the DOM.\n// Wrapping rebases each member's left/top so its absolute position is unchanged:\n// the wrapper sits at the selection bbox top-left, and each child's new left/top\n// is its old left/top minus the wrapper origin (computed client-side, where live\n// layout is available, and passed in via `rebases`). GSAP x/y, CSS translate and\n// --hf-studio-offset vars are deltas relative to flow position and stay untouched.\n\nexport interface WrapElementsResult {\n html: string;\n matched: boolean;\n groupId: string | null;\n error?: string;\n}\n\nexport interface UnwrapElementsResult {\n html: string;\n unwrapped: boolean;\n /** The unwrapped wrapper's id, so callers can strip GSAP that targeted it\n * (the wrapper is gone; a leftover `gsap.set(\"#id\")` would throw at runtime). */\n unwrappedGroupId?: string;\n /** Members (id'd children) with their absolute layout centres (post un-rebase),\n * so the caller can BAKE the group's GSAP transform into each member before\n * stripping it — otherwise the group's moves are lost on ungroup. */\n members?: Array<{ id: string; cx: number; cy: number }>;\n /** The wrapper's layout centre — the pivot for baking the group's rotation/scale. */\n groupCenter?: { cx: number; cy: number };\n}\n\nexport interface ElementRebase {\n target: SourceMutationTarget;\n left: number;\n top: number;\n}\n\nfunction getInlineStylePx(el: Element, property: string): number {\n const style = (isHTMLElement(el) ? el.getAttribute(\"style\") : null) ?? \"\";\n const { props } = parseStyleDecls(style);\n const raw = props.get(property);\n if (!raw) return 0;\n const n = parseFloat(raw);\n return Number.isFinite(n) ? n : 0;\n}\n\nfunction setInlineLeftTop(el: HTMLElement, left: number, top: number): void {\n let style = el.getAttribute(\"style\") ?? \"\";\n style = patchStyleAttrString(style, \"left\", `${left}px`);\n style = patchStyleAttrString(style, \"top\", `${top}px`);\n el.setAttribute(\"style\", style);\n}\n\n// Slug the group name (\"Group 1\" → \"group-1\") into a unique, valid element id.\nfunction uniqueGroupDomId(document: Document, groupId: string): string {\n const base =\n groupId\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\") || \"group\";\n let id = base;\n let n = 2;\n while (document.getElementById(id)) {\n id = `${base}-${n}`;\n n += 1;\n }\n return id;\n}\n\n// fallow-ignore-next-line complexity\nexport function wrapElementsInHtml(\n source: string,\n targets: SourceMutationTarget[],\n groupId: string,\n bbox: { left: number; top: number; width: number; height: number },\n rebases: ElementRebase[],\n): WrapElementsResult {\n const { document, wrappedFragment } = parseSourceDocument(source);\n if (targets.length === 0) {\n return { html: source, matched: false, groupId: null, error: \"no targets\" };\n }\n\n // Resolve + dedupe by element ref (two targets may point at the same node).\n const els: HTMLElement[] = [];\n const seen = new Set<Element>();\n for (const target of targets) {\n const el = findTargetElement(document, target);\n if (!el || !isHTMLElement(el) || seen.has(el)) continue;\n seen.add(el);\n els.push(el);\n }\n if (els.length === 0) {\n return { html: source, matched: false, groupId: null, error: \"no targets matched\" };\n }\n\n // P1: require a single common parent (LCA multi-parent wrapping is P2).\n const parent = els[0]?.parentElement;\n if (!parent || els.some((el) => el.parentElement !== parent)) {\n return {\n html: source,\n matched: false,\n groupId: null,\n error: \"grouped elements must share a single parent\",\n };\n }\n\n // Order members by their position in the parent (= z-order / stacking order).\n const memberSet = new Set<Element>(els);\n const ordered = Array.from(parent.children).filter((c): c is HTMLElement => memberSet.has(c));\n\n // Map each member to its rebased left/top (resolved against the same document).\n const rebaseByEl = new Map<Element, { left: number; top: number }>();\n for (const rebase of rebases) {\n const el = findTargetElement(document, rebase.target);\n if (el) rebaseByEl.set(el, { left: rebase.left, top: rebase.top });\n }\n\n const wrapper = document.createElement(\"div\");\n wrapper.setAttribute(\"data-hf-group\", groupId);\n // A real `id` (slug of the group name) makes the wrapper a first-class node in the\n // clip manifest / timeline parent-map (both keyed by id) and a clean GSAP target —\n // without it the wrapper is invisible to the timeline and breaks child enumeration.\n wrapper.setAttribute(\"id\", uniqueGroupDomId(document, groupId));\n // Adopt the topmost member's stacking level. A group is one stacking unit, so a\n // non-member interleaved between two selected members can't stay \"between\" them\n // once they unify. Matching Figma/Sketch, the group lifts to the topmost selected\n // layer: the wrapper goes at the LAST member's slot and carries the max member\n // z-index — so an interleaved non-member falls below the group instead of hoisting\n // above it, and explicit member z-indexes are honored.\n const memberZIndexes = ordered\n .map((el) =>\n Number.parseInt(\n parseStyleDecls(el.getAttribute(\"style\") ?? \"\").props.get(\"z-index\") ?? \"\",\n 10,\n ),\n )\n .filter((z) => Number.isFinite(z));\n const maxZ = memberZIndexes.length > 0 ? Math.max(...memberZIndexes) : null;\n wrapper.setAttribute(\n \"style\",\n `position: absolute; left: ${bbox.left}px; top: ${bbox.top}px; width: ${bbox.width}px; height: ${bbox.height}px` +\n (maxZ !== null ? `; z-index: ${maxZ}` : \"\"),\n );\n\n // Insert the wrapper at the topmost member's slot, then move members into it.\n parent.insertBefore(wrapper, ordered[ordered.length - 1] ?? null);\n for (const el of ordered) {\n const rebase = rebaseByEl.get(el);\n if (rebase) setInlineLeftTop(el, rebase.left, rebase.top);\n wrapper.appendChild(el); // appendChild moves the node, preserving order\n }\n\n return {\n html: wrappedFragment ? document.body.innerHTML || \"\" : document.toString(),\n matched: true,\n groupId,\n };\n}\n\nexport function unwrapElementsFromHtml(\n source: string,\n groupTarget: SourceMutationTarget,\n): UnwrapElementsResult {\n const { document, wrappedFragment } = parseSourceDocument(source);\n const group = findTargetElement(document, groupTarget);\n if (!group || !isHTMLElement(group)) return { html: source, unwrapped: false };\n // Shape guard mirroring the wrap-side contract: only ever dissolve an actual\n // group wrapper. A stale/desynced selection that resolves to a plain <div>\n // would otherwise be unwrapped — rebasing its children by the parent's origin\n // (silent corruption). Wrap enforces invariants; unwrap must too.\n if (!group.hasAttribute(\"data-hf-group\")) return { html: source, unwrapped: false };\n\n const parent = group.parentElement;\n if (!parent) return { html: source, unwrapped: false };\n\n // Undo the rebase: child absolute position = child (rebased) + wrapper origin.\n const wLeft = getInlineStylePx(group, \"left\");\n const wTop = getInlineStylePx(group, \"top\");\n const groupCenter = {\n cx: wLeft + getInlineStylePx(group, \"width\") / 2,\n cy: wTop + getInlineStylePx(group, \"height\") / 2,\n };\n\n // Move children back to the wrapper's slot, preserving order.\n const members: Array<{ id: string; cx: number; cy: number }> = [];\n for (const child of Array.from(group.children)) {\n if (isHTMLElement(child)) {\n const newLeft = getInlineStylePx(child, \"left\") + wLeft;\n const newTop = getInlineStylePx(child, \"top\") + wTop;\n setInlineLeftTop(child, newLeft, newTop);\n if (child.id) {\n members.push({\n id: child.id,\n cx: newLeft + getInlineStylePx(child, \"width\") / 2,\n cy: newTop + getInlineStylePx(child, \"height\") / 2,\n });\n }\n }\n parent.insertBefore(child, group);\n }\n const groupId = group.id || undefined;\n group.remove();\n\n return {\n html: wrappedFragment ? document.body.innerHTML || \"\" : document.toString(),\n unwrapped: true,\n unwrappedGroupId: groupId,\n members,\n groupCenter,\n };\n}\n","// fallow-ignore-next-line complexity\nexport function parseStyleDecls(style: string): { props: Map<string, string>; order: string[] } {\n const props = new Map<string, string>();\n const order: string[] = [];\n let i = 0;\n while (i < style.length) {\n let depth = 0;\n let inSingle = false;\n let inDouble = false;\n const start = i;\n while (i < style.length) {\n const ch = style[i];\n if (ch === \"'\" && !inDouble) inSingle = !inSingle;\n else if (ch === '\"' && !inSingle) inDouble = !inDouble;\n else if (!inSingle && !inDouble) {\n if (ch === \"(\") depth++;\n else if (ch === \")\") depth = Math.max(0, depth - 1);\n else if (ch === \";\" && depth === 0) break;\n }\n i++;\n }\n const decl = style.slice(start, i).trim();\n i++;\n if (!decl) continue;\n const colon = decl.indexOf(\":\");\n if (colon < 0) continue;\n const key = decl.slice(0, colon).trim();\n const val = decl.slice(colon + 1).trim();\n if (!key) continue;\n if (!props.has(key)) order.push(key);\n props.set(key, val);\n }\n return { props, order };\n}\n\nfunction serializeStyleDecls(props: Map<string, string>, order: string[]): string {\n return order\n .map((k) => `${k}: ${props.get(k) ?? \"\"}`)\n .filter((d) => d.trim())\n .join(\"; \");\n}\n\nexport function patchStyleAttrString(\n style: string,\n property: string,\n value: string | null,\n): string {\n const { props, order } = parseStyleDecls(style);\n if (value === null) {\n props.delete(property);\n const idx = order.indexOf(property);\n if (idx >= 0) order.splice(idx, 1);\n } else {\n if (!props.has(property)) order.push(property);\n props.set(property, value);\n }\n return serializeStyleDecls(props, order);\n}\n"],"mappings":"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","names":[]}
|