@mint-ui/map 1.2.0-test.35 → 1.2.0-test.37
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 +22 -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 +46 -152
- 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 +128 -519
- 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 +419 -1637
- package/dist/index.umd.js +419 -1637
- 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,91 @@ 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;
|
|
104
|
+
var tailHeight = item.tailHeight || 0;
|
|
105
|
+
var offsetX = item.offsetX || 0;
|
|
106
|
+
var offsetY = item.offsetY || 0; // 오프셋을 적용한 마커 중심점 기준으로 바운딩 박스 계산
|
|
217
107
|
|
|
218
108
|
return {
|
|
219
|
-
minX: offset.x - boxWidth / 2,
|
|
220
|
-
minY: offset.y - boxHeight - tailHeight,
|
|
221
|
-
maxX: offset.x + boxWidth / 2,
|
|
222
|
-
maxY: offset.y
|
|
109
|
+
minX: offset.x + offsetX - boxWidth / 2,
|
|
110
|
+
minY: offset.y + offsetY - boxHeight - tailHeight,
|
|
111
|
+
maxX: offset.x + offsetX + boxWidth / 2,
|
|
112
|
+
maxY: offset.y + offsetY
|
|
223
113
|
};
|
|
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
|
-
*/
|
|
114
|
+
}; // 공간 인덱스 빌드 (빠른 Hit Test용)
|
|
239
115
|
|
|
240
116
|
|
|
241
117
|
var buildSpatialIndex = function () {
|
|
242
118
|
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
|
-
*/
|
|
119
|
+
}; // 렌더링 유틸리티 객체
|
|
257
120
|
|
|
258
121
|
|
|
259
122
|
var renderUtils = {
|
|
@@ -261,48 +124,29 @@ var WoongCanvasMarker = function (props) {
|
|
|
261
124
|
return null;
|
|
262
125
|
},
|
|
263
126
|
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
|
-
*/
|
|
127
|
+
}; // Base Layer 렌더링 (뷰포트 컬링 적용, 선택된 마커 제외)
|
|
280
128
|
|
|
281
129
|
var doRenderBase = function () {
|
|
282
130
|
var layer = baseLayerRef.current;
|
|
283
|
-
if (!layer) return;
|
|
284
|
-
|
|
131
|
+
if (!layer) return;
|
|
285
132
|
var shape = layer.findOne('.base-render-shape');
|
|
286
133
|
|
|
287
134
|
if (!shape) {
|
|
288
|
-
// 최초 생성 (한 번만 실행됨)
|
|
289
|
-
// sceneFunc도 여기서 한 번만 설정 (클로저로 최신 데이터 참조)
|
|
290
135
|
shape = new Konva__default["default"].Shape({
|
|
291
136
|
name: 'base-render-shape',
|
|
292
137
|
sceneFunc: function (context, shape) {
|
|
293
138
|
var ctx = context;
|
|
294
|
-
var hovered = hoveredItemRef.current; //
|
|
139
|
+
var hovered = hoveredItemRef.current; // 뷰포트 컬링: 화면에 보이는 항목만 필터링
|
|
295
140
|
|
|
296
|
-
var visibleItems =
|
|
141
|
+
var visibleItems = dataRef.current.filter(function (item) {
|
|
297
142
|
return isInViewport(item);
|
|
298
|
-
})
|
|
143
|
+
}); // topOnHover 옵션: hover된 항목을 나중에 그려서 최상위에 표시
|
|
299
144
|
|
|
300
145
|
if (topOnHover && !renderEvent && hovered) {
|
|
301
|
-
// hover된 항목 제외하고 렌더링
|
|
302
146
|
visibleItems = visibleItems.filter(function (item) {
|
|
303
147
|
return item.id !== hovered.id;
|
|
304
148
|
});
|
|
305
|
-
} // 일반
|
|
149
|
+
} // 일반 항목들 먼저 렌더링
|
|
306
150
|
|
|
307
151
|
|
|
308
152
|
renderBase({
|
|
@@ -311,12 +155,10 @@ var WoongCanvasMarker = function (props) {
|
|
|
311
155
|
selectedIds: selectedIdsRef.current,
|
|
312
156
|
hoveredItem: hovered,
|
|
313
157
|
utils: renderUtils
|
|
314
|
-
}); // hover된 항목을
|
|
158
|
+
}); // hover된 항목을 마지막에 렌더링하여 최상위에 표시
|
|
315
159
|
|
|
316
160
|
if (topOnHover && !renderEvent && hovered) {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
if (isHoveredInViewport) {
|
|
161
|
+
if (isInViewport(hovered)) {
|
|
320
162
|
renderBase({
|
|
321
163
|
ctx: ctx,
|
|
322
164
|
items: [hovered],
|
|
@@ -332,72 +174,26 @@ var WoongCanvasMarker = function (props) {
|
|
|
332
174
|
hitStrokeWidth: 0
|
|
333
175
|
});
|
|
334
176
|
layer.add(shape);
|
|
335
|
-
}
|
|
336
|
-
|
|
177
|
+
}
|
|
337
178
|
|
|
338
179
|
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
|
-
*/
|
|
180
|
+
}; // Event Layer 렌더링 (hover 효과 및 선택 상태 표시)
|
|
378
181
|
|
|
379
182
|
|
|
380
183
|
var doRenderEvent = function () {
|
|
381
184
|
var layer = eventLayerRef.current;
|
|
382
|
-
if (!layer) return;
|
|
383
|
-
if (!renderEvent) return; // 🔥 Shape 재사용: 이미 존재하면 재사용, 없으면 생성
|
|
384
|
-
|
|
185
|
+
if (!layer || !renderEvent) return;
|
|
385
186
|
var shape = layer.findOne('.event-render-shape');
|
|
386
187
|
|
|
387
188
|
if (!shape) {
|
|
388
|
-
// 최초 생성 (한 번만 실행됨)
|
|
389
|
-
// sceneFunc도 여기서 한 번만 설정 (클로저로 최신 데이터 참조)
|
|
390
189
|
shape = new Konva__default["default"].Shape({
|
|
391
190
|
name: 'event-render-shape',
|
|
392
191
|
sceneFunc: function (context, shape) {
|
|
393
|
-
var ctx = context;
|
|
394
|
-
// 성능 최적화: Array.from 대신 직접 변환 (메모리 할당 최소화)
|
|
395
|
-
|
|
192
|
+
var ctx = context;
|
|
396
193
|
var selectedItems = helpers.mapValuesToArray(selectedItemsMapRef.current);
|
|
397
|
-
var hovered = hoveredItemRef.current;
|
|
194
|
+
var hovered = hoveredItemRef.current;
|
|
398
195
|
|
|
399
196
|
if (topOnHover && hovered) {
|
|
400
|
-
// 1. 먼저 일반 항목들 렌더링 (hover된 항목 제외)
|
|
401
197
|
renderEvent({
|
|
402
198
|
ctx: ctx,
|
|
403
199
|
hoveredItem: null,
|
|
@@ -406,13 +202,9 @@ var WoongCanvasMarker = function (props) {
|
|
|
406
202
|
return item.id !== hovered.id;
|
|
407
203
|
}),
|
|
408
204
|
selectedItem: selectedItemRef.current
|
|
409
|
-
});
|
|
410
|
-
|
|
411
|
-
var isHoveredInViewport = enableViewportCulling ? isInViewport(hovered) : true;
|
|
205
|
+
});
|
|
412
206
|
|
|
413
|
-
if (
|
|
414
|
-
// hover된 항목이 선택되어 있다면 hoverSelectedItems에 포함시켜서
|
|
415
|
-
// renderEvent에서 hover 스타일만 적용되도록 함
|
|
207
|
+
if (isInViewport(hovered)) {
|
|
416
208
|
var hoveredIsSelected = selectedItems.some(function (item) {
|
|
417
209
|
return item.id === hovered.id;
|
|
418
210
|
});
|
|
@@ -426,7 +218,6 @@ var WoongCanvasMarker = function (props) {
|
|
|
426
218
|
});
|
|
427
219
|
}
|
|
428
220
|
} else {
|
|
429
|
-
// topOnHover가 false이거나 hover된 항목이 없으면 일반 렌더링
|
|
430
221
|
renderEvent({
|
|
431
222
|
ctx: ctx,
|
|
432
223
|
hoveredItem: hovered,
|
|
@@ -441,28 +232,21 @@ var WoongCanvasMarker = function (props) {
|
|
|
441
232
|
hitStrokeWidth: 0
|
|
442
233
|
});
|
|
443
234
|
layer.add(shape);
|
|
444
|
-
}
|
|
445
|
-
|
|
235
|
+
}
|
|
446
236
|
|
|
447
237
|
layer.batchDraw();
|
|
448
|
-
};
|
|
449
|
-
/**
|
|
450
|
-
* 전체 즉시 렌더링 (IDLE 시 호출)
|
|
451
|
-
*/
|
|
238
|
+
}; // 전체 즉시 렌더링
|
|
452
239
|
|
|
453
240
|
|
|
454
241
|
var renderAllImmediate = function () {
|
|
455
242
|
updateViewport();
|
|
456
243
|
buildSpatialIndex();
|
|
457
244
|
doRenderBase();
|
|
458
|
-
doRenderAnimation();
|
|
459
245
|
doRenderEvent();
|
|
460
|
-
}; //
|
|
461
|
-
// 이벤트 핸들러: 지도 이벤트
|
|
462
|
-
// --------------------------------------------------------------------------
|
|
246
|
+
}; // 지도 이벤트 핸들러 생성
|
|
463
247
|
|
|
464
248
|
|
|
465
|
-
var
|
|
249
|
+
var _f = hooks.createMapEventHandlers({
|
|
466
250
|
controller: controller,
|
|
467
251
|
containerRef: containerRef,
|
|
468
252
|
markerRef: markerRef,
|
|
@@ -473,73 +257,38 @@ var WoongCanvasMarker = function (props) {
|
|
|
473
257
|
boundingBoxCacheRef: boundingBoxCacheRef,
|
|
474
258
|
renderAllImmediate: renderAllImmediate
|
|
475
259
|
}),
|
|
476
|
-
handleIdle =
|
|
477
|
-
handleZoomStart =
|
|
478
|
-
handleZoomEnd =
|
|
479
|
-
handleCenterChanged =
|
|
480
|
-
handleDragStartShared =
|
|
481
|
-
handleDragEndShared =
|
|
482
|
-
/**
|
|
483
|
-
* 드래그 시작 처리 (커서를 grabbing으로 변경)
|
|
484
|
-
*/
|
|
485
|
-
|
|
260
|
+
handleIdle = _f.handleIdle,
|
|
261
|
+
handleZoomStart = _f.handleZoomStart,
|
|
262
|
+
handleZoomEnd = _f.handleZoomEnd,
|
|
263
|
+
handleCenterChanged = _f.handleCenterChanged,
|
|
264
|
+
handleDragStartShared = _f.handleDragStart,
|
|
265
|
+
handleDragEndShared = _f.handleDragEnd;
|
|
486
266
|
|
|
487
267
|
var handleDragStart = function () {
|
|
488
268
|
handleDragStartShared();
|
|
489
269
|
draggingRef.current = true;
|
|
490
270
|
controller.setMapCursor('grabbing');
|
|
491
271
|
};
|
|
492
|
-
/**
|
|
493
|
-
* 드래그 종료 처리 (커서를 기본으로 복원)
|
|
494
|
-
*/
|
|
495
|
-
|
|
496
272
|
|
|
497
273
|
var handleDragEnd = function () {
|
|
498
274
|
handleDragEndShared();
|
|
499
275
|
draggingRef.current = false;
|
|
500
276
|
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
|
-
*/
|
|
277
|
+
}; // Hit Test: 특정 좌표의 마커 찾기
|
|
529
278
|
|
|
530
279
|
|
|
531
280
|
var findData = function (offset) {
|
|
532
|
-
// topOnHover
|
|
281
|
+
// topOnHover 옵션이 켜져 있으면 hover된 항목을 최우선으로 확인
|
|
533
282
|
if (topOnHover && hoveredItemRef.current) {
|
|
534
283
|
var hovered = hoveredItemRef.current;
|
|
535
284
|
|
|
536
285
|
if (utils.isPointInMarkerData(offset, hovered, getOrComputeMarkerOffset)) {
|
|
537
|
-
return hovered;
|
|
286
|
+
return hovered;
|
|
538
287
|
}
|
|
539
|
-
} //
|
|
288
|
+
} // 공간 인덱스에서 후보 항목 조회 (O(1) 수준의 빠른 조회)
|
|
540
289
|
|
|
541
290
|
|
|
542
|
-
var candidates = spatialIndexRef.current.queryPoint(offset.x, offset.y); //
|
|
291
|
+
var candidates = spatialIndexRef.current.queryPoint(offset.x, offset.y); // 역순 순회: 나중에 추가된 항목(최상위)이 먼저 선택되도록
|
|
543
292
|
|
|
544
293
|
for (var i = candidates.length - 1; i >= 0; i--) {
|
|
545
294
|
var item = candidates[i];
|
|
@@ -550,18 +299,7 @@ var WoongCanvasMarker = function (props) {
|
|
|
550
299
|
}
|
|
551
300
|
|
|
552
301
|
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
|
-
*/
|
|
302
|
+
}; // Hover 상태 설정 및 렌더링
|
|
565
303
|
|
|
566
304
|
|
|
567
305
|
var setHovered = function (data) {
|
|
@@ -571,38 +309,18 @@ var WoongCanvasMarker = function (props) {
|
|
|
571
309
|
controller.setMapCursor('grabbing');
|
|
572
310
|
} else {
|
|
573
311
|
controller.setMapCursor(data ? 'pointer' : 'grab');
|
|
574
|
-
}
|
|
575
|
-
|
|
312
|
+
}
|
|
576
313
|
|
|
577
314
|
if (renderEvent) {
|
|
578
|
-
// renderEvent가 있으면 Event Layer에서만 처리 (성능 최적화)
|
|
579
315
|
doRenderEvent();
|
|
580
316
|
} else if (topOnHover) {
|
|
581
|
-
// renderEvent가 없고 topOnHover가 true면 Base Layer에서 처리
|
|
582
317
|
doRenderBase();
|
|
583
318
|
}
|
|
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
|
-
*/
|
|
319
|
+
}; // 클릭 처리: 선택 상태 업데이트
|
|
600
320
|
|
|
601
321
|
|
|
602
322
|
var handleLocalClick = function (data) {
|
|
603
|
-
// 1. 선택 상태 업데이트
|
|
604
323
|
if (enableMultiSelect) {
|
|
605
|
-
// 다중 선택: Set과 Map 동시 업데이트
|
|
606
324
|
var newSelected = new Set(selectedIdsRef.current);
|
|
607
325
|
|
|
608
326
|
if (newSelected.has(data.id)) {
|
|
@@ -615,7 +333,6 @@ var WoongCanvasMarker = function (props) {
|
|
|
615
333
|
|
|
616
334
|
selectedIdsRef.current = newSelected;
|
|
617
335
|
} else {
|
|
618
|
-
// 단일 선택: 토글
|
|
619
336
|
var newSelected = new Set();
|
|
620
337
|
|
|
621
338
|
if (!selectedIdsRef.current.has(data.id)) {
|
|
@@ -629,135 +346,80 @@ var WoongCanvasMarker = function (props) {
|
|
|
629
346
|
selectedIdsRef.current = newSelected;
|
|
630
347
|
}
|
|
631
348
|
|
|
632
|
-
|
|
633
|
-
// 2. Base Layer 재렌더링 (단일 Shape로 최적화되어 빠름)
|
|
634
|
-
doRenderBase(); // 3. Animation Layer 렌더링 (선택된 마커 애니메이션)
|
|
635
|
-
|
|
636
|
-
doRenderAnimation();
|
|
637
|
-
} // 4. Event Layer 렌더링 (hover 처리)
|
|
638
|
-
|
|
639
|
-
|
|
349
|
+
doRenderBase();
|
|
640
350
|
doRenderEvent();
|
|
641
|
-
}; //
|
|
642
|
-
// 이벤트 핸들러: UI 이벤트
|
|
643
|
-
// --------------------------------------------------------------------------
|
|
644
|
-
|
|
645
|
-
/**
|
|
646
|
-
* 클릭 이벤트 처리
|
|
647
|
-
*
|
|
648
|
-
* @param event 클릭 이벤트 파라미터
|
|
649
|
-
*
|
|
650
|
-
* @remarks
|
|
651
|
-
* - Context가 있으면 전역 이벤트 핸들러가 처리하므로 스킵
|
|
652
|
-
* - 상호작용이 비활성화되어 있으면 스킵
|
|
653
|
-
* - Spatial Index를 사용하여 빠른 Hit Test 수행
|
|
654
|
-
*/
|
|
351
|
+
}; // 클릭 이벤트 핸들러
|
|
655
352
|
|
|
656
353
|
|
|
657
354
|
var handleClick = function (event) {
|
|
658
|
-
if (disableInteractionRef.current) return;
|
|
659
|
-
|
|
355
|
+
if (disableInteractionRef.current) return;
|
|
660
356
|
var clickedOffset = helpers.validateEvent(event, context$1, controller);
|
|
661
357
|
if (!clickedOffset) return;
|
|
662
358
|
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
|
-
*/
|
|
359
|
+
if (!data) return;
|
|
360
|
+
handleLocalClick(data);
|
|
361
|
+
onClick === null || onClick === void 0 ? void 0 : onClick(data, selectedIdsRef.current);
|
|
362
|
+
}; // 마우스 이동 이벤트 핸들러 (hover 감지)
|
|
682
363
|
|
|
683
364
|
|
|
684
365
|
var handleMouseMove = function (event) {
|
|
685
|
-
if (disableInteractionRef.current) return;
|
|
686
|
-
|
|
366
|
+
if (disableInteractionRef.current) return;
|
|
687
367
|
var mouseOffset = helpers.validateEvent(event, context$1, controller);
|
|
688
368
|
if (!mouseOffset) return;
|
|
689
369
|
var hoveredItem = findData(mouseOffset);
|
|
690
370
|
var prevHovered = hoveredItemRef.current;
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
}
|
|
697
|
-
};
|
|
698
|
-
/**
|
|
699
|
-
* 마우스가 canvas를 벗어날 때 hover cleanup
|
|
700
|
-
*/
|
|
371
|
+
if (prevHovered === hoveredItem) return;
|
|
372
|
+
setHovered(hoveredItem);
|
|
373
|
+
if (prevHovered) onMouseOut === null || onMouseOut === void 0 ? void 0 : onMouseOut(prevHovered);
|
|
374
|
+
if (hoveredItem) onMouseOver === null || onMouseOver === void 0 ? void 0 : onMouseOver(hoveredItem);
|
|
375
|
+
}; // 마우스가 맵 영역을 벗어날 때 hover 상태 초기화
|
|
701
376
|
|
|
702
377
|
|
|
703
378
|
var handleMouseLeave = function () {
|
|
704
|
-
if (disableInteractionRef.current) return;
|
|
705
|
-
|
|
379
|
+
if (disableInteractionRef.current) return;
|
|
706
380
|
var prevHovered = hoveredItemRef.current;
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
if (onMouseOut) {
|
|
714
|
-
onMouseOut(prevHovered);
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
}; // --------------------------------------------------------------------------
|
|
718
|
-
// Lifecycle: DOM 초기화
|
|
719
|
-
// --------------------------------------------------------------------------
|
|
381
|
+
if (!prevHovered) return;
|
|
382
|
+
hoveredItemRef.current = null;
|
|
383
|
+
controller.setMapCursor('grab');
|
|
384
|
+
doRenderEvent();
|
|
385
|
+
onMouseOut === null || onMouseOut === void 0 ? void 0 : onMouseOut(prevHovered);
|
|
386
|
+
}; // DOM 초기화
|
|
720
387
|
|
|
721
388
|
|
|
722
389
|
React.useEffect(function () {
|
|
723
390
|
divElement.style.width = 'fit-content';
|
|
724
391
|
return function () {
|
|
725
|
-
if (markerRef.current)
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
}
|
|
392
|
+
if (!markerRef.current) return;
|
|
393
|
+
controller.clearDrawable(markerRef.current);
|
|
394
|
+
markerRef.current = undefined;
|
|
729
395
|
};
|
|
730
|
-
}, []); //
|
|
731
|
-
// Lifecycle: 마커 생성/업데이트
|
|
732
|
-
// --------------------------------------------------------------------------
|
|
396
|
+
}, []); // 마커 생성/업데이트
|
|
733
397
|
|
|
734
398
|
React.useEffect(function () {
|
|
735
|
-
if (options)
|
|
736
|
-
|
|
399
|
+
if (!options) return;
|
|
400
|
+
var bounds = controller.getCurrBounds();
|
|
737
401
|
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
402
|
+
var markerOptions = tslib.__assign({
|
|
403
|
+
position: bounds.nw
|
|
404
|
+
}, options);
|
|
741
405
|
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
markerRef.current.element = divElement;
|
|
747
|
-
controller.createMarker(markerRef.current);
|
|
406
|
+
if (markerRef.current) {
|
|
407
|
+
controller.updateMarker(markerRef.current, markerOptions);
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
748
410
|
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
411
|
+
markerRef.current = new MapDrawables.Marker(markerOptions);
|
|
412
|
+
markerRef.current.element = divElement;
|
|
413
|
+
controller.createMarker(markerRef.current);
|
|
752
414
|
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
415
|
+
if (divElement.parentElement) {
|
|
416
|
+
divElement.parentElement.style.pointerEvents = 'none';
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if (options.zIndex !== undefined) {
|
|
420
|
+
controller.setMarkerZIndex(markerRef.current, options.zIndex);
|
|
757
421
|
}
|
|
758
|
-
}, [options]); //
|
|
759
|
-
// Lifecycle: Konva 초기화 및 이벤트 리스너 등록
|
|
760
|
-
// --------------------------------------------------------------------------
|
|
422
|
+
}, [options]); // Konva 초기화 및 이벤트 리스너 등록
|
|
761
423
|
|
|
762
424
|
React.useEffect(function () {
|
|
763
425
|
var mapDiv = controller.mapDivElement;
|
|
@@ -766,34 +428,21 @@ var WoongCanvasMarker = function (props) {
|
|
|
766
428
|
width: mapDiv.offsetWidth,
|
|
767
429
|
height: mapDiv.offsetHeight
|
|
768
430
|
});
|
|
769
|
-
stageRef.current = stage;
|
|
770
|
-
|
|
431
|
+
stageRef.current = stage;
|
|
771
432
|
var baseLayer = new Konva__default["default"].Layer({
|
|
772
|
-
listening: false // 이벤트 리스닝 비활성화로 성능 향상
|
|
773
|
-
|
|
774
|
-
});
|
|
775
|
-
var animationLayer = new Konva__default["default"].Layer({
|
|
776
433
|
listening: false
|
|
777
434
|
});
|
|
778
435
|
var eventLayer = new Konva__default["default"].Layer({
|
|
779
436
|
listening: false
|
|
780
437
|
});
|
|
781
438
|
baseLayerRef.current = baseLayer;
|
|
782
|
-
animationLayerRef.current = animationLayer;
|
|
783
439
|
eventLayerRef.current = eventLayer;
|
|
784
440
|
stage.add(baseLayer);
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
stage.add(animationLayer);
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
stage.add(eventLayer); // 초기 뷰포트 설정
|
|
791
|
-
|
|
792
|
-
updateViewport(); // ResizeObserver (맵 크기 변경 감지)
|
|
441
|
+
stage.add(eventLayer);
|
|
442
|
+
updateViewport(); // ResizeObserver: 맵 크기 변경 감지 (RAF로 debounce)
|
|
793
443
|
|
|
794
444
|
var resizeRafId = null;
|
|
795
445
|
var resizeObserver = new ResizeObserver(function () {
|
|
796
|
-
// RAF로 다음 프레임에 한 번만 실행 (debounce 효과)
|
|
797
446
|
if (resizeRafId !== null) {
|
|
798
447
|
cancelAnimationFrame(resizeRafId);
|
|
799
448
|
}
|
|
@@ -816,10 +465,9 @@ var WoongCanvasMarker = function (props) {
|
|
|
816
465
|
controller.addEventListener('CLICK', handleClick);
|
|
817
466
|
controller.addEventListener('MOUSEMOVE', handleMouseMove);
|
|
818
467
|
controller.addEventListener('DRAGSTART', handleDragStart);
|
|
819
|
-
controller.addEventListener('DRAGEND', handleDragEnd);
|
|
820
|
-
|
|
468
|
+
controller.addEventListener('DRAGEND', handleDragEnd);
|
|
821
469
|
mapDiv.addEventListener('mouseleave', handleMouseLeave);
|
|
822
|
-
renderAllImmediate(); // Context 사용 시 컴포넌트 등록
|
|
470
|
+
renderAllImmediate(); // Context 사용 시 컴포넌트 등록
|
|
823
471
|
|
|
824
472
|
var componentInstance = null;
|
|
825
473
|
|
|
@@ -840,22 +488,17 @@ var WoongCanvasMarker = function (props) {
|
|
|
840
488
|
},
|
|
841
489
|
isInteractionDisabled: function () {
|
|
842
490
|
return disableInteractionRef.current;
|
|
843
|
-
}
|
|
844
|
-
|
|
491
|
+
}
|
|
845
492
|
};
|
|
846
493
|
context$1.registerComponent(componentInstance);
|
|
847
|
-
}
|
|
848
|
-
|
|
494
|
+
}
|
|
849
495
|
|
|
850
496
|
return function () {
|
|
851
|
-
// RAF 정리
|
|
852
497
|
if (resizeRafId !== null) {
|
|
853
498
|
cancelAnimationFrame(resizeRafId);
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
resizeObserver.disconnect(); // 이벤트 리스너 정리
|
|
499
|
+
}
|
|
858
500
|
|
|
501
|
+
resizeObserver.disconnect();
|
|
859
502
|
controller.removeEventListener('IDLE', handleIdle);
|
|
860
503
|
controller.removeEventListener('ZOOMSTART', handleZoomStart);
|
|
861
504
|
controller.removeEventListener('ZOOM_CHANGED', handleZoomEnd);
|
|
@@ -864,59 +507,41 @@ var WoongCanvasMarker = function (props) {
|
|
|
864
507
|
controller.removeEventListener('MOUSEMOVE', handleMouseMove);
|
|
865
508
|
controller.removeEventListener('DRAGSTART', handleDragStart);
|
|
866
509
|
controller.removeEventListener('DRAGEND', handleDragEnd);
|
|
867
|
-
mapDiv.removeEventListener('mouseleave', handleMouseLeave);
|
|
510
|
+
mapDiv.removeEventListener('mouseleave', handleMouseLeave);
|
|
868
511
|
|
|
869
512
|
if (context$1 && componentInstance) {
|
|
870
513
|
context$1.unregisterComponent(componentInstance);
|
|
871
|
-
}
|
|
872
|
-
|
|
514
|
+
}
|
|
873
515
|
|
|
874
516
|
baseLayer.destroyChildren();
|
|
875
|
-
animationLayer.destroyChildren();
|
|
876
517
|
eventLayer.destroyChildren();
|
|
877
|
-
stage.destroy();
|
|
878
|
-
|
|
518
|
+
stage.destroy();
|
|
879
519
|
offsetCacheRef.current.clear();
|
|
880
520
|
boundingBoxCacheRef.current.clear();
|
|
881
521
|
spatialIndexRef.current.clear();
|
|
882
522
|
};
|
|
883
|
-
}, []); //
|
|
884
|
-
// --------------------------------------------------------------------------
|
|
885
|
-
// Lifecycle: disableInteraction 동기화
|
|
886
|
-
// --------------------------------------------------------------------------
|
|
523
|
+
}, []); // disableInteraction 동기화
|
|
887
524
|
|
|
888
525
|
React.useEffect(function () {
|
|
889
526
|
disableInteractionRef.current = disableInteraction;
|
|
890
|
-
}, [disableInteraction]); //
|
|
891
|
-
// Lifecycle: 외부 selectedItems 동기화
|
|
892
|
-
// --------------------------------------------------------------------------
|
|
527
|
+
}, [disableInteraction]); // 외부 selectedItems 동기화
|
|
893
528
|
|
|
894
529
|
React.useEffect(function () {
|
|
895
530
|
if (!stageRef.current) return;
|
|
896
|
-
hooks.syncExternalSelectedItems(externalSelectedItems, selectedIdsRef, selectedItemsMapRef);
|
|
897
|
-
|
|
531
|
+
hooks.syncExternalSelectedItems(externalSelectedItems, selectedIdsRef, selectedItemsMapRef);
|
|
898
532
|
doRenderBase();
|
|
899
|
-
doRenderAnimation();
|
|
900
533
|
doRenderEvent();
|
|
901
|
-
}, [externalSelectedItems]); //
|
|
902
|
-
// --------------------------------------------------------------------------
|
|
903
|
-
// Lifecycle: 외부 selectedItem 변경 시 Event Layer 리렌더링
|
|
904
|
-
// --------------------------------------------------------------------------
|
|
534
|
+
}, [externalSelectedItems]); // 외부 selectedItem 변경 시 Event Layer 리렌더링
|
|
905
535
|
|
|
906
536
|
React.useEffect(function () {
|
|
907
|
-
if (!stageRef.current) return;
|
|
908
|
-
|
|
909
|
-
selectedItemRef.current = externalSelectedItem; // selectedItem이 변경되면 Event Layer만 다시 그림
|
|
910
|
-
|
|
537
|
+
if (!stageRef.current) return;
|
|
538
|
+
selectedItemRef.current = externalSelectedItem;
|
|
911
539
|
doRenderEvent();
|
|
912
|
-
}, [externalSelectedItem]); //
|
|
913
|
-
// Lifecycle: 데이터 변경 시 렌더링
|
|
914
|
-
// --------------------------------------------------------------------------
|
|
540
|
+
}, [externalSelectedItem]); // 데이터 변경 시 렌더링 (캐시 정리 및 선택 상태 동기화)
|
|
915
541
|
|
|
916
542
|
React.useEffect(function () {
|
|
917
|
-
if (!stageRef.current) return;
|
|
918
|
-
|
|
919
|
-
dataRef.current = data; // 데이터 변경 시 즉시 transform 제거 및 캐시 정리 (겹침 방지)
|
|
543
|
+
if (!stageRef.current) return;
|
|
544
|
+
dataRef.current = data;
|
|
920
545
|
|
|
921
546
|
if (containerRef.current) {
|
|
922
547
|
containerRef.current.style.transform = '';
|
|
@@ -926,26 +551,10 @@ var WoongCanvasMarker = function (props) {
|
|
|
926
551
|
accumTranslateRef.current = {
|
|
927
552
|
x: 0,
|
|
928
553
|
y: 0
|
|
929
|
-
};
|
|
930
|
-
|
|
554
|
+
};
|
|
931
555
|
offsetCacheRef.current.clear();
|
|
932
556
|
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
|
-
|
|
557
|
+
selectedItemsMapRef.current = hooks.syncSelectedItems(data, selectedIdsRef.current, selectedItemsMapRef.current);
|
|
949
558
|
renderAllImmediate();
|
|
950
559
|
}, [data]);
|
|
951
560
|
return reactDom.createPortal(React__default["default"].createElement("div", {
|