diff --git a/apps/readest-app/src/__tests__/utils/sel.test.ts b/apps/readest-app/src/__tests__/utils/sel.test.ts index 511f4a06..14f56e4c 100644 --- a/apps/readest-app/src/__tests__/utils/sel.test.ts +++ b/apps/readest-app/src/__tests__/utils/sel.test.ts @@ -6,9 +6,39 @@ import type { Rect, Position } from '@/utils/sel'; // constrainPointWithinRect is also non-exported but exercised through getPosition. // For getPopupPosition we can test directly. -import { getPopupPosition } from '@/utils/sel'; +import { getPopupPosition, isPointInRect } from '@/utils/sel'; describe('sel utilities', () => { + describe('isPointInRect', () => { + const rect: Rect = { left: 100, top: 50, right: 300, bottom: 200 }; + + it('returns true for a point strictly inside the rect', () => { + expect(isPointInRect({ x: 200, y: 100 }, rect)).toBe(true); + }); + + it('treats edges as outside with the default 1px padding', () => { + expect(isPointInRect({ x: 100, y: 100 }, rect)).toBe(false); + expect(isPointInRect({ x: 300, y: 100 }, rect)).toBe(false); + expect(isPointInRect({ x: 200, y: 50 }, rect)).toBe(false); + expect(isPointInRect({ x: 200, y: 200 }, rect)).toBe(false); + }); + + it('returns true on edges when padding is 0', () => { + expect(isPointInRect({ x: 100, y: 50 }, rect, 0)).toBe(true); + expect(isPointInRect({ x: 300, y: 200 }, rect, 0)).toBe(true); + }); + + it('returns false when x is outside', () => { + expect(isPointInRect({ x: 99, y: 100 }, rect)).toBe(false); + expect(isPointInRect({ x: 301, y: 100 }, rect)).toBe(false); + }); + + it('returns false when y is outside', () => { + expect(isPointInRect({ x: 200, y: 49 }, rect)).toBe(false); + expect(isPointInRect({ x: 200, y: 201 }, rect)).toBe(false); + }); + }); + describe('getPopupPosition', () => { const boundingRect: Rect = { top: 0, right: 800, bottom: 600, left: 0 }; diff --git a/apps/readest-app/src/app/reader/components/annotator/Annotator.tsx b/apps/readest-app/src/app/reader/components/annotator/Annotator.tsx index a37d62ae..2c3b4604 100644 --- a/apps/readest-app/src/app/reader/components/annotator/Annotator.tsx +++ b/apps/readest-app/src/app/reader/components/annotator/Annotator.tsx @@ -127,7 +127,7 @@ const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => { // Tall enough to fit a header + 2-3 expanded cards comfortably. The popup // shows all enabled providers stacked (no tabs) so it needs more vertical // room than the legacy single-tab layout. - const dictPopupHeight = Math.min(480, maxHeight); + const dictPopupHeight = Math.min(360, maxHeight); const transPopupWidth = Math.min(480, maxWidth); const transPopupHeight = Math.min(265, maxHeight); const proofreadPopupWidth = Math.min(440, maxWidth); diff --git a/apps/readest-app/src/components/Popup.tsx b/apps/readest-app/src/components/Popup.tsx index 5a762f64..747c2876 100644 --- a/apps/readest-app/src/components/Popup.tsx +++ b/apps/readest-app/src/components/Popup.tsx @@ -1,5 +1,5 @@ import clsx from 'clsx'; -import { Position } from '@/utils/sel'; +import { Position, isPointInRect } from '@/utils/sel'; import { useEffect, useRef, useState } from 'react'; import { useResponsiveSize } from '@/hooks/useResponsiveSize'; import { useKeyDownActions } from '@/hooks/useKeyDownActions'; @@ -128,6 +128,18 @@ const Popup = ({ const outerTriangleStyles = getTriangleStyles(trianglePosition, 7, 0); const innerTriangleStyles = getTriangleStyles(trianglePosition, 7, -1); + const popupHeight = height ?? childrenHeight; + const triangleHidden = !!( + adjustedPosition && + trianglePosition && + isPointInRect(trianglePosition.point, { + left: adjustedPosition.point.x, + top: adjustedPosition.point.y, + right: adjustedPosition.point.x + width, + bottom: adjustedPosition.point.y + popupHeight, + }) + ); + return (