Skip to content

Commit 09f76a2

Browse files
committed
fix overview percent issue
1 parent 71a2a3f commit 09f76a2

1 file changed

Lines changed: 48 additions & 22 deletions

File tree

src/components/mdx/Excalidraw.tsx

Lines changed: 48 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ export function Excalidraw({
7171

7272
// The raw viewBox of the entire exported SVG
7373
const [rawViewBox, setRawViewBox] = useState<number[] | null>(null);
74+
const overviewTargetRef = useRef<number[] | null>(null);
7475
const currentViewBoxRef = useRef<number[]>([0, 0, 100, 100]);
7576

7677
const requestRef = useRef<number | null>(null);
@@ -138,12 +139,21 @@ export function Excalidraw({
138139
${katexStyles}
139140
.katex-display { margin: 0; }
140141
.katex { font-size: 1.1em; }
142+
/* Fallback for mathematical symbols like integrals when KaTeX fonts are not loaded in SVG data URLs */
143+
.katex-mathml { display: none; }
144+
.katex-html { font-family: KaTeX_Main, "Times New Roman", serif; }
145+
.base { font-family: inherit; }
146+
/* Target specific math symbols to use system math fonts if available */
147+
.mop { font-family: "Cambria Math", "STIX Math", "Segoe UI Symbol", "Apple Symbols", serif !important; }
141148
</style>
142149
${html}
143150
</div>
144151
</foreignObject>
145152
</svg>`.trim();
146-
const dataURL = `data:image/svg+xml;base64,${btoa(unescape(encodeURIComponent(svgString)))}`;
153+
// Use modern TextEncoder instead of deprecated unescape for Base64 encoding
154+
const bytes = new TextEncoder().encode(svgString);
155+
const binString = Array.from(bytes, (byte) => String.fromCharCode(byte)).join("");
156+
const dataURL = `data:image/svg+xml;base64,${btoa(binString)}`;
147157
json.files[id] = {
148158
mimeType: "image/svg+xml",
149159
id,
@@ -182,12 +192,12 @@ export function Excalidraw({
182192
if (svgRef.current) {
183193
svgRef.current.setAttribute('viewBox', vb.join(' '));
184194
currentViewBoxRef.current = vb;
185-
if (rawViewBox) {
186-
const z = Math.round((rawViewBox[2] / vb[2]) * 100);
195+
if (overviewTargetRef.current) {
196+
const z = Math.round((overviewTargetRef.current[2] / vb[2]) * 100);
187197
setZoom(z);
188198
}
189199
}
190-
}, [rawViewBox]);
200+
}, []);
191201

192202
// 2. Render SVG
193203
useEffect(() => {
@@ -305,6 +315,10 @@ export function Excalidraw({
305315
const vb = svg.getAttribute('viewBox')?.split(' ').map(parseFloat);
306316
if (vb && vb.length === 4) {
307317
setRawViewBox(vb);
318+
// Use initial overviewTargetRef to keep zoom reference stable
319+
if (!overviewTargetRef.current && viewMode === 'overview') {
320+
overviewTargetRef.current = vb;
321+
}
308322
currentViewBoxRef.current = vb;
309323
setCoordinateOffset({ x: vb[0] - minX, y: vb[1] - minY });
310324
}
@@ -319,7 +333,7 @@ export function Excalidraw({
319333
}
320334
}
321335
renderSvg();
322-
}, [data, viewMode]);
336+
}, [data, viewMode, fontFamily]);
323337

324338
const animate = useCallback((time: number) => {
325339
if (!transitionRef.current) return;
@@ -342,11 +356,11 @@ export function Excalidraw({
342356

343357
// Sync View Logic
344358
const syncView = useCallback(() => {
345-
if (!rawViewBox) return;
359+
if (!rawViewBox || !overviewTargetRef.current) return;
346360

347361
let target: number[];
348362
if (viewMode === 'overview') {
349-
target = rawViewBox;
363+
target = overviewTargetRef.current;
350364
} else {
351365
const frame = frames[currentSlide];
352366
if (frame) {
@@ -359,7 +373,7 @@ export function Excalidraw({
359373
frame.height + p * 2
360374
];
361375
} else {
362-
target = rawViewBox;
376+
target = overviewTargetRef.current;
363377
}
364378
}
365379
updateCamera(target);
@@ -394,9 +408,14 @@ export function Excalidraw({
394408
return () => window.removeEventListener('keydown', handleKeyDown);
395409
}, [viewMode, currentSlide, frames.length]);
396410

397-
// Drag Handlers
398-
const handleMouseDown = (e: React.MouseEvent) => {
399-
e.preventDefault();
411+
// Unified Pointer Drag Handlers (Mouse, Touch, Pen)
412+
const handlePointerDown = (e: React.PointerEvent) => {
413+
// Only handle primary pointer (usually left click or first touch)
414+
if (!e.isPrimary) return;
415+
416+
// Capture pointer so we continue receiving events even if the pointer leaves the container
417+
e.currentTarget.setPointerCapture(e.pointerId);
418+
400419
setIsDragging(true);
401420
dragStartRef.current = {
402421
x: e.clientX,
@@ -407,7 +426,7 @@ export function Excalidraw({
407426
if (requestRef.current) cancelAnimationFrame(requestRef.current);
408427
};
409428

410-
const handleMouseMove = (e: React.MouseEvent) => {
429+
const handlePointerMove = (e: React.PointerEvent) => {
411430
if (!isDragging || !dragStartRef.current || !containerRef.current) return;
412431

413432
const rect = containerRef.current.getBoundingClientRect();
@@ -417,10 +436,6 @@ export function Excalidraw({
417436
const dy = e.clientY - dragStartRef.current.y;
418437

419438
const [vx, vy, vw, vh] = dragStartRef.current.vb;
420-
421-
// Calculate scale. Since we use 'meet', the SVG scale matches the constraining dimension.
422-
// We use the MAX dimension ratio to approximate the zoom level for panning speed.
423-
// This ensures 1px of drag ~= 1px of SVG movement regardless of aspect ratio letterboxing.
424439
const scale = Math.max(vw / rect.width, vh / rect.height);
425440

426441
const newVB = [
@@ -433,9 +448,13 @@ export function Excalidraw({
433448
setSvgViewBox(newVB);
434449
};
435450

436-
const handleMouseUp = () => {
451+
const handlePointerUp = (e: React.PointerEvent) => {
437452
setIsDragging(false);
438453
dragStartRef.current = null;
454+
// Release is automatic but good practice to clear state
455+
if (e.currentTarget.hasPointerCapture(e.pointerId)) {
456+
e.currentTarget.releasePointerCapture(e.pointerId);
457+
}
439458
};
440459

441460
// Zoom Handler
@@ -461,7 +480,13 @@ export function Excalidraw({
461480
return () => el.removeEventListener('wheel', onWheelPassive);
462481
}, [loading, setSvgViewBox]);
463482

464-
const containerStyle = { height: typeof height === 'number' ? `${height}px` : height, width: typeof width === 'number' ? `${width}px` : width };
483+
const containerStyle = {
484+
height: typeof height === 'number' ? `${height}px` : height,
485+
maxHeight: '70vh',
486+
minHeight: '300px',
487+
width: typeof width === 'number' ? `${width}px` : width,
488+
touchAction: 'none' // Prevent native browser gestures (scroll/pinch) from interfering
489+
};
465490

466491
if (!isClient) return <div style={containerStyle} className="bg-gray-50 flex items-center justify-center border-2 border-gray-200 rounded-xl">Initializing...</div>;
467492

@@ -482,10 +507,11 @@ export function Excalidraw({
482507
className={`relative bg-white overflow-hidden select-none group ${isDragging ? 'cursor-grabbing' : 'cursor-grab'}`}
483508
style={containerStyle}
484509
tabIndex={0}
485-
onMouseDown={handleMouseDown}
486-
onMouseMove={handleMouseMove}
487-
onMouseUp={handleMouseUp}
488-
onMouseLeave={handleMouseUp}
510+
onPointerDown={handlePointerDown}
511+
onPointerMove={handlePointerMove}
512+
onPointerUp={handlePointerUp}
513+
onPointerCancel={handlePointerUp}
514+
onPointerLeave={handlePointerUp}
489515
>
490516
<div ref={svgContainerRef} className="absolute inset-0 w-full h-full pointer-events-none" />
491517

0 commit comments

Comments
 (0)