@cyber-harbour/ui 1.0.67 → 1.0.69

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyber-harbour/ui",
3
- "version": "1.0.67",
3
+ "version": "1.0.69",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.mjs",
6
6
  "types": "dist/index.d.ts",
@@ -90,10 +90,8 @@ export const Graph2D: any = forwardRef<Graph2DRef, Graph2DProps>(
90
90
  highlightLinks: new Set(),
91
91
  lastMousePos: { x: 0, y: 0 },
92
92
  mustBeStoppedPropagation: false,
93
- lastHoveredNode: null,
94
93
  mouseStartPos: null,
95
94
  isDragging: false,
96
- lastHoveredNodeRef: null,
97
95
  width: width * RATIO,
98
96
  height: height * RATIO,
99
97
  });
@@ -1062,13 +1060,10 @@ export const Graph2D: any = forwardRef<Graph2DRef, Graph2DProps>(
1062
1060
  const handleNodeHover = useCallback(
1063
1061
  (node: NodeObject | null) => {
1064
1062
  // Перевіряємо, чи вузол той самий, що і останній вузол, на який наводили
1065
- if (node === stateRef.current.lastHoveredNodeRef) {
1063
+ if (node === stateRef.current.hoveredNode) {
1066
1064
  return; // Пропускаємо обробку, якщо це той самий вузол
1067
1065
  }
1068
1066
 
1069
- // Оновлюємо посилання на останній наведений вузол
1070
- stateRef.current.lastHoveredNodeRef = node;
1071
-
1072
1067
  const newHighlightNodes = new Set<NodeObject>();
1073
1068
  const newHighlightLinks = new Set<any>();
1074
1069
 
@@ -1181,6 +1176,49 @@ export const Graph2D: any = forwardRef<Graph2DRef, Graph2DProps>(
1181
1176
  [getNodeAtPosition]
1182
1177
  );
1183
1178
 
1179
+ const getHoveredIndex = useCallback(
1180
+ (x: number, y: number) => {
1181
+ let hoveredIndex = null;
1182
+ if (canvasRef.current && stateRef.current && buttonImages.length > 0) {
1183
+ const buttonRadius = (config.nodeSizeBase * config.nodeAreaFactor) / 2;
1184
+ const rect = canvasRef.current.getBoundingClientRect();
1185
+
1186
+ // Масштабуємо координати відносно розміру відображення полотна
1187
+ const canvasScaleX = canvasRef.current.width / rect.width;
1188
+ const canvasScaleY = canvasRef.current.height / rect.height;
1189
+
1190
+ // Масштабовані координати миші в системі координат полотна
1191
+ const scaledMouseX = x * canvasScaleX;
1192
+ const scaledMouseY = y * canvasScaleY;
1193
+
1194
+ // Застосовуємо поточну трансформацію для отримання світових координат
1195
+ const worldX = (scaledMouseX - stateRef.current.transform.x) / stateRef.current.transform.k;
1196
+ const worldY = (scaledMouseY - stateRef.current.transform.y) / stateRef.current.transform.k;
1197
+
1198
+ // Node position
1199
+ const nodeX = stateRef.current.selectedNode?.x || 0;
1200
+ const nodeY = stateRef.current.selectedNode?.y || 0;
1201
+
1202
+ // Обчислюємо кількість кнопок та їхні сектори
1203
+ const buttonCount = Math.min(buttonImages.length, 8);
1204
+ const sectorAngle = Math.min((Math.PI * 2) / buttonCount, Math.PI);
1205
+
1206
+ // Перевіряємо, чи вказівник миші знаходиться над будь-яким сектором кнопок
1207
+ for (let i = 0; i < buttonCount; i++) {
1208
+ const startAngle = i * sectorAngle;
1209
+ const endAngle = (i + 1) * sectorAngle;
1210
+
1211
+ if (isPointInButtonSector(worldX, worldY, nodeX, nodeY, buttonRadius, startAngle, endAngle)) {
1212
+ hoveredIndex = i;
1213
+ break;
1214
+ }
1215
+ }
1216
+ }
1217
+ return hoveredIndex;
1218
+ },
1219
+ [buttonImages]
1220
+ );
1221
+
1184
1222
  // Обробка руху миші для перетягування та наведення
1185
1223
  const handleMouseMove = useCallback(
1186
1224
  (event: React.MouseEvent<HTMLCanvasElement>) => {
@@ -1274,40 +1312,7 @@ export const Graph2D: any = forwardRef<Graph2DRef, Graph2DProps>(
1274
1312
 
1275
1313
  // Логіка виявлення наведення на кнопки
1276
1314
  if (stateRef.current.selectedNode && canvasRef.current && buttonImages.length > 0) {
1277
- const buttonRadius = (config.nodeSizeBase * config.nodeAreaFactor) / 2;
1278
-
1279
- // Масштабуємо координати відносно розміру відображення полотна
1280
- const canvasScaleX = canvasRef.current.width / rect.width;
1281
- const canvasScaleY = canvasRef.current.height / rect.height;
1282
-
1283
- // Масштабовані координати миші в системі координат полотна
1284
- const scaledMouseX = x * canvasScaleX;
1285
- const scaledMouseY = y * canvasScaleY;
1286
-
1287
- // Застосовуємо поточну трансформацію для отримання світових координат
1288
- const worldX = (scaledMouseX - stateRef.current.transform.x) / stateRef.current.transform.k;
1289
- const worldY = (scaledMouseY - stateRef.current.transform.y) / stateRef.current.transform.k;
1290
-
1291
- // Node position
1292
- const nodeX = stateRef.current.selectedNode.x || 0;
1293
- const nodeY = stateRef.current.selectedNode.y || 0;
1294
-
1295
- // Обчислюємо кількість кнопок та їхні сектори
1296
- const buttonCount = Math.min(buttonImages.length, 8);
1297
- const sectorAngle = Math.min((Math.PI * 2) / buttonCount, Math.PI);
1298
-
1299
- let hoveredIndex = null;
1300
-
1301
- // Перевіряємо, чи вказівник миші знаходиться над будь-яким сектором кнопок
1302
- for (let i = 0; i < buttonCount; i++) {
1303
- const startAngle = i * sectorAngle;
1304
- const endAngle = (i + 1) * sectorAngle;
1305
-
1306
- if (isPointInButtonSector(worldX, worldY, nodeX, nodeY, buttonRadius, startAngle, endAngle)) {
1307
- hoveredIndex = i;
1308
- break;
1309
- }
1310
- }
1315
+ const hoveredIndex = getHoveredIndex(x, y);
1311
1316
  if (hoveredIndex !== null) hoveredNode = stateRef.current.selectedNode; // Set hoveredNode to selectedNode for further processing
1312
1317
  if (hoveredIndex !== stateRef.current.hoveredButtonIndex) {
1313
1318
  shouldRender = true; // Only render if hovered button index has changed
@@ -1340,8 +1345,8 @@ export const Graph2D: any = forwardRef<Graph2DRef, Graph2DProps>(
1340
1345
 
1341
1346
  if (shouldRenderLink) {
1342
1347
  renderCanvas2D();
1348
+ return;
1343
1349
  }
1344
- return;
1345
1350
  }
1346
1351
  }
1347
1352
 
@@ -1371,6 +1376,7 @@ export const Graph2D: any = forwardRef<Graph2DRef, Graph2DProps>(
1371
1376
  handleLinkHover,
1372
1377
  renderCanvas2D,
1373
1378
  isPointInButtonSector,
1379
+ getHoveredIndex,
1374
1380
  ]
1375
1381
  );
1376
1382
 
@@ -1396,7 +1402,9 @@ export const Graph2D: any = forwardRef<Graph2DRef, Graph2DProps>(
1396
1402
  if (rect) {
1397
1403
  const x = event.clientX - rect.left;
1398
1404
  const y = event.clientY - rect.top;
1399
-
1405
+ if (buttonImages.length > 0 && stateRef.current.hoveredButtonIndex === null) {
1406
+ stateRef.current.hoveredButtonIndex = getHoveredIndex(x, y);
1407
+ }
1400
1408
  // Спочатку перевіряємо, чи ми клікаємо на кнопку обраного вузла
1401
1409
  let isButtonClick = false;
1402
1410
  if (
@@ -1409,6 +1417,7 @@ export const Graph2D: any = forwardRef<Graph2DRef, Graph2DProps>(
1409
1417
  if (button && button.onClick) {
1410
1418
  button.onClick(stateRef.current.selectedNode);
1411
1419
  isButtonClick = true;
1420
+ stateRef.current.hoveredButtonIndex = null;
1412
1421
  }
1413
1422
  }
1414
1423
 
@@ -1465,7 +1474,16 @@ export const Graph2D: any = forwardRef<Graph2DRef, Graph2DProps>(
1465
1474
 
1466
1475
  renderCanvas2D();
1467
1476
  },
1468
- [buttons, renderCanvas2D, handleNodeClick, handleBackgroundClick, getLinkAtPosition, handleLinkClick]
1477
+ [
1478
+ buttons,
1479
+ renderCanvas2D,
1480
+ handleNodeClick,
1481
+ handleBackgroundClick,
1482
+ getLinkAtPosition,
1483
+ handleLinkClick,
1484
+ getHoveredIndex,
1485
+ buttonImages,
1486
+ ]
1469
1487
  );
1470
1488
 
1471
1489
  // Обробляємо подію колеса миші для масштабування
@@ -1509,6 +1527,47 @@ export const Graph2D: any = forwardRef<Graph2DRef, Graph2DProps>(
1509
1527
  [renderCanvas2D]
1510
1528
  );
1511
1529
 
1530
+ //Обробка подій на тачскрін девайсах
1531
+ const convertTouchToMouseEvent = (
1532
+ e: TouchEvent | React.TouchEvent<HTMLCanvasElement>,
1533
+ callback: (event: React.MouseEvent<HTMLCanvasElement>) => void
1534
+ ) => {
1535
+ const touch = e.touches[0];
1536
+ if (touch) {
1537
+ // Create a synthetic React.MouseEvent-like object
1538
+ const syntheticEvent = {
1539
+ clientX: touch.clientX,
1540
+ clientY: touch.clientY,
1541
+ } as React.MouseEvent<HTMLCanvasElement>;
1542
+ callback(syntheticEvent);
1543
+ }
1544
+ };
1545
+ const handleTouchStart = useCallback(
1546
+ (e: React.TouchEvent<HTMLCanvasElement>) => {
1547
+ convertTouchToMouseEvent(e, handleMouseDown);
1548
+ },
1549
+ [handleMouseDown]
1550
+ );
1551
+
1552
+ const handleTouchEnd = useCallback(
1553
+ (e: TouchEvent) => {
1554
+ if (stateRef.current.mustBeStoppedPropagation) {
1555
+ e.preventDefault();
1556
+ e.stopPropagation();
1557
+ }
1558
+ stateRef.current.mustBeStoppedPropagation = false;
1559
+ convertTouchToMouseEvent(e, handleMouseUp);
1560
+ },
1561
+ [handleMouseUp]
1562
+ );
1563
+
1564
+ const handleTouchMove = useCallback(
1565
+ (e: React.TouchEvent<HTMLCanvasElement>) => {
1566
+ convertTouchToMouseEvent(e, handleMouseMove);
1567
+ },
1568
+ [handleMouseMove]
1569
+ );
1570
+
1512
1571
  useImperativeHandle(
1513
1572
  ref,
1514
1573
  () => ({
@@ -1647,12 +1706,14 @@ export const Graph2D: any = forwardRef<Graph2DRef, Graph2DProps>(
1647
1706
 
1648
1707
  // Add event listener with passive: false to allow preventDefault
1649
1708
  canvas.addEventListener('wheel', handleWheel, { passive: false });
1709
+ canvas.addEventListener('touchend', handleTouchEnd, { passive: false });
1650
1710
 
1651
1711
  // Clean up when component unmounts
1652
1712
  return () => {
1653
1713
  canvas.removeEventListener('wheel', handleWheel);
1714
+ canvas.removeEventListener('touchend', handleTouchEnd);
1654
1715
  };
1655
- }, [handleWheel]);
1716
+ }, [handleWheel, handleTouchEnd]);
1656
1717
 
1657
1718
  return (
1658
1719
  <Wrapper>
@@ -1665,6 +1726,8 @@ export const Graph2D: any = forwardRef<Graph2DRef, Graph2DProps>(
1665
1726
  onMouseUp={handleMouseUp}
1666
1727
  onMouseLeave={handleMouseUp}
1667
1728
  onClick={handleClick}
1729
+ onTouchStart={handleTouchStart}
1730
+ onTouchMove={handleTouchMove}
1668
1731
  />
1669
1732
  </Wrapper>
1670
1733
  );
@@ -57,10 +57,6 @@ export interface GraphState {
57
57
  lastMousePos: { x: number; y: number };
58
58
  /** Флаг необходимости остановки распространения события */
59
59
  mustBeStoppedPropagation: boolean;
60
- /** Последний узел, над которым был курсор */
61
- lastHoveredNode: NodeObject | null;
62
- /** Кэшированная ссылка на последний узел, над которым был курсор (для избежания лишних вычислений) */
63
- lastHoveredNodeRef: NodeObject | null;
64
60
  /** Начальная позиция курсора при начале перетаскивания */
65
61
  mouseStartPos: { x: number; y: number } | null;
66
62
  /** Флаг режима перетаскивания */
@@ -922,8 +922,8 @@ export const darkThemePx: Theme = {
922
922
  },
923
923
  color: {
924
924
  icon: '#FDC700',
925
- text: '#894B00',
926
- background: '#FFFBE0',
925
+ text: '#FFEDD8',
926
+ background: '#68550E',
927
927
  },
928
928
  },
929
929
  label: {