fix(charts): preserve comparison types and draggable window headings
Keep current lines and historical scatter points in a dedicated comparison chart type. Expose floating headings and use idempotent placement so StrictMode no longer masks production overlap.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useId, useRef, useState, useSyncExternalStore, type ReactNode } from "react";
|
||||
import { useEffect, useId, useLayoutEffect, useRef, useState, useSyncExternalStore, type ReactNode } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { Rnd } from "react-rnd";
|
||||
import { Maximize2, Minimize2, X } from "lucide-react";
|
||||
@@ -48,16 +48,31 @@ export function FloatingWindow({ title, triggerLabel, fillContent = false, openR
|
||||
};
|
||||
|
||||
useEffect(() => () => removeFloatingWindow(id), [id]);
|
||||
useEffect(() => {
|
||||
useLayoutEffect(() => {
|
||||
if (!open) return;
|
||||
const offset = (floatingWindowOrder().filter((windowId) => windowId !== id).length % 5) * 24;
|
||||
setBounds((current) => fitWindow({ ...current, x: current.x + offset, y: current.y + offset }));
|
||||
const others = floatingWindowOrder().filter((windowId) => windowId !== id);
|
||||
const headingHeight = Array.from(floatingWindowHost().querySelectorAll<HTMLElement>(".floating-window-heading"))
|
||||
.reduce((height, element) => Math.max(height, element.offsetHeight), 48);
|
||||
const step = headingHeight + 8;
|
||||
const minHeight = Math.min(260, window.innerHeight - 16);
|
||||
const slots = Math.max(1, Math.floor((window.innerHeight - 64 - minHeight - 8) / step) + 1);
|
||||
const slot = others.length % slots;
|
||||
const y = Math.max(8, Math.min(64 + slot * step, window.innerHeight - minHeight - 8));
|
||||
// Keep the preceding headings exposed. Shorter viewports reduce content
|
||||
// height instead of clamping several windows onto the same title bar.
|
||||
// Absolute placement also makes StrictMode's repeated setup idempotent.
|
||||
setBounds(fitWindow({ x: (window.innerWidth - 800) / 2 + slot * 24, y,
|
||||
width: 800, height: Math.min(560, window.innerHeight - y - 8) }));
|
||||
raiseFloatingWindow(id);
|
||||
if (focusOnOpen.current) heading.current?.focus({ preventScroll: true });
|
||||
focusOnOpen.current = false;
|
||||
const resize = () => {
|
||||
setViewport({ width: window.innerWidth, height: window.innerHeight });
|
||||
setBounds((current) => fitWindow(current));
|
||||
setBounds((current) => {
|
||||
const minimumHeight = Math.min(260, window.innerHeight - 16);
|
||||
const y = Math.max(8, Math.min(current.y, window.innerHeight - minimumHeight - 8));
|
||||
return fitWindow({ ...current, y, height: Math.min(current.height, window.innerHeight - y - 8) });
|
||||
});
|
||||
};
|
||||
window.addEventListener("resize", resize);
|
||||
return () => {
|
||||
@@ -75,9 +90,7 @@ export function FloatingWindow({ title, triggerLabel, fillContent = false, openR
|
||||
aria-label={triggerLabel ?? `放大查看 ${title}`} title="放大查看" aria-haspopup="dialog" aria-expanded={open}
|
||||
onClick={() => {
|
||||
if (!open) {
|
||||
const offset = (order.length % 5) * 24;
|
||||
setViewport({ width: window.innerWidth, height: window.innerHeight });
|
||||
setBounds(fitWindow({ x: (window.innerWidth - 800) / 2 + offset, y: 64 + offset, width: 800, height: 560 }));
|
||||
setMaximized(false);
|
||||
focusOnOpen.current = true;
|
||||
setManuallyOpen(true);
|
||||
|
||||
Reference in New Issue
Block a user