seemore 1.4.0 → 1.4.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "seemore",
3
- "version": "1.4.0",
3
+ "version": "1.4.1",
4
4
  "description": "Let AI write the Markdown. Let seemore show it better — zero config documentation framework.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1,4 +1,6 @@
1
- import { useEffect, useId, useState } from 'react';
1
+ import { useEffect, useId, useRef, useState } from 'react';
2
+ import { DiagramLoader } from './DiagramLoader.js';
3
+ import { useInView } from './useInView.js';
2
4
 
3
5
  /**
4
6
  * D2 compiles to SVG via a WASM build in the browser — same reasoning as `Mermaid`: doing
@@ -7,10 +9,17 @@ import { useEffect, useId, useState } from 'react';
7
9
  */
8
10
  export function D2({ chart }: { chart: string }) {
9
11
  const id = useId().replace(/[^a-zA-Z0-9]/g, '');
12
+ const container = useRef<HTMLDivElement>(null);
13
+ const inView = useInView(container);
10
14
  const [svg, setSvg] = useState<string>();
11
15
  const [error, setError] = useState<string>();
12
16
 
13
17
  useEffect(() => {
18
+ // D2's compile/render pass is synchronous WASM work and can't be interrupted, so a large
19
+ // diagram freezes the main thread — including any pending navigation — the moment it
20
+ // starts. Waiting until the diagram is actually scrolled into view keeps that off the
21
+ // critical path of landing on the page.
22
+ if (!inView) return;
14
23
  let cancelled = false;
15
24
 
16
25
  void (async () => {
@@ -31,7 +40,7 @@ export function D2({ chart }: { chart: string }) {
31
40
  return () => {
32
41
  cancelled = true;
33
42
  };
34
- }, [chart, id]);
43
+ }, [inView, chart, id]);
35
44
 
36
45
  if (error !== undefined) {
37
46
  return (
@@ -43,11 +52,18 @@ export function D2({ chart }: { chart: string }) {
43
52
 
44
53
  return (
45
54
  <div
55
+ ref={container}
46
56
  className="seemore-d2"
47
57
  // The diagram is generated by D2 from the page's own source, not from user input.
48
58
  dangerouslySetInnerHTML={svg === undefined ? undefined : { __html: svg }}
49
59
  >
50
- {svg === undefined ? <pre className="seemore-d2-source">{chart}</pre> : undefined}
60
+ {svg === undefined ? (
61
+ inView ? (
62
+ <DiagramLoader />
63
+ ) : (
64
+ <pre className="seemore-d2-source">{chart}</pre>
65
+ )
66
+ ) : undefined}
51
67
  </div>
52
68
  );
53
69
  }
@@ -0,0 +1,11 @@
1
+ /** Shown by `Mermaid`/`D2` while a diagram's synchronous render pass is in progress. */
2
+ export function DiagramLoader() {
3
+ return (
4
+ <div className="seemore-diagram-loading" role="status">
5
+ <span className="seemore-diagram-spinner" aria-hidden="true" />
6
+ <span className="sr-only">Rendering diagram…</span>
7
+ </div>
8
+ );
9
+ }
10
+
11
+ export default DiagramLoader;
@@ -1,4 +1,6 @@
1
1
  import { useEffect, useId, useRef, useState } from 'react';
2
+ import { DiagramLoader } from './DiagramLoader.js';
3
+ import { useInView } from './useInView.js';
2
4
 
3
5
  /**
4
6
  * Mermaid runs in the browser.
@@ -11,10 +13,16 @@ import { useEffect, useId, useRef, useState } from 'react';
11
13
  export function Mermaid({ chart }: { chart: string }) {
12
14
  const id = useId().replace(/[^a-zA-Z0-9]/g, '');
13
15
  const container = useRef<HTMLDivElement>(null);
16
+ const inView = useInView(container);
14
17
  const [svg, setSvg] = useState<string>();
15
18
  const [error, setError] = useState<string>();
16
19
 
17
20
  useEffect(() => {
21
+ // Mermaid's layout pass is synchronous and can't be interrupted, so a large diagram
22
+ // freezes the main thread — including any pending navigation — the moment it starts.
23
+ // Waiting until the diagram is actually scrolled into view keeps that off the critical
24
+ // path of landing on the page.
25
+ if (!inView) return;
18
26
  let cancelled = false;
19
27
 
20
28
  void (async () => {
@@ -32,7 +40,7 @@ export function Mermaid({ chart }: { chart: string }) {
32
40
  return () => {
33
41
  cancelled = true;
34
42
  };
35
- }, [chart, id]);
43
+ }, [inView, chart, id]);
36
44
 
37
45
  if (error !== undefined) {
38
46
  return (
@@ -49,7 +57,13 @@ export function Mermaid({ chart }: { chart: string }) {
49
57
  // The diagram is generated by mermaid from the page's own source, not from user input.
50
58
  dangerouslySetInnerHTML={svg === undefined ? undefined : { __html: svg }}
51
59
  >
52
- {svg === undefined ? <pre className="seemore-mermaid-source">{chart}</pre> : undefined}
60
+ {svg === undefined ? (
61
+ inView ? (
62
+ <DiagramLoader />
63
+ ) : (
64
+ <pre className="seemore-mermaid-source">{chart}</pre>
65
+ )
66
+ ) : undefined}
53
67
  </div>
54
68
  );
55
69
  }
@@ -0,0 +1,31 @@
1
+ import { useEffect, useState, type RefObject } from 'react';
2
+
3
+ /**
4
+ * True once the element has entered (or nearly entered) the viewport, and stays true
5
+ * afterward — used to defer starting expensive diagram rendering until it's actually needed,
6
+ * rather than the moment a page mounts.
7
+ */
8
+ export function useInView(ref: RefObject<Element | null>): boolean {
9
+ const [inView, setInView] = useState(false);
10
+
11
+ useEffect(() => {
12
+ if (inView) return;
13
+ const node = ref.current;
14
+ if (node === null) return;
15
+
16
+ // Diagrams sit well below the fold on a typical docs page; a margin means scrolling to
17
+ // one feels instant rather than triggering a visible render-in-place.
18
+ const observer = new IntersectionObserver(
19
+ ([entry]) => {
20
+ if (entry?.isIntersecting) setInView(true);
21
+ },
22
+ { rootMargin: '200px' },
23
+ );
24
+ observer.observe(node);
25
+ return () => observer.disconnect();
26
+ }, [ref, inView]);
27
+
28
+ return inView;
29
+ }
30
+
31
+ export default useInView;
@@ -292,6 +292,14 @@
292
292
  @apply my-6 rounded-lg border border-fd-border p-4 text-sm text-fd-muted-foreground;
293
293
  }
294
294
 
295
+ .seemore-diagram-loading {
296
+ @apply my-6 flex items-center justify-center py-8;
297
+ }
298
+
299
+ .seemore-diagram-spinner {
300
+ @apply h-6 w-6 animate-spin rounded-full border-2 border-fd-muted-foreground/30 border-t-fd-muted-foreground;
301
+ }
302
+
295
303
  /* Inline editing (dev only, `content.edit`). The article is the positioning context for
296
304
  the editor layer, which sits over the block being edited. */
297
305
  .seemore-editable {