@mint-ui/map 1.2.0-test.34 → 1.2.0-test.36

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