@mint-ui/map 1.2.0-test.35 → 1.2.0-test.36
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/shared/context.d.ts +9 -71
- package/dist/components/mint-map/core/advanced/shared/context.js +43 -137
- package/dist/components/mint-map/core/advanced/shared/helpers.d.ts +5 -13
- package/dist/components/mint-map/core/advanced/shared/helpers.js +8 -20
- package/dist/components/mint-map/core/advanced/shared/hooks.d.ts +6 -76
- package/dist/components/mint-map/core/advanced/shared/hooks.js +18 -112
- package/dist/components/mint-map/core/advanced/shared/performance.d.ts +9 -188
- package/dist/components/mint-map/core/advanced/shared/performance.js +53 -229
- package/dist/components/mint-map/core/advanced/shared/types.d.ts +18 -153
- package/dist/components/mint-map/core/advanced/shared/types.js +0 -1
- package/dist/components/mint-map/core/advanced/shared/utils.d.ts +21 -126
- package/dist/components/mint-map/core/advanced/shared/utils.js +43 -151
- package/dist/components/mint-map/core/advanced/shared/viewport.d.ts +4 -34
- package/dist/components/mint-map/core/advanced/shared/viewport.js +4 -34
- package/dist/components/mint-map/core/advanced/woongCanvasMarker/WoongCanvasMarker.d.ts +22 -74
- package/dist/components/mint-map/core/advanced/woongCanvasMarker/WoongCanvasMarker.js +122 -516
- package/dist/components/mint-map/core/advanced/woongCanvasPolygon/WoongCanvasPolygon.d.ts +26 -76
- package/dist/components/mint-map/core/advanced/woongCanvasPolygon/WoongCanvasPolygon.js +118 -432
- package/dist/components/mint-map/core/advanced/woongCanvasPolygon/renderer.d.ts +3 -3
- package/dist/index.es.js +409 -1632
- package/dist/index.umd.js +409 -1632
- package/package.json +1 -1
|
@@ -23,9 +23,6 @@ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'defau
|
|
|
23
23
|
var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
|
|
24
24
|
var Konva__default = /*#__PURE__*/_interopDefaultLegacy(Konva);
|
|
25
25
|
|
|
26
|
-
// 메인 컴포넌트
|
|
27
|
-
// ============================================================================
|
|
28
|
-
|
|
29
26
|
var WoongCanvasMarker = function (props) {
|
|
30
27
|
var data = props.data,
|
|
31
28
|
onClick = props.onClick,
|
|
@@ -35,225 +32,88 @@ var WoongCanvasMarker = function (props) {
|
|
|
35
32
|
enableMultiSelect = _a === void 0 ? false : _a,
|
|
36
33
|
_b = props.topOnHover,
|
|
37
34
|
topOnHover = _b === void 0 ? false : _b,
|
|
38
|
-
_c = props.
|
|
39
|
-
|
|
40
|
-
_d = props.
|
|
41
|
-
|
|
42
|
-
_e = props.maxCacheSize,
|
|
43
|
-
maxCacheSize = _e === void 0 ? performance.DEFAULT_MAX_CACHE_SIZE : _e,
|
|
35
|
+
_c = props.cullingMargin,
|
|
36
|
+
cullingMargin = _c === void 0 ? performance.DEFAULT_CULLING_MARGIN : _c,
|
|
37
|
+
_d = props.maxCacheSize,
|
|
38
|
+
maxCacheSize = _d === void 0 ? performance.DEFAULT_MAX_CACHE_SIZE : _d,
|
|
44
39
|
externalSelectedItems = props.selectedItems,
|
|
45
40
|
externalSelectedItem = props.selectedItem,
|
|
46
|
-
|
|
47
|
-
disableInteraction =
|
|
41
|
+
_e = props.disableInteraction,
|
|
42
|
+
disableInteraction = _e === void 0 ? false : _e,
|
|
48
43
|
renderBase = props.renderBase,
|
|
49
|
-
renderAnimation = props.renderAnimation,
|
|
50
44
|
renderEvent = props.renderEvent,
|
|
51
|
-
options = tslib.__rest(props, ["data", "onClick", "onMouseOver", "onMouseOut", "enableMultiSelect", "topOnHover", "
|
|
52
|
-
// Hooks & Context
|
|
53
|
-
// --------------------------------------------------------------------------
|
|
54
|
-
|
|
45
|
+
options = tslib.__rest(props, ["data", "onClick", "onMouseOver", "onMouseOut", "enableMultiSelect", "topOnHover", "cullingMargin", "maxCacheSize", "selectedItems", "selectedItem", "disableInteraction", "renderBase", "renderEvent"]);
|
|
55
46
|
|
|
56
47
|
var controller = MintMapProvider.useMintMapController();
|
|
57
48
|
var context$1 = context.useWoongCanvasContext();
|
|
58
|
-
var currentZIndex = options.zIndex !== undefined ? options.zIndex : 0; //
|
|
59
|
-
// DOM Refs
|
|
60
|
-
// --------------------------------------------------------------------------
|
|
49
|
+
var currentZIndex = options.zIndex !== undefined ? options.zIndex : 0; // DOM Refs
|
|
61
50
|
|
|
62
51
|
var divRef = React.useRef(document.createElement('div'));
|
|
63
52
|
var divElement = divRef.current;
|
|
64
53
|
var containerRef = React.useRef(null);
|
|
65
|
-
var markerRef = React.useRef(); //
|
|
66
|
-
// Konva Refs
|
|
67
|
-
// --------------------------------------------------------------------------
|
|
54
|
+
var markerRef = React.useRef(); // Konva Refs
|
|
68
55
|
|
|
69
56
|
var stageRef = React.useRef(null);
|
|
70
57
|
var baseLayerRef = React.useRef(null);
|
|
71
|
-
var
|
|
72
|
-
var eventLayerRef = React.useRef(null); // --------------------------------------------------------------------------
|
|
73
|
-
// Data Refs - 선택 및 Hover 상태 관리
|
|
74
|
-
// --------------------------------------------------------------------------
|
|
75
|
-
|
|
76
|
-
/** data prop을 ref로 추적 (stale closure 방지, useEffect에서 동기화) */
|
|
77
|
-
|
|
78
|
-
var dataRef = React.useRef(data); // --------------------------------------------------------------------------
|
|
79
|
-
// State Refs - 선택 및 Hover 상태 관리
|
|
80
|
-
// --------------------------------------------------------------------------
|
|
81
|
-
|
|
82
|
-
/** 상호작용 비활성화 상태 (Ref로 관리하여 클로저 문제 해결) */
|
|
58
|
+
var eventLayerRef = React.useRef(null); // 상태 관리 Refs (React 리렌더링 최소화)
|
|
83
59
|
|
|
60
|
+
var dataRef = React.useRef(data);
|
|
84
61
|
var disableInteractionRef = React.useRef(disableInteraction);
|
|
85
|
-
/** 현재 Hover 중인 항목 */
|
|
86
|
-
|
|
87
62
|
var hoveredItemRef = React.useRef(null);
|
|
88
|
-
/** 외부에서 전달된 선택 항목 (Ref로 관리하여 클로저 문제 해결) */
|
|
89
|
-
|
|
90
63
|
var selectedItemRef = React.useRef(externalSelectedItem);
|
|
91
|
-
/**
|
|
92
|
-
* 선택된 항목의 ID Set
|
|
93
|
-
*
|
|
94
|
-
* 용도:
|
|
95
|
-
* 1. onClick 콜백에 전달 - onClick(data, selectedIdsRef.current)
|
|
96
|
-
* 2. 선택 여부 빠른 체크 - selectedIdsRef.current.has(id)
|
|
97
|
-
* 3. 메모리 효율 - ID만 저장 (작음)
|
|
98
|
-
*
|
|
99
|
-
* selectedItemsMapRef와 차이:
|
|
100
|
-
* - selectedIdsRef: ID만 저장 { "id1", "id2" }
|
|
101
|
-
* - selectedItemsMapRef: 전체 객체 저장 { id1: {...}, id2: {...} }
|
|
102
|
-
*
|
|
103
|
-
* 둘 다 필요: ID만 필요한 곳은 이것, 전체 데이터 필요한 곳은 Map
|
|
104
|
-
*/
|
|
105
|
-
|
|
106
64
|
var selectedIdsRef = React.useRef(new Set());
|
|
107
|
-
|
|
108
|
-
* 선택된 항목의 실제 데이터 Map (핵심 성능 최적화!)
|
|
109
|
-
*
|
|
110
|
-
* 목적: doRenderEvent에서 filter() 순회 제거
|
|
111
|
-
* - 이전: markersRef.current.filter() → O(전체 마커 수)
|
|
112
|
-
* - 현재: Map.values() → O(선택된 항목 수)
|
|
113
|
-
*
|
|
114
|
-
* 성능 개선: 10,000개 중 1개 선택 시
|
|
115
|
-
* - 이전: 10,000번 체크
|
|
116
|
-
* - 현재: 1번 접근 (10,000배 빠름!)
|
|
117
|
-
*/
|
|
118
|
-
|
|
119
|
-
var selectedItemsMapRef = React.useRef(new Map()); // --------------------------------------------------------------------------
|
|
120
|
-
// Drag Refs
|
|
121
|
-
// --------------------------------------------------------------------------
|
|
65
|
+
var selectedItemsMapRef = React.useRef(new Map()); // 드래그 상태 Refs
|
|
122
66
|
|
|
123
67
|
var draggingRef = React.useRef(false);
|
|
124
68
|
var prevCenterOffsetRef = React.useRef(null);
|
|
125
69
|
var accumTranslateRef = React.useRef({
|
|
126
70
|
x: 0,
|
|
127
71
|
y: 0
|
|
128
|
-
}); //
|
|
129
|
-
// Performance Refs (캐싱 & 최적화)
|
|
130
|
-
// --------------------------------------------------------------------------
|
|
131
|
-
|
|
132
|
-
/** 좌표 변환 결과 LRU 캐시 */
|
|
72
|
+
}); // 성능 최적화 Refs
|
|
133
73
|
|
|
134
74
|
var offsetCacheRef = React.useRef(new performance.LRUCache(maxCacheSize));
|
|
135
|
-
/** 공간 인덱스 (빠른 Hit Test) */
|
|
136
|
-
|
|
137
75
|
var spatialIndexRef = React.useRef(new performance.SpatialHashGrid(performance.SPATIAL_GRID_CELL_SIZE));
|
|
138
|
-
/** 바운딩 박스 캐시 (Viewport Culling 최적화) */
|
|
139
|
-
|
|
140
76
|
var boundingBoxCacheRef = React.useRef(new Map());
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
var viewportRef = React.useRef(null); // --------------------------------------------------------------------------
|
|
144
|
-
// 유틸리티 함수: 뷰포트 관리
|
|
145
|
-
// --------------------------------------------------------------------------
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* 현재 뷰포트 영역 계산
|
|
149
|
-
*/
|
|
77
|
+
var viewportRef = React.useRef(null); // 뷰포트 영역 계산 (Viewport Culling용)
|
|
150
78
|
|
|
151
79
|
var updateViewport = function () {
|
|
152
80
|
viewport.updateViewport(stageRef.current, cullingMargin, viewportRef);
|
|
153
|
-
};
|
|
154
|
-
/**
|
|
155
|
-
* 아이템이 현재 뷰포트 안에 있는지 확인 (바운딩 박스 캐싱)
|
|
156
|
-
*/
|
|
81
|
+
}; // 뷰포트 내부 여부 확인 (바운딩 박스 캐싱)
|
|
157
82
|
|
|
158
83
|
|
|
159
84
|
var isInViewport = function (item) {
|
|
160
|
-
return viewport.isInViewport(item,
|
|
161
|
-
}; //
|
|
162
|
-
// 유틸리티 함수: 좌표 변환 캐싱
|
|
163
|
-
// --------------------------------------------------------------------------
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* 마커 좌표 변환 결과를 캐시하고 반환
|
|
167
|
-
*
|
|
168
|
-
* @param markerData 마커 데이터
|
|
169
|
-
* @returns 변환된 좌표 또는 null
|
|
170
|
-
*/
|
|
85
|
+
return viewport.isInViewport(item, viewportRef, boundingBoxCacheRef, computeBoundingBox);
|
|
86
|
+
}; // 마커 좌표 변환 (위경도 → 화면 좌표, LRU 캐시 사용)
|
|
171
87
|
|
|
172
88
|
|
|
173
89
|
var getOrComputeMarkerOffset = function (markerData) {
|
|
174
90
|
var cached = offsetCacheRef.current.get(markerData.id);
|
|
175
91
|
if (cached && !Array.isArray(cached)) return cached;
|
|
176
92
|
var result = utils.computeMarkerOffset(markerData, controller);
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
offsetCacheRef.current.set(markerData.id, result);
|
|
180
|
-
}
|
|
181
|
-
|
|
93
|
+
if (!result) return null;
|
|
94
|
+
offsetCacheRef.current.set(markerData.id, result);
|
|
182
95
|
return result;
|
|
183
|
-
}; //
|
|
184
|
-
// 유틸리티 함수: 바운딩 박스 계산
|
|
185
|
-
// --------------------------------------------------------------------------
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* 마커의 바운딩 박스 계산
|
|
189
|
-
*
|
|
190
|
-
* 마커의 화면 상 위치와 크기를 기반으로 바운딩 박스를 계산합니다.
|
|
191
|
-
* Viewport Culling에 사용되며, tailHeight를 포함하여 전체 표시 영역을 계산합니다.
|
|
192
|
-
*
|
|
193
|
-
* @param item 마커 데이터
|
|
194
|
-
* @returns 바운딩 박스 (minX, minY, maxX, maxY) 또는 null (좌표 변환 실패 시)
|
|
195
|
-
*
|
|
196
|
-
* @remarks
|
|
197
|
-
* - **boxHeight**: 마커 본체만 포함 (Hit Test 영역)
|
|
198
|
-
* - **tailHeight**: 마커 꼬리 높이 (Viewport Culling용, 화면에 보이는 전체 영역 포함)
|
|
199
|
-
* - 바운딩 박스는 캐시되어 성능 최적화
|
|
200
|
-
*
|
|
201
|
-
* @example
|
|
202
|
-
* ```typescript
|
|
203
|
-
* const bbox = computeBoundingBox(item);
|
|
204
|
-
* if (!bbox) return; // 계산 실패
|
|
205
|
-
* // bbox.minX, bbox.minY, bbox.maxX, bbox.maxY 사용
|
|
206
|
-
* ```
|
|
207
|
-
*/
|
|
96
|
+
}; // 마커 바운딩 박스 계산 (Viewport Culling 및 Hit Test용)
|
|
208
97
|
|
|
209
98
|
|
|
210
99
|
var computeBoundingBox = function (item) {
|
|
211
|
-
// 마커: 중심점 기준 박스 크기 계산 (꼬리 포함)
|
|
212
100
|
var offset = getOrComputeMarkerOffset(item);
|
|
213
101
|
if (!offset) return null;
|
|
214
102
|
var boxWidth = item.boxWidth || 50;
|
|
215
103
|
var boxHeight = item.boxHeight || 28;
|
|
216
|
-
var tailHeight = item.tailHeight || 0;
|
|
217
|
-
|
|
104
|
+
var tailHeight = item.tailHeight || 0;
|
|
218
105
|
return {
|
|
219
106
|
minX: offset.x - boxWidth / 2,
|
|
220
107
|
minY: offset.y - boxHeight - tailHeight,
|
|
221
108
|
maxX: offset.x + boxWidth / 2,
|
|
222
109
|
maxY: offset.y
|
|
223
110
|
};
|
|
224
|
-
}; //
|
|
225
|
-
// 유틸리티 함수: 공간 인덱싱
|
|
226
|
-
// --------------------------------------------------------------------------
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* 공간 인덱스 빌드 (빠른 Hit Test를 위한 자료구조)
|
|
230
|
-
*
|
|
231
|
-
* 모든 마커의 바운딩 박스를 Spatial Hash Grid에 삽입합니다.
|
|
232
|
-
* 이를 통해 클릭/호버 시 해당 위치 주변의 마커만 빠르게 조회할 수 있습니다.
|
|
233
|
-
*
|
|
234
|
-
* @remarks
|
|
235
|
-
* - 호출 시점: 데이터 변경 시 또는 지도 이동/줌 완료 시
|
|
236
|
-
* - 성능: O(n) 시간복잡도, n은 마커 개수
|
|
237
|
-
* - Hit Test 성능: O(1) 수준 (30,000개 → ~10개 후보만 체크)
|
|
238
|
-
*/
|
|
111
|
+
}; // 공간 인덱스 빌드 (빠른 Hit Test용)
|
|
239
112
|
|
|
240
113
|
|
|
241
114
|
var buildSpatialIndex = function () {
|
|
242
115
|
hooks.buildSpatialIndex(dataRef.current, spatialIndexRef.current, computeBoundingBox);
|
|
243
|
-
}; //
|
|
244
|
-
// 렌더링 함수 결정
|
|
245
|
-
// --------------------------------------------------------------------------
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* 외부 렌더링 함수에 전달할 유틸리티 객체
|
|
249
|
-
*
|
|
250
|
-
* 커스텀 렌더링 함수(renderBase, renderAnimation, renderEvent)에서 사용할
|
|
251
|
-
* 좌표 변환 등의 헬퍼 함수들을 제공합니다.
|
|
252
|
-
*
|
|
253
|
-
* @remarks
|
|
254
|
-
* - getOrComputeMarkerOffset: 마커 좌표 변환 (자동 캐싱)
|
|
255
|
-
* - getOrComputePolygonOffsets: 폴리곤 좌표 변환 (마커에서는 사용 안 함)
|
|
256
|
-
*/
|
|
116
|
+
}; // 렌더링 유틸리티 객체
|
|
257
117
|
|
|
258
118
|
|
|
259
119
|
var renderUtils = {
|
|
@@ -261,48 +121,29 @@ var WoongCanvasMarker = function (props) {
|
|
|
261
121
|
return null;
|
|
262
122
|
},
|
|
263
123
|
getOrComputeMarkerOffset: getOrComputeMarkerOffset
|
|
264
|
-
};
|
|
265
|
-
/** Base Layer에서 사용할 빈 Set (재사용) */
|
|
266
|
-
|
|
267
|
-
React.useRef(new Set());
|
|
268
|
-
/**
|
|
269
|
-
* Base 레이어 렌더링 (뷰포트 컬링 적용, 선택된 마커 제외)
|
|
270
|
-
*
|
|
271
|
-
* 🔥 최적화:
|
|
272
|
-
* 1. Shape 재사용으로 객체 생성/파괴 오버헤드 제거
|
|
273
|
-
* 2. sceneFunc 한 번만 설정 (함수 재생성 제거)
|
|
274
|
-
* 3. 클로저로 최신 데이터 참조
|
|
275
|
-
*
|
|
276
|
-
* 🎯 topOnHover 지원:
|
|
277
|
-
* - renderEvent가 없을 때 Base Layer에서 hover 처리 (fallback)
|
|
278
|
-
* - renderEvent가 있으면 Event Layer에서 처리 (성능 최적화)
|
|
279
|
-
*/
|
|
124
|
+
}; // Base Layer 렌더링 (뷰포트 컬링 적용, 선택된 마커 제외)
|
|
280
125
|
|
|
281
126
|
var doRenderBase = function () {
|
|
282
127
|
var layer = baseLayerRef.current;
|
|
283
|
-
if (!layer) return;
|
|
284
|
-
|
|
128
|
+
if (!layer) return;
|
|
285
129
|
var shape = layer.findOne('.base-render-shape');
|
|
286
130
|
|
|
287
131
|
if (!shape) {
|
|
288
|
-
// 최초 생성 (한 번만 실행됨)
|
|
289
|
-
// sceneFunc도 여기서 한 번만 설정 (클로저로 최신 데이터 참조)
|
|
290
132
|
shape = new Konva__default["default"].Shape({
|
|
291
133
|
name: 'base-render-shape',
|
|
292
134
|
sceneFunc: function (context, shape) {
|
|
293
135
|
var ctx = context;
|
|
294
|
-
var hovered = hoveredItemRef.current; //
|
|
136
|
+
var hovered = hoveredItemRef.current; // 뷰포트 컬링: 화면에 보이는 항목만 필터링
|
|
295
137
|
|
|
296
|
-
var visibleItems =
|
|
138
|
+
var visibleItems = dataRef.current.filter(function (item) {
|
|
297
139
|
return isInViewport(item);
|
|
298
|
-
})
|
|
140
|
+
}); // topOnHover 옵션: hover된 항목을 나중에 그려서 최상위에 표시
|
|
299
141
|
|
|
300
142
|
if (topOnHover && !renderEvent && hovered) {
|
|
301
|
-
// hover된 항목 제외하고 렌더링
|
|
302
143
|
visibleItems = visibleItems.filter(function (item) {
|
|
303
144
|
return item.id !== hovered.id;
|
|
304
145
|
});
|
|
305
|
-
} // 일반
|
|
146
|
+
} // 일반 항목들 먼저 렌더링
|
|
306
147
|
|
|
307
148
|
|
|
308
149
|
renderBase({
|
|
@@ -311,12 +152,10 @@ var WoongCanvasMarker = function (props) {
|
|
|
311
152
|
selectedIds: selectedIdsRef.current,
|
|
312
153
|
hoveredItem: hovered,
|
|
313
154
|
utils: renderUtils
|
|
314
|
-
}); // hover된 항목을
|
|
155
|
+
}); // hover된 항목을 마지막에 렌더링하여 최상위에 표시
|
|
315
156
|
|
|
316
157
|
if (topOnHover && !renderEvent && hovered) {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
if (isHoveredInViewport) {
|
|
158
|
+
if (isInViewport(hovered)) {
|
|
320
159
|
renderBase({
|
|
321
160
|
ctx: ctx,
|
|
322
161
|
items: [hovered],
|
|
@@ -332,72 +171,26 @@ var WoongCanvasMarker = function (props) {
|
|
|
332
171
|
hitStrokeWidth: 0
|
|
333
172
|
});
|
|
334
173
|
layer.add(shape);
|
|
335
|
-
}
|
|
336
|
-
|
|
174
|
+
}
|
|
337
175
|
|
|
338
176
|
layer.batchDraw();
|
|
339
|
-
};
|
|
340
|
-
/**
|
|
341
|
-
* Animation 레이어 렌더링 (선택된 마커 애니메이션)
|
|
342
|
-
*
|
|
343
|
-
* 선택된 마커에 대한 애니메이션 효과를 렌더링합니다.
|
|
344
|
-
* renderAnimation prop이 제공된 경우에만 실행됩니다.
|
|
345
|
-
*
|
|
346
|
-
* @remarks
|
|
347
|
-
* - **성능 최적화**: sceneFunc 내부에서 최신 items 참조
|
|
348
|
-
* - 선택 변경 시에만 재생성
|
|
349
|
-
* - 지도 이동 시에는 기존 Animation 계속 실행
|
|
350
|
-
*/
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
var doRenderAnimation = function () {
|
|
354
|
-
if (!renderAnimation) return;
|
|
355
|
-
var layer = animationLayerRef.current;
|
|
356
|
-
if (!layer) return;
|
|
357
|
-
renderAnimation({
|
|
358
|
-
layer: layer,
|
|
359
|
-
selectedIds: selectedIdsRef.current,
|
|
360
|
-
items: dataRef.current,
|
|
361
|
-
utils: renderUtils
|
|
362
|
-
});
|
|
363
|
-
};
|
|
364
|
-
/**
|
|
365
|
-
* Event 레이어 렌더링 (hover + 선택 상태 표시)
|
|
366
|
-
*
|
|
367
|
-
* 마커의 hover 효과 및 선택 상태를 표시합니다.
|
|
368
|
-
* renderEvent prop이 제공된 경우에만 실행됩니다.
|
|
369
|
-
*
|
|
370
|
-
* @remarks
|
|
371
|
-
* - **성능 최적화**:
|
|
372
|
-
* 1. Shape 재사용으로 객체 생성/파괴 오버헤드 제거
|
|
373
|
-
* 2. sceneFunc 한 번만 설정 (함수 재생성 제거)
|
|
374
|
-
* 3. 클로저로 최신 데이터 참조
|
|
375
|
-
* - **topOnHover 지원**: hover된 항목을 최상단에 렌더링
|
|
376
|
-
* - 선택된 항목은 Map에서 O(1)로 조회하여 성능 최적화
|
|
377
|
-
*/
|
|
177
|
+
}; // Event Layer 렌더링 (hover 효과 및 선택 상태 표시)
|
|
378
178
|
|
|
379
179
|
|
|
380
180
|
var doRenderEvent = function () {
|
|
381
181
|
var layer = eventLayerRef.current;
|
|
382
|
-
if (!layer) return;
|
|
383
|
-
if (!renderEvent) return; // 🔥 Shape 재사용: 이미 존재하면 재사용, 없으면 생성
|
|
384
|
-
|
|
182
|
+
if (!layer || !renderEvent) return;
|
|
385
183
|
var shape = layer.findOne('.event-render-shape');
|
|
386
184
|
|
|
387
185
|
if (!shape) {
|
|
388
|
-
// 최초 생성 (한 번만 실행됨)
|
|
389
|
-
// sceneFunc도 여기서 한 번만 설정 (클로저로 최신 데이터 참조)
|
|
390
186
|
shape = new Konva__default["default"].Shape({
|
|
391
187
|
name: 'event-render-shape',
|
|
392
188
|
sceneFunc: function (context, shape) {
|
|
393
|
-
var ctx = context;
|
|
394
|
-
// 성능 최적화: Array.from 대신 직접 변환 (메모리 할당 최소화)
|
|
395
|
-
|
|
189
|
+
var ctx = context;
|
|
396
190
|
var selectedItems = helpers.mapValuesToArray(selectedItemsMapRef.current);
|
|
397
|
-
var hovered = hoveredItemRef.current;
|
|
191
|
+
var hovered = hoveredItemRef.current;
|
|
398
192
|
|
|
399
193
|
if (topOnHover && hovered) {
|
|
400
|
-
// 1. 먼저 일반 항목들 렌더링 (hover된 항목 제외)
|
|
401
194
|
renderEvent({
|
|
402
195
|
ctx: ctx,
|
|
403
196
|
hoveredItem: null,
|
|
@@ -406,13 +199,9 @@ var WoongCanvasMarker = function (props) {
|
|
|
406
199
|
return item.id !== hovered.id;
|
|
407
200
|
}),
|
|
408
201
|
selectedItem: selectedItemRef.current
|
|
409
|
-
});
|
|
410
|
-
|
|
411
|
-
var isHoveredInViewport = enableViewportCulling ? isInViewport(hovered) : true;
|
|
202
|
+
});
|
|
412
203
|
|
|
413
|
-
if (
|
|
414
|
-
// hover된 항목이 선택되어 있다면 hoverSelectedItems에 포함시켜서
|
|
415
|
-
// renderEvent에서 hover 스타일만 적용되도록 함
|
|
204
|
+
if (isInViewport(hovered)) {
|
|
416
205
|
var hoveredIsSelected = selectedItems.some(function (item) {
|
|
417
206
|
return item.id === hovered.id;
|
|
418
207
|
});
|
|
@@ -426,7 +215,6 @@ var WoongCanvasMarker = function (props) {
|
|
|
426
215
|
});
|
|
427
216
|
}
|
|
428
217
|
} else {
|
|
429
|
-
// topOnHover가 false이거나 hover된 항목이 없으면 일반 렌더링
|
|
430
218
|
renderEvent({
|
|
431
219
|
ctx: ctx,
|
|
432
220
|
hoveredItem: hovered,
|
|
@@ -441,28 +229,21 @@ var WoongCanvasMarker = function (props) {
|
|
|
441
229
|
hitStrokeWidth: 0
|
|
442
230
|
});
|
|
443
231
|
layer.add(shape);
|
|
444
|
-
}
|
|
445
|
-
|
|
232
|
+
}
|
|
446
233
|
|
|
447
234
|
layer.batchDraw();
|
|
448
|
-
};
|
|
449
|
-
/**
|
|
450
|
-
* 전체 즉시 렌더링 (IDLE 시 호출)
|
|
451
|
-
*/
|
|
235
|
+
}; // 전체 즉시 렌더링
|
|
452
236
|
|
|
453
237
|
|
|
454
238
|
var renderAllImmediate = function () {
|
|
455
239
|
updateViewport();
|
|
456
240
|
buildSpatialIndex();
|
|
457
241
|
doRenderBase();
|
|
458
|
-
doRenderAnimation();
|
|
459
242
|
doRenderEvent();
|
|
460
|
-
}; //
|
|
461
|
-
// 이벤트 핸들러: 지도 이벤트
|
|
462
|
-
// --------------------------------------------------------------------------
|
|
243
|
+
}; // 지도 이벤트 핸들러 생성
|
|
463
244
|
|
|
464
245
|
|
|
465
|
-
var
|
|
246
|
+
var _f = hooks.createMapEventHandlers({
|
|
466
247
|
controller: controller,
|
|
467
248
|
containerRef: containerRef,
|
|
468
249
|
markerRef: markerRef,
|
|
@@ -473,73 +254,38 @@ var WoongCanvasMarker = function (props) {
|
|
|
473
254
|
boundingBoxCacheRef: boundingBoxCacheRef,
|
|
474
255
|
renderAllImmediate: renderAllImmediate
|
|
475
256
|
}),
|
|
476
|
-
handleIdle =
|
|
477
|
-
handleZoomStart =
|
|
478
|
-
handleZoomEnd =
|
|
479
|
-
handleCenterChanged =
|
|
480
|
-
handleDragStartShared =
|
|
481
|
-
handleDragEndShared =
|
|
482
|
-
/**
|
|
483
|
-
* 드래그 시작 처리 (커서를 grabbing으로 변경)
|
|
484
|
-
*/
|
|
485
|
-
|
|
257
|
+
handleIdle = _f.handleIdle,
|
|
258
|
+
handleZoomStart = _f.handleZoomStart,
|
|
259
|
+
handleZoomEnd = _f.handleZoomEnd,
|
|
260
|
+
handleCenterChanged = _f.handleCenterChanged,
|
|
261
|
+
handleDragStartShared = _f.handleDragStart,
|
|
262
|
+
handleDragEndShared = _f.handleDragEnd;
|
|
486
263
|
|
|
487
264
|
var handleDragStart = function () {
|
|
488
265
|
handleDragStartShared();
|
|
489
266
|
draggingRef.current = true;
|
|
490
267
|
controller.setMapCursor('grabbing');
|
|
491
268
|
};
|
|
492
|
-
/**
|
|
493
|
-
* 드래그 종료 처리 (커서를 기본으로 복원)
|
|
494
|
-
*/
|
|
495
|
-
|
|
496
269
|
|
|
497
270
|
var handleDragEnd = function () {
|
|
498
271
|
handleDragEndShared();
|
|
499
272
|
draggingRef.current = false;
|
|
500
273
|
controller.setMapCursor('grab');
|
|
501
|
-
}; //
|
|
502
|
-
// Hit Test & 상태 관리
|
|
503
|
-
// --------------------------------------------------------------------------
|
|
504
|
-
|
|
505
|
-
/**
|
|
506
|
-
* 특정 좌표의 마커 데이터 찾기 (Spatial Index 사용)
|
|
507
|
-
*
|
|
508
|
-
* 클릭/호버 이벤트 시 해당 위치에 있는 마커를 찾습니다.
|
|
509
|
-
* Spatial Hash Grid를 사용하여 O(1) 수준의 빠른 Hit Test를 수행합니다.
|
|
510
|
-
*
|
|
511
|
-
* @param offset 검사할 화면 좌표 (픽셀 단위)
|
|
512
|
-
* @returns 찾은 마커 데이터 또는 null (없으면)
|
|
513
|
-
*
|
|
514
|
-
* @remarks
|
|
515
|
-
* - **topOnHover 지원**: topOnHover가 true일 때 현재 hover된 항목을 최우선으로 체크
|
|
516
|
-
* - 시각적으로 최상단에 있는 항목이 hit test에서도 우선됨
|
|
517
|
-
* - **성능**: O(후보 항목 수) - 보통 ~10개 (30,000개 전체를 체크하지 않음)
|
|
518
|
-
* - Spatial Index를 통해 해당 위치 주변의 후보만 추출 후 정확한 Hit Test 수행
|
|
519
|
-
*
|
|
520
|
-
* @example
|
|
521
|
-
* ```typescript
|
|
522
|
-
* const clickedOffset = controller.positionToOffset(event.param.position);
|
|
523
|
-
* const data = findData(clickedOffset);
|
|
524
|
-
* if (data) {
|
|
525
|
-
* // 마커를 찾음
|
|
526
|
-
* }
|
|
527
|
-
* ```
|
|
528
|
-
*/
|
|
274
|
+
}; // Hit Test: 특정 좌표의 마커 찾기
|
|
529
275
|
|
|
530
276
|
|
|
531
277
|
var findData = function (offset) {
|
|
532
|
-
// topOnHover
|
|
278
|
+
// topOnHover 옵션이 켜져 있으면 hover된 항목을 최우선으로 확인
|
|
533
279
|
if (topOnHover && hoveredItemRef.current) {
|
|
534
280
|
var hovered = hoveredItemRef.current;
|
|
535
281
|
|
|
536
282
|
if (utils.isPointInMarkerData(offset, hovered, getOrComputeMarkerOffset)) {
|
|
537
|
-
return hovered;
|
|
283
|
+
return hovered;
|
|
538
284
|
}
|
|
539
|
-
} //
|
|
285
|
+
} // 공간 인덱스에서 후보 항목 조회 (O(1) 수준의 빠른 조회)
|
|
540
286
|
|
|
541
287
|
|
|
542
|
-
var candidates = spatialIndexRef.current.queryPoint(offset.x, offset.y); //
|
|
288
|
+
var candidates = spatialIndexRef.current.queryPoint(offset.x, offset.y); // 역순 순회: 나중에 추가된 항목(최상위)이 먼저 선택되도록
|
|
543
289
|
|
|
544
290
|
for (var i = candidates.length - 1; i >= 0; i--) {
|
|
545
291
|
var item = candidates[i];
|
|
@@ -550,18 +296,7 @@ var WoongCanvasMarker = function (props) {
|
|
|
550
296
|
}
|
|
551
297
|
|
|
552
298
|
return null;
|
|
553
|
-
};
|
|
554
|
-
/**
|
|
555
|
-
* Hover 상태 설정 및 레이어 렌더링
|
|
556
|
-
*
|
|
557
|
-
* @param data hover된 마커/폴리곤 데이터 또는 null
|
|
558
|
-
*
|
|
559
|
-
* 최적화: RAF 제거하여 즉시 렌더링 (16ms 지연 제거)
|
|
560
|
-
*
|
|
561
|
-
* 🎯 topOnHover 지원:
|
|
562
|
-
* - renderEvent가 있으면: Event Layer에서만 처리 (성능 최적화)
|
|
563
|
-
* - renderEvent가 없고 topOnHover=true면: Base Layer에서 처리
|
|
564
|
-
*/
|
|
299
|
+
}; // Hover 상태 설정 및 렌더링
|
|
565
300
|
|
|
566
301
|
|
|
567
302
|
var setHovered = function (data) {
|
|
@@ -571,38 +306,18 @@ var WoongCanvasMarker = function (props) {
|
|
|
571
306
|
controller.setMapCursor('grabbing');
|
|
572
307
|
} else {
|
|
573
308
|
controller.setMapCursor(data ? 'pointer' : 'grab');
|
|
574
|
-
}
|
|
575
|
-
|
|
309
|
+
}
|
|
576
310
|
|
|
577
311
|
if (renderEvent) {
|
|
578
|
-
// renderEvent가 있으면 Event Layer에서만 처리 (성능 최적화)
|
|
579
312
|
doRenderEvent();
|
|
580
313
|
} else if (topOnHover) {
|
|
581
|
-
// renderEvent가 없고 topOnHover가 true면 Base Layer에서 처리
|
|
582
314
|
doRenderBase();
|
|
583
315
|
}
|
|
584
|
-
};
|
|
585
|
-
/**
|
|
586
|
-
* 클릭 처리 (단일/다중 선택)
|
|
587
|
-
*
|
|
588
|
-
* 마커 클릭 시 선택 상태를 업데이트하고 렌더링을 수행합니다.
|
|
589
|
-
*
|
|
590
|
-
* @param data 클릭된 마커 데이터
|
|
591
|
-
*
|
|
592
|
-
* @remarks
|
|
593
|
-
* - **단일 선택**: 기존 선택 해제 후 새로 선택 (토글 가능)
|
|
594
|
-
* - **다중 선택**: enableMultiSelect가 true면 기존 선택 유지하며 추가/제거
|
|
595
|
-
* - **성능 최적화**:
|
|
596
|
-
* - 단일 Shape 렌더링으로 Base Layer 재렌더링 속도 향상
|
|
597
|
-
* - sceneFunc에서 selectedIds를 체크하여 선택된 마커만 스킵
|
|
598
|
-
* - 객체 생성 오버헤드 제거로 1,000개 이상도 부드럽게 처리
|
|
599
|
-
*/
|
|
316
|
+
}; // 클릭 처리: 선택 상태 업데이트
|
|
600
317
|
|
|
601
318
|
|
|
602
319
|
var handleLocalClick = function (data) {
|
|
603
|
-
// 1. 선택 상태 업데이트
|
|
604
320
|
if (enableMultiSelect) {
|
|
605
|
-
// 다중 선택: Set과 Map 동시 업데이트
|
|
606
321
|
var newSelected = new Set(selectedIdsRef.current);
|
|
607
322
|
|
|
608
323
|
if (newSelected.has(data.id)) {
|
|
@@ -615,7 +330,6 @@ var WoongCanvasMarker = function (props) {
|
|
|
615
330
|
|
|
616
331
|
selectedIdsRef.current = newSelected;
|
|
617
332
|
} else {
|
|
618
|
-
// 단일 선택: 토글
|
|
619
333
|
var newSelected = new Set();
|
|
620
334
|
|
|
621
335
|
if (!selectedIdsRef.current.has(data.id)) {
|
|
@@ -629,135 +343,80 @@ var WoongCanvasMarker = function (props) {
|
|
|
629
343
|
selectedIdsRef.current = newSelected;
|
|
630
344
|
}
|
|
631
345
|
|
|
632
|
-
|
|
633
|
-
// 2. Base Layer 재렌더링 (단일 Shape로 최적화되어 빠름)
|
|
634
|
-
doRenderBase(); // 3. Animation Layer 렌더링 (선택된 마커 애니메이션)
|
|
635
|
-
|
|
636
|
-
doRenderAnimation();
|
|
637
|
-
} // 4. Event Layer 렌더링 (hover 처리)
|
|
638
|
-
|
|
639
|
-
|
|
346
|
+
doRenderBase();
|
|
640
347
|
doRenderEvent();
|
|
641
|
-
}; //
|
|
642
|
-
// 이벤트 핸들러: UI 이벤트
|
|
643
|
-
// --------------------------------------------------------------------------
|
|
644
|
-
|
|
645
|
-
/**
|
|
646
|
-
* 클릭 이벤트 처리
|
|
647
|
-
*
|
|
648
|
-
* @param event 클릭 이벤트 파라미터
|
|
649
|
-
*
|
|
650
|
-
* @remarks
|
|
651
|
-
* - Context가 있으면 전역 이벤트 핸들러가 처리하므로 스킵
|
|
652
|
-
* - 상호작용이 비활성화되어 있으면 스킵
|
|
653
|
-
* - Spatial Index를 사용하여 빠른 Hit Test 수행
|
|
654
|
-
*/
|
|
348
|
+
}; // 클릭 이벤트 핸들러
|
|
655
349
|
|
|
656
350
|
|
|
657
351
|
var handleClick = function (event) {
|
|
658
|
-
if (disableInteractionRef.current) return;
|
|
659
|
-
|
|
352
|
+
if (disableInteractionRef.current) return;
|
|
660
353
|
var clickedOffset = helpers.validateEvent(event, context$1, controller);
|
|
661
354
|
if (!clickedOffset) return;
|
|
662
355
|
var data = findData(clickedOffset);
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
if (onClick) {
|
|
668
|
-
onClick(data, selectedIdsRef.current);
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
};
|
|
672
|
-
/**
|
|
673
|
-
* 마우스 이동 이벤트 처리 (hover 감지)
|
|
674
|
-
*
|
|
675
|
-
* @param event 마우스 이동 이벤트 파라미터
|
|
676
|
-
*
|
|
677
|
-
* @remarks
|
|
678
|
-
* - Context가 있으면 전역 이벤트 핸들러가 처리하므로 스킵
|
|
679
|
-
* - 상호작용이 비활성화되어 있으면 스킵
|
|
680
|
-
* - hover 상태 변경 시에만 렌더링 및 콜백 호출 (최적화)
|
|
681
|
-
*/
|
|
356
|
+
if (!data) return;
|
|
357
|
+
handleLocalClick(data);
|
|
358
|
+
onClick === null || onClick === void 0 ? void 0 : onClick(data, selectedIdsRef.current);
|
|
359
|
+
}; // 마우스 이동 이벤트 핸들러 (hover 감지)
|
|
682
360
|
|
|
683
361
|
|
|
684
362
|
var handleMouseMove = function (event) {
|
|
685
|
-
if (disableInteractionRef.current) return;
|
|
686
|
-
|
|
363
|
+
if (disableInteractionRef.current) return;
|
|
687
364
|
var mouseOffset = helpers.validateEvent(event, context$1, controller);
|
|
688
365
|
if (!mouseOffset) return;
|
|
689
366
|
var hoveredItem = findData(mouseOffset);
|
|
690
367
|
var prevHovered = hoveredItemRef.current;
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
}
|
|
697
|
-
};
|
|
698
|
-
/**
|
|
699
|
-
* 마우스가 canvas를 벗어날 때 hover cleanup
|
|
700
|
-
*/
|
|
368
|
+
if (prevHovered === hoveredItem) return;
|
|
369
|
+
setHovered(hoveredItem);
|
|
370
|
+
if (prevHovered) onMouseOut === null || onMouseOut === void 0 ? void 0 : onMouseOut(prevHovered);
|
|
371
|
+
if (hoveredItem) onMouseOver === null || onMouseOver === void 0 ? void 0 : onMouseOver(hoveredItem);
|
|
372
|
+
}; // 마우스가 맵 영역을 벗어날 때 hover 상태 초기화
|
|
701
373
|
|
|
702
374
|
|
|
703
375
|
var handleMouseLeave = function () {
|
|
704
|
-
if (disableInteractionRef.current) return;
|
|
705
|
-
|
|
376
|
+
if (disableInteractionRef.current) return;
|
|
706
377
|
var prevHovered = hoveredItemRef.current;
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
if (onMouseOut) {
|
|
714
|
-
onMouseOut(prevHovered);
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
}; // --------------------------------------------------------------------------
|
|
718
|
-
// Lifecycle: DOM 초기화
|
|
719
|
-
// --------------------------------------------------------------------------
|
|
378
|
+
if (!prevHovered) return;
|
|
379
|
+
hoveredItemRef.current = null;
|
|
380
|
+
controller.setMapCursor('grab');
|
|
381
|
+
doRenderEvent();
|
|
382
|
+
onMouseOut === null || onMouseOut === void 0 ? void 0 : onMouseOut(prevHovered);
|
|
383
|
+
}; // DOM 초기화
|
|
720
384
|
|
|
721
385
|
|
|
722
386
|
React.useEffect(function () {
|
|
723
387
|
divElement.style.width = 'fit-content';
|
|
724
388
|
return function () {
|
|
725
|
-
if (markerRef.current)
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
}
|
|
389
|
+
if (!markerRef.current) return;
|
|
390
|
+
controller.clearDrawable(markerRef.current);
|
|
391
|
+
markerRef.current = undefined;
|
|
729
392
|
};
|
|
730
|
-
}, []); //
|
|
731
|
-
// Lifecycle: 마커 생성/업데이트
|
|
732
|
-
// --------------------------------------------------------------------------
|
|
393
|
+
}, []); // 마커 생성/업데이트
|
|
733
394
|
|
|
734
395
|
React.useEffect(function () {
|
|
735
|
-
if (options)
|
|
736
|
-
|
|
396
|
+
if (!options) return;
|
|
397
|
+
var bounds = controller.getCurrBounds();
|
|
737
398
|
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
399
|
+
var markerOptions = tslib.__assign({
|
|
400
|
+
position: bounds.nw
|
|
401
|
+
}, options);
|
|
741
402
|
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
markerRef.current.element = divElement;
|
|
747
|
-
controller.createMarker(markerRef.current);
|
|
403
|
+
if (markerRef.current) {
|
|
404
|
+
controller.updateMarker(markerRef.current, markerOptions);
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
748
407
|
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
408
|
+
markerRef.current = new MapDrawables.Marker(markerOptions);
|
|
409
|
+
markerRef.current.element = divElement;
|
|
410
|
+
controller.createMarker(markerRef.current);
|
|
752
411
|
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
412
|
+
if (divElement.parentElement) {
|
|
413
|
+
divElement.parentElement.style.pointerEvents = 'none';
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (options.zIndex !== undefined) {
|
|
417
|
+
controller.setMarkerZIndex(markerRef.current, options.zIndex);
|
|
757
418
|
}
|
|
758
|
-
}, [options]); //
|
|
759
|
-
// Lifecycle: Konva 초기화 및 이벤트 리스너 등록
|
|
760
|
-
// --------------------------------------------------------------------------
|
|
419
|
+
}, [options]); // Konva 초기화 및 이벤트 리스너 등록
|
|
761
420
|
|
|
762
421
|
React.useEffect(function () {
|
|
763
422
|
var mapDiv = controller.mapDivElement;
|
|
@@ -766,34 +425,21 @@ var WoongCanvasMarker = function (props) {
|
|
|
766
425
|
width: mapDiv.offsetWidth,
|
|
767
426
|
height: mapDiv.offsetHeight
|
|
768
427
|
});
|
|
769
|
-
stageRef.current = stage;
|
|
770
|
-
|
|
428
|
+
stageRef.current = stage;
|
|
771
429
|
var baseLayer = new Konva__default["default"].Layer({
|
|
772
|
-
listening: false // 이벤트 리스닝 비활성화로 성능 향상
|
|
773
|
-
|
|
774
|
-
});
|
|
775
|
-
var animationLayer = new Konva__default["default"].Layer({
|
|
776
430
|
listening: false
|
|
777
431
|
});
|
|
778
432
|
var eventLayer = new Konva__default["default"].Layer({
|
|
779
433
|
listening: false
|
|
780
434
|
});
|
|
781
435
|
baseLayerRef.current = baseLayer;
|
|
782
|
-
animationLayerRef.current = animationLayer;
|
|
783
436
|
eventLayerRef.current = eventLayer;
|
|
784
437
|
stage.add(baseLayer);
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
stage.add(animationLayer);
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
stage.add(eventLayer); // 초기 뷰포트 설정
|
|
791
|
-
|
|
792
|
-
updateViewport(); // ResizeObserver (맵 크기 변경 감지)
|
|
438
|
+
stage.add(eventLayer);
|
|
439
|
+
updateViewport(); // ResizeObserver: 맵 크기 변경 감지 (RAF로 debounce)
|
|
793
440
|
|
|
794
441
|
var resizeRafId = null;
|
|
795
442
|
var resizeObserver = new ResizeObserver(function () {
|
|
796
|
-
// RAF로 다음 프레임에 한 번만 실행 (debounce 효과)
|
|
797
443
|
if (resizeRafId !== null) {
|
|
798
444
|
cancelAnimationFrame(resizeRafId);
|
|
799
445
|
}
|
|
@@ -816,10 +462,9 @@ var WoongCanvasMarker = function (props) {
|
|
|
816
462
|
controller.addEventListener('CLICK', handleClick);
|
|
817
463
|
controller.addEventListener('MOUSEMOVE', handleMouseMove);
|
|
818
464
|
controller.addEventListener('DRAGSTART', handleDragStart);
|
|
819
|
-
controller.addEventListener('DRAGEND', handleDragEnd);
|
|
820
|
-
|
|
465
|
+
controller.addEventListener('DRAGEND', handleDragEnd);
|
|
821
466
|
mapDiv.addEventListener('mouseleave', handleMouseLeave);
|
|
822
|
-
renderAllImmediate(); // Context 사용 시 컴포넌트 등록
|
|
467
|
+
renderAllImmediate(); // Context 사용 시 컴포넌트 등록
|
|
823
468
|
|
|
824
469
|
var componentInstance = null;
|
|
825
470
|
|
|
@@ -840,22 +485,17 @@ var WoongCanvasMarker = function (props) {
|
|
|
840
485
|
},
|
|
841
486
|
isInteractionDisabled: function () {
|
|
842
487
|
return disableInteractionRef.current;
|
|
843
|
-
}
|
|
844
|
-
|
|
488
|
+
}
|
|
845
489
|
};
|
|
846
490
|
context$1.registerComponent(componentInstance);
|
|
847
|
-
}
|
|
848
|
-
|
|
491
|
+
}
|
|
849
492
|
|
|
850
493
|
return function () {
|
|
851
|
-
// RAF 정리
|
|
852
494
|
if (resizeRafId !== null) {
|
|
853
495
|
cancelAnimationFrame(resizeRafId);
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
resizeObserver.disconnect(); // 이벤트 리스너 정리
|
|
496
|
+
}
|
|
858
497
|
|
|
498
|
+
resizeObserver.disconnect();
|
|
859
499
|
controller.removeEventListener('IDLE', handleIdle);
|
|
860
500
|
controller.removeEventListener('ZOOMSTART', handleZoomStart);
|
|
861
501
|
controller.removeEventListener('ZOOM_CHANGED', handleZoomEnd);
|
|
@@ -864,59 +504,41 @@ var WoongCanvasMarker = function (props) {
|
|
|
864
504
|
controller.removeEventListener('MOUSEMOVE', handleMouseMove);
|
|
865
505
|
controller.removeEventListener('DRAGSTART', handleDragStart);
|
|
866
506
|
controller.removeEventListener('DRAGEND', handleDragEnd);
|
|
867
|
-
mapDiv.removeEventListener('mouseleave', handleMouseLeave);
|
|
507
|
+
mapDiv.removeEventListener('mouseleave', handleMouseLeave);
|
|
868
508
|
|
|
869
509
|
if (context$1 && componentInstance) {
|
|
870
510
|
context$1.unregisterComponent(componentInstance);
|
|
871
|
-
}
|
|
872
|
-
|
|
511
|
+
}
|
|
873
512
|
|
|
874
513
|
baseLayer.destroyChildren();
|
|
875
|
-
animationLayer.destroyChildren();
|
|
876
514
|
eventLayer.destroyChildren();
|
|
877
|
-
stage.destroy();
|
|
878
|
-
|
|
515
|
+
stage.destroy();
|
|
879
516
|
offsetCacheRef.current.clear();
|
|
880
517
|
boundingBoxCacheRef.current.clear();
|
|
881
518
|
spatialIndexRef.current.clear();
|
|
882
519
|
};
|
|
883
|
-
}, []); //
|
|
884
|
-
// --------------------------------------------------------------------------
|
|
885
|
-
// Lifecycle: disableInteraction 동기화
|
|
886
|
-
// --------------------------------------------------------------------------
|
|
520
|
+
}, []); // disableInteraction 동기화
|
|
887
521
|
|
|
888
522
|
React.useEffect(function () {
|
|
889
523
|
disableInteractionRef.current = disableInteraction;
|
|
890
|
-
}, [disableInteraction]); //
|
|
891
|
-
// Lifecycle: 외부 selectedItems 동기화
|
|
892
|
-
// --------------------------------------------------------------------------
|
|
524
|
+
}, [disableInteraction]); // 외부 selectedItems 동기화
|
|
893
525
|
|
|
894
526
|
React.useEffect(function () {
|
|
895
527
|
if (!stageRef.current) return;
|
|
896
|
-
hooks.syncExternalSelectedItems(externalSelectedItems, selectedIdsRef, selectedItemsMapRef);
|
|
897
|
-
|
|
528
|
+
hooks.syncExternalSelectedItems(externalSelectedItems, selectedIdsRef, selectedItemsMapRef);
|
|
898
529
|
doRenderBase();
|
|
899
|
-
doRenderAnimation();
|
|
900
530
|
doRenderEvent();
|
|
901
|
-
}, [externalSelectedItems]); //
|
|
902
|
-
// --------------------------------------------------------------------------
|
|
903
|
-
// Lifecycle: 외부 selectedItem 변경 시 Event Layer 리렌더링
|
|
904
|
-
// --------------------------------------------------------------------------
|
|
531
|
+
}, [externalSelectedItems]); // 외부 selectedItem 변경 시 Event Layer 리렌더링
|
|
905
532
|
|
|
906
533
|
React.useEffect(function () {
|
|
907
|
-
if (!stageRef.current) return;
|
|
908
|
-
|
|
909
|
-
selectedItemRef.current = externalSelectedItem; // selectedItem이 변경되면 Event Layer만 다시 그림
|
|
910
|
-
|
|
534
|
+
if (!stageRef.current) return;
|
|
535
|
+
selectedItemRef.current = externalSelectedItem;
|
|
911
536
|
doRenderEvent();
|
|
912
|
-
}, [externalSelectedItem]); //
|
|
913
|
-
// Lifecycle: 데이터 변경 시 렌더링
|
|
914
|
-
// --------------------------------------------------------------------------
|
|
537
|
+
}, [externalSelectedItem]); // 데이터 변경 시 렌더링 (캐시 정리 및 선택 상태 동기화)
|
|
915
538
|
|
|
916
539
|
React.useEffect(function () {
|
|
917
|
-
if (!stageRef.current) return;
|
|
918
|
-
|
|
919
|
-
dataRef.current = data; // 데이터 변경 시 즉시 transform 제거 및 캐시 정리 (겹침 방지)
|
|
540
|
+
if (!stageRef.current) return;
|
|
541
|
+
dataRef.current = data;
|
|
920
542
|
|
|
921
543
|
if (containerRef.current) {
|
|
922
544
|
containerRef.current.style.transform = '';
|
|
@@ -926,26 +548,10 @@ var WoongCanvasMarker = function (props) {
|
|
|
926
548
|
accumTranslateRef.current = {
|
|
927
549
|
x: 0,
|
|
928
550
|
y: 0
|
|
929
|
-
};
|
|
930
|
-
|
|
551
|
+
};
|
|
931
552
|
offsetCacheRef.current.clear();
|
|
932
553
|
boundingBoxCacheRef.current.clear();
|
|
933
|
-
|
|
934
|
-
* 선택 상태 동기화 (최적화 버전)
|
|
935
|
-
*
|
|
936
|
-
* data가 변경되면 selectedItemsMapRef도 업데이트 필요
|
|
937
|
-
* (참조가 바뀌므로 기존 Map의 데이터는 stale 상태)
|
|
938
|
-
*
|
|
939
|
-
* 🔥 중요: 화면 밖 데이터도 선택 상태 유지!
|
|
940
|
-
* - 현재 data에 있으면 최신 데이터로 업데이트
|
|
941
|
-
* - 없으면 기존 selectedItemsMapRef의 데이터 유지
|
|
942
|
-
*
|
|
943
|
-
* 최적화: data를 Map으로 먼저 변환하여 find() 순회 제거
|
|
944
|
-
* - O(전체 데이터 수 + 선택된 개수) - 매우 효율적
|
|
945
|
-
*/
|
|
946
|
-
|
|
947
|
-
selectedItemsMapRef.current = hooks.syncSelectedItems(data, selectedIdsRef.current, selectedItemsMapRef.current); // 즉시 렌더링
|
|
948
|
-
|
|
554
|
+
selectedItemsMapRef.current = hooks.syncSelectedItems(data, selectedIdsRef.current, selectedItemsMapRef.current);
|
|
949
555
|
renderAllImmediate();
|
|
950
556
|
}, [data]);
|
|
951
557
|
return reactDom.createPortal(React__default["default"].createElement("div", {
|