import React, { useEffect, useRef } from 'react';
import { Transformer } from 'react-konva';
import Konva from 'konva';
import { useGridStore, useSelectionStore } from '../store';
import { Page } from '../models/pages/page';
import RenderPrimitiveByType from './RenderPrimitiveByType';
import { calculateLinePoints } from '../utils/calculateLinePoints';
import { useStageInteractionContext } from '../hooks/useStageInteractionContext';
import { Subject } from 'rxjs';
import { getObstacles } from '../utils/getObstacles';
import { EditorPrimitives } from '../models/primitives/enums/editor-primitives';
import { useDocumentContext } from '../hooks/useDocumentContext';

const connectionSource = new Subject<{
  pageId: string | number;
  fromId: string | number;
  toId: string | number;
  points: number[];
}>();
export const connection$ = connectionSource.asObservable();

interface RenderPageProps {
  page: Page;
}

const RenderPage: React.FC<RenderPageProps> = ({ page }) => {
  const stageInteraction = useStageInteractionContext();
  const editorDocument = useDocumentContext();
  const transformerRef = useRef<Konva.Transformer>(null);
  const { selectedObjects, connection } = useSelectionStore();
  const gridSize = useGridStore((state) => state.gridSize); // Получаем gridSize

  // Храним «исходный угол» и флаг вращения
  const initialRotationRef = useRef(0);
  const isRotatingRef = useRef(false);

  useEffect(() => {
    const transformer = transformerRef.current;
    if (transformer) {
      const layer = transformer.getLayer();
      if (!layer) {
        return;
      }
      // const rotation = transformer.rotation();
      const nodes = layer
        .getChildren()
        .filter((node) => selectedObjects.map((object) => object.id).includes(node.id()));

      // Если nodes не изменились, не обновляем трансформер
      const currentNodes = transformer.nodes();
      if (currentNodes.length === nodes.length && currentNodes.every((node, index) => node === nodes[index])) {
        return;
      }

      transformer.ignoreStroke(!(nodes?.length === 1 && nodes[0].getClassName() === 'Line'));
      transformer.zIndex(10000);
      transformer.nodes(nodes);
      // transformer.rotation(rotation);
      layer.batchDraw();
    }
  }, [gridSize, page.height, page.width, selectedObjects, editorDocument.activePage]);

  // Обработчик завершения трансформации
  const handleTransformEnd = (e) => {
    // console.log('2');
    const node = e.target;
    if (!node) return;

    // Синхронизируем угол поворота трансформера с углом поворота группы
    const transformer = transformerRef.current;
    if (transformer) {
      transformer.rotation(node.rotation());

      // Принудительно обновляем трансформер
      transformer.update();
      transformer.getLayer()?.batchDraw();
    }

    isRotatingRef.current = false;
  };

  useEffect(() => {
    if (!connection || !transformerRef.current) {
      return;
    }

    const newPoints = calculateLinePoints(
      {
        x: connection.fromPosition.x,
        y: connection.fromPosition.y,
      },
      { x: connection.toPosition.x, y: connection.toPosition.y },
      connection.fromParentId,
      connection.toParentId,
      stageInteraction.scale,
      stageInteraction.point,
      getObstacles(transformerRef.current.getLayer()),
    );

    connectionSource.next({ pageId: page.id, fromId: connection.fromId, toId: connection.toId, points: newPoints });
  }, [connection]);

  // Функция привязки к сетке
  const snapToGrid = (value: number): number => {
    return Math.round(value / gridSize) * gridSize;
  };

  return (
    <>
      {Array.from(page.primitives.values())
        .filter((x) => x.type !== EditorPrimitives.Frame)
        .sort((a, b) => a.order - b.order)
        .map((primitive) => (
          <RenderPrimitiveByType key={primitive.id} primitive={primitive} />
        ))}
      {!selectedObjects.some((object) => object.id?.toString().includes('connection-')) && (
        <Transformer
          id={'service-transformer'}
          ref={transformerRef}
          keepRatio={false}
          ignoreStroke={true}
          enabledAnchors={[
            'top-left',
            'top-right',
            'bottom-left',
            'bottom-right',
            'top-center',
            'bottom-center',
            'middle-left',
            'middle-right',
          ]}
          rotationSnaps={[0, 45, 90, 135, 180, 225, 270, 315]}
          boundBoxFunc={(oldBox, newBox) => {
            // limit resize
            if (newBox.width < 10 || newBox.height < 10) {
              transformerRef.current?.stopTransform();
              return oldBox;
            }

            // Блокируем растяжение по высоте для линий
            const nodes = transformerRef.current?.nodes();
            if (nodes?.length === 1 && nodes[0].getClassName() === 'Line') {
              newBox.height = oldBox.height;
            }

            // Получаем текущий масштаб и смещение сцены
            const { scale: stageScale, point: stagePoint } = stageInteraction;

            const adjustedX = Math.max(newBox.x, stagePoint.x);
            const adjustedY = Math.max(newBox.y, stagePoint.y);

            // Преобразуем координаты примитива в абсолютные (относительно сцены)
            const absoluteX = (adjustedX - stagePoint.x) / stageScale;
            const absoluteY = (adjustedY - stagePoint.y) / stageScale;

            // Рассчитываем максимальные допустимые размеры
            const maxWidth = (Math.floor(page.width / gridSize) * gridSize - absoluteX) * stageScale;
            const maxHeight = (Math.floor(page.height / gridSize) * gridSize - absoluteY) * stageScale;

            // Ограничиваем размеры, чтобы примитив не выходил за границы листа
            const adjustedWidth = Math.min(newBox.width, maxWidth);
            const adjustedHeight = Math.min(newBox.height, maxHeight);

            return {
              ...newBox,
              x: adjustedX,
              y: adjustedY,
              width: adjustedWidth,
              height: adjustedHeight,
            };
          }}
          // При любом старте трансформации сохраняем «текущий» угол
          onTransformStart={(e) => {
            const node = e.target; // это общий Shape/Group, над которым трансформер
            if (node) {
              initialRotationRef.current = node.rotation();
            }
            isRotatingRef.current = false;
          }}
          // На каждом шаге проверяем, изменился ли угол
          onTransform={(e) => {
            const node = e.target;
            if (!node) return;
            const currentRotation = node.rotation();
            const diff = Math.abs(currentRotation - initialRotationRef.current);

            // При желании можно поставить порог в градусах, чтобы
            // исключить всякие мелкие неточности float. Например, 0.01.
            if (diff > 0.001) {
              isRotatingRef.current = true;
            }
          }}
          // Когда закончили трансформацию — сбрасываем флаг
          onTransformEnd={handleTransformEnd}
          anchorDragBoundFunc={(oldPos, newPos) => {
            // Если сейчас «идёт вращение», просто возвращаем старую позицию
            // (отменяя логику привязки)
            if (isRotatingRef.current) {
              return newPos;
            }

            const stageScale = stageInteraction.scale; // Get the stage scale
            const stagePoint = stageInteraction.point; // Get the stage position (e.g., stage offset)

            // Calculate the absolute position on the stage
            const absoluteX = (newPos.x - stagePoint.x) / stageScale;
            const absoluteY = (newPos.y - stagePoint.y) / stageScale;

            // Snap to grid
            const snappedX = snapToGrid(absoluteX);
            const snappedY = snapToGrid(absoluteY);

            // Convert back to stage-relative coordinates
            const adjustedX = snappedX * stageScale + stagePoint.x;
            const adjustedY = snappedY * stageScale + stagePoint.y;

            return {
              x: adjustedX,
              y: adjustedY,
            };
          }}
        />
      )}
    </>
  );
};

export default RenderPage;

это мой код. сейчас я сделала код для поворота группы коннекторов. функция handleTransformEnd синхронизирует угол поворота трансформера для группы. но у меня есть еще такой код 
import React, { useCallback, useContext, useEffect, useRef, useState } from 'react';
import { Group, Layer, Line, Rect, Stage } from 'react-konva';
import Grid from './Grid';
import { SizeContext } from '../contexts/size-context';
import { useStageInteractionContext } from '../hooks/useStageInteractionContext';
import { useDocumentContext } from '../hooks/useDocumentContext';
import Konva from 'konva';
import WebFont from 'webfontloader';
import { SelectedObject, useGridStore, useSelectionStore } from '../store';
import { ContextMenu } from 'primereact/contextmenu';
import { MenuItem } from 'primereact/menuitem';
import { Subject, tap } from 'rxjs';
import { Icon } from '@tecon/svg-icons';
import { IPoint } from '../models/infrastucture/point';
import { useEditorToolsStore } from '../store/editorTools-store';
import { Actions } from '../enums/actions';
import RenderPage from '../renderers/RenderPage';
import RenderFrame from '../renderers/RenderFrame';
import { EditorPrimitives } from '../models/primitives/enums/editor-primitives';
import { Frame } from '../models/frames/frame';
import { previewConnection$ } from '../renderers/RenderConnector';
import { useModalDialogStore } from '../store/modalDialog-store';
import { ModalKey } from '../constants';
import { Page } from '../models/pages/page';
import {
  findCrossPageLink,
  findInPageLinkCouple,
  findPageLinkParent,
  getPrimitivesPosition,
} from '../utils/primitiveSearch';
import { findPageById } from '../utils/inDocumentSearch';
import useBroadcastChannel from '../hooks/useBroadcastChannel';

export enum ObjectChange {
  Remove,
  Copy,
  Paste,
  Add,
  AddLink,
}

const objectChangeSource = new Subject<{
  pageId: string | number | undefined;
  id: Array<string | number | SelectedObject>;
  action: ObjectChange;
  data?: object;
}>();
export const objectChange$ = objectChangeSource.asObservable();

const dragSource = new Subject<{ pageId: string | number; position: IPoint }>();
export const drag$ = dragSource.asObservable();

const inversionSource = new Subject<{ id: string | number; inversion: boolean }>();
export const inversion$ = inversionSource.asObservable();

const breakSource = new Subject<{ id: string | number; position: IPoint }>();
export const break$ = breakSource.asObservable();

const goTreeSource = new Subject<{ id: string }>();
export const goTree$ = goTreeSource.asObservable();

interface GraphEditorProps {
  isDrag: boolean;
  onDragEnd: () => void;
  onDragEnter: () => void;
  onDragLeave: () => void;
  actionSource: Subject<{ action: Actions; pageId?: string | number; x?: number; y?: number }>;
}

const GraphEditor = ({ isDrag, onDragEnd, onDragEnter, onDragLeave, actionSource }: GraphEditorProps) => {
  const editorDocument = useDocumentContext();
  const stageInteraction = useStageInteractionContext();
  const size = useContext(SizeContext);
  const stageRef = useRef<Konva.Stage | null>(null);
  const [fontLoaded, setFontLoaded] = useState(false);
  const [pointerPosition, setPointerPosition] = useState<IPoint>({ x: 0, y: 0 });
  const [previewConnection, setPreviewConnection] = useState<number[]>([]);
  const [isLinkClicked, setIsLinkClicked] = useState(false);
  const isConnectorEndPointClicked = useRef<boolean>(false);
  const [isSelectingMode, setIsSelectingMode] = useState(false);
  const [selectionRect, setSelectionRect] = useState({ x: 0, y: 0, width: 0, height: 0 });
  const [isMouseUpOutOfComponent, setIsMouseUpOutOfComponent] = useState<boolean>(false);
  const isSelectingModeBlocked = useRef<boolean>(false);
  const selectedObjects = useSelectionStore((state) => state.selectedObjects);
  const setSelectedObjects = useSelectionStore((state) => state.setSelectedObjects);
  const selectedObjectReferenceId = useSelectionStore((state) => state.selectedObjectReferenceId);
  const setSelectedObjectReferenceId = useSelectionStore((state) => state.setSelectedObjectReferenceId);
  const [isDraggingStage, setIsDraggingStage] = useState(false);
  const editorToolsStore = useEditorToolsStore();
  const gridSize = useGridStore((state) => state.gridSize);
  const { setModalKey } = useModalDialogStore();

  const copyBroadcastChannel = useBroadcastChannel<Array<string | number>>('tss-graph-editor-copy-channel');

  useEffect(() => {
    WebFont.load({
      custom: {
        families: ['GOST Type A'], // Name of the font
        urls: ['../../index.css'], // URL to the font file
      },
      active: () => {
        setFontLoaded(true);
        console.log('Fonts loaded successfully');
      },
      inactive: () => {
        console.error('Failed to load fonts');
      },
    });
  }, []);

  useEffect(() => {
    console.log('active page changed', editorDocument.activePage?.id);
  }, [editorDocument.activePage]);

  useEffect(() => {
    const handleKeyDown = (e: KeyboardEvent) => {
      if (e.key === 'Escape') {
        editorToolsStore.setAction(null);
        editorToolsStore.setPageId(null);
      }
    };

    window.addEventListener('keydown', handleKeyDown);
    window.addEventListener('mouseup', mouseUpListener);
    window.addEventListener('mousedown', mouseDownListener);

    return () => {
      window.removeEventListener('keydown', handleKeyDown);
      window.removeEventListener('mouseup', mouseUpListener);
      window.removeEventListener('mousedown', mouseDownListener);
    };
  }, []);

  useEffect(() => {
    setSelectedObjectReferenceId(null);
  }, [editorDocument.activePage?.id]);

  useEffect(() => {
    if (isMouseUpOutOfComponent) {
      handleMouseUp();
      setIsMouseUpOutOfComponent(false);
    }
  }, [isMouseUpOutOfComponent]);

  const handleStageClick = (e: Konva.KonvaEventObject<MouseEvent>) => {
    switch (e.evt.button) {
      case 0: {
        if (e.target === stageRef.current || e.target.id() === 'page') {
          setSelectedObjectReferenceId(null);
        }
        break;
      }
    }

    checkClickOnEmptyStagePlace(e);
    isLinkClickedOn(e);

    if (editorToolsStore.action) {
      addSimplePrimitiveOnStage();
    }
  };

  const checkClickOnEmptyStagePlace = (e: Konva.KonvaEventObject<MouseEvent>) => {
    const stage = e.target.getStage();
    const pointerPosition = stage?.getPointerPosition();
    // Проверяем, есть ли объект под курсором
    const shape = pointerPosition && stage?.getIntersection(pointerPosition);

    if (isSelectingMode) {
      return;
    }

    if (!shape || shape.attrs.id === 'page') {
      setSelectedObjects([]);
    }
  };

  /**
   * Проверяет, был ли щелчок по ссылке
   * @param e
   */
  const isLinkClickedOn = (e: Konva.KonvaEventObject<MouseEvent>) => {
    const parentType = e.target.parent?.attrs.parentType;

    if (parentType === EditorPrimitives.InlinePageLink || parentType === EditorPrimitives.CrossPageLink) {
      setIsLinkClicked(true);
    } else {
      setIsLinkClicked(false);
    }
  };

  const addSimplePrimitiveOnStage = () => {
    if (!editorToolsStore.action || !editorToolsStore.pageId) {
      return;
    }

    const pointerPosition = getPointerPositionOnStage();

    actionSource.next({
      action: editorToolsStore.action,
      pageId: editorToolsStore.pageId,
      x: Math.round(pointerPosition.x / gridSize) * gridSize,
      y: Math.round(pointerPosition.y / gridSize) * gridSize,
    });

    editorToolsStore.setAction(null);
    editorToolsStore.setPageId(null);
  };

  const onDelete = () => {
    console.log('objects deletion', selectedObjects);
    const ids: (string | number)[] = [];
    selectedObjects?.forEach((object) =>
      object?.id.toString().startsWith('connection-')
        ? ids.push(object?.id.toString().replace(/connection-(.+?)-segment-\d+/, '$1'))
        : ids.push(object?.id),
    );

    objectChangeSource.next({
      pageId: editorDocument.activePage?.id,
      id: [...ids],
      action: ObjectChange.Remove,
    });
    setSelectedObjects([]);
    setSelectedObjectReferenceId(null);
  };

  const onPaste = () => {
    // Get the mouse position relative to the stage
    const stage = stageRef.current;
    const mousePosition = stage?.getPointerPosition() ?? { x: 0, y: 0 };

    // Calculate the position relative to the page
    const pageX = (mousePosition.x - stageInteraction.point.x) / stageInteraction.scale;
    const pageY = (mousePosition.y - stageInteraction.point.y) / stageInteraction.scale;

    // Use the calculated coordinates
    objectChangeSource.next({
      pageId: editorDocument.activePage?.id,
      id: copyBroadcastChannel.message,
      action: ObjectChange.Paste,
      data: { x: pageX, y: pageY },
    });
  };

  /**
   * Инвертирует коннектор
   * @param connector
   */
  const onInversion = (connector: Konva.Shape) => {
    const inversion = connector.getAttr('inversion');
    inversionSource.next({
      id: connector.id().toString().replace('connector-', ''),
      inversion: !inversion,
    });
    connector.setAttr('inversion', !inversion);
  };

  /**
   * Добавляет излом на связь
   */
  const onAddBreak = (id: string | undefined) => {
    const stage = stageRef.current;
    const mousePosition = stage?.getPointerPosition() ?? { x: 0, y: 0 };
    const x = (mousePosition.x - stageInteraction.point.x) / stageInteraction.scale;
    const y = (mousePosition.y - stageInteraction.point.y) / stageInteraction.scale;

    if (mousePosition) {
      if (!id) return;

      breakSource.next({
        id: id,
        position: { x: x, y: y },
      });
    }
  };

  /**
   * Осуществляет переход к объекту в древе объектов
   */
  const onGoTree = () => {
    if (!selectedObjectReferenceId) {
      return;
    }
    goTreeSource.next({ id: selectedObjectReferenceId });
    setSelectedObjectReferenceId(null);
  };

  /**
   * Вызывает диалоговое окно элементов, связанных с выделенном сигналом
   */
  const showObjectLinks = () => {
    setModalKey(ModalKey.SELECT_SIGNAL_USAGE);
  };

  /**
   * Осуществляет переход к входной или выходной ссылке
   */
  const onGoToLink = () => {
    const selectedLinkId = selectedObjects[0].id;
    const activePageId = editorDocument.activePage?.id;
    const pages = editorDocument.pages;

    findPageLink(activePageId, selectedLinkId, pages);
  };

  /**
   * Находит пары ссылок на страницах документа
   * @param activePageId - id активной страницы
   * @param linkId - id ссылки
   * @param pages -массив страниц документа
   */
  const findPageLink = (
    activePageId: string | undefined,
    linkId: string | number | undefined,
    pages: Page[] | undefined,
  ) => {
    if (!activePageId || !linkId || !pages) return;

    const activePage = pages.find((page) => page.id === activePageId);
    const primitiveParent = findPageLinkParent(linkId, activePage?.primitives, null);
    const coupleInPageLinkId =
      primitiveParent && findInPageLinkCouple(linkId, activePage?.primitives, primitiveParent?.id);

    //если ссылка внутристраничная
    if (coupleInPageLinkId) {
      setSelectedObjects([{ id: coupleInPageLinkId }]);
    }
    //если ссылка межстраничная
    else {
      for (const page of pages) {
        const crossPagePrimitive = findCrossPageLink(
          primitiveParent?.linkPageId,
          page.primitives,
          primitiveParent?.pageId,
          null,
        );
        if (crossPagePrimitive) {
          const page = findPageById(pages, crossPagePrimitive?.pageId);
          const inPageCouplePrimitiveId = [...(crossPagePrimitive?.primitives?.keys() || [])][0];

          editorDocument.setActivePage(page);
          inPageCouplePrimitiveId && setSelectedObjects([{ id: inPageCouplePrimitiveId }]);
          break;
        }
      }
    }
  };

  /**
   * Обработчик отображения контекстного меню
   * @param e
   */
  const handleContextMenu = (e: Konva.KonvaEventObject<MouseEvent>) => {
    e.evt.preventDefault(); // Prevent the default browser context menu

    const getMenuItems = (): MenuItem[] => {
      // Не отображаем меню для рамки
      if (e.target.parent?.id().startsWith('frame-') && copyBroadcastChannel.message?.length === 0) {
        return [];
      }

      // Меню для коннектора
      if (e.target.id().startsWith('connector-')) {
        return [
          {
            label: 'Инверсия',
            command: () => {
              onInversion(e.target as Konva.Shape);
            },
            icon: <Icon name={'InputOutputDarkblue'} size={25} />,
          },
        ];
      }

      // Меню для связи
      if (e.target.parent?.id().startsWith('connection-')) {
        return [
          {
            label: 'Излом',
            icon: <i className='pi pi-wave-pulse' style={{ color: '#1769AA' }} />,
            command: () => onAddBreak(e.target.parent?.id()),
          },
          {
            label: 'Удалить',
            icon: <Icon name={'Trash'} size={25} />,
            command: onDelete,
          },
        ];
      }

      // Меню для оставшихся элементов
      if (selectedObjects.length !== 0 || copyBroadcastChannel.message?.length !== 0) {
        return [
          {
            disabled: selectedObjects?.length === 0 || selectedObjects?.length > 1,
            label: 'Создать ссылку',
            icon: <Icon name={'LinkDarkblue'} size={25} />,
            command: () => {
              if (!selectedObjects[0]?.id) return;

              objectChangeSource.next({
                pageId: editorDocument.activePage?.id,
                id: [selectedObjects[0].id],
                action: ObjectChange.AddLink,
              });
            },
          },
          {
            disabled: selectedObjects?.length === 0,
            label: 'Копировать',
            icon: <Icon name={'CopyDarkblue'} size={25} />,
            command: () =>
              objectChangeSource.next({
                pageId: editorDocument.activePage?.id,
                id: [...selectedObjects],
                action: ObjectChange.Copy,
              }),
          },
          {
            disabled: copyBroadcastChannel.message?.length === 0,
            label: 'Вставить',
            icon: <Icon name={'PasteDarkblue'} size={25} />,
            command: onPaste,
          },
          {
            disabled: selectedObjectReferenceId === null,
            label: 'Выделить в древе',
            icon: <Icon name={'TreeDarkblue'} size={25} />,
            command: onGoTree,
          },
          {
            disabled: selectedObjectReferenceId === null,
            label: 'Показать связанные объекты',
            icon: <Icon name={'SearchDarkblue'} size={25} />,
            command: showObjectLinks,
          },
          {
            disabled: selectedObjects?.length === 0 || !isLinkClicked,
            label: 'Перейти по ссылке',
            icon: <Icon name={'ImportDarkblue'} size={25} />,
            command: onGoToLink,
          },
          { separator: true },
          {
            disabled: selectedObjects?.length === 0,
            label: 'Удалить',
            icon: <Icon name={'Trash'} size={25} />,
            command: onDelete,
          },
        ];
      }
      return [];
    };

    let menu: MenuItem[] = getMenuItems();
    if (selectedObjects.length === 0 || selectedObjects.length > 1) {
      if (menu.length > 1) {
        menu = menu.slice(1);
      }
    } else {
      const primitive = editorDocument.activePage?.primitives.get(selectedObjects[0].id);
      if (primitive && 'isResultSignal' in primitive && primitive.isResultSignal) {
        menu = menu.slice(1);
      }
    }

    // Устанавливаем элементы меню
    setMenuItems(menu);

    if (cm.current) {
      if (menu.length > 0) {
        requestAnimationFrame(() => {
          cm.current?.show(e.evt as unknown as React.SyntheticEvent);
        });
      } else {
        cm.current?.hide(e.evt as unknown as React.SyntheticEvent);
      }
    }
  };

  const cm = useRef<ContextMenu | null>(null);
  const [menuItems, setMenuItems] = useState<MenuItem[]>([]);

  const onKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
    if (e.code === 'Delete' && selectedObjects?.length > 0) {
      onDelete();
    }

    if (e.ctrlKey && e.code === 'KeyC' && selectedObjects?.length > 0) {
      objectChangeSource.next({
        pageId: editorDocument.activePage?.id,
        id: [...selectedObjects],
        action: ObjectChange.Copy,
      });
    }

    if (e.ctrlKey && e.code === 'KeyV') {
      onPaste();
    }
  };

  /**
   * Получение положения мыши на сцене
   * @returns {{x: number, y: number}} - Положение мыши на сцене.
   */
  const getPointerPositionOnStage = useCallback((): IPoint => {
    if (!stageRef.current) return { x: 0, y: 0 };
    const pointerPosition = stageRef.current.getPointerPosition();
    if (!pointerPosition) {
      return { x: 0, y: 0 };
    }

    const x = (pointerPosition.x - stageInteraction.point.x) / stageInteraction.scale;
    const y = (pointerPosition.y - stageInteraction.point.y) / stageInteraction.scale;

    return { x: x, y: y };
  }, [stageInteraction.point.x, stageInteraction.point.y, stageInteraction.scale]);

  /**
   * Обработчик события перетаскивания над элементом
   * @param {React.DragEvent<HTMLDivElement>} e - Событие перетаскивания
   */
  const handleDragOver = useCallback(
    (e: React.DragEvent<HTMLDivElement>) => {
      if (!isDrag) {
        return;
      }
      e.preventDefault();
      stageRef.current?.setPointersPositions(e);

      // Вычисляем смещение относительно предыдущей позиции
      const pointerPosition = getPointerPositionOnStage();

      setPointerPosition({ x: pointerPosition.x, y: pointerPosition.y });
    },
    [isDrag, getPointerPositionOnStage],
  );

  /**
   * Обработчик события отпускания перетаскиваемого элемента
   */
  const handleDrop = useCallback(() => {
    if (!editorDocument.activePage) {
      return;
    }

    const pointerPosition = getPointerPositionOnStage();

    dragSource.next({
      position: {
        x: Math.round(pointerPosition.x / gridSize) * gridSize,
        y: Math.round(pointerPosition.y / gridSize) * gridSize,
      },
      pageId: editorDocument.activePage.id,
    });

    // Удаляем превью
  }, [editorDocument.activePage, getPointerPositionOnStage, gridSize]);

  useEffect(() => {
    const subscription = previewConnection$
      .pipe(
        tap(async ({ points }) => {
          setPreviewConnection(points);
        }),
      )
      .subscribe({ error: (err) => console.log(err) });

    return () => subscription.unsubscribe();
  }, []);

  /**
   * Функция для включения режима перемещения сцены
   */
  const enableStageDragging = () => {
    setIsDraggingStage(true);

    // Делаем всю сцену draggable
    stageRef.current?.draggable(true);
  };

  /**
   * Функция для выключения режима перемещения сцены
   */
  const disableStageDragging = () => {
    setIsDraggingStage(false);

    // Отключаем draggable у сцены
    stageRef.current?.draggable(false);
  };

  /**
   * Обработчик кликов по сцене
   */
  const handleMouseDown = (e: Konva.KonvaEventObject<MouseEvent>) => {
    if (e.target.attrs.id === 'page' || e.target.attrs.id === 'stage' || e.target.attrs.id?.includes('frame')) {
      isSelectingModeBlocked.current = false;
    } else {
      isSelectingModeBlocked.current = true;
      return;
    }

    //если нажата левая кнопка => запомним координаты левой верхней точки рамки для множественного выделения
    if (e.evt.button === 0) {
      const pointerPosition = getPointerPositionOnStage();
      setSelectionRect({ x: pointerPosition.x, y: pointerPosition.y, width: 0, height: 0 });

      if (e.target.attrs.id && e.target.attrs.id.toString().includes('connector'))
        isConnectorEndPointClicked.current = true;
    }

    // Проверяем, нажата ли средняя кнопка мыши
    if (e.evt.button === 1) {
      enableStageDragging();
    }
  };

  const handleOnMouseOver = (e: React.MouseEvent<HTMLDivElement>) => {
    if (!editorToolsStore.action) {
      return;
    }
    e.preventDefault();
    stageRef.current?.setPointersPositions(e);

    // Вычисляем смещение относительно предыдущей позиции
    const pointerPosition = getPointerPositionOnStage();

    setPointerPosition({ x: pointerPosition.x, y: pointerPosition.y });
  };

  const handleMouseMove = (e: Konva.KonvaEventObject<MouseEvent>) => {
    //если зажата левая кнопка мыши
    if (e.evt.buttons === 1) {
      if (isConnectorEndPointClicked.current || isSelectingModeBlocked.current) return;

      setIsSelectingMode(true);

      const pointerPosition = getPointerPositionOnStage();
      pointerPosition &&
        setSelectionRect((prevRect) => ({
          ...prevRect,
          width: pointerPosition.x - prevRect.x,
          height: pointerPosition.y - prevRect.y,
        }));
    }
  };

  const handleMouseUp = () => {
    disableStageDragging();
    setIsSelectingMode(false);
    isConnectorEndPointClicked.current = false;

    const selectionArea = {
      x: Math.min(selectionRect.x, selectionRect.x + selectionRect.width),
      y: Math.min(selectionRect.y, selectionRect.y + selectionRect.height),
      width: Math.abs(selectionRect.width),
      height: Math.abs(selectionRect.height),
    };

    if (!selectionArea.width || !selectionArea.height || !editorDocument) return;

    const allPrimitivesOnPage = getPrimitivesPosition(editorDocument.activePage?.primitives);

    const selectedItems = allPrimitivesOnPage?.filter(
      (primitive) =>
        primitive.points &&
        primitive.points?.x >= selectionArea.x &&
        primitive.points?.x <= selectionArea.x + selectionArea.width &&
        primitive.points?.y >= selectionArea.y &&
        primitive.points?.y <= selectionArea.y + selectionArea.height,
    );

    selectedItems && setSelectedObjects(selectedItems);
  };

  useEffect(() => {
    setSelectionRect({ x: 0, y: 0, width: 0, height: 0 });
  }, [editorDocument.activePage?.id]);

  const mouseUpListener = () => {
    setIsMouseUpOutOfComponent(true);
    isSelectingModeBlocked.current = false;
  };

  const mouseDownListener = (e: MouseEvent) => {
    if (e.target?.className?.includes('p-splitter-gutter')) {
      isSelectingModeBlocked.current = true;
    }
  };

  return fontLoaded ? (
    fontLoaded && editorDocument.activePage && (
      <div
        style={{ outline: 'none' }}
        tabIndex={1}
        onKeyDown={onKeyDown}
        onDrop={handleDrop}
        onDragOver={handleDragOver}
        onDragEnter={onDragEnter}
        onDragLeave={onDragLeave}
        onDragEnd={onDragEnd}
        onMouseMove={handleOnMouseOver}
      >
        <Stage
          id={'stage'}
          ref={stageRef}
          width={size.width}
          height={size.height}
          scaleX={stageInteraction.scale}
          scaleY={stageInteraction.scale}
          x={stageInteraction.point.x}
          y={stageInteraction.point.y}
          onWheel={stageInteraction.handleWheel}
          onClick={handleStageClick}
          onContextMenu={handleContextMenu}
          onMouseDown={handleMouseDown}
          onMouseUp={handleMouseUp}
          onMouseMove={handleMouseMove}
          onDragMove={(e) => {
            if (isDraggingStage) {
              // Меняем позицию канвы, только если активен режим перетаскивания сцены
              stageInteraction.setPoint(e.currentTarget.position());
            }
          }}
          dragBoundFunc={(pos) => {
            // размеры листа:
            const sheetWidth = editorDocument.activePage?.width ?? 0;
            const sheetHeight = editorDocument.activePage?.height ?? 0;

            const offset = 600; // на 600 пикселей от границы листа

            // Фактические размеры контента с учетом масштаба
            const scaledWidth = sheetWidth * stageInteraction.scale;
            const scaledHeight = sheetHeight * stageInteraction.scale;

            // Расчёт границ:
            // maxX = offset означает, что можно сдвинуть сцену на 600 пикселей вправо за границу контента
            // minX = size.width - scaledWidth - offset
            // Когда stage.x = size.width - scaledWidth – это позиция, при которой правый край контента совмещен с правым краем видимой области.
            // Отнимаем ещё offset, чтобы разрешить выход за границу на 600 пикселей.

            const minX = size.width - scaledWidth - offset;

            const minY = size.height - scaledHeight - offset;
            return {
              x: Math.min(Math.max(pos.x, minX), offset),
              y: Math.min(Math.max(pos.y, minY), offset),
            };
          }}
        >
          <Layer>
            <Rect
              id={'page'}
              key={`p-${editorDocument.activePage.id}`}
              x={0}
              y={0}
              width={editorDocument.activePage.width}
              height={editorDocument.activePage.height}
              strokeWidth={1}
              dash={[5, 10]}
              stroke={'#cf03fc'}
            />
            {Array.from(editorDocument.activePage.primitives.values())
              .filter((x) => x.type === EditorPrimitives.Frame)
              .map((x) => (
                <RenderFrame key={x.id} {...(x as Frame)} />
              ))}
            {isSelectingMode && (
              <Rect
                x={selectionRect.x}
                y={selectionRect.y}
                width={selectionRect.width}
                height={selectionRect.height}
                fill='rgba(0, 162, 255, 0.2)'
                stroke='blue'
                strokeWidth={0}
              />
            )}
            {/*{previewDraggingPrimitive && <RenderPrimitiveByType primitive={previewDraggingPrimitive} />}*/}
            <Line points={previewConnection} stroke='grey' strokeWidth={1} dash={[4, 4]} />

            {(isDrag || editorToolsStore.action) && (
              <Group
                x={Math.round(pointerPosition.x / gridSize) * gridSize - 2.5}
                y={Math.round(pointerPosition.y / gridSize) * gridSize - 2.5}
              >
                <Line points={[0, 0, 5, 5]} stroke={'black'} strokeWidth={1} />
                <Line points={[5, 0, 0, 5]} stroke={'black'} strokeWidth={1} />
              </Group>
            )}
          </Layer>
          <Layer>
            <Grid />
          </Layer>
          <Layer id={'pageLayer'}>
            <RenderPage page={editorDocument.activePage} />
          </Layer>
        </Stage>

        {/* Context Menu */}
        <ContextMenu ref={cm} model={menuItems} />
      </div>
    )
  ) : (
    <></>
  );
};

export default GraphEditor;

и сразу после завершения трансформации я попадаю в функцию handleMouseUp и тут у меня съезжают выделенные элементы. потому что после поворота точки селектед ареа и реального трансформера не совпадают 
Как мне это исправить