@marimo-team/islands 0.19.7-dev10 → 0.19.7-dev15

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.
@@ -132,7 +132,7 @@ export function useEnrichCellOutputs() {
132
132
  const results = await Promise.all(
133
133
  cellsToCaptureScreenshot.map(async ([cellId]) => {
134
134
  try {
135
- const dataUrl = await getImageDataUrlForCell(cellId);
135
+ const dataUrl = await getImageDataUrlForCell(cellId, false);
136
136
  if (!dataUrl) {
137
137
  Logger.error(`Failed to capture screenshot for cell ${cellId}`);
138
138
  return null;
@@ -158,7 +158,7 @@ describe("getImageDataUrlForCell", () => {
158
158
  expect(toPng).toHaveBeenCalledWith(mockElement);
159
159
  });
160
160
 
161
- it("should add printing classes before capture", async () => {
161
+ it("should add printing classes before capture when enablePrintMode is true", async () => {
162
162
  vi.mocked(toPng).mockImplementation(async () => {
163
163
  // Check classes are applied during capture
164
164
  expect(mockElement.classList.contains("printing-output")).toBe(true);
@@ -167,18 +167,42 @@ describe("getImageDataUrlForCell", () => {
167
167
  return mockDataUrl;
168
168
  });
169
169
 
170
- await getImageDataUrlForCell("cell-1" as CellId);
170
+ await getImageDataUrlForCell("cell-1" as CellId, true);
171
171
  });
172
172
 
173
- it("should remove printing classes after capture", async () => {
173
+ it("should remove printing classes after capture when enablePrintMode is true", async () => {
174
174
  vi.mocked(toPng).mockResolvedValue(mockDataUrl);
175
175
 
176
- await getImageDataUrlForCell("cell-1" as CellId);
176
+ await getImageDataUrlForCell("cell-1" as CellId, true);
177
177
 
178
178
  expect(mockElement.classList.contains("printing-output")).toBe(false);
179
179
  expect(document.body.classList.contains("printing")).toBe(false);
180
180
  });
181
181
 
182
+ it("should add printing-output but NOT body.printing when enablePrintMode is false", async () => {
183
+ vi.mocked(toPng).mockImplementation(async () => {
184
+ // printing-output should still be added to the element
185
+ expect(mockElement.classList.contains("printing-output")).toBe(true);
186
+ // but body.printing should NOT be added
187
+ expect(document.body.classList.contains("printing")).toBe(false);
188
+ expect(mockElement.style.overflow).toBe("auto");
189
+ return mockDataUrl;
190
+ });
191
+
192
+ await getImageDataUrlForCell("cell-1" as CellId, false);
193
+ });
194
+
195
+ it("should cleanup printing-output when enablePrintMode is false", async () => {
196
+ mockElement.style.overflow = "hidden";
197
+ vi.mocked(toPng).mockResolvedValue(mockDataUrl);
198
+
199
+ await getImageDataUrlForCell("cell-1" as CellId, false);
200
+
201
+ expect(mockElement.classList.contains("printing-output")).toBe(false);
202
+ expect(document.body.classList.contains("printing")).toBe(false);
203
+ expect(mockElement.style.overflow).toBe("hidden");
204
+ });
205
+
182
206
  it("should restore original overflow style after capture", async () => {
183
207
  mockElement.style.overflow = "hidden";
184
208
  vi.mocked(toPng).mockResolvedValue(mockDataUrl);
@@ -207,7 +231,7 @@ describe("getImageDataUrlForCell", () => {
207
231
  expect(mockElement.style.overflow).toBe("scroll");
208
232
  });
209
233
 
210
- it("should maintain body.printing during concurrent captures", async () => {
234
+ it("should maintain body.printing during concurrent captures when enablePrintMode is true", async () => {
211
235
  // Create a second element
212
236
  const mockElement2 = document.createElement("div");
213
237
  mockElement2.id = CellOutputId.create("cell-2" as CellId);
@@ -241,9 +265,9 @@ describe("getImageDataUrlForCell", () => {
241
265
  return mockDataUrl;
242
266
  });
243
267
 
244
- // Start both captures concurrently
245
- const capture1 = getImageDataUrlForCell("cell-1" as CellId);
246
- const capture2 = getImageDataUrlForCell("cell-2" as CellId);
268
+ // Start both captures concurrently with enablePrintMode = true
269
+ const capture1 = getImageDataUrlForCell("cell-1" as CellId, true);
270
+ const capture2 = getImageDataUrlForCell("cell-2" as CellId, true);
247
271
 
248
272
  // Let second capture complete first
249
273
  resolveSecond!();
@@ -264,6 +288,30 @@ describe("getImageDataUrlForCell", () => {
264
288
 
265
289
  mockElement2.remove();
266
290
  });
291
+
292
+ it("should not interfere with body.printing during concurrent captures when enablePrintMode is false", async () => {
293
+ // Create a second element
294
+ const mockElement2 = document.createElement("div");
295
+ mockElement2.id = CellOutputId.create("cell-2" as CellId);
296
+ document.body.append(mockElement2);
297
+
298
+ vi.mocked(toPng).mockImplementation(async () => {
299
+ // body.printing should never be added when enablePrintMode is false
300
+ expect(document.body.classList.contains("printing")).toBe(false);
301
+ return mockDataUrl;
302
+ });
303
+
304
+ // Start both captures concurrently with enablePrintMode = false
305
+ const capture1 = getImageDataUrlForCell("cell-1" as CellId, false);
306
+ const capture2 = getImageDataUrlForCell("cell-2" as CellId, false);
307
+
308
+ await Promise.all([capture1, capture2]);
309
+
310
+ // body.printing should still not be present
311
+ expect(document.body.classList.contains("printing")).toBe(false);
312
+
313
+ mockElement2.remove();
314
+ });
267
315
  });
268
316
 
269
317
  describe("downloadHTMLAsImage", () => {
@@ -342,7 +390,7 @@ describe("downloadHTMLAsImage", () => {
342
390
  expect(cleanup).toHaveBeenCalled();
343
391
  });
344
392
 
345
- it("should not add body.printing when prepare is provided", async () => {
393
+ it("should delegate body.printing management to prepare function", async () => {
346
394
  let bodyPrintingDuringCapture = false;
347
395
  vi.mocked(toPng).mockImplementation(async () => {
348
396
  // Capture the state during toPng execution
@@ -350,7 +398,14 @@ describe("downloadHTMLAsImage", () => {
350
398
  return mockDataUrl;
351
399
  });
352
400
  const cleanup = vi.fn();
353
- const prepare = vi.fn().mockReturnValue(cleanup);
401
+ // Mock prepare that adds body.printing
402
+ const prepare = vi.fn().mockImplementation(() => {
403
+ document.body.classList.add("printing");
404
+ return () => {
405
+ document.body.classList.remove("printing");
406
+ cleanup();
407
+ };
408
+ });
354
409
 
355
410
  await downloadHTMLAsImage({
356
411
  element: mockElement,
@@ -358,9 +413,8 @@ describe("downloadHTMLAsImage", () => {
358
413
  prepare,
359
414
  });
360
415
 
361
- // body.printing should NOT be added by downloadHTMLAsImage when prepare is provided
362
- // (the prepare function is responsible for managing its own classes)
363
- expect(bodyPrintingDuringCapture).toBe(false);
416
+ // body.printing should be added by prepare function
417
+ expect(bodyPrintingDuringCapture).toBe(true);
364
418
  expect(document.body.classList.contains("printing")).toBe(false);
365
419
  expect(prepare).toHaveBeenCalledWith(mockElement);
366
420
  expect(cleanup).toHaveBeenCalled();
@@ -59,34 +59,51 @@ function releaseBodyPrinting() {
59
59
  }
60
60
  }
61
61
 
62
- /*
62
+ /**
63
63
  * Prepare a cell element for screenshot capture.
64
- * Returns a cleanup function that should be called when the screenshot is complete.
64
+ *
65
+ * @param element - The cell output element to prepare
66
+ * @param enablePrintMode - When true, adds a 'printing' class to the body.
67
+ * This can cause layout shifts that cause the page to scroll.
68
+ * @returns A cleanup function to restore the element's original state
65
69
  */
66
- function prepareCellElementForScreenshot(element: HTMLElement) {
70
+ function prepareCellElementForScreenshot(
71
+ element: HTMLElement,
72
+ enablePrintMode: boolean,
73
+ ) {
67
74
  element.classList.add("printing-output");
68
- acquireBodyPrinting();
75
+ if (enablePrintMode) {
76
+ acquireBodyPrinting();
77
+ }
69
78
  const originalOverflow = element.style.overflow;
70
79
  element.style.overflow = "auto";
71
80
 
72
81
  return () => {
73
82
  element.classList.remove("printing-output");
74
- releaseBodyPrinting();
83
+ if (enablePrintMode) {
84
+ releaseBodyPrinting();
85
+ }
75
86
  element.style.overflow = originalOverflow;
76
87
  };
77
88
  }
78
89
 
79
90
  /**
80
91
  * Capture a cell output as a PNG data URL.
92
+ *
93
+ * @param cellId - The ID of the cell to capture
94
+ * @param enablePrintMode - When true, enables print mode which adds a 'printing' class to the body.
95
+ * This can cause layout shifts that cause the page to scroll.
96
+ * @returns The PNG as a data URL, or undefined if the cell element wasn't found
81
97
  */
82
98
  export async function getImageDataUrlForCell(
83
99
  cellId: CellId,
100
+ enablePrintMode = true,
84
101
  ): Promise<string | undefined> {
85
102
  const element = findElementForCell(cellId);
86
103
  if (!element) {
87
104
  return;
88
105
  }
89
- const cleanup = prepareCellElementForScreenshot(element);
106
+ const cleanup = prepareCellElementForScreenshot(element, enablePrintMode);
90
107
 
91
108
  try {
92
109
  return await toPng(element);
@@ -110,7 +127,7 @@ export async function downloadCellOutputAsImage(
110
127
  await downloadHTMLAsImage({
111
128
  element,
112
129
  filename,
113
- prepare: prepareCellElementForScreenshot,
130
+ prepare: () => prepareCellElementForScreenshot(element, true),
114
131
  });
115
132
  }
116
133
 
@@ -127,7 +144,6 @@ export async function downloadHTMLAsImage(opts: {
127
144
 
128
145
  let cleanup: (() => void) | undefined;
129
146
  if (prepare) {
130
- // Let the prepare function handle adding classes (e.g., body.printing)
131
147
  cleanup = prepare(element);
132
148
  } else {
133
149
  // When no prepare function is provided (e.g., downloading full notebook),