@mint-ui/map 1.2.0-test.11 → 1.2.0-test.13

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.
package/dist/index.es.js CHANGED
@@ -961,6 +961,29 @@ var hexToRgba = function (hexColor, alpha) {
961
961
 
962
962
  throw new Error('Invalid hex color format');
963
963
  };
964
+ var tempCanvas = document.createElement('canvas');
965
+ var tempCtx = tempCanvas.getContext('2d');
966
+ /**
967
+ * 텍스트 박스의 너비를 계산합니다.
968
+ *
969
+ * @param {Object} params - 파라미터 객체
970
+ * @param {string} params.text - 측정할 텍스트
971
+ * @param {string} params.fontConfig - 폰트 설정 (예: 'bold 16px Arial')
972
+ * @param {number} params.padding - 텍스트 박스에 적용할 패딩 값
973
+ * @param {number} params.minWidth - 최소 너비
974
+ * @returns {number} 계산된 텍스트 박스의 너비
975
+ */
976
+
977
+ var calculateTextBoxWidth = function (_a) {
978
+ var text = _a.text,
979
+ fontConfig = _a.fontConfig,
980
+ padding = _a.padding,
981
+ minWidth = _a.minWidth;
982
+ if (!tempCtx) return 0;
983
+ tempCtx.font = fontConfig;
984
+ var textWidth = tempCtx.measureText(text).width;
985
+ return Math.max(minWidth, textWidth + padding);
986
+ };
964
987
 
965
988
  var KonvaMarkerContext = createContext(null);
966
989
  var KonvaMarkerProvider = function (_a) {
@@ -5613,20 +5636,8 @@ var renderPolygonEvent = function (selectedFillColor, selectedStrokeColor, selec
5613
5636
  // 메인 컴포넌트
5614
5637
  // ============================================================================
5615
5638
 
5616
- /**
5617
- * Konva 기반 고성능 마커/폴리곤 렌더링 컴포넌트
5618
- *
5619
- * 특징:
5620
- * - Base/Event 레이어 분리로 성능 최적화
5621
- * - LRU 캐시로 좌표 변환 결과 캐싱
5622
- * - Spatial Hash Grid로 빠른 Hit Test
5623
- * - Viewport Culling으로 보이는 영역만 렌더링
5624
- *
5625
- * @template T 마커 데이터의 추가 속성 타입
5626
- */
5627
-
5628
5639
  var WoongCanvasLayerComponent = function (props) {
5629
- var markers = props.markers,
5640
+ var data = props.data,
5630
5641
  dataType = props.dataType,
5631
5642
  onClick = props.onClick,
5632
5643
  onMouseOver = props.onMouseOver,
@@ -5645,11 +5656,13 @@ var WoongCanvasLayerComponent = function (props) {
5645
5656
  externalSelectedItem = props.selectedItem,
5646
5657
  _f = props.disableInteraction,
5647
5658
  disableInteraction = _f === void 0 ? false : _f,
5648
- options = __rest(props, ["markers", "dataType", "onClick", "onMouseOver", "onMouseOut", "enableMultiSelect", "topOnHover", "enableViewportCulling", "cullingMargin", "maxCacheSize", "selectedItems", "selectedItem", "disableInteraction"]); // --------------------------------------------------------------------------
5659
+ options = __rest(props, ["data", "dataType", "onClick", "onMouseOver", "onMouseOut", "enableMultiSelect", "topOnHover", "enableViewportCulling", "cullingMargin", "maxCacheSize", "selectedItems", "selectedItem", "disableInteraction"]); // renderConfig 추출 (MARKER 모드에서만 존재)
5660
+
5661
+
5662
+ var renderConfig = dataType === CanvasDataType.MARKER ? props.renderConfig : undefined; // --------------------------------------------------------------------------
5649
5663
  // Hooks & Context
5650
5664
  // --------------------------------------------------------------------------
5651
5665
 
5652
-
5653
5666
  var controller = useMintMapController();
5654
5667
  var context = useKonvaMarkerContext();
5655
5668
  var currentZIndex = options.zIndex !== undefined ? options.zIndex : 0; // --------------------------------------------------------------------------
@@ -5670,9 +5683,9 @@ var WoongCanvasLayerComponent = function (props) {
5670
5683
  // Data Refs - 선택 및 Hover 상태 관리
5671
5684
  // --------------------------------------------------------------------------
5672
5685
 
5673
- /** markers prop을 ref로 추적 (stale closure 방지, useEffect에서 동기화) */
5686
+ /** data prop을 ref로 추적 (stale closure 방지, useEffect에서 동기화) */
5674
5687
 
5675
- var markersRef = useRef(markers); // --------------------------------------------------------------------------
5688
+ var markersRef = useRef(data); // --------------------------------------------------------------------------
5676
5689
  // State Refs - 선택 및 Hover 상태 관리
5677
5690
  // --------------------------------------------------------------------------
5678
5691
 
@@ -5844,6 +5857,7 @@ var WoongCanvasLayerComponent = function (props) {
5844
5857
  };
5845
5858
  /**
5846
5859
  * 마커 좌표 변환 결과를 캐시하고 반환
5860
+ *
5847
5861
  * @param markerData 마커 데이터
5848
5862
  * @returns 변환된 좌표 또는 null
5849
5863
  */
@@ -5980,7 +5994,8 @@ var WoongCanvasLayerComponent = function (props) {
5980
5994
  ctx: ctx,
5981
5995
  items: visibleMarkers,
5982
5996
  selectedIds: selectedIdsRef.current,
5983
- utils: renderUtils
5997
+ utils: renderUtils,
5998
+ config: renderConfig
5984
5999
  });
5985
6000
  },
5986
6001
  perfectDrawEnabled: false,
@@ -6010,7 +6025,8 @@ var WoongCanvasLayerComponent = function (props) {
6010
6025
  layer: layer,
6011
6026
  selectedIds: selectedIdsRef.current,
6012
6027
  items: markersRef.current,
6013
- utils: renderUtils
6028
+ utils: renderUtils,
6029
+ config: renderConfig
6014
6030
  });
6015
6031
  };
6016
6032
  /**
@@ -6051,7 +6067,8 @@ var WoongCanvasLayerComponent = function (props) {
6051
6067
  hoveredItem: hoveredItemRef.current,
6052
6068
  utils: renderUtils,
6053
6069
  selectedItems: selectedItems,
6054
- selectedItem: selectedItemRef.current
6070
+ selectedItem: selectedItemRef.current,
6071
+ config: renderConfig
6055
6072
  });
6056
6073
  },
6057
6074
  perfectDrawEnabled: false,
@@ -6314,11 +6331,11 @@ var WoongCanvasLayerComponent = function (props) {
6314
6331
 
6315
6332
  try {
6316
6333
  var clickedOffset = controller.positionToOffset(event.param.position);
6317
- var data = findData(clickedOffset);
6334
+ var data_1 = findData(clickedOffset);
6318
6335
 
6319
- if (data) {
6320
- handleLocalClick(data);
6321
- onClick(data, selectedIdsRef.current);
6336
+ if (data_1) {
6337
+ handleLocalClick(data_1);
6338
+ onClick(data_1, selectedIdsRef.current);
6322
6339
  }
6323
6340
  } catch (error) {
6324
6341
  console.error('[WoongKonvaMarker] handleClick error:', error);
@@ -6595,13 +6612,13 @@ var WoongCanvasLayerComponent = function (props) {
6595
6612
 
6596
6613
  doRenderEvent();
6597
6614
  }, [externalSelectedItem]); // --------------------------------------------------------------------------
6598
- // Lifecycle: 마커 데이터 변경 시 렌더링
6615
+ // Lifecycle: 데이터 변경 시 렌더링
6599
6616
  // --------------------------------------------------------------------------
6600
6617
 
6601
6618
  useEffect(function () {
6602
6619
  if (!stageRef.current) return; // markersRef 동기화
6603
6620
 
6604
- markersRef.current = markers; // 데이터 변경 시 즉시 transform 제거 및 캐시 정리 (겹침 방지)
6621
+ markersRef.current = data; // 데이터 변경 시 즉시 transform 제거 및 캐시 정리 (겹침 방지)
6605
6622
 
6606
6623
  if (containerRef.current) {
6607
6624
  containerRef.current.style.transform = '';
@@ -6618,18 +6635,18 @@ var WoongCanvasLayerComponent = function (props) {
6618
6635
  /**
6619
6636
  * 선택 상태 동기화 (최적화 버전)
6620
6637
  *
6621
- * markers가 변경되면 selectedItemsMapRef도 업데이트 필요
6638
+ * data가 변경되면 selectedItemsMapRef도 업데이트 필요
6622
6639
  * (참조가 바뀌므로 기존 Map의 데이터는 stale 상태)
6623
6640
  *
6624
- * 🔥 중요: 화면 밖 마커도 선택 상태 유지!
6625
- * - 현재 markers에 있으면 최신 데이터로 업데이트
6641
+ * 🔥 중요: 화면 밖 데이터도 선택 상태 유지!
6642
+ * - 현재 data에 있으면 최신 데이터로 업데이트
6626
6643
  * - 없으면 기존 selectedItemsMapRef의 데이터 유지
6627
6644
  *
6628
- * 최적화: markers를 Map으로 먼저 변환하여 find() 순회 제거
6629
- * - O(전체 마커 수 + 선택된 개수) - 매우 효율적
6645
+ * 최적화: data를 Map으로 먼저 변환하여 find() 순회 제거
6646
+ * - O(전체 데이터 수 + 선택된 개수) - 매우 효율적
6630
6647
  */
6631
6648
 
6632
- var markersMap = new Map(markers.map(function (m) {
6649
+ var markersMap = new Map(data.map(function (m) {
6633
6650
  return [m.id, m];
6634
6651
  }));
6635
6652
  var newSelectedItemsMap = new Map();
@@ -6652,7 +6669,7 @@ var WoongCanvasLayerComponent = function (props) {
6652
6669
  selectedItemsMapRef.current = newSelectedItemsMap; // 즉시 렌더링
6653
6670
 
6654
6671
  renderAllImmediate();
6655
- }, [markers]);
6672
+ }, [data]);
6656
6673
  return createPortal(React.createElement("div", {
6657
6674
  ref: containerRef,
6658
6675
  style: {
@@ -6663,27 +6680,96 @@ var WoongCanvasLayerComponent = function (props) {
6663
6680
  }), divElement);
6664
6681
  };
6665
6682
  /**
6666
- * 🔥 React.memo 최적화: 마커 배열과 selectedItems 변경 체크
6683
+ * 🚀 WoongCanvasLayer - Konva 기반 초고성능 마커/폴리곤 렌더링 컴포넌트
6684
+ *
6685
+ * ## 📌 주요 특징
6686
+ * - **30,000개 이상의 폴리곤/마커를 60fps로 렌더링**
6687
+ * - **Multi-Layer 아키텍처**: Base/Animation/Event 레이어 분리
6688
+ * - **Spatial Hash Grid**: O(1) 수준의 빠른 Hit Test
6689
+ * - **LRU 캐시**: 좌표 변환 결과 캐싱으로 성능 최적화
6690
+ * - **Viewport Culling**: 화면에 보이는 영역만 렌더링
6691
+ * - **Discriminated Union Props**: 타입 안전한 MARKER/POLYGON 모드
6667
6692
  *
6668
- * 비교 전략:
6669
- * 1. markers 배열 비교
6670
- * 2. selectedItems 배열 비교 (외부 제어)
6693
+ * ## 🎯 사용 방법
6671
6694
  *
6672
- * 주의: JSON.stringify() 사용 금지! (매우 느림)
6695
+ * ### 1️⃣ POLYGON 모드 (자동 렌더링)
6696
+ * ```tsx
6697
+ * <WoongCanvasLayer
6698
+ * dataType={CanvasDataType.POLYGON}
6699
+ * data={polygons}
6700
+ * baseFillColor="rgba(255, 100, 100, 0.5)"
6701
+ * baseStrokeColor="rgba(200, 50, 50, 0.8)"
6702
+ * baseLineWidth={2}
6703
+ * selectedFillColor="rgba(255, 193, 7, 0.7)"
6704
+ * selectedStrokeColor="rgba(255, 152, 0, 1)"
6705
+ * selectedLineWidth={4}
6706
+ * hoveredFillColor="rgba(100, 150, 255, 0.8)" // optional
6707
+ * hoveredStrokeColor="rgba(0, 100, 200, 1)" // optional
6708
+ * hoveredLineWidth={3} // optional
6709
+ * enableMultiSelect={true}
6710
+ * onClick={handleClick}
6711
+ * />
6712
+ * ```
6713
+ *
6714
+ * ### 2️⃣ MARKER 모드 (커스텀 렌더링)
6715
+ * ```tsx
6716
+ * <WoongCanvasLayer
6717
+ * dataType={CanvasDataType.MARKER}
6718
+ * data={markers}
6719
+ * renderBase={renderMarkerBase} // required
6720
+ * renderAnimation={renderMarkerAnimation} // optional
6721
+ * renderEvent={renderMarkerEvent} // optional
6722
+ * topOnHover={true}
6723
+ * onClick={handleClick}
6724
+ * />
6725
+ * ```
6726
+ *
6727
+ * ## 📊 데이터 형식
6728
+ * ```typescript
6729
+ * const data: KonvaCanvasData<T>[] = [
6730
+ * {
6731
+ * id: 'unique-id',
6732
+ * position: new Position(lat, lng),
6733
+ * // POLYGON: paths 필수
6734
+ * paths: [[[lat, lng], [lat, lng], ...]],
6735
+ * // MARKER: boxWidth/boxHeight 권장 (Hit Test 정확도)
6736
+ * boxWidth: 60,
6737
+ * boxHeight: 75,
6738
+ * // 커스텀 데이터
6739
+ * ...customData
6740
+ * }
6741
+ * ];
6742
+ * ```
6743
+ *
6744
+ * ## ⚡ 성능 최적화 팁
6745
+ * 1. **동적 boxWidth 계산**: `measureText()`로 실제 너비 계산 후 전달
6746
+ * 2. **enableViewportCulling**: 대량 데이터 시 필수 (기본 true)
6747
+ * 3. **selectedItems 외부 관리**: 상태를 외부에서 관리하여 리렌더링 최소화
6748
+ * 4. **React.memo 최적화**: 컴포넌트가 자동으로 불필요한 리렌더링 방지
6749
+ *
6750
+ * @template T 마커/폴리곤 데이터의 추가 속성 타입
6751
+ *
6752
+ * @example
6753
+ * // 동적 boxWidth 계산 예시
6754
+ * const tempCtx = document.createElement('canvas').getContext('2d');
6755
+ * tempCtx.font = 'bold 15px Arial';
6756
+ * const boxWidth = Math.max(60, tempCtx.measureText(text).width + 20);
6757
+ *
6758
+ * @see {@link https://github.com/your-repo/docs/WoongCanvasLayer.md} 전체 문서
6673
6759
  */
6674
6760
 
6675
6761
 
6676
6762
  var WoongCanvasLayer = React.memo(WoongCanvasLayerComponent, function (prevProps, nextProps) {
6677
- // 1. markers 비교
6678
- var prevMarkers = prevProps.markers;
6679
- var nextMarkers = nextProps.markers; // 참조가 같으면 스킵
6763
+ // 1. data 비교
6764
+ var prevData = prevProps.data;
6765
+ var nextData = nextProps.data; // 참조가 같으면 스킵
6680
6766
 
6681
- if (prevMarkers !== nextMarkers) {
6767
+ if (prevData !== nextData) {
6682
6768
  // 길이가 다르면 변경됨
6683
- if (prevMarkers.length !== nextMarkers.length) return false; // 각 마커의 ID 비교
6769
+ if (prevData.length !== nextData.length) return false; // 각 데이터의 ID 비교
6684
6770
 
6685
- for (var i = 0; i < prevMarkers.length; i++) {
6686
- if (prevMarkers[i].id !== nextMarkers[i].id) {
6771
+ for (var i = 0; i < prevData.length; i++) {
6772
+ if (prevData[i].id !== nextData[i].id) {
6687
6773
  return false; // 변경됨 → 리렌더링
6688
6774
  }
6689
6775
  }
@@ -6698,18 +6784,6 @@ var WoongCanvasLayer = React.memo(WoongCanvasLayerComponent, function (prevProps
6698
6784
  return false; // 변경됨 → 리렌더링
6699
6785
  }
6700
6786
 
6701
- if (prevProps.onClick !== nextProps.onClick) {
6702
- return false; // 변경됨 → 리렌더링
6703
- }
6704
-
6705
- if (prevProps.onMouseOver !== nextProps.onMouseOver) {
6706
- return false; // 변경됨 → 리렌더링
6707
- }
6708
-
6709
- if (prevProps.onMouseOut !== nextProps.onMouseOut) {
6710
- return false; // 변경됨 → 리렌더링
6711
- }
6712
-
6713
6787
  if (prevProps.disableInteraction !== nextProps.disableInteraction) {
6714
6788
  return false; // 변경됨 → 리렌더링
6715
6789
  }
@@ -9548,4 +9622,4 @@ function MintMap(_a) {
9548
9622
  }), loading));
9549
9623
  }
9550
9624
 
9551
- export { AnimationPlayer, Bounds, CanvasDataType, CanvasMarker, CanvasMarkerClaude, CanvasMarkerHanquf, CircleMarker, DEFAULT_CULLING_MARGIN, DEFAULT_MAX_CACHE_SIZE, Drawable, GeoCalulator, GoogleMintMapController, KonvaMarkerProvider, LRUCache, MapBuildingProjection, MapCanvasMarkerWrapper, MapCanvasWrapper, MapControlWrapper, MapEvent, MapLoadingWithImage, MapMarkerWrapper, MapPolygonWrapper, MapPolylineWrapper, MapUIEvent, Marker, MintMap, MintMapCanvasRenderer, MintMapController, MintMapCore, MintMapProvider, NaverMintMapController, Offset, PointLoading, Polygon, PolygonCalculator, PolygonMarker, Polyline, Position, SPATIAL_GRID_CELL_SIZE, SVGCircle, SVGPolygon, SVGRect, Spacing, SpatialHashGrid, Status, WoongCanvasLayer, computeMarkerOffset, computePolygonOffsets, getClusterInfo, getMapOfType, hexToRgba, isPointInMarkerData, isPointInPolygon, isPointInPolygonData, log, useKonvaMarkerContext, useMarkerMoving, useMintMapController, waiting };
9625
+ export { AnimationPlayer, Bounds, CanvasDataType, CanvasMarker, CanvasMarkerClaude, CanvasMarkerHanquf, CircleMarker, DEFAULT_CULLING_MARGIN, DEFAULT_MAX_CACHE_SIZE, Drawable, GeoCalulator, GoogleMintMapController, KonvaMarkerProvider, LRUCache, MapBuildingProjection, MapCanvasMarkerWrapper, MapCanvasWrapper, MapControlWrapper, MapEvent, MapLoadingWithImage, MapMarkerWrapper, MapPolygonWrapper, MapPolylineWrapper, MapUIEvent, Marker, MintMap, MintMapCanvasRenderer, MintMapController, MintMapCore, MintMapProvider, NaverMintMapController, Offset, PointLoading, Polygon, PolygonCalculator, PolygonMarker, Polyline, Position, SPATIAL_GRID_CELL_SIZE, SVGCircle, SVGPolygon, SVGRect, Spacing, SpatialHashGrid, Status, WoongCanvasLayer, calculateTextBoxWidth, computeMarkerOffset, computePolygonOffsets, getClusterInfo, getMapOfType, hexToRgba, isPointInMarkerData, isPointInPolygon, isPointInPolygonData, log, useKonvaMarkerContext, useMarkerMoving, useMintMapController, waiting };
package/dist/index.js CHANGED
@@ -65,6 +65,7 @@ Object.defineProperty(exports, 'CanvasDataType', {
65
65
  enumerable: true,
66
66
  get: function () { return types.CanvasDataType; }
67
67
  });
68
+ exports.calculateTextBoxWidth = utils.calculateTextBoxWidth;
68
69
  exports.computeMarkerOffset = utils.computeMarkerOffset;
69
70
  exports.computePolygonOffsets = utils.computePolygonOffsets;
70
71
  exports.hexToRgba = utils.hexToRgba;
package/dist/index.umd.js CHANGED
@@ -965,6 +965,29 @@
965
965
 
966
966
  throw new Error('Invalid hex color format');
967
967
  };
968
+ var tempCanvas = document.createElement('canvas');
969
+ var tempCtx = tempCanvas.getContext('2d');
970
+ /**
971
+ * 텍스트 박스의 너비를 계산합니다.
972
+ *
973
+ * @param {Object} params - 파라미터 객체
974
+ * @param {string} params.text - 측정할 텍스트
975
+ * @param {string} params.fontConfig - 폰트 설정 (예: 'bold 16px Arial')
976
+ * @param {number} params.padding - 텍스트 박스에 적용할 패딩 값
977
+ * @param {number} params.minWidth - 최소 너비
978
+ * @returns {number} 계산된 텍스트 박스의 너비
979
+ */
980
+
981
+ var calculateTextBoxWidth = function (_a) {
982
+ var text = _a.text,
983
+ fontConfig = _a.fontConfig,
984
+ padding = _a.padding,
985
+ minWidth = _a.minWidth;
986
+ if (!tempCtx) return 0;
987
+ tempCtx.font = fontConfig;
988
+ var textWidth = tempCtx.measureText(text).width;
989
+ return Math.max(minWidth, textWidth + padding);
990
+ };
968
991
 
969
992
  var KonvaMarkerContext = React.createContext(null);
970
993
  var KonvaMarkerProvider = function (_a) {
@@ -5617,20 +5640,8 @@
5617
5640
  // 메인 컴포넌트
5618
5641
  // ============================================================================
5619
5642
 
5620
- /**
5621
- * Konva 기반 고성능 마커/폴리곤 렌더링 컴포넌트
5622
- *
5623
- * 특징:
5624
- * - Base/Event 레이어 분리로 성능 최적화
5625
- * - LRU 캐시로 좌표 변환 결과 캐싱
5626
- * - Spatial Hash Grid로 빠른 Hit Test
5627
- * - Viewport Culling으로 보이는 영역만 렌더링
5628
- *
5629
- * @template T 마커 데이터의 추가 속성 타입
5630
- */
5631
-
5632
5643
  var WoongCanvasLayerComponent = function (props) {
5633
- var markers = props.markers,
5644
+ var data = props.data,
5634
5645
  dataType = props.dataType,
5635
5646
  onClick = props.onClick,
5636
5647
  onMouseOver = props.onMouseOver,
@@ -5649,11 +5660,13 @@
5649
5660
  externalSelectedItem = props.selectedItem,
5650
5661
  _f = props.disableInteraction,
5651
5662
  disableInteraction = _f === void 0 ? false : _f,
5652
- options = tslib.__rest(props, ["markers", "dataType", "onClick", "onMouseOver", "onMouseOut", "enableMultiSelect", "topOnHover", "enableViewportCulling", "cullingMargin", "maxCacheSize", "selectedItems", "selectedItem", "disableInteraction"]); // --------------------------------------------------------------------------
5663
+ options = tslib.__rest(props, ["data", "dataType", "onClick", "onMouseOver", "onMouseOut", "enableMultiSelect", "topOnHover", "enableViewportCulling", "cullingMargin", "maxCacheSize", "selectedItems", "selectedItem", "disableInteraction"]); // renderConfig 추출 (MARKER 모드에서만 존재)
5664
+
5665
+
5666
+ var renderConfig = dataType === exports.CanvasDataType.MARKER ? props.renderConfig : undefined; // --------------------------------------------------------------------------
5653
5667
  // Hooks & Context
5654
5668
  // --------------------------------------------------------------------------
5655
5669
 
5656
-
5657
5670
  var controller = useMintMapController();
5658
5671
  var context = useKonvaMarkerContext();
5659
5672
  var currentZIndex = options.zIndex !== undefined ? options.zIndex : 0; // --------------------------------------------------------------------------
@@ -5674,9 +5687,9 @@
5674
5687
  // Data Refs - 선택 및 Hover 상태 관리
5675
5688
  // --------------------------------------------------------------------------
5676
5689
 
5677
- /** markers prop을 ref로 추적 (stale closure 방지, useEffect에서 동기화) */
5690
+ /** data prop을 ref로 추적 (stale closure 방지, useEffect에서 동기화) */
5678
5691
 
5679
- var markersRef = React.useRef(markers); // --------------------------------------------------------------------------
5692
+ var markersRef = React.useRef(data); // --------------------------------------------------------------------------
5680
5693
  // State Refs - 선택 및 Hover 상태 관리
5681
5694
  // --------------------------------------------------------------------------
5682
5695
 
@@ -5848,6 +5861,7 @@
5848
5861
  };
5849
5862
  /**
5850
5863
  * 마커 좌표 변환 결과를 캐시하고 반환
5864
+ *
5851
5865
  * @param markerData 마커 데이터
5852
5866
  * @returns 변환된 좌표 또는 null
5853
5867
  */
@@ -5984,7 +5998,8 @@
5984
5998
  ctx: ctx,
5985
5999
  items: visibleMarkers,
5986
6000
  selectedIds: selectedIdsRef.current,
5987
- utils: renderUtils
6001
+ utils: renderUtils,
6002
+ config: renderConfig
5988
6003
  });
5989
6004
  },
5990
6005
  perfectDrawEnabled: false,
@@ -6014,7 +6029,8 @@
6014
6029
  layer: layer,
6015
6030
  selectedIds: selectedIdsRef.current,
6016
6031
  items: markersRef.current,
6017
- utils: renderUtils
6032
+ utils: renderUtils,
6033
+ config: renderConfig
6018
6034
  });
6019
6035
  };
6020
6036
  /**
@@ -6055,7 +6071,8 @@
6055
6071
  hoveredItem: hoveredItemRef.current,
6056
6072
  utils: renderUtils,
6057
6073
  selectedItems: selectedItems,
6058
- selectedItem: selectedItemRef.current
6074
+ selectedItem: selectedItemRef.current,
6075
+ config: renderConfig
6059
6076
  });
6060
6077
  },
6061
6078
  perfectDrawEnabled: false,
@@ -6318,11 +6335,11 @@
6318
6335
 
6319
6336
  try {
6320
6337
  var clickedOffset = controller.positionToOffset(event.param.position);
6321
- var data = findData(clickedOffset);
6338
+ var data_1 = findData(clickedOffset);
6322
6339
 
6323
- if (data) {
6324
- handleLocalClick(data);
6325
- onClick(data, selectedIdsRef.current);
6340
+ if (data_1) {
6341
+ handleLocalClick(data_1);
6342
+ onClick(data_1, selectedIdsRef.current);
6326
6343
  }
6327
6344
  } catch (error) {
6328
6345
  console.error('[WoongKonvaMarker] handleClick error:', error);
@@ -6599,13 +6616,13 @@
6599
6616
 
6600
6617
  doRenderEvent();
6601
6618
  }, [externalSelectedItem]); // --------------------------------------------------------------------------
6602
- // Lifecycle: 마커 데이터 변경 시 렌더링
6619
+ // Lifecycle: 데이터 변경 시 렌더링
6603
6620
  // --------------------------------------------------------------------------
6604
6621
 
6605
6622
  React.useEffect(function () {
6606
6623
  if (!stageRef.current) return; // markersRef 동기화
6607
6624
 
6608
- markersRef.current = markers; // 데이터 변경 시 즉시 transform 제거 및 캐시 정리 (겹침 방지)
6625
+ markersRef.current = data; // 데이터 변경 시 즉시 transform 제거 및 캐시 정리 (겹침 방지)
6609
6626
 
6610
6627
  if (containerRef.current) {
6611
6628
  containerRef.current.style.transform = '';
@@ -6622,18 +6639,18 @@
6622
6639
  /**
6623
6640
  * 선택 상태 동기화 (최적화 버전)
6624
6641
  *
6625
- * markers가 변경되면 selectedItemsMapRef도 업데이트 필요
6642
+ * data가 변경되면 selectedItemsMapRef도 업데이트 필요
6626
6643
  * (참조가 바뀌므로 기존 Map의 데이터는 stale 상태)
6627
6644
  *
6628
- * 🔥 중요: 화면 밖 마커도 선택 상태 유지!
6629
- * - 현재 markers에 있으면 최신 데이터로 업데이트
6645
+ * 🔥 중요: 화면 밖 데이터도 선택 상태 유지!
6646
+ * - 현재 data에 있으면 최신 데이터로 업데이트
6630
6647
  * - 없으면 기존 selectedItemsMapRef의 데이터 유지
6631
6648
  *
6632
- * 최적화: markers를 Map으로 먼저 변환하여 find() 순회 제거
6633
- * - O(전체 마커 수 + 선택된 개수) - 매우 효율적
6649
+ * 최적화: data를 Map으로 먼저 변환하여 find() 순회 제거
6650
+ * - O(전체 데이터 수 + 선택된 개수) - 매우 효율적
6634
6651
  */
6635
6652
 
6636
- var markersMap = new Map(markers.map(function (m) {
6653
+ var markersMap = new Map(data.map(function (m) {
6637
6654
  return [m.id, m];
6638
6655
  }));
6639
6656
  var newSelectedItemsMap = new Map();
@@ -6656,7 +6673,7 @@
6656
6673
  selectedItemsMapRef.current = newSelectedItemsMap; // 즉시 렌더링
6657
6674
 
6658
6675
  renderAllImmediate();
6659
- }, [markers]);
6676
+ }, [data]);
6660
6677
  return reactDom.createPortal(React__default["default"].createElement("div", {
6661
6678
  ref: containerRef,
6662
6679
  style: {
@@ -6667,27 +6684,96 @@
6667
6684
  }), divElement);
6668
6685
  };
6669
6686
  /**
6670
- * 🔥 React.memo 최적화: 마커 배열과 selectedItems 변경 체크
6687
+ * 🚀 WoongCanvasLayer - Konva 기반 초고성능 마커/폴리곤 렌더링 컴포넌트
6688
+ *
6689
+ * ## 📌 주요 특징
6690
+ * - **30,000개 이상의 폴리곤/마커를 60fps로 렌더링**
6691
+ * - **Multi-Layer 아키텍처**: Base/Animation/Event 레이어 분리
6692
+ * - **Spatial Hash Grid**: O(1) 수준의 빠른 Hit Test
6693
+ * - **LRU 캐시**: 좌표 변환 결과 캐싱으로 성능 최적화
6694
+ * - **Viewport Culling**: 화면에 보이는 영역만 렌더링
6695
+ * - **Discriminated Union Props**: 타입 안전한 MARKER/POLYGON 모드
6671
6696
  *
6672
- * 비교 전략:
6673
- * 1. markers 배열 비교
6674
- * 2. selectedItems 배열 비교 (외부 제어)
6697
+ * ## 🎯 사용 방법
6675
6698
  *
6676
- * 주의: JSON.stringify() 사용 금지! (매우 느림)
6699
+ * ### 1️⃣ POLYGON 모드 (자동 렌더링)
6700
+ * ```tsx
6701
+ * <WoongCanvasLayer
6702
+ * dataType={CanvasDataType.POLYGON}
6703
+ * data={polygons}
6704
+ * baseFillColor="rgba(255, 100, 100, 0.5)"
6705
+ * baseStrokeColor="rgba(200, 50, 50, 0.8)"
6706
+ * baseLineWidth={2}
6707
+ * selectedFillColor="rgba(255, 193, 7, 0.7)"
6708
+ * selectedStrokeColor="rgba(255, 152, 0, 1)"
6709
+ * selectedLineWidth={4}
6710
+ * hoveredFillColor="rgba(100, 150, 255, 0.8)" // optional
6711
+ * hoveredStrokeColor="rgba(0, 100, 200, 1)" // optional
6712
+ * hoveredLineWidth={3} // optional
6713
+ * enableMultiSelect={true}
6714
+ * onClick={handleClick}
6715
+ * />
6716
+ * ```
6717
+ *
6718
+ * ### 2️⃣ MARKER 모드 (커스텀 렌더링)
6719
+ * ```tsx
6720
+ * <WoongCanvasLayer
6721
+ * dataType={CanvasDataType.MARKER}
6722
+ * data={markers}
6723
+ * renderBase={renderMarkerBase} // required
6724
+ * renderAnimation={renderMarkerAnimation} // optional
6725
+ * renderEvent={renderMarkerEvent} // optional
6726
+ * topOnHover={true}
6727
+ * onClick={handleClick}
6728
+ * />
6729
+ * ```
6730
+ *
6731
+ * ## 📊 데이터 형식
6732
+ * ```typescript
6733
+ * const data: KonvaCanvasData<T>[] = [
6734
+ * {
6735
+ * id: 'unique-id',
6736
+ * position: new Position(lat, lng),
6737
+ * // POLYGON: paths 필수
6738
+ * paths: [[[lat, lng], [lat, lng], ...]],
6739
+ * // MARKER: boxWidth/boxHeight 권장 (Hit Test 정확도)
6740
+ * boxWidth: 60,
6741
+ * boxHeight: 75,
6742
+ * // 커스텀 데이터
6743
+ * ...customData
6744
+ * }
6745
+ * ];
6746
+ * ```
6747
+ *
6748
+ * ## ⚡ 성능 최적화 팁
6749
+ * 1. **동적 boxWidth 계산**: `measureText()`로 실제 너비 계산 후 전달
6750
+ * 2. **enableViewportCulling**: 대량 데이터 시 필수 (기본 true)
6751
+ * 3. **selectedItems 외부 관리**: 상태를 외부에서 관리하여 리렌더링 최소화
6752
+ * 4. **React.memo 최적화**: 컴포넌트가 자동으로 불필요한 리렌더링 방지
6753
+ *
6754
+ * @template T 마커/폴리곤 데이터의 추가 속성 타입
6755
+ *
6756
+ * @example
6757
+ * // 동적 boxWidth 계산 예시
6758
+ * const tempCtx = document.createElement('canvas').getContext('2d');
6759
+ * tempCtx.font = 'bold 15px Arial';
6760
+ * const boxWidth = Math.max(60, tempCtx.measureText(text).width + 20);
6761
+ *
6762
+ * @see {@link https://github.com/your-repo/docs/WoongCanvasLayer.md} 전체 문서
6677
6763
  */
6678
6764
 
6679
6765
 
6680
6766
  var WoongCanvasLayer = React__default["default"].memo(WoongCanvasLayerComponent, function (prevProps, nextProps) {
6681
- // 1. markers 비교
6682
- var prevMarkers = prevProps.markers;
6683
- var nextMarkers = nextProps.markers; // 참조가 같으면 스킵
6767
+ // 1. data 비교
6768
+ var prevData = prevProps.data;
6769
+ var nextData = nextProps.data; // 참조가 같으면 스킵
6684
6770
 
6685
- if (prevMarkers !== nextMarkers) {
6771
+ if (prevData !== nextData) {
6686
6772
  // 길이가 다르면 변경됨
6687
- if (prevMarkers.length !== nextMarkers.length) return false; // 각 마커의 ID 비교
6773
+ if (prevData.length !== nextData.length) return false; // 각 데이터의 ID 비교
6688
6774
 
6689
- for (var i = 0; i < prevMarkers.length; i++) {
6690
- if (prevMarkers[i].id !== nextMarkers[i].id) {
6775
+ for (var i = 0; i < prevData.length; i++) {
6776
+ if (prevData[i].id !== nextData[i].id) {
6691
6777
  return false; // 변경됨 → 리렌더링
6692
6778
  }
6693
6779
  }
@@ -6702,18 +6788,6 @@
6702
6788
  return false; // 변경됨 → 리렌더링
6703
6789
  }
6704
6790
 
6705
- if (prevProps.onClick !== nextProps.onClick) {
6706
- return false; // 변경됨 → 리렌더링
6707
- }
6708
-
6709
- if (prevProps.onMouseOver !== nextProps.onMouseOver) {
6710
- return false; // 변경됨 → 리렌더링
6711
- }
6712
-
6713
- if (prevProps.onMouseOut !== nextProps.onMouseOut) {
6714
- return false; // 변경됨 → 리렌더링
6715
- }
6716
-
6717
6791
  if (prevProps.disableInteraction !== nextProps.disableInteraction) {
6718
6792
  return false; // 변경됨 → 리렌더링
6719
6793
  }
@@ -9597,6 +9671,7 @@
9597
9671
  exports.SpatialHashGrid = SpatialHashGrid;
9598
9672
  exports.Status = Status;
9599
9673
  exports.WoongCanvasLayer = WoongCanvasLayer;
9674
+ exports.calculateTextBoxWidth = calculateTextBoxWidth;
9600
9675
  exports.computeMarkerOffset = computeMarkerOffset;
9601
9676
  exports.computePolygonOffsets = computePolygonOffsets;
9602
9677
  exports.getClusterInfo = getClusterInfo;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mint-ui/map",
3
- "version": "1.2.0-test.11",
3
+ "version": "1.2.0-test.13",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.es.js",
6
6
  "browser": "./dist/index.umd.js",
@@ -1,11 +0,0 @@
1
- /// <reference types="react" />
2
- interface Cluster {
3
- buildingCnt: number;
4
- lat: number;
5
- lng: number;
6
- }
7
- export interface ClusterMarkerProps {
8
- cluster: Cluster;
9
- }
10
- declare const ClusterMarker: ({ cluster }: ClusterMarkerProps) => JSX.Element;
11
- export default ClusterMarker;