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