jamdesk 1.1.96 → 1.1.97

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": "jamdesk",
3
- "version": "1.1.96",
3
+ "version": "1.1.97",
4
4
  "description": "CLI for Jamdesk — build, preview, and deploy documentation sites from MDX. Dev server with hot reload, 50+ components, OpenAPI support, AI search, and Mintlify migration",
5
5
  "keywords": [
6
6
  "jamdesk",
@@ -1,17 +1,26 @@
1
1
  'use client';
2
2
 
3
3
  import dynamic from 'next/dynamic';
4
- import { useMemo, useState } from 'react';
4
+ import { useMemo, useRef, useState } from 'react';
5
5
  import { readCachedHeight } from './mermaidCache';
6
6
  import { useIsomorphicLayoutEffect } from '../../hooks/useIsomorphicLayoutEffect';
7
7
 
8
8
  // Neutral floor reserved in the SSR markup itself (present on the browser's
9
9
  // first paint, before any JS) so a cache miss / pre-hydration window shows
10
10
  // correctly-sized empty space instead of a collapse or a grey pulse. Roughly
11
- // the prior skeleton footprint; refined to the exact diagram height on a
12
- // cache hit by the layout effect below.
11
+ // the prior skeleton footprint; a cache hit refines it to the exact diagram
12
+ // height pre-paint, and once the diagram renders the floor is released
13
+ // entirely (see the layout effect below).
13
14
  const DEFAULT_MERMAID_RESERVE_PX = 192;
14
15
 
16
+ // Sentinel: floor released, MermaidInner's own min-height governs the box.
17
+ const FLOOR_RELEASED_PX = 0;
18
+
19
+ // A rendered diagram (or the error box) is always taller than this; the empty
20
+ // pre-render container is 0px. Distinguishes "content has painted" from "still
21
+ // reserving the gap" so the floor is released only once it is dead weight.
22
+ const RENDERED_FLOOR_RELEASE_PX = 8;
23
+
15
24
  const MermaidInner = dynamic(
16
25
  () => import('./MermaidInner').then(mod => ({ default: mod.MermaidInner })),
17
26
  { ssr: false, loading: () => null }
@@ -25,21 +34,49 @@ interface MermaidProps {
25
34
  }
26
35
 
27
36
  export function Mermaid({ children, className, minWidth }: MermaidProps) {
37
+ const wrapperRef = useRef<HTMLDivElement>(null);
38
+
28
39
  // Server + first (hydration) render emit the constant default — identical
29
40
  // markup, so no hydration mismatch. The sessionStorage read happens only
30
41
  // in the post-hydration layout effect, never in this initializer.
31
42
  const [reservedPx, setReservedPx] = useState(DEFAULT_MERMAID_RESERVE_PX);
32
43
 
33
- // Fires once, after the hydration commit, synchronously before that
34
- // commit's paint and BEFORE the MermaidInner chunk has resolved or
35
- // injected anything. On a cache hit, refine the reserved space to the
36
- // exact diagram height so the SVG paints into already-exact space.
37
- // Distinct from the reverted spike: one pre-paint settle before injection,
38
- // never a re-render around an already-injected node. minWidth still flows
39
- // to MermaidInner unchanged; the wrapper owns height only.
44
+ // The reserved min-height only bridges the pre-render gap; it must never
45
+ // outlive the diagram or it leaves dead space under any diagram shorter
46
+ // than the floor (the cache-miss path never refines it down).
40
47
  useIsomorphicLayoutEffect(() => {
41
- const h = readCachedHeight(children);
42
- if (h > 0) setReservedPx(h);
48
+ const wrapper = wrapperRef.current;
49
+
50
+ // Relies on MermaidInner rendering exactly one HTMLElement root (the
51
+ // diagram container or the error box); if that contract changes, this
52
+ // height probe silently targets the wrong node.
53
+ const releaseIfRendered = (): boolean => {
54
+ const content = wrapper?.firstElementChild as HTMLElement | null;
55
+ if (content && content.clientHeight > RENDERED_FLOOR_RELEASE_PX) {
56
+ setReservedPx(FLOOR_RELEASED_PX);
57
+ return true;
58
+ }
59
+ return false;
60
+ };
61
+
62
+ // Already painted (warm soft-nav: SVG hydrated synchronously from cache) —
63
+ // the floor is already dead weight, skip the cache refine entirely.
64
+ if (releaseIfRendered()) return;
65
+
66
+ // Not yet painted: cache hit → reserve the exact height pre-paint (zero
67
+ // shift); cache miss / new diagram → hold the default floor for its gap.
68
+ const cached = readCachedHeight(children);
69
+ setReservedPx(cached > 0 ? cached : DEFAULT_MERMAID_RESERVE_PX);
70
+
71
+ if (!wrapper || typeof MutationObserver === 'undefined') return;
72
+ // childList only — no `attributes`: MermaidInner's post-commit style pass
73
+ // mutates SVG child styles heavily; observing attributes would fire a
74
+ // callback storm. The SVG-insert is a childList mutation, all we need.
75
+ const mo = new MutationObserver(() => {
76
+ if (releaseIfRendered()) mo.disconnect();
77
+ });
78
+ mo.observe(wrapper, { childList: true, subtree: true });
79
+ return () => mo.disconnect();
43
80
  }, [children]);
44
81
 
45
82
  // Stable element identity across reservedPx changes. The pre-paint
@@ -63,7 +100,7 @@ export function Mermaid({ children, className, minWidth }: MermaidProps) {
63
100
  );
64
101
 
65
102
  return (
66
- <div data-testid="mermaid-wrapper" style={{ minHeight: reservedPx }}>
103
+ <div ref={wrapperRef} data-testid="mermaid-wrapper" style={{ minHeight: reservedPx }}>
67
104
  {inner}
68
105
  </div>
69
106
  );