@mint-ui/map 1.2.0-test.35 → 1.2.0-test.37

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