@mint-ui/map 1.2.0-test.17 → 1.2.0-test.19

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.
@@ -71,7 +71,7 @@ var WoongCanvasLayer = function (props) {
71
71
 
72
72
  /** data prop을 ref로 추적 (stale closure 방지, useEffect에서 동기화) */
73
73
 
74
- var markersRef = React.useRef(data); // --------------------------------------------------------------------------
74
+ var dataRef = React.useRef(data); // --------------------------------------------------------------------------
75
75
  // State Refs - 선택 및 Hover 상태 관리
76
76
  // --------------------------------------------------------------------------
77
77
 
@@ -222,6 +222,10 @@ var WoongCanvasLayer = function (props) {
222
222
  /**
223
223
  * 아이템의 바운딩 박스 계산 (폴리곤/마커 공통)
224
224
  *
225
+ * 🎯 마커의 경우:
226
+ * - boxHeight: 본체만 (Hit Test 영역)
227
+ * - tailHeight: 꼬리 높이 (Viewport Culling용, 화면에 보이는 전체 영역)
228
+ *
225
229
  * @param item 마커 또는 폴리곤 데이터
226
230
  * @returns 바운딩 박스 또는 null
227
231
  */
@@ -262,14 +266,16 @@ var WoongCanvasLayer = function (props) {
262
266
  maxY: maxY
263
267
  };
264
268
  } else {
265
- // 마커: 중심점 기준 박스 크기 계산
269
+ // 마커: 중심점 기준 박스 크기 계산 (꼬리 포함)
266
270
  var offset = getOrComputeMarkerOffset(item);
267
271
  if (!offset) return null;
268
272
  var boxWidth = item.boxWidth || 50;
269
273
  var boxHeight = item.boxHeight || 28;
274
+ var tailHeight = item.tailHeight || 0; // 🎯 tailHeight 사용 (Viewport Culling용)
275
+
270
276
  return {
271
277
  minX: offset.x - boxWidth / 2,
272
- minY: offset.y - boxHeight - 6,
278
+ minY: offset.y - boxHeight - tailHeight,
273
279
  maxX: offset.x + boxWidth / 2,
274
280
  maxY: offset.y
275
281
  };
@@ -286,10 +292,10 @@ var WoongCanvasLayer = function (props) {
286
292
  var buildSpatialIndex = function () {
287
293
  var spatial = spatialIndexRef.current;
288
294
  spatial.clear();
289
- var currentMarkers = markersRef.current;
295
+ var currentData = dataRef.current;
290
296
 
291
- for (var _i = 0, currentMarkers_1 = currentMarkers; _i < currentMarkers_1.length; _i++) {
292
- var item = currentMarkers_1[_i]; // 바운딩 박스 계산 (공통 함수 사용)
297
+ for (var _i = 0, currentData_1 = currentData; _i < currentData_1.length; _i++) {
298
+ var item = currentData_1[_i]; // 바운딩 박스 계산 (공통 함수 사용)
293
299
 
294
300
  var bbox = computeBoundingBox(item);
295
301
 
@@ -332,6 +338,10 @@ var WoongCanvasLayer = function (props) {
332
338
  * 1. Shape 재사용으로 객체 생성/파괴 오버헤드 제거
333
339
  * 2. sceneFunc 한 번만 설정 (함수 재생성 제거)
334
340
  * 3. 클로저로 최신 데이터 참조
341
+ *
342
+ * 🎯 topOnHover 지원:
343
+ * - renderEvent가 없을 때 Base Layer에서 hover 처리 (fallback)
344
+ * - renderEvent가 있으면 Event Layer에서 처리 (성능 최적화)
335
345
  */
336
346
 
337
347
  var doRenderBase = function () {
@@ -346,17 +356,40 @@ var WoongCanvasLayer = function (props) {
346
356
  shape = new Konva__default["default"].Shape({
347
357
  name: 'base-render-shape',
348
358
  sceneFunc: function (context, shape) {
349
- var ctx = context; // 클로저로 최신 ref 값 참조
359
+ var ctx = context;
360
+ var hovered = hoveredItemRef.current; // 클로저로 최신 ref 값 참조
350
361
 
351
- var visibleMarkers = enableViewportCulling ? markersRef.current.filter(function (item) {
362
+ var visibleItems = enableViewportCulling ? dataRef.current.filter(function (item) {
352
363
  return isInViewport(item);
353
- }) : markersRef.current;
364
+ }) : dataRef.current; // topOnHover가 true이고 renderEvent가 없으면 Base Layer에서 hover 처리
365
+
366
+ if (topOnHover && !renderEvent && hovered) {
367
+ // hover된 항목 제외하고 렌더링
368
+ visibleItems = visibleItems.filter(function (item) {
369
+ return item.id !== hovered.id;
370
+ });
371
+ } // 일반 항목 렌더링
372
+
373
+
354
374
  renderBase({
355
375
  ctx: ctx,
356
- items: visibleMarkers,
376
+ items: visibleItems,
357
377
  selectedIds: selectedIdsRef.current,
358
378
  utils: renderUtils
359
- });
379
+ }); // hover된 항목을 최상단에 렌더링 (renderEvent가 없을 때만)
380
+
381
+ if (topOnHover && !renderEvent && hovered) {
382
+ var isHoveredInViewport = enableViewportCulling ? isInViewport(hovered) : true;
383
+
384
+ if (isHoveredInViewport) {
385
+ renderBase({
386
+ ctx: ctx,
387
+ items: [hovered],
388
+ selectedIds: selectedIdsRef.current,
389
+ utils: renderUtils
390
+ });
391
+ }
392
+ }
360
393
  },
361
394
  perfectDrawEnabled: false,
362
395
  listening: false,
@@ -384,7 +417,7 @@ var WoongCanvasLayer = function (props) {
384
417
  renderAnimation({
385
418
  layer: layer,
386
419
  selectedIds: selectedIdsRef.current,
387
- items: markersRef.current,
420
+ items: dataRef.current,
388
421
  utils: renderUtils
389
422
  });
390
423
  };
@@ -589,12 +622,15 @@ var WoongCanvasLayer = function (props) {
589
622
  return null;
590
623
  };
591
624
  /**
592
- * Hover 상태 설정 및 Event 레이어 렌더링
625
+ * Hover 상태 설정 및 레이어 렌더링
593
626
  *
594
627
  * @param data hover된 마커/폴리곤 데이터 또는 null
595
628
  *
596
629
  * 최적화: RAF 제거하여 즉시 렌더링 (16ms 지연 제거)
597
- * topOnHover가 true일 때는 Base 레이어도 다시 그려서 z-order 변경
630
+ *
631
+ * 🎯 topOnHover 지원:
632
+ * - renderEvent가 있으면: Event Layer에서만 처리 (성능 최적화)
633
+ * - renderEvent가 없고 topOnHover=true면: Base Layer에서 처리
598
634
  */
599
635
 
600
636
 
@@ -606,10 +642,15 @@ var WoongCanvasLayer = function (props) {
606
642
  } else {
607
643
  controller.setMapCursor(data ? 'pointer' : 'grab');
608
644
  } // 즉시 렌더링 (RAF 없이)
609
- // topOnHover는 Event Layer에서만 처리 (Base Layer 재렌더링 제거로 성능 향상)
610
645
 
611
646
 
612
- doRenderEvent();
647
+ if (renderEvent) {
648
+ // renderEvent가 있으면 Event Layer에서만 처리 (성능 최적화)
649
+ doRenderEvent();
650
+ } else if (topOnHover) {
651
+ // renderEvent가 없고 topOnHover가 true면 Base Layer에서 처리
652
+ doRenderBase();
653
+ }
613
654
  };
614
655
  /**
615
656
  * 클릭 처리 (단일/다중 선택)
@@ -965,9 +1006,9 @@ var WoongCanvasLayer = function (props) {
965
1006
  // --------------------------------------------------------------------------
966
1007
 
967
1008
  React.useEffect(function () {
968
- if (!stageRef.current) return; // markersRef 동기화
1009
+ if (!stageRef.current) return; // dataRef 동기화
969
1010
 
970
- markersRef.current = data; // 데이터 변경 시 즉시 transform 제거 및 캐시 정리 (겹침 방지)
1011
+ dataRef.current = data; // 데이터 변경 시 즉시 transform 제거 및 캐시 정리 (겹침 방지)
971
1012
 
972
1013
  if (containerRef.current) {
973
1014
  containerRef.current.style.transform = '';
@@ -995,13 +1036,13 @@ var WoongCanvasLayer = function (props) {
995
1036
  * - O(전체 데이터 수 + 선택된 개수) - 매우 효율적
996
1037
  */
997
1038
 
998
- var markersMap = new Map(data.map(function (m) {
1039
+ var dataMap = new Map(data.map(function (m) {
999
1040
  return [m.id, m];
1000
1041
  }));
1001
1042
  var newSelectedItemsMap = new Map();
1002
1043
  selectedIdsRef.current.forEach(function (id) {
1003
- // 현재 markers에 있으면 최신 데이터 사용
1004
- var currentItem = markersMap.get(id);
1044
+ // 현재 data에 있으면 최신 데이터 사용
1045
+ var currentItem = dataMap.get(id);
1005
1046
 
1006
1047
  if (currentItem) {
1007
1048
  newSelectedItemsMap.set(id, currentItem);
@@ -1013,7 +1054,7 @@ var WoongCanvasLayer = function (props) {
1013
1054
  newSelectedItemsMap.set(id, prevItem);
1014
1055
  }
1015
1056
  }
1016
- }); // selectedIdsRef는 그대로 유지 (화면 밖 마커도 선택 상태 유지)
1057
+ }); // selectedIdsRef는 그대로 유지 (화면 밖 항목도 선택 상태 유지)
1017
1058
 
1018
1059
  selectedItemsMapRef.current = newSelectedItemsMap; // 즉시 렌더링
1019
1060
 
@@ -27,6 +27,7 @@ export interface KonvaCanvasOption {
27
27
  position: Position;
28
28
  boxWidth?: number;
29
29
  boxHeight?: number;
30
+ tailHeight?: number;
30
31
  paths?: Paths;
31
32
  isDonutPolygon?: boolean;
32
33
  }
@@ -26,7 +26,12 @@ export declare const isPointInPolygon: (point: Offset, polygon: number[][]) => b
26
26
  */
27
27
  export declare const isPointInPolygonData: (clickedOffset: Offset, polygonData: KonvaCanvasData<any>, getPolygonOffsets: (data: KonvaCanvasData<any>) => number[][][][] | null) => boolean;
28
28
  /**
29
- * 마커 히트 테스트
29
+ * 마커 히트 테스트 (클릭/hover 영역 체크)
30
+ *
31
+ * 🎯 중요: 꼬리(tail)는 Hit Test 영역에서 제외됩니다!
32
+ * - markerOffset.y는 마커 최하단(꼬리 끝) 좌표
33
+ * - boxHeight는 마커 본체만 포함 (꼬리 제외)
34
+ * - tailHeight만큼 위로 올려서 본체만 Hit Test 영역으로 사용
30
35
  */
31
36
  export declare const isPointInMarkerData: (clickedOffset: Offset, markerData: KonvaCanvasData<any>, getMarkerOffset: (data: KonvaCanvasData<any>) => Offset | null) => boolean;
32
37
  export declare const hexToRgba: (hexColor: string, alpha?: number) => string;
@@ -143,7 +143,12 @@ var isPointInPolygonData = function (clickedOffset, polygonData, getPolygonOffse
143
143
  return false;
144
144
  };
145
145
  /**
146
- * 마커 히트 테스트
146
+ * 마커 히트 테스트 (클릭/hover 영역 체크)
147
+ *
148
+ * 🎯 중요: 꼬리(tail)는 Hit Test 영역에서 제외됩니다!
149
+ * - markerOffset.y는 마커 최하단(꼬리 끝) 좌표
150
+ * - boxHeight는 마커 본체만 포함 (꼬리 제외)
151
+ * - tailHeight만큼 위로 올려서 본체만 Hit Test 영역으로 사용
147
152
  */
148
153
 
149
154
  var isPointInMarkerData = function (clickedOffset, markerData, getMarkerOffset) {
@@ -151,9 +156,11 @@ var isPointInMarkerData = function (clickedOffset, markerData, getMarkerOffset)
151
156
  if (!markerOffset) return false;
152
157
  var boxWidth = markerData.boxWidth || 50;
153
158
  var boxHeight = markerData.boxHeight || 28;
154
- var tailHeight = 6;
159
+ var tailHeight = markerData.tailHeight || 0; // 🎯 tailHeight 사용!
160
+
155
161
  var x = markerOffset.x - boxWidth / 2;
156
- var y = markerOffset.y - boxHeight - tailHeight;
162
+ var y = markerOffset.y - boxHeight - tailHeight; // 🔥 꼬리만큼 위로!
163
+
157
164
  return clickedOffset.x >= x && clickedOffset.x <= x + boxWidth && clickedOffset.y >= y && clickedOffset.y <= y + boxHeight;
158
165
  };
159
166
  var hexToRgba = function (hexColor, alpha) {
package/dist/index.es.js CHANGED
@@ -931,7 +931,12 @@ var isPointInPolygonData = function (clickedOffset, polygonData, getPolygonOffse
931
931
  return false;
932
932
  };
933
933
  /**
934
- * 마커 히트 테스트
934
+ * 마커 히트 테스트 (클릭/hover 영역 체크)
935
+ *
936
+ * 🎯 중요: 꼬리(tail)는 Hit Test 영역에서 제외됩니다!
937
+ * - markerOffset.y는 마커 최하단(꼬리 끝) 좌표
938
+ * - boxHeight는 마커 본체만 포함 (꼬리 제외)
939
+ * - tailHeight만큼 위로 올려서 본체만 Hit Test 영역으로 사용
935
940
  */
936
941
 
937
942
  var isPointInMarkerData = function (clickedOffset, markerData, getMarkerOffset) {
@@ -939,9 +944,11 @@ var isPointInMarkerData = function (clickedOffset, markerData, getMarkerOffset)
939
944
  if (!markerOffset) return false;
940
945
  var boxWidth = markerData.boxWidth || 50;
941
946
  var boxHeight = markerData.boxHeight || 28;
942
- var tailHeight = 6;
947
+ var tailHeight = markerData.tailHeight || 0; // 🎯 tailHeight 사용!
948
+
943
949
  var x = markerOffset.x - boxWidth / 2;
944
- var y = markerOffset.y - boxHeight - tailHeight;
950
+ var y = markerOffset.y - boxHeight - tailHeight; // 🔥 꼬리만큼 위로!
951
+
945
952
  return clickedOffset.x >= x && clickedOffset.x <= x + boxWidth && clickedOffset.y >= y && clickedOffset.y <= y + boxHeight;
946
953
  };
947
954
  var hexToRgba = function (hexColor, alpha) {
@@ -5691,7 +5698,7 @@ var WoongCanvasLayer = function (props) {
5691
5698
 
5692
5699
  /** data prop을 ref로 추적 (stale closure 방지, useEffect에서 동기화) */
5693
5700
 
5694
- var markersRef = useRef(data); // --------------------------------------------------------------------------
5701
+ var dataRef = useRef(data); // --------------------------------------------------------------------------
5695
5702
  // State Refs - 선택 및 Hover 상태 관리
5696
5703
  // --------------------------------------------------------------------------
5697
5704
 
@@ -5842,6 +5849,10 @@ var WoongCanvasLayer = function (props) {
5842
5849
  /**
5843
5850
  * 아이템의 바운딩 박스 계산 (폴리곤/마커 공통)
5844
5851
  *
5852
+ * 🎯 마커의 경우:
5853
+ * - boxHeight: 본체만 (Hit Test 영역)
5854
+ * - tailHeight: 꼬리 높이 (Viewport Culling용, 화면에 보이는 전체 영역)
5855
+ *
5845
5856
  * @param item 마커 또는 폴리곤 데이터
5846
5857
  * @returns 바운딩 박스 또는 null
5847
5858
  */
@@ -5882,14 +5893,16 @@ var WoongCanvasLayer = function (props) {
5882
5893
  maxY: maxY
5883
5894
  };
5884
5895
  } else {
5885
- // 마커: 중심점 기준 박스 크기 계산
5896
+ // 마커: 중심점 기준 박스 크기 계산 (꼬리 포함)
5886
5897
  var offset = getOrComputeMarkerOffset(item);
5887
5898
  if (!offset) return null;
5888
5899
  var boxWidth = item.boxWidth || 50;
5889
5900
  var boxHeight = item.boxHeight || 28;
5901
+ var tailHeight = item.tailHeight || 0; // 🎯 tailHeight 사용 (Viewport Culling용)
5902
+
5890
5903
  return {
5891
5904
  minX: offset.x - boxWidth / 2,
5892
- minY: offset.y - boxHeight - 6,
5905
+ minY: offset.y - boxHeight - tailHeight,
5893
5906
  maxX: offset.x + boxWidth / 2,
5894
5907
  maxY: offset.y
5895
5908
  };
@@ -5906,10 +5919,10 @@ var WoongCanvasLayer = function (props) {
5906
5919
  var buildSpatialIndex = function () {
5907
5920
  var spatial = spatialIndexRef.current;
5908
5921
  spatial.clear();
5909
- var currentMarkers = markersRef.current;
5922
+ var currentData = dataRef.current;
5910
5923
 
5911
- for (var _i = 0, currentMarkers_1 = currentMarkers; _i < currentMarkers_1.length; _i++) {
5912
- var item = currentMarkers_1[_i]; // 바운딩 박스 계산 (공통 함수 사용)
5924
+ for (var _i = 0, currentData_1 = currentData; _i < currentData_1.length; _i++) {
5925
+ var item = currentData_1[_i]; // 바운딩 박스 계산 (공통 함수 사용)
5913
5926
 
5914
5927
  var bbox = computeBoundingBox(item);
5915
5928
 
@@ -5952,6 +5965,10 @@ var WoongCanvasLayer = function (props) {
5952
5965
  * 1. Shape 재사용으로 객체 생성/파괴 오버헤드 제거
5953
5966
  * 2. sceneFunc 한 번만 설정 (함수 재생성 제거)
5954
5967
  * 3. 클로저로 최신 데이터 참조
5968
+ *
5969
+ * 🎯 topOnHover 지원:
5970
+ * - renderEvent가 없을 때 Base Layer에서 hover 처리 (fallback)
5971
+ * - renderEvent가 있으면 Event Layer에서 처리 (성능 최적화)
5955
5972
  */
5956
5973
 
5957
5974
  var doRenderBase = function () {
@@ -5966,17 +5983,40 @@ var WoongCanvasLayer = function (props) {
5966
5983
  shape = new Konva.Shape({
5967
5984
  name: 'base-render-shape',
5968
5985
  sceneFunc: function (context, shape) {
5969
- var ctx = context; // 클로저로 최신 ref 값 참조
5986
+ var ctx = context;
5987
+ var hovered = hoveredItemRef.current; // 클로저로 최신 ref 값 참조
5970
5988
 
5971
- var visibleMarkers = enableViewportCulling ? markersRef.current.filter(function (item) {
5989
+ var visibleItems = enableViewportCulling ? dataRef.current.filter(function (item) {
5972
5990
  return isInViewport(item);
5973
- }) : markersRef.current;
5991
+ }) : dataRef.current; // topOnHover가 true이고 renderEvent가 없으면 Base Layer에서 hover 처리
5992
+
5993
+ if (topOnHover && !renderEvent && hovered) {
5994
+ // hover된 항목 제외하고 렌더링
5995
+ visibleItems = visibleItems.filter(function (item) {
5996
+ return item.id !== hovered.id;
5997
+ });
5998
+ } // 일반 항목 렌더링
5999
+
6000
+
5974
6001
  renderBase({
5975
6002
  ctx: ctx,
5976
- items: visibleMarkers,
6003
+ items: visibleItems,
5977
6004
  selectedIds: selectedIdsRef.current,
5978
6005
  utils: renderUtils
5979
- });
6006
+ }); // hover된 항목을 최상단에 렌더링 (renderEvent가 없을 때만)
6007
+
6008
+ if (topOnHover && !renderEvent && hovered) {
6009
+ var isHoveredInViewport = enableViewportCulling ? isInViewport(hovered) : true;
6010
+
6011
+ if (isHoveredInViewport) {
6012
+ renderBase({
6013
+ ctx: ctx,
6014
+ items: [hovered],
6015
+ selectedIds: selectedIdsRef.current,
6016
+ utils: renderUtils
6017
+ });
6018
+ }
6019
+ }
5980
6020
  },
5981
6021
  perfectDrawEnabled: false,
5982
6022
  listening: false,
@@ -6004,7 +6044,7 @@ var WoongCanvasLayer = function (props) {
6004
6044
  renderAnimation({
6005
6045
  layer: layer,
6006
6046
  selectedIds: selectedIdsRef.current,
6007
- items: markersRef.current,
6047
+ items: dataRef.current,
6008
6048
  utils: renderUtils
6009
6049
  });
6010
6050
  };
@@ -6209,12 +6249,15 @@ var WoongCanvasLayer = function (props) {
6209
6249
  return null;
6210
6250
  };
6211
6251
  /**
6212
- * Hover 상태 설정 및 Event 레이어 렌더링
6252
+ * Hover 상태 설정 및 레이어 렌더링
6213
6253
  *
6214
6254
  * @param data hover된 마커/폴리곤 데이터 또는 null
6215
6255
  *
6216
6256
  * 최적화: RAF 제거하여 즉시 렌더링 (16ms 지연 제거)
6217
- * topOnHover가 true일 때는 Base 레이어도 다시 그려서 z-order 변경
6257
+ *
6258
+ * 🎯 topOnHover 지원:
6259
+ * - renderEvent가 있으면: Event Layer에서만 처리 (성능 최적화)
6260
+ * - renderEvent가 없고 topOnHover=true면: Base Layer에서 처리
6218
6261
  */
6219
6262
 
6220
6263
 
@@ -6226,10 +6269,15 @@ var WoongCanvasLayer = function (props) {
6226
6269
  } else {
6227
6270
  controller.setMapCursor(data ? 'pointer' : 'grab');
6228
6271
  } // 즉시 렌더링 (RAF 없이)
6229
- // topOnHover는 Event Layer에서만 처리 (Base Layer 재렌더링 제거로 성능 향상)
6230
6272
 
6231
6273
 
6232
- doRenderEvent();
6274
+ if (renderEvent) {
6275
+ // renderEvent가 있으면 Event Layer에서만 처리 (성능 최적화)
6276
+ doRenderEvent();
6277
+ } else if (topOnHover) {
6278
+ // renderEvent가 없고 topOnHover가 true면 Base Layer에서 처리
6279
+ doRenderBase();
6280
+ }
6233
6281
  };
6234
6282
  /**
6235
6283
  * 클릭 처리 (단일/다중 선택)
@@ -6585,9 +6633,9 @@ var WoongCanvasLayer = function (props) {
6585
6633
  // --------------------------------------------------------------------------
6586
6634
 
6587
6635
  useEffect(function () {
6588
- if (!stageRef.current) return; // markersRef 동기화
6636
+ if (!stageRef.current) return; // dataRef 동기화
6589
6637
 
6590
- markersRef.current = data; // 데이터 변경 시 즉시 transform 제거 및 캐시 정리 (겹침 방지)
6638
+ dataRef.current = data; // 데이터 변경 시 즉시 transform 제거 및 캐시 정리 (겹침 방지)
6591
6639
 
6592
6640
  if (containerRef.current) {
6593
6641
  containerRef.current.style.transform = '';
@@ -6615,13 +6663,13 @@ var WoongCanvasLayer = function (props) {
6615
6663
  * - O(전체 데이터 수 + 선택된 개수) - 매우 효율적
6616
6664
  */
6617
6665
 
6618
- var markersMap = new Map(data.map(function (m) {
6666
+ var dataMap = new Map(data.map(function (m) {
6619
6667
  return [m.id, m];
6620
6668
  }));
6621
6669
  var newSelectedItemsMap = new Map();
6622
6670
  selectedIdsRef.current.forEach(function (id) {
6623
- // 현재 markers에 있으면 최신 데이터 사용
6624
- var currentItem = markersMap.get(id);
6671
+ // 현재 data에 있으면 최신 데이터 사용
6672
+ var currentItem = dataMap.get(id);
6625
6673
 
6626
6674
  if (currentItem) {
6627
6675
  newSelectedItemsMap.set(id, currentItem);
@@ -6633,7 +6681,7 @@ var WoongCanvasLayer = function (props) {
6633
6681
  newSelectedItemsMap.set(id, prevItem);
6634
6682
  }
6635
6683
  }
6636
- }); // selectedIdsRef는 그대로 유지 (화면 밖 마커도 선택 상태 유지)
6684
+ }); // selectedIdsRef는 그대로 유지 (화면 밖 항목도 선택 상태 유지)
6637
6685
 
6638
6686
  selectedItemsMapRef.current = newSelectedItemsMap; // 즉시 렌더링
6639
6687
 
package/dist/index.umd.js CHANGED
@@ -935,7 +935,12 @@
935
935
  return false;
936
936
  };
937
937
  /**
938
- * 마커 히트 테스트
938
+ * 마커 히트 테스트 (클릭/hover 영역 체크)
939
+ *
940
+ * 🎯 중요: 꼬리(tail)는 Hit Test 영역에서 제외됩니다!
941
+ * - markerOffset.y는 마커 최하단(꼬리 끝) 좌표
942
+ * - boxHeight는 마커 본체만 포함 (꼬리 제외)
943
+ * - tailHeight만큼 위로 올려서 본체만 Hit Test 영역으로 사용
939
944
  */
940
945
 
941
946
  var isPointInMarkerData = function (clickedOffset, markerData, getMarkerOffset) {
@@ -943,9 +948,11 @@
943
948
  if (!markerOffset) return false;
944
949
  var boxWidth = markerData.boxWidth || 50;
945
950
  var boxHeight = markerData.boxHeight || 28;
946
- var tailHeight = 6;
951
+ var tailHeight = markerData.tailHeight || 0; // 🎯 tailHeight 사용!
952
+
947
953
  var x = markerOffset.x - boxWidth / 2;
948
- var y = markerOffset.y - boxHeight - tailHeight;
954
+ var y = markerOffset.y - boxHeight - tailHeight; // 🔥 꼬리만큼 위로!
955
+
949
956
  return clickedOffset.x >= x && clickedOffset.x <= x + boxWidth && clickedOffset.y >= y && clickedOffset.y <= y + boxHeight;
950
957
  };
951
958
  var hexToRgba = function (hexColor, alpha) {
@@ -5695,7 +5702,7 @@
5695
5702
 
5696
5703
  /** data prop을 ref로 추적 (stale closure 방지, useEffect에서 동기화) */
5697
5704
 
5698
- var markersRef = React.useRef(data); // --------------------------------------------------------------------------
5705
+ var dataRef = React.useRef(data); // --------------------------------------------------------------------------
5699
5706
  // State Refs - 선택 및 Hover 상태 관리
5700
5707
  // --------------------------------------------------------------------------
5701
5708
 
@@ -5846,6 +5853,10 @@
5846
5853
  /**
5847
5854
  * 아이템의 바운딩 박스 계산 (폴리곤/마커 공통)
5848
5855
  *
5856
+ * 🎯 마커의 경우:
5857
+ * - boxHeight: 본체만 (Hit Test 영역)
5858
+ * - tailHeight: 꼬리 높이 (Viewport Culling용, 화면에 보이는 전체 영역)
5859
+ *
5849
5860
  * @param item 마커 또는 폴리곤 데이터
5850
5861
  * @returns 바운딩 박스 또는 null
5851
5862
  */
@@ -5886,14 +5897,16 @@
5886
5897
  maxY: maxY
5887
5898
  };
5888
5899
  } else {
5889
- // 마커: 중심점 기준 박스 크기 계산
5900
+ // 마커: 중심점 기준 박스 크기 계산 (꼬리 포함)
5890
5901
  var offset = getOrComputeMarkerOffset(item);
5891
5902
  if (!offset) return null;
5892
5903
  var boxWidth = item.boxWidth || 50;
5893
5904
  var boxHeight = item.boxHeight || 28;
5905
+ var tailHeight = item.tailHeight || 0; // 🎯 tailHeight 사용 (Viewport Culling용)
5906
+
5894
5907
  return {
5895
5908
  minX: offset.x - boxWidth / 2,
5896
- minY: offset.y - boxHeight - 6,
5909
+ minY: offset.y - boxHeight - tailHeight,
5897
5910
  maxX: offset.x + boxWidth / 2,
5898
5911
  maxY: offset.y
5899
5912
  };
@@ -5910,10 +5923,10 @@
5910
5923
  var buildSpatialIndex = function () {
5911
5924
  var spatial = spatialIndexRef.current;
5912
5925
  spatial.clear();
5913
- var currentMarkers = markersRef.current;
5926
+ var currentData = dataRef.current;
5914
5927
 
5915
- for (var _i = 0, currentMarkers_1 = currentMarkers; _i < currentMarkers_1.length; _i++) {
5916
- var item = currentMarkers_1[_i]; // 바운딩 박스 계산 (공통 함수 사용)
5928
+ for (var _i = 0, currentData_1 = currentData; _i < currentData_1.length; _i++) {
5929
+ var item = currentData_1[_i]; // 바운딩 박스 계산 (공통 함수 사용)
5917
5930
 
5918
5931
  var bbox = computeBoundingBox(item);
5919
5932
 
@@ -5956,6 +5969,10 @@
5956
5969
  * 1. Shape 재사용으로 객체 생성/파괴 오버헤드 제거
5957
5970
  * 2. sceneFunc 한 번만 설정 (함수 재생성 제거)
5958
5971
  * 3. 클로저로 최신 데이터 참조
5972
+ *
5973
+ * 🎯 topOnHover 지원:
5974
+ * - renderEvent가 없을 때 Base Layer에서 hover 처리 (fallback)
5975
+ * - renderEvent가 있으면 Event Layer에서 처리 (성능 최적화)
5959
5976
  */
5960
5977
 
5961
5978
  var doRenderBase = function () {
@@ -5970,17 +5987,40 @@
5970
5987
  shape = new Konva__default["default"].Shape({
5971
5988
  name: 'base-render-shape',
5972
5989
  sceneFunc: function (context, shape) {
5973
- var ctx = context; // 클로저로 최신 ref 값 참조
5990
+ var ctx = context;
5991
+ var hovered = hoveredItemRef.current; // 클로저로 최신 ref 값 참조
5974
5992
 
5975
- var visibleMarkers = enableViewportCulling ? markersRef.current.filter(function (item) {
5993
+ var visibleItems = enableViewportCulling ? dataRef.current.filter(function (item) {
5976
5994
  return isInViewport(item);
5977
- }) : markersRef.current;
5995
+ }) : dataRef.current; // topOnHover가 true이고 renderEvent가 없으면 Base Layer에서 hover 처리
5996
+
5997
+ if (topOnHover && !renderEvent && hovered) {
5998
+ // hover된 항목 제외하고 렌더링
5999
+ visibleItems = visibleItems.filter(function (item) {
6000
+ return item.id !== hovered.id;
6001
+ });
6002
+ } // 일반 항목 렌더링
6003
+
6004
+
5978
6005
  renderBase({
5979
6006
  ctx: ctx,
5980
- items: visibleMarkers,
6007
+ items: visibleItems,
5981
6008
  selectedIds: selectedIdsRef.current,
5982
6009
  utils: renderUtils
5983
- });
6010
+ }); // hover된 항목을 최상단에 렌더링 (renderEvent가 없을 때만)
6011
+
6012
+ if (topOnHover && !renderEvent && hovered) {
6013
+ var isHoveredInViewport = enableViewportCulling ? isInViewport(hovered) : true;
6014
+
6015
+ if (isHoveredInViewport) {
6016
+ renderBase({
6017
+ ctx: ctx,
6018
+ items: [hovered],
6019
+ selectedIds: selectedIdsRef.current,
6020
+ utils: renderUtils
6021
+ });
6022
+ }
6023
+ }
5984
6024
  },
5985
6025
  perfectDrawEnabled: false,
5986
6026
  listening: false,
@@ -6008,7 +6048,7 @@
6008
6048
  renderAnimation({
6009
6049
  layer: layer,
6010
6050
  selectedIds: selectedIdsRef.current,
6011
- items: markersRef.current,
6051
+ items: dataRef.current,
6012
6052
  utils: renderUtils
6013
6053
  });
6014
6054
  };
@@ -6213,12 +6253,15 @@
6213
6253
  return null;
6214
6254
  };
6215
6255
  /**
6216
- * Hover 상태 설정 및 Event 레이어 렌더링
6256
+ * Hover 상태 설정 및 레이어 렌더링
6217
6257
  *
6218
6258
  * @param data hover된 마커/폴리곤 데이터 또는 null
6219
6259
  *
6220
6260
  * 최적화: RAF 제거하여 즉시 렌더링 (16ms 지연 제거)
6221
- * topOnHover가 true일 때는 Base 레이어도 다시 그려서 z-order 변경
6261
+ *
6262
+ * 🎯 topOnHover 지원:
6263
+ * - renderEvent가 있으면: Event Layer에서만 처리 (성능 최적화)
6264
+ * - renderEvent가 없고 topOnHover=true면: Base Layer에서 처리
6222
6265
  */
6223
6266
 
6224
6267
 
@@ -6230,10 +6273,15 @@
6230
6273
  } else {
6231
6274
  controller.setMapCursor(data ? 'pointer' : 'grab');
6232
6275
  } // 즉시 렌더링 (RAF 없이)
6233
- // topOnHover는 Event Layer에서만 처리 (Base Layer 재렌더링 제거로 성능 향상)
6234
6276
 
6235
6277
 
6236
- doRenderEvent();
6278
+ if (renderEvent) {
6279
+ // renderEvent가 있으면 Event Layer에서만 처리 (성능 최적화)
6280
+ doRenderEvent();
6281
+ } else if (topOnHover) {
6282
+ // renderEvent가 없고 topOnHover가 true면 Base Layer에서 처리
6283
+ doRenderBase();
6284
+ }
6237
6285
  };
6238
6286
  /**
6239
6287
  * 클릭 처리 (단일/다중 선택)
@@ -6589,9 +6637,9 @@
6589
6637
  // --------------------------------------------------------------------------
6590
6638
 
6591
6639
  React.useEffect(function () {
6592
- if (!stageRef.current) return; // markersRef 동기화
6640
+ if (!stageRef.current) return; // dataRef 동기화
6593
6641
 
6594
- markersRef.current = data; // 데이터 변경 시 즉시 transform 제거 및 캐시 정리 (겹침 방지)
6642
+ dataRef.current = data; // 데이터 변경 시 즉시 transform 제거 및 캐시 정리 (겹침 방지)
6595
6643
 
6596
6644
  if (containerRef.current) {
6597
6645
  containerRef.current.style.transform = '';
@@ -6619,13 +6667,13 @@
6619
6667
  * - O(전체 데이터 수 + 선택된 개수) - 매우 효율적
6620
6668
  */
6621
6669
 
6622
- var markersMap = new Map(data.map(function (m) {
6670
+ var dataMap = new Map(data.map(function (m) {
6623
6671
  return [m.id, m];
6624
6672
  }));
6625
6673
  var newSelectedItemsMap = new Map();
6626
6674
  selectedIdsRef.current.forEach(function (id) {
6627
- // 현재 markers에 있으면 최신 데이터 사용
6628
- var currentItem = markersMap.get(id);
6675
+ // 현재 data에 있으면 최신 데이터 사용
6676
+ var currentItem = dataMap.get(id);
6629
6677
 
6630
6678
  if (currentItem) {
6631
6679
  newSelectedItemsMap.set(id, currentItem);
@@ -6637,7 +6685,7 @@
6637
6685
  newSelectedItemsMap.set(id, prevItem);
6638
6686
  }
6639
6687
  }
6640
- }); // selectedIdsRef는 그대로 유지 (화면 밖 마커도 선택 상태 유지)
6688
+ }); // selectedIdsRef는 그대로 유지 (화면 밖 항목도 선택 상태 유지)
6641
6689
 
6642
6690
  selectedItemsMapRef.current = newSelectedItemsMap; // 즉시 렌더링
6643
6691
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mint-ui/map",
3
- "version": "1.2.0-test.17",
3
+ "version": "1.2.0-test.19",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.es.js",
6
6
  "browser": "./dist/index.umd.js",