@editframe/elements 0.35.0-beta → 0.36.0-beta

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.
Files changed (36) hide show
  1. package/dist/elements/EFImage.js +11 -2
  2. package/dist/elements/EFImage.js.map +1 -1
  3. package/dist/elements/EFTemporal.js +1 -0
  4. package/dist/elements/EFTemporal.js.map +1 -1
  5. package/dist/elements/EFTimegroup.d.ts +40 -6
  6. package/dist/elements/EFTimegroup.js +127 -8
  7. package/dist/elements/EFTimegroup.js.map +1 -1
  8. package/dist/elements/updateAnimations.js +38 -15
  9. package/dist/elements/updateAnimations.js.map +1 -1
  10. package/dist/gui/EFWorkbench.js +10 -12
  11. package/dist/gui/EFWorkbench.js.map +1 -1
  12. package/dist/gui/TWMixin.js +1 -1
  13. package/dist/gui/TWMixin.js.map +1 -1
  14. package/dist/preview/FrameController.js +6 -1
  15. package/dist/preview/FrameController.js.map +1 -1
  16. package/dist/preview/encoding/canvasEncoder.js.map +1 -1
  17. package/dist/preview/encoding/mainThreadEncoder.js +3 -0
  18. package/dist/preview/encoding/mainThreadEncoder.js.map +1 -1
  19. package/dist/preview/renderTimegroupPreview.js +57 -55
  20. package/dist/preview/renderTimegroupPreview.js.map +1 -1
  21. package/dist/preview/renderTimegroupToCanvas.js +22 -23
  22. package/dist/preview/renderTimegroupToCanvas.js.map +1 -1
  23. package/dist/preview/renderTimegroupToVideo.d.ts +2 -1
  24. package/dist/preview/renderTimegroupToVideo.js +77 -40
  25. package/dist/preview/renderTimegroupToVideo.js.map +1 -1
  26. package/dist/preview/rendering/renderToImage.d.ts +1 -0
  27. package/dist/preview/rendering/renderToImage.js +1 -26
  28. package/dist/preview/rendering/renderToImage.js.map +1 -1
  29. package/dist/preview/rendering/renderToImageForeignObject.js +34 -6
  30. package/dist/preview/rendering/renderToImageForeignObject.js.map +1 -1
  31. package/dist/preview/rendering/serializeTimelineDirect.js +379 -0
  32. package/dist/preview/rendering/serializeTimelineDirect.js.map +1 -0
  33. package/dist/render/EFRenderAPI.js +45 -0
  34. package/dist/render/EFRenderAPI.js.map +1 -1
  35. package/dist/style.css +12 -0
  36. package/package.json +2 -2
@@ -0,0 +1,379 @@
1
+ import { isVisibleAtTime } from "../previewTypes.js";
2
+ import { collectDocumentStyles } from "../renderTimegroupPreview.js";
3
+ import { encodeCanvasesInParallel } from "../encoding/canvasEncoder.js";
4
+
5
+ //#region src/preview/rendering/serializeTimelineDirect.ts
6
+ /**
7
+ * Elements to skip entirely when serializing.
8
+ * NOTE: SLOT is NOT skipped - it's handled specially to serialize light DOM children.
9
+ */
10
+ const SKIP_TAGS = new Set([
11
+ "EF-AUDIO",
12
+ "EF-THUMBNAIL-STRIP",
13
+ "EF-FILMSTRIP",
14
+ "EF-TIMELINE",
15
+ "EF-WORKBENCH",
16
+ "SCRIPT",
17
+ "STYLE",
18
+ "TEMPLATE"
19
+ ]);
20
+ /**
21
+ * HTML void elements - these cannot have children and must be self-closing in XHTML.
22
+ * Using `<br />` instead of `<br></br>`.
23
+ */
24
+ const VOID_ELEMENTS = new Set([
25
+ "area",
26
+ "base",
27
+ "br",
28
+ "col",
29
+ "embed",
30
+ "hr",
31
+ "img",
32
+ "input",
33
+ "link",
34
+ "meta",
35
+ "param",
36
+ "source",
37
+ "track",
38
+ "wbr"
39
+ ]);
40
+ /**
41
+ * CSS properties to serialize as inline styles.
42
+ */
43
+ const SERIALIZED_STYLE_PROPERTIES = [
44
+ "display",
45
+ "visibility",
46
+ "opacity",
47
+ "position",
48
+ "top",
49
+ "right",
50
+ "bottom",
51
+ "left",
52
+ "zIndex",
53
+ "width",
54
+ "height",
55
+ "minWidth",
56
+ "minHeight",
57
+ "maxWidth",
58
+ "maxHeight",
59
+ "flexGrow",
60
+ "flexShrink",
61
+ "flexBasis",
62
+ "flexDirection",
63
+ "flexWrap",
64
+ "justifyContent",
65
+ "alignItems",
66
+ "alignContent",
67
+ "alignSelf",
68
+ "gap",
69
+ "gridTemplate",
70
+ "gridColumn",
71
+ "gridRow",
72
+ "gridArea",
73
+ "margin",
74
+ "padding",
75
+ "boxSizing",
76
+ "border",
77
+ "borderTop",
78
+ "borderRight",
79
+ "borderBottom",
80
+ "borderLeft",
81
+ "borderRadius",
82
+ "background",
83
+ "color",
84
+ "boxShadow",
85
+ "filter",
86
+ "backdropFilter",
87
+ "clipPath",
88
+ "fontFamily",
89
+ "fontSize",
90
+ "fontWeight",
91
+ "fontStyle",
92
+ "fontVariant",
93
+ "textAlign",
94
+ "textDecoration",
95
+ "textTransform",
96
+ "letterSpacing",
97
+ "wordSpacing",
98
+ "whiteSpace",
99
+ "textOverflow",
100
+ "lineHeight",
101
+ "verticalAlign",
102
+ "transform",
103
+ "transformOrigin",
104
+ "transformStyle",
105
+ "perspective",
106
+ "perspectiveOrigin",
107
+ "backfaceVisibility",
108
+ "cursor",
109
+ "pointerEvents",
110
+ "userSelect",
111
+ "overflow",
112
+ "objectFit",
113
+ "objectPosition"
114
+ ];
115
+ /**
116
+ * Caption child elements that should preserve display:none.
117
+ * These use display:none for content visibility, not temporal visibility.
118
+ */
119
+ const CAPTION_CHILD_TAGS = new Set([
120
+ "EF-CAPTIONS-ACTIVE-WORD",
121
+ "EF-CAPTIONS-BEFORE-ACTIVE-WORD",
122
+ "EF-CAPTIONS-AFTER-ACTIVE-WORD",
123
+ "EF-CAPTIONS-SEGMENT"
124
+ ]);
125
+ /**
126
+ * Escape special XML characters.
127
+ */
128
+ function escapeXML(str) {
129
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
130
+ }
131
+ /**
132
+ * Serialize computed styles as inline style string.
133
+ * Handles display:none → block conversion for non-caption elements
134
+ * (temporal visibility is handled separately).
135
+ */
136
+ function serializeComputedStyles(element) {
137
+ const styles = getComputedStyle(element);
138
+ const styleParts = [];
139
+ const tagName = element.tagName;
140
+ const isCaptionChild = CAPTION_CHILD_TAGS.has(tagName);
141
+ for (const prop of SERIALIZED_STYLE_PROPERTIES) {
142
+ const value = styles[prop];
143
+ if (!value || value === "") continue;
144
+ let finalValue = value;
145
+ if (prop === "display") {
146
+ if (tagName === "EF-TEXT") finalValue = element.getAttribute("split") === "line" ? "flex" : "inline-flex";
147
+ else if (tagName === "EF-TEXT-SEGMENT") finalValue = element.hasAttribute("data-line-segment") ? "block" : "inline-block";
148
+ else if (value === "none" && !isCaptionChild) finalValue = "block";
149
+ }
150
+ if (prop === "visibility") finalValue = "visible";
151
+ if (prop === "clipPath") continue;
152
+ const kebab = prop.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
153
+ styleParts.push(`${kebab}:${finalValue}`);
154
+ }
155
+ styleParts.push("animation:none", "transition:none");
156
+ return styleParts.join(";");
157
+ }
158
+ /**
159
+ * Serialize element attributes (excluding style, id, xmlns, event handlers).
160
+ */
161
+ function serializeAttributes(element, parts) {
162
+ for (const attr of element.attributes) {
163
+ const name = attr.name.toLowerCase();
164
+ if (name === "id" || name === "style" || name === "xmlns" || name.startsWith("on")) continue;
165
+ parts.push(` ${attr.name}="${escapeXML(attr.value)}"`);
166
+ }
167
+ }
168
+ /**
169
+ * Check if a canvas element should preserve alpha channel.
170
+ * EF-WAVEFORM always needs alpha, EF-IMAGE checks hasAlpha property.
171
+ */
172
+ function shouldPreserveAlpha(sourceElement) {
173
+ const tagName = sourceElement.tagName;
174
+ if (tagName === "EF-WAVEFORM") return true;
175
+ if (tagName === "EF-IMAGE") return "hasAlpha" in sourceElement && sourceElement.hasAlpha === true;
176
+ return false;
177
+ }
178
+ /**
179
+ * Create a snapshot copy of a canvas's current pixels.
180
+ * This captures the pixels synchronously before any async encoding,
181
+ * preventing race conditions where the source canvas is modified.
182
+ */
183
+ function snapshotCanvas(canvas, scale, preserveAlpha) {
184
+ const targetWidth = Math.max(1, Math.floor(canvas.width * scale));
185
+ const targetHeight = Math.max(1, Math.floor(canvas.height * scale));
186
+ const copy = document.createElement("canvas");
187
+ copy.width = targetWidth;
188
+ copy.height = targetHeight;
189
+ if (preserveAlpha) copy.dataset.preserveAlpha = "true";
190
+ const ctx = copy.getContext("2d");
191
+ if (ctx && canvas.width > 0 && canvas.height > 0) ctx.drawImage(canvas, 0, 0, targetWidth, targetHeight);
192
+ return copy;
193
+ }
194
+ /**
195
+ * Serialize a canvas element as an <img> with base64 data URL.
196
+ * Creates a snapshot of current pixels before async encoding to prevent race conditions.
197
+ *
198
+ * OPTIMIZATION: Calculate optimal encoding resolution based on:
199
+ * 1. CSS display size (how big it actually appears)
200
+ * 2. Video export scale (output resolution multiplier)
201
+ * 3. Quality multiplier (for sharpness, default 1.5x)
202
+ */
203
+ function serializeCanvas(sourceElement, canvas, parts, canvasJobs, options) {
204
+ const width = canvas.width;
205
+ const height = canvas.height;
206
+ if (width === 0 || height === 0) return;
207
+ const styleStr = serializeComputedStyles(sourceElement);
208
+ const computedStyle = getComputedStyle(sourceElement);
209
+ const computedWidth = computedStyle.width;
210
+ const computedHeight = computedStyle.height;
211
+ const styleParts = styleStr ? styleStr.split(";").filter((s) => s.trim()) : [];
212
+ const hasWidth = styleParts.some((s) => s.trim().startsWith("width:"));
213
+ const hasHeight = styleParts.some((s) => s.trim().startsWith("height:"));
214
+ if (!hasWidth) styleParts.push(`width:${computedWidth || `${width}px`}`);
215
+ if (!hasHeight) styleParts.push(`height:${computedHeight || `${height}px`}`);
216
+ styleParts.push(`display:block`);
217
+ const finalStyle = styleParts.join(";");
218
+ const preserveAlpha = shouldPreserveAlpha(sourceElement);
219
+ let optimalScale = options.canvasScale;
220
+ const qualityMultiplier = 1.5;
221
+ try {
222
+ const cssWidth = parseFloat(computedWidth) || canvas.width;
223
+ const cssHeight = parseFloat(computedHeight) || canvas.height;
224
+ const displayScaleX = cssWidth / canvas.width;
225
+ const displayScaleY = cssHeight / canvas.height;
226
+ const displayScale = Math.min(displayScaleX, displayScaleY);
227
+ optimalScale = Math.min(1, displayScale * options.canvasScale * qualityMultiplier);
228
+ } catch (e) {
229
+ console.warn(`[serializeCanvas] Failed to get computed style for ${sourceElement.tagName}:`, e);
230
+ }
231
+ const snapshot = snapshotCanvas(canvas, optimalScale, preserveAlpha);
232
+ parts.push(`<img style="${escapeXML(finalStyle)}" src="`);
233
+ const promiseIndex = parts.length;
234
+ const sourceMap = /* @__PURE__ */ new WeakMap();
235
+ sourceMap.set(snapshot, sourceElement);
236
+ const encodePromise = encodeCanvasesInParallel([snapshot], {
237
+ scale: 1,
238
+ renderContext: options.renderContext,
239
+ sourceMap
240
+ }).then((results) => results[0]?.dataUrl || "");
241
+ parts.push(encodePromise);
242
+ canvasJobs.push({
243
+ canvas: snapshot,
244
+ sourceElement,
245
+ promiseIndex
246
+ });
247
+ parts.push("\" />");
248
+ }
249
+ /**
250
+ * Serialize an image element as a canvas (for shadow DOM img elements).
251
+ */
252
+ function serializeImageAsCanvas(sourceElement, img, parts, canvasJobs, options) {
253
+ const canvas = document.createElement("canvas");
254
+ canvas.width = img.naturalWidth;
255
+ canvas.height = img.naturalHeight;
256
+ const ctx = canvas.getContext("2d");
257
+ if (ctx) try {
258
+ ctx.drawImage(img, 0, 0);
259
+ } catch (e) {
260
+ return;
261
+ }
262
+ serializeCanvas(sourceElement, canvas, parts, canvasJobs, options);
263
+ }
264
+ /**
265
+ * Serialize slotted light DOM children of a host element.
266
+ */
267
+ function serializeSlottedContent(slotHost, parts, canvasJobs, options, parentIsSVG) {
268
+ for (const slottedChild of slotHost.childNodes) if (slottedChild.nodeType === Node.TEXT_NODE) {
269
+ const text = slottedChild.textContent;
270
+ if (text && text.length > 0) parts.push(escapeXML(text));
271
+ } else if (slottedChild.nodeType === Node.ELEMENT_NODE) serializeElement(slottedChild, parts, canvasJobs, options, parentIsSVG, null);
272
+ }
273
+ /**
274
+ * Recursively serialize an element and its children to XML parts.
275
+ * @param slotHost - When serializing inside shadow DOM, the custom element whose light DOM children should be serialized for slots
276
+ */
277
+ function serializeElement(element, parts, canvasJobs, options, parentIsSVG = false, slotHost = null) {
278
+ if (SKIP_TAGS.has(element.tagName)) return;
279
+ if (element.tagName === "SLOT" && slotHost) {
280
+ serializeSlottedContent(slotHost, parts, canvasJobs, options, parentIsSVG);
281
+ return;
282
+ }
283
+ if (!isTemporallyVisible(element, options.timeMs)) return;
284
+ if (element.tagName.includes("-") && element.shadowRoot) {
285
+ const shadowCanvas = element.shadowRoot.querySelector("canvas");
286
+ if (shadowCanvas) {
287
+ serializeCanvas(element, shadowCanvas, parts, canvasJobs, options);
288
+ return;
289
+ }
290
+ const shadowImg = element.shadowRoot.querySelector("img");
291
+ if (shadowImg?.complete && shadowImg.naturalWidth > 0) {
292
+ serializeImageAsCanvas(element, shadowImg, parts, canvasJobs, options);
293
+ return;
294
+ }
295
+ const computedDisplay = getComputedStyle(element).display;
296
+ const containerTag = computedDisplay === "inline" || computedDisplay === "inline-block" || computedDisplay === "inline-flex" ? "span" : "div";
297
+ let styleStr$1 = serializeComputedStyles(element);
298
+ parts.push(`<${containerTag}`);
299
+ for (const attr of element.attributes) {
300
+ const name = attr.name.toLowerCase();
301
+ if (name === "class" || name.startsWith("data-")) parts.push(` ${attr.name}="${escapeXML(attr.value)}"`);
302
+ }
303
+ if (styleStr$1) parts.push(` style="${escapeXML(styleStr$1)}"`);
304
+ parts.push(">");
305
+ for (const child of element.shadowRoot.childNodes) if (child.nodeType === Node.TEXT_NODE) {
306
+ const text = child.textContent;
307
+ if (text && text.length > 0) parts.push(escapeXML(text));
308
+ } else if (child.nodeType === Node.ELEMENT_NODE) serializeElement(child, parts, canvasJobs, options, parentIsSVG, element);
309
+ parts.push(`</${containerTag}>`);
310
+ return;
311
+ }
312
+ if (element instanceof HTMLCanvasElement) {
313
+ serializeCanvas(element, element, parts, canvasJobs, options);
314
+ return;
315
+ }
316
+ const tagName = element.tagName.toLowerCase();
317
+ const isSVG = element instanceof SVGElement;
318
+ const isVoid = VOID_ELEMENTS.has(tagName);
319
+ if (isSVG && !parentIsSVG) parts.push(`<${tagName} xmlns="http://www.w3.org/2000/svg"`);
320
+ else parts.push(`<${tagName}`);
321
+ serializeAttributes(element, parts);
322
+ const styleStr = serializeComputedStyles(element);
323
+ if (styleStr) parts.push(` style="${escapeXML(styleStr)}"`);
324
+ if (isVoid) {
325
+ parts.push(" />");
326
+ return;
327
+ }
328
+ parts.push(">");
329
+ const children = element.shadowRoot?.childNodes || element.childNodes;
330
+ for (const child of children) if (child.nodeType === Node.TEXT_NODE) {
331
+ const text = child.textContent;
332
+ if (text && text.length > 0) parts.push(escapeXML(text));
333
+ } else if (child.nodeType === Node.ELEMENT_NODE) serializeElement(child, parts, canvasJobs, options, isSVG, slotHost);
334
+ parts.push(`</${tagName}>`);
335
+ }
336
+ /**
337
+ * Check if an element is temporally visible at the given time.
338
+ * Returns false if the element or any ancestor is outside its temporal bounds.
339
+ */
340
+ function isTemporallyVisible(element, timeMs) {
341
+ if (!isVisibleAtTime(element, timeMs)) return false;
342
+ return true;
343
+ }
344
+ /**
345
+ * Serialize a timeline element directly to XHTML string.
346
+ *
347
+ * @param timeline - The timeline element to serialize (e.g., EFTimegroup)
348
+ * @param width - Output width
349
+ * @param height - Output height
350
+ * @param options - Serialization options (renderContext, canvasScale, timeMs)
351
+ * @returns XHTML string with all canvases encoded as base64 data URLs
352
+ */
353
+ async function serializeTimelineToXHTML(timeline, width, height, options) {
354
+ const parts = [];
355
+ const canvasJobs = [];
356
+ const documentStyles = collectDocumentStyles();
357
+ parts.push(`<div xmlns="http://www.w3.org/1999/xhtml" style="width:${width}px;height:${height}px;overflow:hidden;position:relative;">`);
358
+ if (documentStyles) parts.push(`<style type="text/css"><![CDATA[${documentStyles}]]></style>`);
359
+ serializeElement(timeline, parts, canvasJobs, options);
360
+ parts.push("</div>");
361
+ return (await Promise.all(parts)).join("");
362
+ }
363
+ /**
364
+ * Serialize timeline to SVG foreignObject data URI (ready for rendering).
365
+ *
366
+ * @param timeline - The timeline element to serialize
367
+ * @param width - Output width
368
+ * @param height - Output height
369
+ * @param options - Serialization options
370
+ * @returns SVG data URI
371
+ */
372
+ async function serializeTimelineToDataUri(timeline, width, height, options) {
373
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}"><foreignObject x="0" y="0" width="${width}" height="${height}">${await serializeTimelineToXHTML(timeline, width, height, options)}</foreignObject></svg>`;
374
+ return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
375
+ }
376
+
377
+ //#endregion
378
+ export { serializeTimelineToDataUri };
379
+ //# sourceMappingURL=serializeTimelineDirect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serializeTimelineDirect.js","names":["styleParts: string[]","styleStr","parts: Array<string | Promise<string>>","canvasJobs: CanvasJob[]"],"sources":["../../../src/preview/rendering/serializeTimelineDirect.ts"],"sourcesContent":["/**\n * Direct timeline serialization - no intermediate passive structure.\n * \n * Walks the timeline DOM once and builds XML string directly with promise parts\n * for async canvas encoding. 3x faster than DOM creation + XMLSerializer.\n * \n * Architecture:\n * 1. Walk timeline recursively\n * 2. Build array of string parts (some are promises for canvas encoding)\n * 3. Handle shadow DOM by serializing shadow content instead of light DOM\n * 4. Await all promises\n * 5. Join parts into final XML\n */\n\nimport { encodeCanvasesInParallel } from \"../encoding/canvasEncoder.js\";\nimport type { RenderContext } from \"../RenderContext.js\";\nimport { isVisibleAtTime } from \"../previewTypes.js\";\nimport { collectDocumentStyles } from \"../renderTimegroupPreview.js\";\n\n/**\n * Elements to skip entirely when serializing.\n * NOTE: SLOT is NOT skipped - it's handled specially to serialize light DOM children.\n */\nconst SKIP_TAGS = new Set([\n \"EF-AUDIO\",\n \"EF-THUMBNAIL-STRIP\",\n \"EF-FILMSTRIP\",\n \"EF-TIMELINE\",\n \"EF-WORKBENCH\",\n \"SCRIPT\",\n \"STYLE\",\n \"TEMPLATE\",\n]);\n\n\n/**\n * HTML void elements - these cannot have children and must be self-closing in XHTML.\n * Using `<br />` instead of `<br></br>`.\n */\nconst VOID_ELEMENTS = new Set([\n \"area\", \"base\", \"br\", \"col\", \"embed\", \"hr\", \"img\", \"input\",\n \"link\", \"meta\", \"param\", \"source\", \"track\", \"wbr\",\n]);\n\n/**\n * CSS properties to serialize as inline styles.\n */\nconst SERIALIZED_STYLE_PROPERTIES = [\n \"display\", \"visibility\", \"opacity\",\n \"position\", \"top\", \"right\", \"bottom\", \"left\", \"zIndex\",\n \"width\", \"height\", \"minWidth\", \"minHeight\", \"maxWidth\", \"maxHeight\",\n \"flexGrow\", \"flexShrink\", \"flexBasis\", \"flexDirection\", \"flexWrap\",\n \"justifyContent\", \"alignItems\", \"alignContent\", \"alignSelf\", \"gap\",\n \"gridTemplate\", \"gridColumn\", \"gridRow\", \"gridArea\",\n \"margin\", \"padding\", \"boxSizing\",\n \"border\", \"borderTop\", \"borderRight\", \"borderBottom\", \"borderLeft\", \"borderRadius\",\n \"background\", \"color\", \"boxShadow\", \"filter\", \"backdropFilter\", \"clipPath\",\n \"fontFamily\", \"fontSize\", \"fontWeight\", \"fontStyle\", \"fontVariant\",\n \"textAlign\", \"textDecoration\", \"textTransform\",\n \"letterSpacing\", \"wordSpacing\", \"whiteSpace\", \"textOverflow\", \"lineHeight\",\n \"verticalAlign\",\n \"transform\", \"transformOrigin\", \"transformStyle\",\n \"perspective\", \"perspectiveOrigin\", \"backfaceVisibility\",\n \"cursor\", \"pointerEvents\", \"userSelect\", \"overflow\", \"objectFit\", \"objectPosition\",\n] as const;\n\n/**\n * Caption child elements that should preserve display:none.\n * These use display:none for content visibility, not temporal visibility.\n */\nconst CAPTION_CHILD_TAGS = new Set([\n 'EF-CAPTIONS-ACTIVE-WORD',\n 'EF-CAPTIONS-BEFORE-ACTIVE-WORD',\n 'EF-CAPTIONS-AFTER-ACTIVE-WORD',\n 'EF-CAPTIONS-SEGMENT',\n]);\n\ninterface SerializationOptions {\n renderContext?: RenderContext;\n canvasScale: number;\n timeMs: number;\n}\n\ninterface CanvasJob {\n canvas: HTMLCanvasElement;\n sourceElement: Element;\n promiseIndex: number;\n}\n\n/**\n * Escape special XML characters.\n */\nfunction escapeXML(str: string): string {\n return str\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&apos;');\n}\n\n\n/**\n * Serialize computed styles as inline style string.\n * Handles display:none → block conversion for non-caption elements\n * (temporal visibility is handled separately).\n */\nfunction serializeComputedStyles(element: Element): string {\n const styles = getComputedStyle(element);\n const styleParts: string[] = [];\n const tagName = element.tagName;\n const isCaptionChild = CAPTION_CHILD_TAGS.has(tagName);\n \n for (const prop of SERIALIZED_STYLE_PROPERTIES) {\n const value = styles[prop as any];\n // Skip only truly empty values\n if (!value || value === '') {\n continue;\n }\n \n // Handle display property specially\n let finalValue = value;\n if (prop === 'display') {\n // Fix for cloned Lit elements: shadow DOM stylesheets aren't adopted properly\n // so computed display is wrong. Use the correct values based on element type.\n if (tagName === 'EF-TEXT') {\n // EFText: inline-flex (or flex for split=\"line\")\n finalValue = element.getAttribute('split') === 'line' ? 'flex' : 'inline-flex';\n } else if (tagName === 'EF-TEXT-SEGMENT') {\n // EFTextSegment: inline-block (or block for data-line-segment)\n finalValue = element.hasAttribute('data-line-segment') ? 'block' : 'inline-block';\n }\n // For non-caption elements, convert display:none to block since temporal\n // visibility is handled separately, not by CSS display\n else if (value === 'none' && !isCaptionChild) {\n finalValue = 'block';\n }\n }\n \n // Force visibility:visible - the source container may have visibility:hidden\n // for off-screen rendering, but we want the serialized output to be visible\n if (prop === 'visibility') {\n finalValue = 'visible';\n }\n \n // Skip clipPath - clones always render without clip-path\n // (source may have clip-path: inset(100%) from proxy mode)\n if (prop === 'clipPath') {\n continue;\n }\n \n // Convert camelCase to kebab-case\n const kebab = prop.replace(/[A-Z]/g, m => `-${m.toLowerCase()}`);\n styleParts.push(`${kebab}:${finalValue}`);\n }\n \n // Disable animations/transitions to prevent re-animation\n styleParts.push('animation:none', 'transition:none');\n \n return styleParts.join(';');\n}\n\n/**\n * Serialize element attributes (excluding style, id, xmlns, event handlers).\n */\nfunction serializeAttributes(element: Element, parts: Array<string | Promise<string>>): void {\n for (const attr of element.attributes) {\n const name = attr.name.toLowerCase();\n // Skip: id, style, xmlns (namespace handled separately), event handlers\n if (name === 'id' || name === 'style' || name === 'xmlns' || name.startsWith('on')) {\n continue;\n }\n parts.push(` ${attr.name}=\"${escapeXML(attr.value)}\"`);\n }\n}\n\n/**\n * Check if a canvas element should preserve alpha channel.\n * EF-WAVEFORM always needs alpha, EF-IMAGE checks hasAlpha property.\n */\nfunction shouldPreserveAlpha(sourceElement: Element): boolean {\n const tagName = sourceElement.tagName;\n if (tagName === 'EF-WAVEFORM') {\n return true;\n }\n if (tagName === 'EF-IMAGE') {\n return 'hasAlpha' in sourceElement && (sourceElement as any).hasAlpha === true;\n }\n return false;\n}\n\n/**\n * Create a snapshot copy of a canvas's current pixels.\n * This captures the pixels synchronously before any async encoding,\n * preventing race conditions where the source canvas is modified.\n */\nfunction snapshotCanvas(\n canvas: HTMLCanvasElement,\n scale: number,\n preserveAlpha: boolean\n): HTMLCanvasElement {\n const targetWidth = Math.max(1, Math.floor(canvas.width * scale));\n const targetHeight = Math.max(1, Math.floor(canvas.height * scale));\n \n const copy = document.createElement('canvas');\n copy.width = targetWidth;\n copy.height = targetHeight;\n \n if (preserveAlpha) {\n copy.dataset.preserveAlpha = 'true';\n }\n \n const ctx = copy.getContext('2d');\n if (ctx && canvas.width > 0 && canvas.height > 0) {\n // drawImage with scaling is SYNCHRONOUS - pixels are copied immediately\n ctx.drawImage(canvas, 0, 0, targetWidth, targetHeight);\n }\n \n return copy;\n}\n\n/**\n * Serialize a canvas element as an <img> with base64 data URL.\n * Creates a snapshot of current pixels before async encoding to prevent race conditions.\n * \n * OPTIMIZATION: Calculate optimal encoding resolution based on:\n * 1. CSS display size (how big it actually appears)\n * 2. Video export scale (output resolution multiplier)\n * 3. Quality multiplier (for sharpness, default 1.5x)\n */\nfunction serializeCanvas(\n sourceElement: Element,\n canvas: HTMLCanvasElement,\n parts: Array<string | Promise<string>>,\n canvasJobs: CanvasJob[],\n options: SerializationOptions\n): void {\n // Use intrinsic canvas dimensions, not computed styles (which may be zoom-affected)\n const width = canvas.width;\n const height = canvas.height;\n \n // Skip empty canvases\n if (width === 0 || height === 0) {\n return;\n }\n \n // Get all computed styles from source element\n const styleStr = serializeComputedStyles(sourceElement);\n \n // Get computed dimensions from source element (respects CSS like w-[420px])\n const computedStyle = getComputedStyle(sourceElement);\n const computedWidth = computedStyle.width;\n const computedHeight = computedStyle.height;\n \n // Use computed dimensions if available, otherwise fall back to canvas natural dimensions\n const styleParts = styleStr ? styleStr.split(';').filter(s => s.trim()) : [];\n \n // Only override dimensions if they weren't already captured from computed styles\n const hasWidth = styleParts.some(s => s.trim().startsWith('width:'));\n const hasHeight = styleParts.some(s => s.trim().startsWith('height:'));\n \n if (!hasWidth) {\n styleParts.push(`width:${computedWidth || `${width}px`}`);\n }\n if (!hasHeight) {\n styleParts.push(`height:${computedHeight || `${height}px`}`);\n }\n styleParts.push(`display:block`);\n \n const finalStyle = styleParts.join(';');\n \n // Check if we need to preserve alpha channel\n const preserveAlpha = shouldPreserveAlpha(sourceElement);\n \n // CRITICAL: Calculate optimal encoding scale BEFORE creating snapshot.\n // This prevents encoding at full resolution when CSS display size is much smaller.\n let optimalScale = options.canvasScale; // Start with video export scale\n const qualityMultiplier = 1.5; // Encode at 1.5x display size for quality\n \n try {\n const cssWidth = parseFloat(computedWidth) || canvas.width;\n const cssHeight = parseFloat(computedHeight) || canvas.height;\n \n // Calculate how much smaller the display is vs natural size\n const displayScaleX = cssWidth / canvas.width;\n const displayScaleY = cssHeight / canvas.height;\n const displayScale = Math.min(displayScaleX, displayScaleY);\n \n // Combine display scale, video scale, and quality multiplier\n // Clamp to 1.0 max (never upscale beyond natural resolution)\n optimalScale = Math.min(1.0, displayScale * options.canvasScale * qualityMultiplier);\n } catch (e) {\n // Fallback to just video scale if we can't get computed style\n console.warn(`[serializeCanvas] Failed to get computed style for ${sourceElement.tagName}:`, e);\n }\n \n // CRITICAL: Create a snapshot of canvas pixels SYNCHRONOUSLY before any async work.\n // This prevents race conditions where concurrent renders overwrite the shared\n // shadow canvas while encoding is in progress.\n const snapshot = snapshotCanvas(canvas, optimalScale, preserveAlpha);\n \n // Open img tag with all styles from source element\n parts.push(`<img style=\"${escapeXML(finalStyle)}\" src=\"`);\n \n // Kick off async encoding of the SNAPSHOT (not the live canvas)\n const promiseIndex = parts.length;\n const sourceMap = new WeakMap<HTMLCanvasElement, Element>();\n sourceMap.set(snapshot, sourceElement);\n \n // Snapshot is already scaled, so encode at 1.0 scale\n const encodePromise = encodeCanvasesInParallel([snapshot], {\n scale: 1.0,\n renderContext: options.renderContext,\n sourceMap,\n }).then(results => results[0]?.dataUrl || '');\n \n parts.push(encodePromise);\n canvasJobs.push({ canvas: snapshot, sourceElement, promiseIndex });\n \n // Close img tag\n parts.push('\" />');\n}\n\n/**\n * Serialize an image element as a canvas (for shadow DOM img elements).\n */\nfunction serializeImageAsCanvas(\n sourceElement: Element,\n img: HTMLImageElement,\n parts: Array<string | Promise<string>>,\n canvasJobs: CanvasJob[],\n options: SerializationOptions\n): void {\n // Convert img to canvas for serialization\n const canvas = document.createElement('canvas');\n canvas.width = img.naturalWidth;\n canvas.height = img.naturalHeight;\n \n const ctx = canvas.getContext('2d');\n if (ctx) {\n try {\n ctx.drawImage(img, 0, 0);\n } catch (e) {\n // Cross-origin image - skip\n return;\n }\n }\n \n serializeCanvas(sourceElement, canvas, parts, canvasJobs, options);\n}\n\n/**\n * Serialize slotted light DOM children of a host element.\n */\nfunction serializeSlottedContent(\n slotHost: Element,\n parts: Array<string | Promise<string>>,\n canvasJobs: CanvasJob[],\n options: SerializationOptions,\n parentIsSVG: boolean\n): void {\n for (const slottedChild of slotHost.childNodes) {\n if (slottedChild.nodeType === Node.TEXT_NODE) {\n const text = slottedChild.textContent;\n if (text && text.length > 0) {\n parts.push(escapeXML(text));\n }\n } else if (slottedChild.nodeType === Node.ELEMENT_NODE) {\n serializeElement(slottedChild as Element, parts, canvasJobs, options, parentIsSVG, null);\n }\n }\n}\n\n/**\n * Recursively serialize an element and its children to XML parts.\n * @param slotHost - When serializing inside shadow DOM, the custom element whose light DOM children should be serialized for slots\n */\nfunction serializeElement(\n element: Element,\n parts: Array<string | Promise<string>>,\n canvasJobs: CanvasJob[],\n options: SerializationOptions,\n parentIsSVG = false,\n slotHost: Element | null = null\n): void {\n // Skip certain elements\n if (SKIP_TAGS.has(element.tagName)) {\n return;\n }\n \n // Handle SLOT elements - serialize light DOM children of the slot host\n if (element.tagName === 'SLOT' && slotHost) {\n serializeSlottedContent(slotHost, parts, canvasJobs, options, parentIsSVG);\n return;\n }\n \n // Check temporal visibility - skip elements outside their time bounds\n // This is non-destructive (doesn't modify DOM)\n // NOTE: We do NOT check CSS visibility/display here because:\n // 1. The container may have visibility:hidden for off-screen rendering\n // 2. Temporal elements control their own visibility via time bounds\n if (!isTemporallyVisible(element, options.timeMs)) {\n return;\n }\n \n // Custom element with shadow DOM?\n const isCustom = element.tagName.includes('-');\n if (isCustom && element.shadowRoot) {\n const shadowCanvas = element.shadowRoot.querySelector('canvas');\n if (shadowCanvas) {\n serializeCanvas(element, shadowCanvas, parts, canvasJobs, options);\n return;\n }\n \n const shadowImg = element.shadowRoot.querySelector('img');\n if (shadowImg?.complete && shadowImg.naturalWidth > 0) {\n serializeImageAsCanvas(element, shadowImg, parts, canvasJobs, options);\n return;\n }\n \n // Serialize custom element with its styles, then shadow DOM content inside\n // Use span for inline/inline-block/inline-flex elements to preserve inline behavior\n const computedDisplay = getComputedStyle(element).display;\n const isInline = computedDisplay === 'inline' || computedDisplay === 'inline-block' || computedDisplay === 'inline-flex';\n const containerTag = isInline ? 'span' : 'div';\n \n let styleStr = serializeComputedStyles(element);\n \n \n parts.push(`<${containerTag}`);\n \n // Copy data attributes and class from custom element\n for (const attr of element.attributes) {\n const name = attr.name.toLowerCase();\n if (name === 'class' || name.startsWith('data-')) {\n parts.push(` ${attr.name}=\"${escapeXML(attr.value)}\"`);\n }\n }\n \n if (styleStr) {\n parts.push(` style=\"${escapeXML(styleStr)}\"`);\n }\n parts.push('>');\n \n // Serialize shadow DOM content with this element as the slot host\n for (const child of element.shadowRoot.childNodes) {\n if (child.nodeType === Node.TEXT_NODE) {\n const text = child.textContent;\n if (text && text.length > 0) {\n parts.push(escapeXML(text));\n }\n } else if (child.nodeType === Node.ELEMENT_NODE) {\n // Pass this element as slotHost so nested SLOTs can access light DOM children\n serializeElement(child as Element, parts, canvasJobs, options, parentIsSVG, element);\n }\n }\n \n parts.push(`</${containerTag}>`);\n return;\n }\n \n // Raw canvas in light DOM\n if (element instanceof HTMLCanvasElement) {\n serializeCanvas(element, element, parts, canvasJobs, options);\n return;\n }\n \n // Standard element - serialize to XHTML\n const tagName = element.tagName.toLowerCase();\n const isSVG = element instanceof SVGElement;\n const isVoid = VOID_ELEMENTS.has(tagName);\n \n // Open tag with namespace (only add xmlns for root SVG elements, not children)\n if (isSVG && !parentIsSVG) {\n // Root SVG element - needs xmlns declaration\n parts.push(`<${tagName} xmlns=\"http://www.w3.org/2000/svg\"`);\n } else {\n parts.push(`<${tagName}`);\n }\n \n // Attributes\n serializeAttributes(element, parts);\n \n // Computed styles as inline style attribute\n const styleStr = serializeComputedStyles(element);\n if (styleStr) {\n parts.push(` style=\"${escapeXML(styleStr)}\"`);\n }\n \n // Void elements: self-close with /> (XHTML requirement)\n if (isVoid) {\n parts.push(' />');\n return;\n }\n \n parts.push('>');\n \n // Children (shadow or light)\n const children = element.shadowRoot?.childNodes || element.childNodes;\n for (const child of children) {\n if (child.nodeType === Node.TEXT_NODE) {\n const text = child.textContent;\n if (text && text.length > 0) {\n parts.push(escapeXML(text));\n }\n } else if (child.nodeType === Node.ELEMENT_NODE) {\n // Preserve slotHost when recursing into standard elements inside shadow DOM\n serializeElement(child as Element, parts, canvasJobs, options, isSVG, slotHost);\n }\n }\n \n // Close tag\n parts.push(`</${tagName}>`);\n}\n\n/**\n * Check if an element is temporally visible at the given time.\n * Returns false if the element or any ancestor is outside its temporal bounds.\n */\nfunction isTemporallyVisible(element: Element, timeMs: number): boolean {\n // Check this element's temporal bounds\n if (!isVisibleAtTime(element, timeMs)) {\n return false;\n }\n return true;\n}\n\n/**\n * Serialize a timeline element directly to XHTML string.\n * \n * @param timeline - The timeline element to serialize (e.g., EFTimegroup)\n * @param width - Output width\n * @param height - Output height\n * @param options - Serialization options (renderContext, canvasScale, timeMs)\n * @returns XHTML string with all canvases encoded as base64 data URLs\n */\nexport async function serializeTimelineToXHTML(\n timeline: Element,\n width: number,\n height: number,\n options: SerializationOptions\n): Promise<string> {\n // Note: Temporal visibility is checked non-destructively during serialization\n // We do NOT modify the source DOM - this allows serializing the main timeline safely\n \n const parts: Array<string | Promise<string>> = [];\n const canvasJobs: CanvasJob[] = [];\n \n // Collect document styles for proper CSS cascade\n const documentStyles = collectDocumentStyles();\n \n // Open wrapper div with embedded styles\n parts.push(\n `<div xmlns=\"http://www.w3.org/1999/xhtml\" ` +\n `style=\"width:${width}px;height:${height}px;overflow:hidden;position:relative;\">`\n );\n \n // Inject document styles (CSS content is wrapped in CDATA to avoid XML escaping issues)\n if (documentStyles) {\n parts.push(`<style type=\"text/css\"><![CDATA[${documentStyles}]]></style>`);\n }\n \n // Recursively serialize timeline\n serializeElement(timeline, parts, canvasJobs, options);\n \n // Close wrapper\n parts.push('</div>');\n \n // Wait for all canvas encodings to complete\n const resolvedParts = await Promise.all(parts);\n \n // Join into final XHTML string\n const xhtml = resolvedParts.join('');\n \n return xhtml;\n}\n\n/**\n * Serialize timeline to SVG foreignObject data URI (ready for rendering).\n * \n * @param timeline - The timeline element to serialize\n * @param width - Output width\n * @param height - Output height\n * @param options - Serialization options\n * @returns SVG data URI\n */\nexport async function serializeTimelineToDataUri(\n timeline: Element,\n width: number,\n height: number,\n options: SerializationOptions\n): Promise<string> {\n const xhtml = await serializeTimelineToXHTML(timeline, width, height, options);\n \n // Wrap in SVG foreignObject\n // Use explicit pixel dimensions for foreignObject to match SVG viewport exactly\n const svg = \n `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${width}\" height=\"${height}\">` +\n `<foreignObject x=\"0\" y=\"0\" width=\"${width}\" height=\"${height}\">${xhtml}</foreignObject>` +\n `</svg>`;\n \n // Use percent-encoding instead of base64 for faster encoding\n // encodeURIComponent is faster than btoa(unescape(encodeURIComponent()))\n return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;\n}\n"],"mappings":"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"}
@@ -14,6 +14,49 @@ function setWorkbenchRendering(rendering) {
14
14
  const workbench = document.querySelector("ef-workbench");
15
15
  if (workbench) workbench.rendering = rendering;
16
16
  }
17
+ async function waitForTimegroupDimensions(timegroup) {
18
+ console.log("[EFRenderAPI] Waiting for stylesheets to load...");
19
+ console.log(`[EFRenderAPI] Found ${document.styleSheets.length} stylesheets`);
20
+ const styleLinks = Array.from(document.querySelectorAll("link[rel=\"stylesheet\"]"));
21
+ console.log(`[EFRenderAPI] Found ${styleLinks.length} stylesheet <link> elements`);
22
+ styleLinks.forEach((link, i) => {
23
+ const href = link.href;
24
+ const sheet = link.sheet;
25
+ console.log(`[EFRenderAPI] [${i}] ${href}`);
26
+ try {
27
+ const rulesCount = sheet ? sheet.cssRules.length : 0;
28
+ console.log(`[EFRenderAPI] loaded: ${!!sheet}, rules: ${rulesCount}`);
29
+ if (sheet && sheet.cssRules.length > 0) {
30
+ const firstRules = Array.from(sheet.cssRules).slice(0, 5).map((r) => r.cssText.substring(0, 100));
31
+ console.log(`[EFRenderAPI] first rules:`, firstRules);
32
+ const hasWidthClass = Array.from(sheet.cssRules).some((r) => r.cssText.includes("w-\\[1080px\\]") || r.cssText.includes("width: 1080px"));
33
+ console.log(`[EFRenderAPI] has w-[1080px] class: ${hasWidthClass}`);
34
+ }
35
+ } catch (e) {
36
+ console.log(`[EFRenderAPI] Error reading stylesheet rules:`, e);
37
+ }
38
+ });
39
+ await Promise.all(Array.from(document.styleSheets).map((sheet) => {
40
+ if (sheet.href) {
41
+ const link = Array.from(document.querySelectorAll("link[rel=\"stylesheet\"]")).find((l) => l.href === sheet.href);
42
+ if (link && !link.sheet) {
43
+ console.log(`[EFRenderAPI] Waiting for stylesheet: ${sheet.href}`);
44
+ return new Promise((resolve) => {
45
+ link.addEventListener("load", resolve);
46
+ link.addEventListener("error", resolve);
47
+ });
48
+ }
49
+ }
50
+ return Promise.resolve();
51
+ }));
52
+ timegroup.offsetHeight;
53
+ if (!timegroup.offsetWidth || !timegroup.offsetHeight) {
54
+ const computedWidth = getComputedStyle(timegroup).width;
55
+ const computedHeight = getComputedStyle(timegroup).height;
56
+ throw new Error(`Timegroup has no dimensions (${timegroup.offsetWidth}x${timegroup.offsetHeight}). Computed styles: width=${computedWidth}, height=${computedHeight}. Classes: "${timegroup.className}". \n\nTailwind CSS did not generate styles for these classes. Check that:\n1. Your Tailwind config 'content' array includes the HTML file\n2. Tailwind CSS is properly configured in your project\n3. The dev server successfully compiled CSS (check for Tailwind warnings above)`);
57
+ }
58
+ console.log(`[EFRenderAPI] Timegroup dimensions ready: ${timegroup.offsetWidth}x${timegroup.offsetHeight}`);
59
+ }
17
60
  const api = {
18
61
  async renderStreaming(options = {}) {
19
62
  const timegroup = findRootTimegroup();
@@ -21,6 +64,7 @@ const api = {
21
64
  if (typeof window === "undefined" || !window.onRenderChunk) throw new Error("window.onRenderChunk is not set. Call page.exposeFunction('onRenderChunk', callback) from Playwright first.");
22
65
  setWorkbenchRendering(true);
23
66
  try {
67
+ await waitForTimegroupDimensions(timegroup);
24
68
  await timegroup.waitForMediaDurations();
25
69
  const chunkWriter = new WritableStream({ write(chunk) {
26
70
  console.error("Writing chunk", chunk);
@@ -42,6 +86,7 @@ const api = {
42
86
  if (!timegroup) throw new Error("No ef-timegroup found. Cannot render.");
43
87
  setWorkbenchRendering(true);
44
88
  try {
89
+ await waitForTimegroupDimensions(timegroup);
45
90
  await timegroup.waitForMediaDurations();
46
91
  const onProgress = options.onProgress || window.onRenderProgress;
47
92
  const buffer = await renderTimegroupToVideo(timegroup, {
@@ -1 +1 @@
1
- {"version":3,"file":"EFRenderAPI.js","names":["api: IEFRenderAPI"],"sources":["../../src/render/EFRenderAPI.ts"],"sourcesContent":["/**\n * Window API for programmatic video rendering.\n * \n * Exposes renderTimegroupToVideo for use from Playwright/CLI.\n * Supports streaming output and custom data injection.\n */\n\nimport type { EFTimegroup } from \"../elements/EFTimegroup.js\";\nimport type { EFWorkbench } from \"../gui/EFWorkbench.js\";\nimport { getRenderInfo, type RenderInfo } from \"../getRenderInfo.js\";\nimport {\n renderTimegroupToVideo,\n type RenderToVideoOptions,\n type RenderProgress,\n} from \"../preview/renderTimegroupToVideo.js\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface IEFRenderAPI {\n /**\n * Render with streaming output (calls window.onRenderChunk for each chunk).\n * Use this for CLI/Playwright to avoid memory buffering.\n */\n renderStreaming(options?: RenderToVideoOptions): Promise<void>;\n\n /**\n * Render and return buffer (for shorter videos or in-browser use).\n * Returns the video as Uint8Array.\n */\n render(options?: RenderToVideoOptions): Promise<Uint8Array>;\n\n /**\n * Get render info (dimensions, duration, assets).\n * Same as the exported getRenderInfo function.\n */\n getRenderInfo(): Promise<RenderInfo>;\n\n /**\n * Check if SDK is ready for rendering.\n * Returns true if a root timegroup is found.\n */\n isReady(): boolean;\n}\n\ndeclare global {\n interface Window {\n EF_RENDER?: IEFRenderAPI;\n EF_RENDER_DATA?: Record<string, unknown>;\n onRenderChunk?: (chunk: Uint8Array) => void; // Set by Playwright\n onRenderProgress?: (progress: RenderProgress) => void; // Optional progress callback\n }\n}\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\nfunction findRootTimegroup(): EFTimegroup | null {\n // Try to find timegroup from workbench first\n const workbench = document.querySelector(\"ef-workbench\") as EFWorkbench | null;\n if (workbench) {\n const timegroup = workbench.querySelector(\"ef-timegroup\") as EFTimegroup | null;\n if (timegroup) {\n return timegroup;\n }\n }\n\n // Fallback: find first root timegroup\n const rootTimegroup = document.querySelector(\"ef-timegroup\") as EFTimegroup | null;\n return rootTimegroup;\n}\n\nfunction setWorkbenchRendering(rendering: boolean): void {\n const workbench = document.querySelector(\"ef-workbench\") as EFWorkbench | null;\n if (workbench) {\n workbench.rendering = rendering;\n }\n}\n\nconst api: IEFRenderAPI = {\n async renderStreaming(options: RenderToVideoOptions = {}): Promise<void> {\n const timegroup = findRootTimegroup();\n if (!timegroup) {\n throw new Error(\"No ef-timegroup found. Cannot render.\");\n }\n\n // Check if window.onRenderChunk is available\n if (typeof window === \"undefined\" || !window.onRenderChunk) {\n throw new Error(\n \"window.onRenderChunk is not set. \" +\n \"Call page.exposeFunction('onRenderChunk', callback) from Playwright first.\"\n );\n }\n\n // Hide workbench UI during render\n setWorkbenchRendering(true);\n\n try {\n // Wait for media to be ready\n await timegroup.waitForMediaDurations();\n\n // Create custom writable stream that calls window.onRenderChunk\n const chunkWriter = new WritableStream<Uint8Array>({\n write(chunk: Uint8Array) {\n console.error(\"Writing chunk\", chunk);\n if (window.onRenderChunk) {\n window.onRenderChunk(chunk);\n }\n },\n });\n\n // Merge progress callback if window.onRenderProgress is set\n const onProgress = options.onProgress || window.onRenderProgress;\n\n // Render with custom stream\n await renderTimegroupToVideo(timegroup, {\n ...options,\n customWritableStream: chunkWriter,\n onProgress,\n returnBuffer: false,\n });\n } finally {\n // Restore workbench UI\n setWorkbenchRendering(false);\n }\n },\n\n async render(options: RenderToVideoOptions = {}): Promise<Uint8Array> {\n const timegroup = findRootTimegroup();\n if (!timegroup) {\n throw new Error(\"No ef-timegroup found. Cannot render.\");\n }\n\n // Hide workbench UI during render\n setWorkbenchRendering(true);\n\n try {\n // Wait for media to be ready\n await timegroup.waitForMediaDurations();\n\n // Merge progress callback if window.onRenderProgress is set\n const onProgress = options.onProgress || window.onRenderProgress;\n\n const buffer = await renderTimegroupToVideo(timegroup, {\n ...options,\n returnBuffer: true,\n onProgress,\n });\n\n if (!buffer) {\n throw new Error(\"Render failed: no buffer returned\");\n }\n\n return buffer;\n } finally {\n // Restore workbench UI\n setWorkbenchRendering(false);\n }\n },\n\n async getRenderInfo(): Promise<RenderInfo> {\n return getRenderInfo();\n },\n\n isReady(): boolean {\n return findRootTimegroup() !== null;\n },\n};\n\n// Export and register on window\nif (typeof window !== \"undefined\") {\n window.EF_RENDER = api;\n}\n\nexport { api as EFRenderAPI };\nexport type { IEFRenderAPI as EFRenderAPIInterface };\n"],"mappings":";;;;AA2DA,SAAS,oBAAwC;CAE/C,MAAM,YAAY,SAAS,cAAc,eAAe;AACxD,KAAI,WAAW;EACb,MAAM,YAAY,UAAU,cAAc,eAAe;AACzD,MAAI,UACF,QAAO;;AAMX,QADsB,SAAS,cAAc,eAAe;;AAI9D,SAAS,sBAAsB,WAA0B;CACvD,MAAM,YAAY,SAAS,cAAc,eAAe;AACxD,KAAI,UACF,WAAU,YAAY;;AAI1B,MAAMA,MAAoB;CACxB,MAAM,gBAAgB,UAAgC,EAAE,EAAiB;EACvE,MAAM,YAAY,mBAAmB;AACrC,MAAI,CAAC,UACH,OAAM,IAAI,MAAM,wCAAwC;AAI1D,MAAI,OAAO,WAAW,eAAe,CAAC,OAAO,cAC3C,OAAM,IAAI,MACR,8GAED;AAIH,wBAAsB,KAAK;AAE3B,MAAI;AAEF,SAAM,UAAU,uBAAuB;GAGvC,MAAM,cAAc,IAAI,eAA2B,EACjD,MAAM,OAAmB;AACvB,YAAQ,MAAM,iBAAiB,MAAM;AACrC,QAAI,OAAO,cACT,QAAO,cAAc,MAAM;MAGhC,CAAC;GAGF,MAAM,aAAa,QAAQ,cAAc,OAAO;AAGhD,SAAM,uBAAuB,WAAW;IACtC,GAAG;IACH,sBAAsB;IACtB;IACA,cAAc;IACf,CAAC;YACM;AAER,yBAAsB,MAAM;;;CAIhC,MAAM,OAAO,UAAgC,EAAE,EAAuB;EACpE,MAAM,YAAY,mBAAmB;AACrC,MAAI,CAAC,UACH,OAAM,IAAI,MAAM,wCAAwC;AAI1D,wBAAsB,KAAK;AAE3B,MAAI;AAEF,SAAM,UAAU,uBAAuB;GAGvC,MAAM,aAAa,QAAQ,cAAc,OAAO;GAEhD,MAAM,SAAS,MAAM,uBAAuB,WAAW;IACrD,GAAG;IACH,cAAc;IACd;IACD,CAAC;AAEF,OAAI,CAAC,OACH,OAAM,IAAI,MAAM,oCAAoC;AAGtD,UAAO;YACC;AAER,yBAAsB,MAAM;;;CAIhC,MAAM,gBAAqC;AACzC,SAAO,eAAe;;CAGxB,UAAmB;AACjB,SAAO,mBAAmB,KAAK;;CAElC;AAGD,IAAI,OAAO,WAAW,YACpB,QAAO,YAAY"}
1
+ {"version":3,"file":"EFRenderAPI.js","names":["api: IEFRenderAPI"],"sources":["../../src/render/EFRenderAPI.ts"],"sourcesContent":["/**\n * Window API for programmatic video rendering.\n * \n * Exposes renderTimegroupToVideo for use from Playwright/CLI.\n * Supports streaming output and custom data injection.\n */\n\nimport type { EFTimegroup } from \"../elements/EFTimegroup.js\";\nimport type { EFWorkbench } from \"../gui/EFWorkbench.js\";\nimport { getRenderInfo, type RenderInfo } from \"../getRenderInfo.js\";\nimport {\n renderTimegroupToVideo,\n type RenderToVideoOptions,\n type RenderProgress,\n} from \"../preview/renderTimegroupToVideo.js\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface IEFRenderAPI {\n /**\n * Render with streaming output (calls window.onRenderChunk for each chunk).\n * Use this for CLI/Playwright to avoid memory buffering.\n */\n renderStreaming(options?: RenderToVideoOptions): Promise<void>;\n\n /**\n * Render and return buffer (for shorter videos or in-browser use).\n * Returns the video as Uint8Array.\n */\n render(options?: RenderToVideoOptions): Promise<Uint8Array>;\n\n /**\n * Get render info (dimensions, duration, assets).\n * Same as the exported getRenderInfo function.\n */\n getRenderInfo(): Promise<RenderInfo>;\n\n /**\n * Check if SDK is ready for rendering.\n * Returns true if a root timegroup is found.\n */\n isReady(): boolean;\n}\n\ndeclare global {\n interface Window {\n EF_RENDER?: IEFRenderAPI;\n EF_RENDER_DATA?: Record<string, unknown>;\n onRenderChunk?: (chunk: Uint8Array) => void; // Set by Playwright\n onRenderProgress?: (progress: RenderProgress) => void; // Optional progress callback\n }\n}\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\nfunction findRootTimegroup(): EFTimegroup | null {\n // Try to find timegroup from workbench first\n const workbench = document.querySelector(\"ef-workbench\") as EFWorkbench | null;\n if (workbench) {\n const timegroup = workbench.querySelector(\"ef-timegroup\") as EFTimegroup | null;\n if (timegroup) {\n return timegroup;\n }\n }\n\n // Fallback: find first root timegroup\n const rootTimegroup = document.querySelector(\"ef-timegroup\") as EFTimegroup | null;\n return rootTimegroup;\n}\n\nfunction setWorkbenchRendering(rendering: boolean): void {\n const workbench = document.querySelector(\"ef-workbench\") as EFWorkbench | null;\n if (workbench) {\n workbench.rendering = rendering;\n }\n}\n\nasync function waitForTimegroupDimensions(timegroup: EFTimegroup): Promise<void> {\n // Wait for all stylesheets to load first\n console.log('[EFRenderAPI] Waiting for stylesheets to load...');\n console.log(`[EFRenderAPI] Found ${document.styleSheets.length} stylesheets`);\n \n const styleLinks = Array.from(document.querySelectorAll('link[rel=\"stylesheet\"]'));\n console.log(`[EFRenderAPI] Found ${styleLinks.length} stylesheet <link> elements`);\n styleLinks.forEach((link, i) => {\n const href = (link as HTMLLinkElement).href;\n const sheet = (link as HTMLLinkElement).sheet;\n console.log(`[EFRenderAPI] [${i}] ${href}`);\n try {\n const rulesCount = sheet ? sheet.cssRules.length : 0;\n console.log(`[EFRenderAPI] loaded: ${!!sheet}, rules: ${rulesCount}`);\n if (sheet && sheet.cssRules.length > 0) {\n // Log first few rules to see what CSS is loaded\n const firstRules = Array.from(sheet.cssRules).slice(0, 5).map(r => r.cssText.substring(0, 100));\n console.log(`[EFRenderAPI] first rules:`, firstRules);\n \n // Search for the specific Tailwind classes we need\n const hasWidthClass = Array.from(sheet.cssRules).some(r => \n r.cssText.includes('w-\\\\[1080px\\\\]') || r.cssText.includes('width: 1080px')\n );\n console.log(`[EFRenderAPI] has w-[1080px] class: ${hasWidthClass}`);\n }\n } catch (e) {\n console.log(`[EFRenderAPI] Error reading stylesheet rules:`, e);\n }\n });\n \n await Promise.all(\n Array.from(document.styleSheets).map((sheet) => {\n if (sheet.href) {\n // Check if stylesheet is from a <link> tag and wait for it\n const link = Array.from(document.querySelectorAll('link[rel=\"stylesheet\"]')).find(\n (l) => (l as HTMLLinkElement).href === sheet.href\n );\n if (link && !(link as HTMLLinkElement).sheet) {\n console.log(`[EFRenderAPI] Waiting for stylesheet: ${sheet.href}`);\n return new Promise((resolve) => {\n link.addEventListener('load', resolve);\n link.addEventListener('error', resolve);\n });\n }\n }\n return Promise.resolve();\n })\n );\n \n // Force layout immediately after stylesheets load\n void timegroup.offsetHeight;\n \n if (!timegroup.offsetWidth || !timegroup.offsetHeight) {\n const computedWidth = getComputedStyle(timegroup).width;\n const computedHeight = getComputedStyle(timegroup).height;\n \n throw new Error(\n `Timegroup has no dimensions (${timegroup.offsetWidth}x${timegroup.offsetHeight}). ` +\n `Computed styles: width=${computedWidth}, height=${computedHeight}. ` +\n `Classes: \"${timegroup.className}\". ` +\n `\\n\\nTailwind CSS did not generate styles for these classes. ` +\n `Check that:\\n` +\n `1. Your Tailwind config 'content' array includes the HTML file\\n` +\n `2. Tailwind CSS is properly configured in your project\\n` +\n `3. The dev server successfully compiled CSS (check for Tailwind warnings above)`\n );\n }\n \n console.log(`[EFRenderAPI] Timegroup dimensions ready: ${timegroup.offsetWidth}x${timegroup.offsetHeight}`);\n}\n\nconst api: IEFRenderAPI = {\n async renderStreaming(options: RenderToVideoOptions = {}): Promise<void> {\n const timegroup = findRootTimegroup();\n if (!timegroup) {\n throw new Error(\"No ef-timegroup found. Cannot render.\");\n }\n\n // Check if window.onRenderChunk is available\n if (typeof window === \"undefined\" || !window.onRenderChunk) {\n throw new Error(\n \"window.onRenderChunk is not set. \" +\n \"Call page.exposeFunction('onRenderChunk', callback) from Playwright first.\"\n );\n }\n\n // Hide workbench UI during render\n setWorkbenchRendering(true);\n\n try {\n // Wait for timegroup to have dimensions\n await waitForTimegroupDimensions(timegroup);\n \n // Wait for media to be ready\n await timegroup.waitForMediaDurations();\n\n // Create custom writable stream that calls window.onRenderChunk\n const chunkWriter = new WritableStream<Uint8Array>({\n write(chunk: Uint8Array) {\n console.error(\"Writing chunk\", chunk);\n if (window.onRenderChunk) {\n window.onRenderChunk(chunk);\n }\n },\n });\n\n // Merge progress callback if window.onRenderProgress is set\n const onProgress = options.onProgress || window.onRenderProgress;\n\n // Render with custom stream\n await renderTimegroupToVideo(timegroup, {\n ...options,\n customWritableStream: chunkWriter,\n onProgress,\n returnBuffer: false,\n });\n } finally {\n // Restore workbench UI\n setWorkbenchRendering(false);\n }\n },\n\n async render(options: RenderToVideoOptions = {}): Promise<Uint8Array> {\n const timegroup = findRootTimegroup();\n if (!timegroup) {\n throw new Error(\"No ef-timegroup found. Cannot render.\");\n }\n\n // Hide workbench UI during render\n setWorkbenchRendering(true);\n\n try {\n // Wait for timegroup to have dimensions\n await waitForTimegroupDimensions(timegroup);\n \n // Wait for media to be ready\n await timegroup.waitForMediaDurations();\n\n // Merge progress callback if window.onRenderProgress is set\n const onProgress = options.onProgress || window.onRenderProgress;\n\n const buffer = await renderTimegroupToVideo(timegroup, {\n ...options,\n returnBuffer: true,\n onProgress,\n });\n\n if (!buffer) {\n throw new Error(\"Render failed: no buffer returned\");\n }\n\n return buffer;\n } finally {\n // Restore workbench UI\n setWorkbenchRendering(false);\n }\n },\n\n async getRenderInfo(): Promise<RenderInfo> {\n return getRenderInfo();\n },\n\n isReady(): boolean {\n return findRootTimegroup() !== null;\n },\n};\n\n// Export and register on window\nif (typeof window !== \"undefined\") {\n window.EF_RENDER = api;\n}\n\nexport { api as EFRenderAPI };\nexport type { IEFRenderAPI as EFRenderAPIInterface };\n"],"mappings":"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"}
package/dist/style.css CHANGED
@@ -565,6 +565,9 @@ video {
565
565
  .h-\[1\.1rem\] {
566
566
  height: 1.1rem;
567
567
  }
568
+ .h-\[1080px\] {
569
+ height: 1080px;
570
+ }
568
571
  .h-\[200px\] {
569
572
  height: 200px;
570
573
  }
@@ -595,9 +598,18 @@ video {
595
598
  .w-\[1000px\] {
596
599
  width: 1000px;
597
600
  }
601
+ .w-\[1080px\] {
602
+ width: 1080px;
603
+ }
604
+ .w-\[1920px\] {
605
+ width: 1920px;
606
+ }
598
607
  .w-\[200px\] {
599
608
  width: 200px;
600
609
  }
610
+ .w-\[420px\] {
611
+ width: 420px;
612
+ }
601
613
  .w-\[480px\] {
602
614
  width: 480px;
603
615
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@editframe/elements",
3
- "version": "0.35.0-beta",
3
+ "version": "0.36.0-beta",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -13,7 +13,7 @@
13
13
  "license": "UNLICENSED",
14
14
  "dependencies": {
15
15
  "@bramus/style-observer": "^1.3.0",
16
- "@editframe/assets": "0.35.0-beta",
16
+ "@editframe/assets": "0.36.0-beta",
17
17
  "@lit/context": "^1.1.6",
18
18
  "@opentelemetry/api": "^1.9.0",
19
19
  "@opentelemetry/context-zone": "^1.26.0",