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