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