# Description of Changes <!-- Please provide a summary of the changes, including: - What was changed - Why the change was made - Any challenges encountered Closes #(issue_number) --> --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details. --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: EthanHealy01 <ethan.healy.21@gmail.com> Co-authored-by: EthanHealy01 <80844253+EthanHealy01@users.noreply.github.com>
120 lines
3.5 KiB
TypeScript
120 lines
3.5 KiB
TypeScript
import { useLayoutEffect, useState, RefObject, useRef } from 'react';
|
|
|
|
export interface ToolPanelGeometry {
|
|
left: number;
|
|
top: number;
|
|
width: number;
|
|
height: number;
|
|
}
|
|
|
|
interface UseToolPanelGeometryOptions {
|
|
enabled: boolean;
|
|
toolPanelRef: RefObject<HTMLDivElement | null>;
|
|
quickAccessRef: RefObject<HTMLDivElement | null>;
|
|
rightRailRef?: RefObject<HTMLDivElement | null>;
|
|
}
|
|
|
|
export function useToolPanelGeometry({
|
|
enabled,
|
|
toolPanelRef,
|
|
quickAccessRef,
|
|
rightRailRef,
|
|
}: UseToolPanelGeometryOptions) {
|
|
const [geometry, setGeometry] = useState<ToolPanelGeometry | null>(null);
|
|
const scheduleUpdateRef = useRef<() => void>(() => {});
|
|
|
|
useLayoutEffect(() => {
|
|
if (!enabled) {
|
|
setGeometry(null);
|
|
return;
|
|
}
|
|
|
|
const panelEl = toolPanelRef.current;
|
|
if (!panelEl) {
|
|
setGeometry(null);
|
|
return;
|
|
}
|
|
|
|
const rightRailEl = () => (rightRailRef?.current ?? null);
|
|
|
|
let rafId: number | null = null;
|
|
|
|
const computeAndSetGeometry = () => {
|
|
const rect = panelEl.getBoundingClientRect();
|
|
const rail = rightRailEl();
|
|
const rightOffset = rail ? Math.max(0, window.innerWidth - rail.getBoundingClientRect().left) : 0;
|
|
const width = Math.max(360, window.innerWidth - rect.left - rightOffset);
|
|
const height = Math.max(rect.height, window.innerHeight - rect.top);
|
|
setGeometry({
|
|
left: rect.left,
|
|
top: rect.top,
|
|
width,
|
|
height,
|
|
});
|
|
};
|
|
|
|
const scheduleUpdate = () => {
|
|
if (rafId !== null) {
|
|
cancelAnimationFrame(rafId);
|
|
}
|
|
rafId = requestAnimationFrame(() => {
|
|
computeAndSetGeometry();
|
|
rafId = null;
|
|
});
|
|
};
|
|
scheduleUpdateRef.current = scheduleUpdate;
|
|
|
|
// Initial geometry calculation (no debounce)
|
|
computeAndSetGeometry();
|
|
|
|
let resizeObserver: ResizeObserver | null = null;
|
|
if (typeof ResizeObserver !== 'undefined') {
|
|
resizeObserver = new ResizeObserver(() => scheduleUpdate());
|
|
resizeObserver.observe(panelEl);
|
|
if (quickAccessRef.current) {
|
|
resizeObserver.observe(quickAccessRef.current);
|
|
}
|
|
const rail = rightRailEl();
|
|
if (rail) {
|
|
resizeObserver.observe(rail);
|
|
}
|
|
// Observe root element to react to viewport-driven layout changes
|
|
if (document.documentElement) {
|
|
resizeObserver.observe(document.documentElement);
|
|
}
|
|
} else {
|
|
// Fallback for environments without ResizeObserver
|
|
const handleResize = () => scheduleUpdate();
|
|
window.addEventListener('resize', handleResize);
|
|
// Ensure cleanup of the fallback listener
|
|
resizeObserver = {
|
|
disconnect: () => window.removeEventListener('resize', handleResize),
|
|
} as unknown as ResizeObserver;
|
|
}
|
|
|
|
return () => {
|
|
if (rafId !== null) {
|
|
cancelAnimationFrame(rafId);
|
|
}
|
|
scheduleUpdateRef.current = () => {};
|
|
resizeObserver?.disconnect();
|
|
};
|
|
}, [enabled, quickAccessRef, toolPanelRef, rightRailRef]);
|
|
|
|
// Secondary effect: (re)attach observers when refs' .current become available later
|
|
useLayoutEffect(() => {
|
|
if (!enabled) return;
|
|
if (typeof ResizeObserver === 'undefined') return;
|
|
const qa = quickAccessRef.current;
|
|
const rail = rightRailRef?.current ?? null;
|
|
if (!qa && !rail) return;
|
|
|
|
const ro = new ResizeObserver(() => scheduleUpdateRef.current());
|
|
if (qa) ro.observe(qa);
|
|
if (rail) ro.observe(rail);
|
|
return () => ro.disconnect();
|
|
}, [enabled, quickAccessRef.current, rightRailRef?.current]);
|
|
|
|
return geometry;
|
|
}
|