@marimo-team/islands 0.17.3 → 0.17.5

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.
@@ -25,7 +25,7 @@ import { t as useAsyncData } from "./useAsyncData-Dp2V69OV.js";
25
25
  import { n as formats } from "./vega-loader.browser-BK7-IO9k.js";
26
26
  import "./precisionRound-DMLkFNYv.js";
27
27
  import "./linear-CgANSWNu.js";
28
- import { t as j } from "./react-vega-B9eMrRW1.js";
28
+ import { t as j } from "./react-vega-C3G6aCB7.js";
29
29
  import "./ordinal-pQYxWJYN.js";
30
30
  import "./time-U9NHhrDC.js";
31
31
  import "./range-sX2tw-Jz.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marimo-team/islands",
3
- "version": "0.17.3",
3
+ "version": "0.17.5",
4
4
  "main": "dist/main.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -168,7 +168,7 @@
168
168
  "typescript-memoize": "^1.1.1",
169
169
  "use-acp": "0.2.5",
170
170
  "use-resize-observer": "^9.1.0",
171
- "vega-lite": "^6.4.1",
171
+ "vega-lite": "6.2.0",
172
172
  "vega-loader": "^5.1.0",
173
173
  "vega-parser": "^7.1.0",
174
174
  "vega-tooltip": "^1.1.0",
@@ -27,8 +27,10 @@ describe("MarkdownLanguageAdapter", () => {
27
27
  const out = adapter.transformOut(innerCode, metadata);
28
28
  expect(out).toMatchInlineSnapshot(`
29
29
  [
30
- "mo.md(r""" """)",
31
- 10,
30
+ "mo.md(r"""
31
+
32
+ """)",
33
+ 12,
32
34
  ]
33
35
  `);
34
36
  });
@@ -227,8 +229,10 @@ describe("MarkdownLanguageAdapter", () => {
227
229
  it("empty string", () => {
228
230
  const code = "";
229
231
  const [wrappedCode, offset] = adapter.transformOut(code, metadata);
230
- expect(wrappedCode).toBe(`mo.md(""" """)`);
231
- expect(offset).toBe(9);
232
+ expect(wrappedCode).toBe(`mo.md("""
233
+
234
+ """)`);
235
+ expect(offset).toBe(11);
232
236
  });
233
237
 
234
238
  it("defaults to r-string when there is no last quote prefix", () => {
@@ -236,15 +240,19 @@ describe("MarkdownLanguageAdapter", () => {
236
240
  const code = "Hello world";
237
241
  metadata.quotePrefix = "r";
238
242
  const [wrappedCode, offset] = adapter.transformOut(code, metadata);
239
- expect(wrappedCode).toBe(`mo.md(r"""Hello world""")`);
240
- expect(offset).toBe(10);
243
+ expect(wrappedCode).toBe(`mo.md(r"""
244
+ Hello world
245
+ """)`);
246
+ expect(offset).toBe(12);
241
247
  });
242
248
 
243
249
  it("single line", () => {
244
250
  const code = "Hello world";
245
251
  const [wrappedCode, offset] = adapter.transformOut(code, metadata);
246
- expect(wrappedCode).toBe(`mo.md("""Hello world""")`);
247
- expect(offset).toBe(9);
252
+ expect(wrappedCode).toBe(`mo.md("""
253
+ Hello world
254
+ """)`);
255
+ expect(offset).toBe(11);
248
256
  });
249
257
 
250
258
  it("starts with quote", () => {
@@ -283,21 +291,36 @@ describe("MarkdownLanguageAdapter", () => {
283
291
  });
284
292
 
285
293
  it("should escape triple quotes in the Markdown code", () => {
286
- const code = 'Markdown with an escaped """quote"""!!';
294
+ const code = 'Markdown with an escaped """quote""""!!';
287
295
  const [wrappedCode, offset] = adapter.transformOut(code, metadata);
288
296
  expect(wrappedCode).toBe(
289
- `mo.md("""Markdown with an escaped \\"""quote\\"""!!""")`,
297
+ `mo.md("""
298
+ Markdown with an escaped "\\""quote"\\""\\"!!
299
+ """)`,
290
300
  );
291
- expect(offset).toBe(9);
301
+ expect(offset).toBe(11);
302
+ });
303
+
304
+ it("should escape triple quotes in the Markdown code with backslash", () => {
305
+ const code = 'Markdown with an escaped \\"""quote\\""""!!';
306
+ const [wrappedCode, offset] = adapter.transformOut(code, metadata);
307
+ expect(wrappedCode).toBe(
308
+ `mo.md("""
309
+ Markdown with an escaped \\"\\""quote\\"\\""\\"!!
310
+ """)`,
311
+ );
312
+ expect(offset).toBe(11);
292
313
  });
293
314
 
294
315
  it("should preserve r strings", () => {
295
316
  const code = String.raw`$\nu = \mathllap{}\cdot\mathllap{\alpha}$`;
296
317
  metadata.quotePrefix = "r";
297
318
  const [wrappedCode, offset] = adapter.transformOut(code, metadata);
298
- const pythonCode = `mo.md(r"""$\\nu = \\mathllap{}\\cdot\\mathllap{\\alpha}$""")`;
319
+ const pythonCode = `mo.md(r"""
320
+ $\\nu = \\mathllap{}\\cdot\\mathllap{\\alpha}$
321
+ """)`;
299
322
  expect(wrappedCode).toBe(pythonCode);
300
- expect(offset).toBe(10);
323
+ expect(offset).toBe(12);
301
324
  });
302
325
 
303
326
  it("should handle f-strings in transformOut", () => {
@@ -146,8 +146,12 @@ describe("splitEditor", () => {
146
146
  selection: { anchor: "Hello,".length },
147
147
  });
148
148
  const result = splitEditor(mockEditor);
149
- expect(result.beforeCursorCode).toEqual('mo.md("""Hello,""")');
150
- expect(result.afterCursorCode).toEqual('mo.md(""" World!""")');
149
+ expect(result.beforeCursorCode).toEqual(`mo.md("""
150
+ Hello,
151
+ """)`);
152
+ expect(result.afterCursorCode).toEqual(`mo.md("""
153
+ World!
154
+ """)`);
151
155
  });
152
156
 
153
157
  // f-strings not currently supported
package/src/css/md.css CHANGED
@@ -78,8 +78,14 @@ a .markdown iconify-icon:first-child {
78
78
  margin-inline-end: 0.4em;
79
79
  }
80
80
 
81
+ iconify-icon {
82
+ display: inline-flex;
83
+ align-items: center;
84
+ }
85
+
81
86
  /* align icons with buttons better */
82
87
  button .markdown .paragraph {
88
+ display: inline-flex;
83
89
  align-items: baseline;
84
90
  gap: 0.4em;
85
91
 
@@ -462,4 +462,31 @@ describe("sanitizeHtml", () => {
462
462
  `"<details><summary>Click me</summary><p>Hidden content</p></details>"`,
463
463
  );
464
464
  });
465
+
466
+ test("preserves iconify-icon custom element", () => {
467
+ const html = '<iconify-icon icon="lucide:leaf"></iconify-icon>';
468
+ expect(sanitizeHtml(html)).toMatchInlineSnapshot(
469
+ `"<iconify-icon icon="lucide:leaf"></iconify-icon>"`,
470
+ );
471
+ });
472
+
473
+ test("preserves iconify-icon with all attributes", () => {
474
+ const html =
475
+ '<iconify-icon icon="lucide:rocket" width="24px" height="24px" inline="" flip="horizontal" rotate="90deg" style="color: blue;"></iconify-icon>';
476
+ expect(sanitizeHtml(html)).toMatchInlineSnapshot(
477
+ `"<iconify-icon icon="lucide:rocket" width="24px" height="24px" inline="" flip="horizontal" rotate="90deg" style="color: blue;"></iconify-icon>"`,
478
+ );
479
+ });
480
+
481
+ test("preserves self-closing iconify-icon", () => {
482
+ const html = '<iconify-icon icon="lucide:star" />';
483
+ expect(sanitizeHtml(html)).toMatchInlineSnapshot(
484
+ `"<iconify-icon icon="lucide:star"></iconify-icon>"`,
485
+ );
486
+ });
487
+
488
+ test("still removes other non-marimo/non-iconify custom elements", () => {
489
+ const html = "<some-custom-element>Content</some-custom-element>";
490
+ expect(sanitizeHtml(html)).toMatchInlineSnapshot(`"Content"`);
491
+ });
465
492
  });
@@ -75,7 +75,7 @@ export function sanitizeHtml(html: string) {
75
75
  // glue elements like style, script or others to document.body and prevent unintuitive browser behavior in several edge-cases
76
76
  FORCE_BODY: true,
77
77
  CUSTOM_ELEMENT_HANDLING: {
78
- tagNameCheck: /^marimo-[A-Za-z][\w-]*$/,
78
+ tagNameCheck: /^(marimo-[A-Za-z][\w-]*|iconify-icon)$/,
79
79
  attributeNameCheck: /^[A-Za-z][\w-]*$/,
80
80
  },
81
81
  };
@@ -1,5 +1,5 @@
1
1
  /* Copyright 2024 Marimo. All rights reserved. */
2
- import { type JSX, useLayoutEffect, useRef } from "react";
2
+ import { type JSX, useLayoutEffect, useRef, useState } from "react";
3
3
 
4
4
  import { z } from "zod";
5
5
  import { once } from "@/utils/once";
@@ -21,7 +21,10 @@ export class TexPlugin implements IStatelessPlugin<{}> {
21
21
 
22
22
  render(props: IStatelessPluginProps<{}>): JSX.Element {
23
23
  return (
24
- <TexComponent tex={props.host.textContent || props.host.innerHTML} />
24
+ <TexComponent
25
+ host={props.host}
26
+ tex={props.host.textContent || props.host.innerHTML}
27
+ />
25
28
  );
26
29
  }
27
30
  }
@@ -67,8 +70,33 @@ async function renderLatex(mount: HTMLElement, tex: string): Promise<void> {
67
70
  }
68
71
  }
69
72
 
70
- const TexComponent = ({ tex }: { tex: string }): JSX.Element => {
73
+ const TexComponent = ({
74
+ host,
75
+ tex,
76
+ }: {
77
+ host: HTMLElement;
78
+ tex: string;
79
+ }): JSX.Element => {
71
80
  const ref = useRef<HTMLSpanElement>(null);
81
+ const [currentTex, setCurrentTex] = useState(tex);
82
+
83
+ // Watch for changes to the host element's direct children
84
+ useLayoutEffect(() => {
85
+ const observer = new MutationObserver(() => {
86
+ const newTex = host.textContent || host.innerHTML;
87
+ setCurrentTex(newTex);
88
+ });
89
+
90
+ observer.observe(host, {
91
+ childList: true,
92
+ characterData: true,
93
+ subtree: true,
94
+ });
95
+
96
+ return () => {
97
+ observer.disconnect();
98
+ };
99
+ }, [host]);
72
100
 
73
101
  // The arithmatex markdown extension we use in Python produces nested
74
102
  // marimo-tex tags when $$...$$ math is used in a paragraph, with dummy
@@ -94,9 +122,9 @@ const TexComponent = ({ tex }: { tex: string }): JSX.Element => {
94
122
  // Re-render when the text content changes.
95
123
  useLayoutEffect(() => {
96
124
  if (ref.current) {
97
- renderLatex(ref.current, tex);
125
+ renderLatex(ref.current, currentTex);
98
126
  }
99
- }, [tex]);
127
+ }, [currentTex]);
100
128
 
101
129
  return <span ref={ref} />;
102
130
  };