@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 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 (c) {
61457
- let r2 = document.createElement("style"), d = document.createTextNode(c);
61458
- r2.appendChild(d), e.firstChild ? e.insertBefore(r2, e.firstChild) : e.appendChild(r2);
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-dev40"), showCodeInRunModeAtom = atom(true);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marimo-team/islands",
3
- "version": "0.19.7-dev40",
3
+ "version": "0.19.7-dev42",
4
4
  "main": "dist/main.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -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 result =
198
- settled[i].status === "fulfilled" ? settled[i].value : undefined;
199
- if (result) {
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 restore original overflow style after capture", async () => {
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).mockImplementation(async () => {
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(document.body.classList.contains("printing")).toBe(false);
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 apply cell-specific preparation", async () => {
410
- vi.mocked(toPng).mockImplementation(async () => {
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 cleanup after download", async () => {
420
- mockElement.style.overflow = "visible";
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
- expect(document.body.classList.contains("printing")).toBe(false);
426
- expect(mockElement.style.overflow).toBe("visible");
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
 
@@ -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 cleanup = prepareCellElementForScreenshot(element);
89
-
90
- try {
91
- const startTime = Date.now();
92
- const dataUrl = await toPng(element);
93
- const timeTaken = Date.now() - startTime;
94
- if (timeTaken > THRESHOLD_TIME_MS) {
95
- Logger.debug(
96
- "toPng operation for element",
97
- element,
98
- `took ${timeTaken} ms (exceeds threshold)`,
99
- );
100
- }
101
- return dataUrl;
102
- } finally {
103
- cleanup();
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
- return downloadByURL(dataUrl, Filenames.toPNG(filename));
107
+ downloadByURL(dataUrl, Filenames.toPNG(filename));
119
108
  }
120
109
 
121
110
  export const ADD_PRINTING_CLASS = (): (() => void) => {