@marimo-team/islands 0.19.7-dev40 → 0.19.7-dev42
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.
- package/dist/main.js +5 -5
- package/package.json +1 -1
- package/src/core/export/hooks.ts +3 -4
- package/src/utils/__tests__/download.test.tsx +71 -44
- package/src/utils/download.ts +24 -35
package/dist/main.js
CHANGED
|
@@ -61452,10 +61452,10 @@ ${r}
|
|
|
61452
61452
|
}))).join("\n");
|
|
61453
61453
|
}
|
|
61454
61454
|
async function embedWebFonts(e, r) {
|
|
61455
|
-
let c = r.fontEmbedCSS == null ? r.skipFonts ? null : await getWebFontCSS(e, r) : r.fontEmbedCSS;
|
|
61456
|
-
if (
|
|
61457
|
-
let r2 = document.createElement("style"),
|
|
61458
|
-
r2.appendChild(
|
|
61455
|
+
let c = r.fontEmbedCSS == null ? r.skipFonts ? null : await getWebFontCSS(e, r) : r.fontEmbedCSS, d = r.extraStyleContent == null ? c : r.extraStyleContent.concat(c || "");
|
|
61456
|
+
if (d) {
|
|
61457
|
+
let r2 = document.createElement("style"), c2 = document.createTextNode(d);
|
|
61458
|
+
r2.appendChild(c2), e.firstChild ? e.insertBefore(r2, e.firstChild) : e.appendChild(r2);
|
|
61459
61459
|
}
|
|
61460
61460
|
}
|
|
61461
61461
|
async function toSvg(e, r = {}) {
|
|
@@ -73168,7 +73168,7 @@ Image URL: ${r.imageUrl}`)), contextToXml({
|
|
|
73168
73168
|
return Logger.warn("Failed to get version from mount config"), null;
|
|
73169
73169
|
}
|
|
73170
73170
|
}
|
|
73171
|
-
const marimoVersionAtom = atom(getVersionFromMountConfig() || "0.19.7-
|
|
73171
|
+
const marimoVersionAtom = atom(getVersionFromMountConfig() || "0.19.7-dev42"), showCodeInRunModeAtom = atom(true);
|
|
73172
73172
|
atom(null);
|
|
73173
73173
|
var import_compiler_runtime$88 = require_compiler_runtime();
|
|
73174
73174
|
function useKeydownOnElement(e, r) {
|
package/package.json
CHANGED
package/src/core/export/hooks.ts
CHANGED
|
@@ -194,10 +194,9 @@ export function useEnrichCellOutputs(): (
|
|
|
194
194
|
inFlightWaiters.map(({ promise }) => promise),
|
|
195
195
|
);
|
|
196
196
|
for (const [i, { cellId }] of inFlightWaiters.entries()) {
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
results[cellId] = result;
|
|
197
|
+
const settledResult = settled[i];
|
|
198
|
+
if (settledResult.status === "fulfilled" && settledResult.value) {
|
|
199
|
+
results[cellId] = settledResult.value;
|
|
201
200
|
}
|
|
202
201
|
}
|
|
203
202
|
|
|
@@ -166,13 +166,63 @@ describe("getImageDataUrlForCell", () => {
|
|
|
166
166
|
);
|
|
167
167
|
});
|
|
168
168
|
|
|
169
|
-
it("should
|
|
169
|
+
it("should pass style options to prevent clipping", async () => {
|
|
170
|
+
vi.mocked(toPng).mockResolvedValue(mockDataUrl);
|
|
171
|
+
|
|
172
|
+
await getImageDataUrlForCell("cell-1" as CellId);
|
|
173
|
+
|
|
174
|
+
expect(toPng).toHaveBeenCalledWith(
|
|
175
|
+
mockElement,
|
|
176
|
+
expect.objectContaining({
|
|
177
|
+
style: {
|
|
178
|
+
maxHeight: "none",
|
|
179
|
+
overflow: "visible",
|
|
180
|
+
},
|
|
181
|
+
}),
|
|
182
|
+
);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("should pass scrollHeight as height option", async () => {
|
|
186
|
+
// Set up element with scrollHeight
|
|
187
|
+
Object.defineProperty(mockElement, "scrollHeight", {
|
|
188
|
+
value: 500,
|
|
189
|
+
configurable: true,
|
|
190
|
+
});
|
|
191
|
+
vi.mocked(toPng).mockResolvedValue(mockDataUrl);
|
|
192
|
+
|
|
193
|
+
await getImageDataUrlForCell("cell-1" as CellId);
|
|
194
|
+
|
|
195
|
+
expect(toPng).toHaveBeenCalledWith(
|
|
196
|
+
mockElement,
|
|
197
|
+
expect.objectContaining({
|
|
198
|
+
height: 500,
|
|
199
|
+
}),
|
|
200
|
+
);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it("should pass scrollbar hiding styles via extraStyleContent", async () => {
|
|
204
|
+
vi.mocked(toPng).mockResolvedValue(mockDataUrl);
|
|
205
|
+
|
|
206
|
+
await getImageDataUrlForCell("cell-1" as CellId);
|
|
207
|
+
|
|
208
|
+
expect(toPng).toHaveBeenCalledWith(
|
|
209
|
+
mockElement,
|
|
210
|
+
expect.objectContaining({
|
|
211
|
+
extraStyleContent: expect.stringContaining("scrollbar-width: none"),
|
|
212
|
+
}),
|
|
213
|
+
);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it("should not modify the live DOM element", async () => {
|
|
170
217
|
mockElement.style.overflow = "hidden";
|
|
218
|
+
mockElement.style.maxHeight = "100px";
|
|
171
219
|
vi.mocked(toPng).mockResolvedValue(mockDataUrl);
|
|
172
220
|
|
|
173
221
|
await getImageDataUrlForCell("cell-1" as CellId);
|
|
174
222
|
|
|
223
|
+
// DOM should remain unchanged
|
|
175
224
|
expect(mockElement.style.overflow).toBe("hidden");
|
|
225
|
+
expect(mockElement.style.maxHeight).toBe("100px");
|
|
176
226
|
});
|
|
177
227
|
|
|
178
228
|
it("should throw error on failure", async () => {
|
|
@@ -183,34 +233,20 @@ describe("getImageDataUrlForCell", () => {
|
|
|
183
233
|
);
|
|
184
234
|
});
|
|
185
235
|
|
|
186
|
-
it("should cleanup even on failure", async () => {
|
|
187
|
-
mockElement.style.overflow = "scroll";
|
|
188
|
-
vi.mocked(toPng).mockRejectedValue(new Error("Capture failed"));
|
|
189
|
-
|
|
190
|
-
await expect(getImageDataUrlForCell("cell-1" as CellId)).rejects.toThrow();
|
|
191
|
-
|
|
192
|
-
expect(document.body.classList.contains("printing")).toBe(false);
|
|
193
|
-
expect(mockElement.style.overflow).toBe("scroll");
|
|
194
|
-
});
|
|
195
|
-
|
|
196
236
|
it("should handle concurrent captures correctly", async () => {
|
|
197
237
|
// Create a second element
|
|
198
238
|
const mockElement2 = document.createElement("div");
|
|
199
239
|
mockElement2.id = CellOutputId.create("cell-2" as CellId);
|
|
200
240
|
document.body.append(mockElement2);
|
|
201
241
|
|
|
202
|
-
vi.mocked(toPng).
|
|
203
|
-
// body.printing should not be added during cell captures
|
|
204
|
-
expect(document.body.classList.contains("printing")).toBe(false);
|
|
205
|
-
return mockDataUrl;
|
|
206
|
-
});
|
|
242
|
+
vi.mocked(toPng).mockResolvedValue(mockDataUrl);
|
|
207
243
|
|
|
208
244
|
const capture1 = getImageDataUrlForCell("cell-1" as CellId);
|
|
209
245
|
const capture2 = getImageDataUrlForCell("cell-2" as CellId);
|
|
210
246
|
|
|
211
247
|
await Promise.all([capture1, capture2]);
|
|
212
248
|
|
|
213
|
-
expect(
|
|
249
|
+
expect(toPng).toHaveBeenCalledTimes(2);
|
|
214
250
|
|
|
215
251
|
mockElement2.remove();
|
|
216
252
|
});
|
|
@@ -266,23 +302,6 @@ describe("downloadHTMLAsImage", () => {
|
|
|
266
302
|
expect(mockAnchor.click).toHaveBeenCalled();
|
|
267
303
|
});
|
|
268
304
|
|
|
269
|
-
it("should add body.printing class without prepare function", async () => {
|
|
270
|
-
vi.mocked(toPng).mockImplementation(async () => {
|
|
271
|
-
expect(document.body.classList.contains("printing")).toBe(true);
|
|
272
|
-
return mockDataUrl;
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
await downloadHTMLAsImage({ element: mockElement, filename: "test" });
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
it("should remove body.printing class after download without prepare", async () => {
|
|
279
|
-
vi.mocked(toPng).mockResolvedValue(mockDataUrl);
|
|
280
|
-
|
|
281
|
-
await downloadHTMLAsImage({ element: mockElement, filename: "test" });
|
|
282
|
-
|
|
283
|
-
expect(document.body.classList.contains("printing")).toBe(false);
|
|
284
|
-
});
|
|
285
|
-
|
|
286
305
|
it("should use prepare function when provided", async () => {
|
|
287
306
|
vi.mocked(toPng).mockResolvedValue(mockDataUrl);
|
|
288
307
|
const cleanup = vi.fn();
|
|
@@ -406,24 +425,32 @@ describe("downloadCellOutputAsImage", () => {
|
|
|
406
425
|
expect(mockAnchor.download).toBe("result.png");
|
|
407
426
|
});
|
|
408
427
|
|
|
409
|
-
it("should
|
|
410
|
-
vi.mocked(toPng).
|
|
411
|
-
// Check that cell-specific classes are applied
|
|
412
|
-
expect(mockElement.style.overflow).toBe("visible");
|
|
413
|
-
return mockDataUrl;
|
|
414
|
-
});
|
|
428
|
+
it("should pass style options to toPng for full content capture", async () => {
|
|
429
|
+
vi.mocked(toPng).mockResolvedValue(mockDataUrl);
|
|
415
430
|
|
|
416
431
|
await downloadCellOutputAsImage("cell-1" as CellId, "result");
|
|
432
|
+
|
|
433
|
+
expect(toPng).toHaveBeenCalledWith(
|
|
434
|
+
mockElement,
|
|
435
|
+
expect.objectContaining({
|
|
436
|
+
style: {
|
|
437
|
+
maxHeight: "none",
|
|
438
|
+
overflow: "visible",
|
|
439
|
+
},
|
|
440
|
+
}),
|
|
441
|
+
);
|
|
417
442
|
});
|
|
418
443
|
|
|
419
|
-
it("should
|
|
420
|
-
mockElement.style.overflow = "
|
|
444
|
+
it("should not modify the live DOM element", async () => {
|
|
445
|
+
mockElement.style.overflow = "hidden";
|
|
446
|
+
mockElement.style.maxHeight = "100px";
|
|
421
447
|
vi.mocked(toPng).mockResolvedValue(mockDataUrl);
|
|
422
448
|
|
|
423
449
|
await downloadCellOutputAsImage("cell-1" as CellId, "result");
|
|
424
450
|
|
|
425
|
-
|
|
426
|
-
expect(mockElement.style.overflow).toBe("
|
|
451
|
+
// DOM should remain unchanged
|
|
452
|
+
expect(mockElement.style.overflow).toBe("hidden");
|
|
453
|
+
expect(mockElement.style.maxHeight).toBe("100px");
|
|
427
454
|
});
|
|
428
455
|
});
|
|
429
456
|
|
package/src/utils/download.ts
CHANGED
|
@@ -46,25 +46,11 @@ function findElementForCell(cellId: CellId): HTMLElement | undefined {
|
|
|
46
46
|
return element;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
/**
|
|
50
|
-
* Prepare a cell element for screenshot capture.
|
|
51
|
-
*
|
|
52
|
-
* @param element - The cell output element to prepare
|
|
53
|
-
* @returns A cleanup function to restore the element's original state
|
|
54
|
-
*/
|
|
55
|
-
function prepareCellElementForScreenshot(element: HTMLElement) {
|
|
56
|
-
const originalOverflow = element.style.overflow;
|
|
57
|
-
const maxHeight = element.style.maxHeight;
|
|
58
|
-
element.style.overflow = "visible";
|
|
59
|
-
element.style.maxHeight = "none";
|
|
60
|
-
|
|
61
|
-
return () => {
|
|
62
|
-
element.style.overflow = originalOverflow;
|
|
63
|
-
element.style.maxHeight = maxHeight;
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
|
|
67
49
|
const THRESHOLD_TIME_MS = 500;
|
|
50
|
+
const HIDE_SCROLLBAR_STYLES = `
|
|
51
|
+
* { scrollbar-width: none; -ms-overflow-style: none; }
|
|
52
|
+
*::-webkit-scrollbar { display: none; }
|
|
53
|
+
`;
|
|
68
54
|
|
|
69
55
|
/**
|
|
70
56
|
* Capture a cell output as a PNG data URL.
|
|
@@ -85,23 +71,26 @@ export async function getImageDataUrlForCell(
|
|
|
85
71
|
return iframeDataUrl;
|
|
86
72
|
}
|
|
87
73
|
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
74
|
+
const startTime = Date.now();
|
|
75
|
+
const dataUrl = await toPng(element, {
|
|
76
|
+
extraStyleContent: HIDE_SCROLLBAR_STYLES,
|
|
77
|
+
// Add these styles so the element output is not clipped
|
|
78
|
+
// Width can be clipped since pdf has limited width
|
|
79
|
+
style: {
|
|
80
|
+
maxHeight: "none",
|
|
81
|
+
overflow: "visible",
|
|
82
|
+
},
|
|
83
|
+
height: element.scrollHeight,
|
|
84
|
+
});
|
|
85
|
+
const timeTaken = Date.now() - startTime;
|
|
86
|
+
if (timeTaken > THRESHOLD_TIME_MS) {
|
|
87
|
+
Logger.debug(
|
|
88
|
+
"toPng operation for element",
|
|
89
|
+
element,
|
|
90
|
+
`took ${timeTaken} ms (exceeds threshold)`,
|
|
91
|
+
);
|
|
104
92
|
}
|
|
93
|
+
return dataUrl;
|
|
105
94
|
}
|
|
106
95
|
|
|
107
96
|
/**
|
|
@@ -115,7 +104,7 @@ export async function downloadCellOutputAsImage(
|
|
|
115
104
|
if (!dataUrl) {
|
|
116
105
|
return;
|
|
117
106
|
}
|
|
118
|
-
|
|
107
|
+
downloadByURL(dataUrl, Filenames.toPNG(filename));
|
|
119
108
|
}
|
|
120
109
|
|
|
121
110
|
export const ADD_PRINTING_CLASS = (): (() => void) => {
|