@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.
- package/dist/components/mint-map/core/advanced/woongCanvas/WoongCanvasLayer.js +63 -22
- package/dist/components/mint-map/core/advanced/woongCanvas/shared/types.d.ts +1 -0
- package/dist/components/mint-map/core/advanced/woongCanvas/shared/utils.d.ts +6 -1
- package/dist/components/mint-map/core/advanced/woongCanvas/shared/utils.js +10 -3
- package/dist/index.es.js +73 -25
- package/dist/index.umd.js +73 -25
- package/package.json +1 -1
|
@@ -71,7 +71,7 @@ var WoongCanvasLayer = function (props) {
|
|
|
71
71
|
|
|
72
72
|
/** data prop을 ref로 추적 (stale closure 방지, useEffect에서 동기화) */
|
|
73
73
|
|
|
74
|
-
var
|
|
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 -
|
|
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
|
|
295
|
+
var currentData = dataRef.current;
|
|
290
296
|
|
|
291
|
-
for (var _i = 0,
|
|
292
|
-
var item =
|
|
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;
|
|
359
|
+
var ctx = context;
|
|
360
|
+
var hovered = hoveredItemRef.current; // 클로저로 최신 ref 값 참조
|
|
350
361
|
|
|
351
|
-
var
|
|
362
|
+
var visibleItems = enableViewportCulling ? dataRef.current.filter(function (item) {
|
|
352
363
|
return isInViewport(item);
|
|
353
|
-
}) :
|
|
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:
|
|
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:
|
|
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 상태 설정 및
|
|
625
|
+
* Hover 상태 설정 및 레이어 렌더링
|
|
593
626
|
*
|
|
594
627
|
* @param data hover된 마커/폴리곤 데이터 또는 null
|
|
595
628
|
*
|
|
596
629
|
* 최적화: RAF 제거하여 즉시 렌더링 (16ms 지연 제거)
|
|
597
|
-
*
|
|
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
|
-
|
|
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; //
|
|
1009
|
+
if (!stageRef.current) return; // dataRef 동기화
|
|
969
1010
|
|
|
970
|
-
|
|
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
|
|
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
|
-
// 현재
|
|
1004
|
-
var currentItem =
|
|
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
|
|
|
@@ -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 =
|
|
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 =
|
|
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
|
|
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 -
|
|
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
|
|
5922
|
+
var currentData = dataRef.current;
|
|
5910
5923
|
|
|
5911
|
-
for (var _i = 0,
|
|
5912
|
-
var item =
|
|
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;
|
|
5986
|
+
var ctx = context;
|
|
5987
|
+
var hovered = hoveredItemRef.current; // 클로저로 최신 ref 값 참조
|
|
5970
5988
|
|
|
5971
|
-
var
|
|
5989
|
+
var visibleItems = enableViewportCulling ? dataRef.current.filter(function (item) {
|
|
5972
5990
|
return isInViewport(item);
|
|
5973
|
-
}) :
|
|
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:
|
|
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:
|
|
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 상태 설정 및
|
|
6252
|
+
* Hover 상태 설정 및 레이어 렌더링
|
|
6213
6253
|
*
|
|
6214
6254
|
* @param data hover된 마커/폴리곤 데이터 또는 null
|
|
6215
6255
|
*
|
|
6216
6256
|
* 최적화: RAF 제거하여 즉시 렌더링 (16ms 지연 제거)
|
|
6217
|
-
*
|
|
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
|
-
|
|
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; //
|
|
6636
|
+
if (!stageRef.current) return; // dataRef 동기화
|
|
6589
6637
|
|
|
6590
|
-
|
|
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
|
|
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
|
-
// 현재
|
|
6624
|
-
var currentItem =
|
|
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 =
|
|
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
|
|
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 -
|
|
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
|
|
5926
|
+
var currentData = dataRef.current;
|
|
5914
5927
|
|
|
5915
|
-
for (var _i = 0,
|
|
5916
|
-
var item =
|
|
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;
|
|
5990
|
+
var ctx = context;
|
|
5991
|
+
var hovered = hoveredItemRef.current; // 클로저로 최신 ref 값 참조
|
|
5974
5992
|
|
|
5975
|
-
var
|
|
5993
|
+
var visibleItems = enableViewportCulling ? dataRef.current.filter(function (item) {
|
|
5976
5994
|
return isInViewport(item);
|
|
5977
|
-
}) :
|
|
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:
|
|
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:
|
|
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 상태 설정 및
|
|
6256
|
+
* Hover 상태 설정 및 레이어 렌더링
|
|
6217
6257
|
*
|
|
6218
6258
|
* @param data hover된 마커/폴리곤 데이터 또는 null
|
|
6219
6259
|
*
|
|
6220
6260
|
* 최적화: RAF 제거하여 즉시 렌더링 (16ms 지연 제거)
|
|
6221
|
-
*
|
|
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
|
-
|
|
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; //
|
|
6640
|
+
if (!stageRef.current) return; // dataRef 동기화
|
|
6593
6641
|
|
|
6594
|
-
|
|
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
|
|
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
|
-
// 현재
|
|
6628
|
-
var currentItem =
|
|
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
|
|