Add hover effects and dynamic anchor computation to Station component
- Integrated `onTextHover` callback for handling text hover states. - Implemented dynamic anchor calculation using `useMemo` for improved positioning. - Updated visual feedback by highlighting labels on hover. - Adjusted offset calculations and added interactive pointer movement refinements. - Added `packageManager` field to `package.json`.
This commit is contained in:
		| @@ -1,5 +1,5 @@ | ||||
| import { FederatedMouseEvent, Graphics } from "pixi.js"; | ||||
| import { useCallback, useState, useEffect, useRef, FC } from "react"; | ||||
| import {useCallback, useState, useEffect, useRef, FC, useMemo} from "react"; | ||||
| import { observer } from "mobx-react-lite"; | ||||
|  | ||||
| // --- Заглушки для зависимостей (замените на ваши реальные импорты) --- | ||||
| @@ -67,6 +67,8 @@ interface StationProps { | ||||
|   labelAlign?: LabelAlign; | ||||
|   /** Callback для изменения внутреннего выравнивания */ | ||||
|   onLabelAlignChange?: (align: LabelAlign) => void; | ||||
|   /** Callback для отслеживания наведения на текст */ | ||||
|   onTextHover?: (isHovered: boolean) => void; | ||||
| } | ||||
|  | ||||
| interface LabelAlignmentControlProps { | ||||
| @@ -82,6 +84,18 @@ interface LabelAlignmentControlProps { | ||||
| interface StationLabelProps | ||||
|   extends Omit<StationProps, "ruLabelAnchor" | "nameLabelAnchor"> {} | ||||
|  | ||||
| const getAnchorFromOffset = (offsetX: number, offsetY: number): { x: number; y: number } => { | ||||
|   if (offsetX === 0 && offsetY === 0) { | ||||
|     return { x: 0.5, y: 0.5 }; | ||||
|   } | ||||
|  | ||||
|   const length = Math.hypot(offsetX, offsetY); | ||||
|   const nx = offsetX / length; | ||||
|   const ny = offsetY / length; | ||||
|  | ||||
|   return { x: (1 - nx) / 2, y: (1 - ny) / 2 }; | ||||
| }; | ||||
|  | ||||
| // ========================================================================= | ||||
| // Компонент: Панель управления выравниванием в стиле УрФУ | ||||
| // ========================================================================= | ||||
| @@ -240,6 +254,7 @@ const StationLabel = observer( | ||||
|     labelBlockAnchor: labelBlockAnchorProp, | ||||
|     labelAlign: labelAlignProp = "center", | ||||
|     onLabelAlignChange, | ||||
|     onTextHover, | ||||
|   }: Readonly<StationLabelProps>) => { | ||||
|     const { language } = languageStore; | ||||
|     const { rotation, scale } = useTransform(); | ||||
| @@ -272,6 +287,7 @@ const StationLabel = observer( | ||||
|         hideTimer.current = null; | ||||
|       } | ||||
|       setIsHovered(true); | ||||
|       onTextHover?.(true); // Call the callback to indicate text is hovered | ||||
|     }; | ||||
|  | ||||
|     const handleControlPointerEnter = () => { | ||||
| @@ -282,6 +298,7 @@ const StationLabel = observer( | ||||
|       } | ||||
|       setIsControlHovered(true); | ||||
|       setIsHovered(true); | ||||
|       onTextHover?.(true); // Call the callback to indicate text/control is hovered | ||||
|     }; | ||||
|  | ||||
|     const handleControlPointerLeave = () => { | ||||
| @@ -290,6 +307,7 @@ const StationLabel = observer( | ||||
|       if (!isHovered) { | ||||
|         hideTimer.current = setTimeout(() => { | ||||
|           setIsHovered(false); | ||||
|           onTextHover?.(false); // Call the callback to indicate neither text nor control is hovered | ||||
|         }, 0); | ||||
|       } | ||||
|     }; | ||||
| @@ -302,6 +320,7 @@ const StationLabel = observer( | ||||
|         if (!isControlHovered) { | ||||
|           setIsControlHovered(false); | ||||
|         } | ||||
|         onTextHover?.(false); // Call the callback to indicate text is no longer hovered | ||||
|       }, 100); // Увеличиваем время до скрытия панели | ||||
|     }; | ||||
|  | ||||
| @@ -347,7 +366,7 @@ const StationLabel = observer( | ||||
|     const compensatedRuFontSize = (26 * 0.75) / scale; | ||||
|     const compensatedNameFontSize = (16 * 0.75) / scale; | ||||
|     const minDistance = 30; | ||||
|     const compensatedOffset = Math.max(minDistance / scale, 24 / scale); | ||||
|     const compensatedOffset = minDistance / scale; | ||||
|     const textBlockPosition = anchorPoint || position; | ||||
|     const finalLabelBlockAnchor = labelBlockAnchorProp || "right center"; | ||||
|     const labelBlockAnchor = | ||||
| @@ -372,17 +391,20 @@ const StationLabel = observer( | ||||
|  | ||||
|     const handlePointerMove = (e: FederatedMouseEvent) => { | ||||
|       if (!isPointerDown) return; | ||||
|  | ||||
|       if (!isDragging) { | ||||
|         const dx = e.global.x - mouseStartPos.current.x; | ||||
|         const dy = e.global.y - mouseStartPos.current.y; | ||||
|         if (Math.hypot(dx, dy) > 3) setIsDragging(true); | ||||
|         else return; | ||||
|       } | ||||
|       const dx = (e.global.x - mouseStartPos.current.x) / scale; | ||||
|       const dy = (e.global.y - mouseStartPos.current.y) / scale; | ||||
|  | ||||
|       const dx_screen = e.global.x - mouseStartPos.current.x; | ||||
|       const dy_screen = e.global.y - mouseStartPos.current.y; | ||||
|  | ||||
|       const newPosition = { | ||||
|         x: dragStartPos.current.x + dx, | ||||
|         y: dragStartPos.current.y + dy, | ||||
|         x: dragStartPos.current.x + dx_screen, | ||||
|         y: dragStartPos.current.y + dy_screen, | ||||
|       }; | ||||
|  | ||||
|       // Проверяем, изменилась ли позиция | ||||
| @@ -410,6 +432,11 @@ const StationLabel = observer( | ||||
|       setStationAlign(station.id, numericAlign); | ||||
|     }; | ||||
|  | ||||
|     const dynamicAnchor = useMemo( | ||||
|         () => getAnchorFromOffset(position.x, position.y), | ||||
|         [position.x, position.y] | ||||
|     ); | ||||
|  | ||||
|     // Функция для расчета позиции нижнего лейбла относительно ширины верхнего | ||||
|     const getSecondLabelPosition = (): number => { | ||||
|       if (!ruLabelWidth) return 0; | ||||
| @@ -459,13 +486,11 @@ const StationLabel = observer( | ||||
|         onGlobalPointerMove={handlePointerMove} | ||||
|       > | ||||
|         <pixiContainer | ||||
|           position={{ | ||||
|             x: | ||||
|               textBlockPosition.x + | ||||
|               compensatedOffset * (labelBlockAnchor.x === 1 ? -1 : 1), | ||||
|             y: textBlockPosition.y, | ||||
|           }} | ||||
|           anchor={labelBlockAnchor} | ||||
|             position={{ | ||||
|               x: (position.x + Math.cos(Math.atan2(position.y, position.x))) / scale, | ||||
|               y: (position.y + Math.sin(Math.atan2(position.y, position.x))) / scale, | ||||
|             }} | ||||
|           anchor={dynamicAnchor} | ||||
|         > | ||||
|           {ruLabel && ( | ||||
|             <pixiText | ||||
| @@ -524,6 +549,8 @@ export const Station = ({ | ||||
|   labelAlign, | ||||
|   onLabelAlignChange, | ||||
| }: Readonly<StationProps>) => { | ||||
|   const [isTextHovered, setIsTextHovered] = useState(false); | ||||
|  | ||||
|   const draw = useCallback( | ||||
|     (g: Graphics) => { | ||||
|       g.clear(); | ||||
| @@ -531,12 +558,22 @@ export const Station = ({ | ||||
|         station.latitude, | ||||
|         station.longitude | ||||
|       ); | ||||
|  | ||||
|       const radius = STATION_RADIUS; | ||||
|       const strokeWidth = STATION_OUTLINE_WIDTH; | ||||
|  | ||||
|       g.circle(coordinates.x * UP_SCALE, coordinates.y * UP_SCALE, radius); | ||||
|       g.fill({ color: PATH_COLOR }); | ||||
|       g.stroke({ color: BACKGROUND_COLOR, width: STATION_OUTLINE_WIDTH }); | ||||
|  | ||||
|       // Change fill color when text is hovered | ||||
|       if (isTextHovered) { | ||||
|         g.fill({ color: 0x00AAFF }); // Highlight color when hovered | ||||
|         g.stroke({ color: 0xFFFFFF, width: strokeWidth + 1 }); // Brighter outline when hovered | ||||
|       } else { | ||||
|         g.fill({ color: PATH_COLOR }); | ||||
|         g.stroke({ color: BACKGROUND_COLOR, width: strokeWidth }); | ||||
|       } | ||||
|     }, | ||||
|     [station.latitude, station.longitude] | ||||
|     [station.latitude, station.longitude, isTextHovered] | ||||
|   ); | ||||
|  | ||||
|   return ( | ||||
| @@ -549,6 +586,7 @@ export const Station = ({ | ||||
|         labelBlockAnchor={labelBlockAnchor} | ||||
|         labelAlign={labelAlign} | ||||
|         onLabelAlignChange={onLabelAlignChange} | ||||
|         onTextHover={setIsTextHovered} | ||||
|       /> | ||||
|     </pixiContainer> | ||||
|   ); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user