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

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.
@@ -9,8 +9,7 @@ var MapTypes = require('../types/MapTypes.js');
9
9
  var MintMapProvider = require('./provider/MintMapProvider.js');
10
10
  var MintMapCore_module = require('./MintMapCore.module.scss.js');
11
11
  require('./advanced/woongCanvas/shared/types.js');
12
- require('../types/MapDrawables.js');
13
- require('../types/MapEventTypes.js');
12
+ require('./advanced/woongCanvas/shared/utils.js');
14
13
  var context = require('./advanced/woongCanvas/shared/context.js');
15
14
  require('./advanced/woongCanvas/shared/performance.js');
16
15
 
@@ -7,8 +7,8 @@ export type { KonvaCanvasMarkerOption, Paths, KonvaCanvasMarkerData, CustomRende
7
7
  * 공통 Props (MARKER와 POLYGON 모두 사용)
8
8
  */
9
9
  interface WoongCanvasLayerBaseProps<T> extends Pick<MarkerOptions, 'zIndex' | 'anchor' | 'visible'> {
10
- /** 렌더링할 마커 데이터 배열 */
11
- markers: KonvaCanvasMarkerData<T>[];
10
+ /** 렌더링할 데이터 배열 (마커 또는 폴리곤) */
11
+ data: KonvaCanvasMarkerData<T>[];
12
12
  /** 마커 클릭 시 호출되는 콜백 (선택) */
13
13
  onClick?: (payload: KonvaCanvasMarkerData<T>, selectedIds: Set<string>) => void;
14
14
  /** 마커에 마우스 오버 시 호출되는 콜백 (선택) */
@@ -81,13 +81,82 @@ interface WoongCanvasLayerPropsForPolygon<T> extends WoongCanvasLayerBaseProps<T
81
81
  */
82
82
  export declare type WoongCanvasLayerProps<T> = WoongCanvasLayerPropsForMarker<T> | WoongCanvasLayerPropsForPolygon<T>;
83
83
  /**
84
- * 🔥 React.memo 최적화: 마커 배열과 selectedItems 변경 체크
84
+ * 🚀 WoongCanvasLayer - Konva 기반 초고성능 마커/폴리곤 렌더링 컴포넌트
85
85
  *
86
- * 비교 전략:
87
- * 1. markers 배열 비교
88
- * 2. selectedItems 배열 비교 (외부 제어)
86
+ * ## 📌 주요 특징
87
+ * - **30,000개 이상의 폴리곤/마커를 60fps로 렌더링**
88
+ * - **Multi-Layer 아키텍처**: Base/Animation/Event 레이어 분리
89
+ * - **Spatial Hash Grid**: O(1) 수준의 빠른 Hit Test
90
+ * - **LRU 캐시**: 좌표 변환 결과 캐싱으로 성능 최적화
91
+ * - **Viewport Culling**: 화면에 보이는 영역만 렌더링
92
+ * - **Discriminated Union Props**: 타입 안전한 MARKER/POLYGON 모드
89
93
  *
90
- * 주의: JSON.stringify() 사용 금지! (매우 느림)
94
+ * ## 🎯 사용 방법
95
+ *
96
+ * ### 1️⃣ POLYGON 모드 (자동 렌더링)
97
+ * ```tsx
98
+ * <WoongCanvasLayer
99
+ * dataType={CanvasDataType.POLYGON}
100
+ * data={polygons}
101
+ * baseFillColor="rgba(255, 100, 100, 0.5)"
102
+ * baseStrokeColor="rgba(200, 50, 50, 0.8)"
103
+ * baseLineWidth={2}
104
+ * selectedFillColor="rgba(255, 193, 7, 0.7)"
105
+ * selectedStrokeColor="rgba(255, 152, 0, 1)"
106
+ * selectedLineWidth={4}
107
+ * hoveredFillColor="rgba(100, 150, 255, 0.8)" // optional
108
+ * hoveredStrokeColor="rgba(0, 100, 200, 1)" // optional
109
+ * hoveredLineWidth={3} // optional
110
+ * enableMultiSelect={true}
111
+ * onClick={handleClick}
112
+ * />
113
+ * ```
114
+ *
115
+ * ### 2️⃣ MARKER 모드 (커스텀 렌더링)
116
+ * ```tsx
117
+ * <WoongCanvasLayer
118
+ * dataType={CanvasDataType.MARKER}
119
+ * data={markers}
120
+ * renderBase={renderMarkerBase} // required
121
+ * renderAnimation={renderMarkerAnimation} // optional
122
+ * renderEvent={renderMarkerEvent} // optional
123
+ * topOnHover={true}
124
+ * onClick={handleClick}
125
+ * />
126
+ * ```
127
+ *
128
+ * ## 📊 데이터 형식
129
+ * ```typescript
130
+ * const data: KonvaCanvasMarkerData<T>[] = [
131
+ * {
132
+ * id: 'unique-id',
133
+ * position: new Position(lat, lng),
134
+ * // POLYGON: paths 필수
135
+ * paths: [[[lat, lng], [lat, lng], ...]],
136
+ * // MARKER: boxWidth/boxHeight 권장 (Hit Test 정확도)
137
+ * boxWidth: 60,
138
+ * boxHeight: 75,
139
+ * // 커스텀 데이터
140
+ * ...customData
141
+ * }
142
+ * ];
143
+ * ```
144
+ *
145
+ * ## ⚡ 성능 최적화 팁
146
+ * 1. **동적 boxWidth 계산**: `measureText()`로 실제 너비 계산 후 전달
147
+ * 2. **enableViewportCulling**: 대량 데이터 시 필수 (기본 true)
148
+ * 3. **selectedItems 외부 관리**: 상태를 외부에서 관리하여 리렌더링 최소화
149
+ * 4. **React.memo 최적화**: 컴포넌트가 자동으로 불필요한 리렌더링 방지
150
+ *
151
+ * @template T 마커/폴리곤 데이터의 추가 속성 타입
152
+ *
153
+ * @example
154
+ * // 동적 boxWidth 계산 예시
155
+ * const tempCtx = document.createElement('canvas').getContext('2d');
156
+ * tempCtx.font = 'bold 15px Arial';
157
+ * const boxWidth = Math.max(60, tempCtx.measureText(text).width + 20);
158
+ *
159
+ * @see {@link https://github.com/your-repo/docs/WoongCanvasLayer.md} 전체 문서
91
160
  */
92
161
  declare const WoongCanvasLayer: <T>(props: WoongCanvasLayerProps<T>) => React.ReactPortal;
93
162
  export default WoongCanvasLayer;
@@ -24,20 +24,8 @@ var Konva__default = /*#__PURE__*/_interopDefaultLegacy(Konva);
24
24
  // 메인 컴포넌트
25
25
  // ============================================================================
26
26
 
27
- /**
28
- * Konva 기반 고성능 마커/폴리곤 렌더링 컴포넌트
29
- *
30
- * 특징:
31
- * - Base/Event 레이어 분리로 성능 최적화
32
- * - LRU 캐시로 좌표 변환 결과 캐싱
33
- * - Spatial Hash Grid로 빠른 Hit Test
34
- * - Viewport Culling으로 보이는 영역만 렌더링
35
- *
36
- * @template T 마커 데이터의 추가 속성 타입
37
- */
38
-
39
27
  var WoongCanvasLayerComponent = function (props) {
40
- var markers = props.markers,
28
+ var data = props.data,
41
29
  dataType = props.dataType,
42
30
  onClick = props.onClick,
43
31
  onMouseOver = props.onMouseOver,
@@ -56,7 +44,7 @@ var WoongCanvasLayerComponent = function (props) {
56
44
  externalSelectedItem = props.selectedItem,
57
45
  _f = props.disableInteraction,
58
46
  disableInteraction = _f === void 0 ? false : _f,
59
- options = tslib.__rest(props, ["markers", "dataType", "onClick", "onMouseOver", "onMouseOut", "enableMultiSelect", "topOnHover", "enableViewportCulling", "cullingMargin", "maxCacheSize", "selectedItems", "selectedItem", "disableInteraction"]); // --------------------------------------------------------------------------
47
+ options = tslib.__rest(props, ["data", "dataType", "onClick", "onMouseOver", "onMouseOut", "enableMultiSelect", "topOnHover", "enableViewportCulling", "cullingMargin", "maxCacheSize", "selectedItems", "selectedItem", "disableInteraction"]); // --------------------------------------------------------------------------
60
48
  // Hooks & Context
61
49
  // --------------------------------------------------------------------------
62
50
 
@@ -81,9 +69,9 @@ var WoongCanvasLayerComponent = function (props) {
81
69
  // Data Refs - 선택 및 Hover 상태 관리
82
70
  // --------------------------------------------------------------------------
83
71
 
84
- /** markers prop을 ref로 추적 (stale closure 방지, useEffect에서 동기화) */
72
+ /** data prop을 ref로 추적 (stale closure 방지, useEffect에서 동기화) */
85
73
 
86
- var markersRef = React.useRef(markers); // --------------------------------------------------------------------------
74
+ var markersRef = React.useRef(data); // --------------------------------------------------------------------------
87
75
  // State Refs - 선택 및 Hover 상태 관리
88
76
  // --------------------------------------------------------------------------
89
77
 
@@ -255,6 +243,7 @@ var WoongCanvasLayerComponent = function (props) {
255
243
  };
256
244
  /**
257
245
  * 마커 좌표 변환 결과를 캐시하고 반환
246
+ *
258
247
  * @param markerData 마커 데이터
259
248
  * @returns 변환된 좌표 또는 null
260
249
  */
@@ -725,11 +714,11 @@ var WoongCanvasLayerComponent = function (props) {
725
714
 
726
715
  try {
727
716
  var clickedOffset = controller.positionToOffset(event.param.position);
728
- var data = findData(clickedOffset);
717
+ var data_1 = findData(clickedOffset);
729
718
 
730
- if (data) {
731
- handleLocalClick(data);
732
- onClick(data, selectedIdsRef.current);
719
+ if (data_1) {
720
+ handleLocalClick(data_1);
721
+ onClick(data_1, selectedIdsRef.current);
733
722
  }
734
723
  } catch (error) {
735
724
  console.error('[WoongKonvaMarker] handleClick error:', error);
@@ -1006,13 +995,13 @@ var WoongCanvasLayerComponent = function (props) {
1006
995
 
1007
996
  doRenderEvent();
1008
997
  }, [externalSelectedItem]); // --------------------------------------------------------------------------
1009
- // Lifecycle: 마커 데이터 변경 시 렌더링
998
+ // Lifecycle: 데이터 변경 시 렌더링
1010
999
  // --------------------------------------------------------------------------
1011
1000
 
1012
1001
  React.useEffect(function () {
1013
1002
  if (!stageRef.current) return; // markersRef 동기화
1014
1003
 
1015
- markersRef.current = markers; // 데이터 변경 시 즉시 transform 제거 및 캐시 정리 (겹침 방지)
1004
+ markersRef.current = data; // 데이터 변경 시 즉시 transform 제거 및 캐시 정리 (겹침 방지)
1016
1005
 
1017
1006
  if (containerRef.current) {
1018
1007
  containerRef.current.style.transform = '';
@@ -1029,18 +1018,18 @@ var WoongCanvasLayerComponent = function (props) {
1029
1018
  /**
1030
1019
  * 선택 상태 동기화 (최적화 버전)
1031
1020
  *
1032
- * markers가 변경되면 selectedItemsMapRef도 업데이트 필요
1021
+ * data가 변경되면 selectedItemsMapRef도 업데이트 필요
1033
1022
  * (참조가 바뀌므로 기존 Map의 데이터는 stale 상태)
1034
1023
  *
1035
- * 🔥 중요: 화면 밖 마커도 선택 상태 유지!
1036
- * - 현재 markers에 있으면 최신 데이터로 업데이트
1024
+ * 🔥 중요: 화면 밖 데이터도 선택 상태 유지!
1025
+ * - 현재 data에 있으면 최신 데이터로 업데이트
1037
1026
  * - 없으면 기존 selectedItemsMapRef의 데이터 유지
1038
1027
  *
1039
- * 최적화: markers를 Map으로 먼저 변환하여 find() 순회 제거
1040
- * - O(전체 마커 수 + 선택된 개수) - 매우 효율적
1028
+ * 최적화: data를 Map으로 먼저 변환하여 find() 순회 제거
1029
+ * - O(전체 데이터 수 + 선택된 개수) - 매우 효율적
1041
1030
  */
1042
1031
 
1043
- var markersMap = new Map(markers.map(function (m) {
1032
+ var markersMap = new Map(data.map(function (m) {
1044
1033
  return [m.id, m];
1045
1034
  }));
1046
1035
  var newSelectedItemsMap = new Map();
@@ -1063,7 +1052,7 @@ var WoongCanvasLayerComponent = function (props) {
1063
1052
  selectedItemsMapRef.current = newSelectedItemsMap; // 즉시 렌더링
1064
1053
 
1065
1054
  renderAllImmediate();
1066
- }, [markers]);
1055
+ }, [data]);
1067
1056
  return reactDom.createPortal(React__default["default"].createElement("div", {
1068
1057
  ref: containerRef,
1069
1058
  style: {
@@ -1074,27 +1063,96 @@ var WoongCanvasLayerComponent = function (props) {
1074
1063
  }), divElement);
1075
1064
  };
1076
1065
  /**
1077
- * 🔥 React.memo 최적화: 마커 배열과 selectedItems 변경 체크
1066
+ * 🚀 WoongCanvasLayer - Konva 기반 초고성능 마커/폴리곤 렌더링 컴포넌트
1067
+ *
1068
+ * ## 📌 주요 특징
1069
+ * - **30,000개 이상의 폴리곤/마커를 60fps로 렌더링**
1070
+ * - **Multi-Layer 아키텍처**: Base/Animation/Event 레이어 분리
1071
+ * - **Spatial Hash Grid**: O(1) 수준의 빠른 Hit Test
1072
+ * - **LRU 캐시**: 좌표 변환 결과 캐싱으로 성능 최적화
1073
+ * - **Viewport Culling**: 화면에 보이는 영역만 렌더링
1074
+ * - **Discriminated Union Props**: 타입 안전한 MARKER/POLYGON 모드
1075
+ *
1076
+ * ## 🎯 사용 방법
1077
+ *
1078
+ * ### 1️⃣ POLYGON 모드 (자동 렌더링)
1079
+ * ```tsx
1080
+ * <WoongCanvasLayer
1081
+ * dataType={CanvasDataType.POLYGON}
1082
+ * data={polygons}
1083
+ * baseFillColor="rgba(255, 100, 100, 0.5)"
1084
+ * baseStrokeColor="rgba(200, 50, 50, 0.8)"
1085
+ * baseLineWidth={2}
1086
+ * selectedFillColor="rgba(255, 193, 7, 0.7)"
1087
+ * selectedStrokeColor="rgba(255, 152, 0, 1)"
1088
+ * selectedLineWidth={4}
1089
+ * hoveredFillColor="rgba(100, 150, 255, 0.8)" // optional
1090
+ * hoveredStrokeColor="rgba(0, 100, 200, 1)" // optional
1091
+ * hoveredLineWidth={3} // optional
1092
+ * enableMultiSelect={true}
1093
+ * onClick={handleClick}
1094
+ * />
1095
+ * ```
1096
+ *
1097
+ * ### 2️⃣ MARKER 모드 (커스텀 렌더링)
1098
+ * ```tsx
1099
+ * <WoongCanvasLayer
1100
+ * dataType={CanvasDataType.MARKER}
1101
+ * data={markers}
1102
+ * renderBase={renderMarkerBase} // required
1103
+ * renderAnimation={renderMarkerAnimation} // optional
1104
+ * renderEvent={renderMarkerEvent} // optional
1105
+ * topOnHover={true}
1106
+ * onClick={handleClick}
1107
+ * />
1108
+ * ```
1109
+ *
1110
+ * ## 📊 데이터 형식
1111
+ * ```typescript
1112
+ * const data: KonvaCanvasMarkerData<T>[] = [
1113
+ * {
1114
+ * id: 'unique-id',
1115
+ * position: new Position(lat, lng),
1116
+ * // POLYGON: paths 필수
1117
+ * paths: [[[lat, lng], [lat, lng], ...]],
1118
+ * // MARKER: boxWidth/boxHeight 권장 (Hit Test 정확도)
1119
+ * boxWidth: 60,
1120
+ * boxHeight: 75,
1121
+ * // 커스텀 데이터
1122
+ * ...customData
1123
+ * }
1124
+ * ];
1125
+ * ```
1078
1126
  *
1079
- * 비교 전략:
1080
- * 1. markers 배열 비교
1081
- * 2. selectedItems 배열 비교 (외부 제어)
1127
+ * ## ⚡ 성능 최적화 팁
1128
+ * 1. **동적 boxWidth 계산**: `measureText()`로 실제 너비 계산 후 전달
1129
+ * 2. **enableViewportCulling**: 대량 데이터 시 필수 (기본 true)
1130
+ * 3. **selectedItems 외부 관리**: 상태를 외부에서 관리하여 리렌더링 최소화
1131
+ * 4. **React.memo 최적화**: 컴포넌트가 자동으로 불필요한 리렌더링 방지
1082
1132
  *
1083
- * 주의: JSON.stringify() 사용 금지! (매우 느림)
1133
+ * @template T 마커/폴리곤 데이터의 추가 속성 타입
1134
+ *
1135
+ * @example
1136
+ * // 동적 boxWidth 계산 예시
1137
+ * const tempCtx = document.createElement('canvas').getContext('2d');
1138
+ * tempCtx.font = 'bold 15px Arial';
1139
+ * const boxWidth = Math.max(60, tempCtx.measureText(text).width + 20);
1140
+ *
1141
+ * @see {@link https://github.com/your-repo/docs/WoongCanvasLayer.md} 전체 문서
1084
1142
  */
1085
1143
 
1086
1144
 
1087
1145
  var WoongCanvasLayer = React__default["default"].memo(WoongCanvasLayerComponent, function (prevProps, nextProps) {
1088
- // 1. markers 비교
1089
- var prevMarkers = prevProps.markers;
1090
- var nextMarkers = nextProps.markers; // 참조가 같으면 스킵
1146
+ // 1. data 비교
1147
+ var prevData = prevProps.data;
1148
+ var nextData = nextProps.data; // 참조가 같으면 스킵
1091
1149
 
1092
- if (prevMarkers !== nextMarkers) {
1150
+ if (prevData !== nextData) {
1093
1151
  // 길이가 다르면 변경됨
1094
- if (prevMarkers.length !== nextMarkers.length) return false; // 각 마커의 ID 비교
1152
+ if (prevData.length !== nextData.length) return false; // 각 데이터의 ID 비교
1095
1153
 
1096
- for (var i = 0; i < prevMarkers.length; i++) {
1097
- if (prevMarkers[i].id !== nextMarkers[i].id) {
1154
+ for (var i = 0; i < prevData.length; i++) {
1155
+ if (prevData[i].id !== nextData[i].id) {
1098
1156
  return false; // 변경됨 → 리렌더링
1099
1157
  }
1100
1158
  }
@@ -1109,18 +1167,6 @@ var WoongCanvasLayer = React__default["default"].memo(WoongCanvasLayerComponent,
1109
1167
  return false; // 변경됨 → 리렌더링
1110
1168
  }
1111
1169
 
1112
- if (prevProps.onClick !== nextProps.onClick) {
1113
- return false; // 변경됨 → 리렌더링
1114
- }
1115
-
1116
- if (prevProps.onMouseOver !== nextProps.onMouseOver) {
1117
- return false; // 변경됨 → 리렌더링
1118
- }
1119
-
1120
- if (prevProps.onMouseOut !== nextProps.onMouseOut) {
1121
- return false; // 변경됨 → 리렌더링
1122
- }
1123
-
1124
1170
  if (prevProps.disableInteraction !== nextProps.disableInteraction) {
1125
1171
  return false; // 변경됨 → 리렌더링
1126
1172
  }
@@ -40,10 +40,104 @@ export interface KonvaCanvasMarkerOption {
40
40
  */
41
41
  export declare type KonvaCanvasMarkerData<T = {}> = T & KonvaCanvasMarkerOption;
42
42
  /**
43
- * 렌더링 유틸리티 함수들
43
+ * 🛠️ 렌더링 유틸리티 함수들
44
+ *
45
+ * WoongCanvasLayer가 제공하는 헬퍼 함수 모음입니다.
46
+ * 커스텀 렌더링 함수 내에서 좌표 변환 시 사용하세요.
47
+ *
48
+ * ## 주요 기능
49
+ * - **자동 캐싱**: 좌표 변환 결과를 LRU 캐시에 저장 (성능 최적화)
50
+ * - **지도 좌표 → 화면 좌표**: 위경도를 픽셀 좌표로 자동 변환
51
+ * - **null 안전성**: 변환 실패 시 null 반환 (안전한 예외 처리)
52
+ *
53
+ * @template T 마커/폴리곤 데이터의 추가 속성 타입
54
+ *
55
+ * @example
56
+ * // 마커 렌더링 예시
57
+ * const renderBase = ({ ctx, items, utils }) => {
58
+ * for (const item of items) {
59
+ * const offset = utils.getOrComputeMarkerOffset(item);
60
+ * if (!offset) continue; // 변환 실패 시 스킵
61
+ *
62
+ * ctx.fillRect(offset.x, offset.y, 50, 50);
63
+ * }
64
+ * };
65
+ *
66
+ * @example
67
+ * // 폴리곤 렌더링 예시
68
+ * const renderBase = ({ ctx, items, utils }) => {
69
+ * for (const item of items) {
70
+ * const offsets = utils.getOrComputePolygonOffsets(item);
71
+ * if (!offsets) continue;
72
+ *
73
+ * for (const multiPolygon of offsets) {
74
+ * for (const polygon of multiPolygon) {
75
+ * ctx.beginPath();
76
+ * ctx.moveTo(polygon[0][0], polygon[0][1]);
77
+ * for (let i = 1; i < polygon.length; i++) {
78
+ * ctx.lineTo(polygon[i][0], polygon[i][1]);
79
+ * }
80
+ * ctx.closePath();
81
+ * ctx.fill();
82
+ * }
83
+ * }
84
+ * }
85
+ * };
44
86
  */
45
87
  export interface RenderUtils<T> {
88
+ /**
89
+ * 폴리곤의 위경도 좌표를 화면 픽셀 좌표로 변환합니다.
90
+ *
91
+ * - **자동 캐싱**: 동일한 폴리곤은 캐시에서 즉시 반환 (성능 최적화)
92
+ * - **MultiPolygon 지원**: GeoJSON MultiPolygon 형식 지원
93
+ * - **Donut Polygon 지원**: 구멍이 있는 폴리곤 지원
94
+ *
95
+ * @param polygonData 폴리곤 데이터 (paths 필드 필수)
96
+ * @returns 변환된 픽셀 좌표 배열 (4차원 배열) 또는 null (변환 실패 시)
97
+ *
98
+ * @example
99
+ * const offsets = utils.getOrComputePolygonOffsets(polygonItem);
100
+ * if (!offsets) return; // 변환 실패
101
+ *
102
+ * // offsets 구조: [MultiPolygon][Polygon][Point][x/y]
103
+ * for (const multiPolygon of offsets) {
104
+ * for (const polygon of multiPolygon) {
105
+ * ctx.beginPath();
106
+ * ctx.moveTo(polygon[0][0], polygon[0][1]);
107
+ * for (let i = 1; i < polygon.length; i++) {
108
+ * ctx.lineTo(polygon[i][0], polygon[i][1]);
109
+ * }
110
+ * ctx.closePath();
111
+ * ctx.fill();
112
+ * }
113
+ * }
114
+ */
46
115
  getOrComputePolygonOffsets: (polygonData: KonvaCanvasMarkerData<T>) => number[][][][] | null;
116
+ /**
117
+ * 마커의 위경도 좌표를 화면 픽셀 좌표로 변환합니다.
118
+ *
119
+ * - **자동 캐싱**: 동일한 마커는 캐시에서 즉시 반환 (성능 최적화)
120
+ * - **중심점 기준**: 반환된 좌표는 마커의 중심점 (x, y)
121
+ *
122
+ * @param markerData 마커 데이터 (position 필드 필수)
123
+ * @returns 변환된 픽셀 좌표 { x, y } 또는 null (변환 실패 시)
124
+ *
125
+ * @example
126
+ * const offset = utils.getOrComputeMarkerOffset(markerItem);
127
+ * if (!offset) return; // 변환 실패
128
+ *
129
+ * // offset.x, offset.y는 화면 픽셀 좌표
130
+ * const boxWidth = markerItem.boxWidth || 60;
131
+ * const boxHeight = markerItem.boxHeight || 75;
132
+ *
133
+ * // 중앙 정렬, 하단 기준으로 그리기
134
+ * ctx.fillRect(
135
+ * offset.x - boxWidth / 2,
136
+ * offset.y - boxHeight,
137
+ * boxWidth,
138
+ * boxHeight
139
+ * );
140
+ */
47
141
  getOrComputeMarkerOffset: (markerData: KonvaCanvasMarkerData<T>) => Offset | null;
48
142
  }
49
143
  /**
@@ -30,3 +30,19 @@ export declare const isPointInPolygonData: (clickedOffset: Offset, polygonData:
30
30
  */
31
31
  export declare const isPointInMarkerData: (clickedOffset: Offset, markerData: KonvaCanvasMarkerData<any>, getMarkerOffset: (data: KonvaCanvasMarkerData<any>) => Offset | null) => boolean;
32
32
  export declare const hexToRgba: (hexColor: string, alpha?: number) => string;
33
+ /**
34
+ * 텍스트 박스의 너비를 계산합니다.
35
+ *
36
+ * @param {Object} params - 파라미터 객체
37
+ * @param {string} params.text - 측정할 텍스트
38
+ * @param {string} params.fontConfig - 폰트 설정 (예: 'bold 16px Arial')
39
+ * @param {number} params.padding - 텍스트 박스에 적용할 패딩 값
40
+ * @param {number} params.minWidth - 최소 너비
41
+ * @returns {number} 계산된 텍스트 박스의 너비
42
+ */
43
+ export declare const calculateTextBoxWidth: ({ text, fontConfig, padding, minWidth, }: {
44
+ text: string;
45
+ fontConfig: string;
46
+ padding: number;
47
+ minWidth: number;
48
+ }) => number;
@@ -173,7 +173,31 @@ var hexToRgba = function (hexColor, alpha) {
173
173
 
174
174
  throw new Error('Invalid hex color format');
175
175
  };
176
+ var tempCanvas = document.createElement('canvas');
177
+ var tempCtx = tempCanvas.getContext('2d');
178
+ /**
179
+ * 텍스트 박스의 너비를 계산합니다.
180
+ *
181
+ * @param {Object} params - 파라미터 객체
182
+ * @param {string} params.text - 측정할 텍스트
183
+ * @param {string} params.fontConfig - 폰트 설정 (예: 'bold 16px Arial')
184
+ * @param {number} params.padding - 텍스트 박스에 적용할 패딩 값
185
+ * @param {number} params.minWidth - 최소 너비
186
+ * @returns {number} 계산된 텍스트 박스의 너비
187
+ */
188
+
189
+ var calculateTextBoxWidth = function (_a) {
190
+ var text = _a.text,
191
+ fontConfig = _a.fontConfig,
192
+ padding = _a.padding,
193
+ minWidth = _a.minWidth;
194
+ if (!tempCtx) return 0;
195
+ tempCtx.font = fontConfig;
196
+ var textWidth = tempCtx.measureText(text).width;
197
+ return Math.max(minWidth, textWidth + padding);
198
+ };
176
199
 
200
+ exports.calculateTextBoxWidth = calculateTextBoxWidth;
177
201
  exports.computeMarkerOffset = computeMarkerOffset;
178
202
  exports.computePolygonOffsets = computePolygonOffsets;
179
203
  exports.hexToRgba = hexToRgba;
@@ -22,6 +22,7 @@ require('../core/advanced/canvas/CanvasMarkerClaude.js');
22
22
  require('../core/advanced/MapLoadingComponents.js');
23
23
  require('../core/advanced/woongCanvas/WoongCanvasLayer.js');
24
24
  require('../core/advanced/woongCanvas/shared/types.js');
25
+ require('../core/advanced/woongCanvas/shared/utils.js');
25
26
  require('../core/advanced/woongCanvas/shared/context.js');
26
27
  require('../core/advanced/woongCanvas/shared/performance.js');
27
28
  require('../core/wrapper/MapControlWrapper.js');
@@ -23,6 +23,7 @@ require('../core/advanced/canvas/CanvasMarkerClaude.js');
23
23
  require('../core/advanced/MapLoadingComponents.js');
24
24
  require('../core/advanced/woongCanvas/WoongCanvasLayer.js');
25
25
  require('../core/advanced/woongCanvas/shared/types.js');
26
+ require('../core/advanced/woongCanvas/shared/utils.js');
26
27
  require('../core/advanced/woongCanvas/shared/context.js');
27
28
  require('../core/advanced/woongCanvas/shared/performance.js');
28
29
  require('../core/wrapper/MapControlWrapper.js');
@@ -22,6 +22,7 @@ require('../core/advanced/canvas/CanvasMarkerClaude.js');
22
22
  require('../core/advanced/MapLoadingComponents.js');
23
23
  require('../core/advanced/woongCanvas/WoongCanvasLayer.js');
24
24
  require('../core/advanced/woongCanvas/shared/types.js');
25
+ require('../core/advanced/woongCanvas/shared/utils.js');
25
26
  require('../core/advanced/woongCanvas/shared/context.js');
26
27
  require('../core/advanced/woongCanvas/shared/performance.js');
27
28
  require('../core/wrapper/MapControlWrapper.js');
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,7 +5656,7 @@ 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"]); // --------------------------------------------------------------------------
5649
5660
  // Hooks & Context
5650
5661
  // --------------------------------------------------------------------------
5651
5662
 
@@ -5670,9 +5681,9 @@ var WoongCanvasLayerComponent = function (props) {
5670
5681
  // Data Refs - 선택 및 Hover 상태 관리
5671
5682
  // --------------------------------------------------------------------------
5672
5683
 
5673
- /** markers prop을 ref로 추적 (stale closure 방지, useEffect에서 동기화) */
5684
+ /** data prop을 ref로 추적 (stale closure 방지, useEffect에서 동기화) */
5674
5685
 
5675
- var markersRef = useRef(markers); // --------------------------------------------------------------------------
5686
+ var markersRef = useRef(data); // --------------------------------------------------------------------------
5676
5687
  // State Refs - 선택 및 Hover 상태 관리
5677
5688
  // --------------------------------------------------------------------------
5678
5689
 
@@ -5844,6 +5855,7 @@ var WoongCanvasLayerComponent = function (props) {
5844
5855
  };
5845
5856
  /**
5846
5857
  * 마커 좌표 변환 결과를 캐시하고 반환
5858
+ *
5847
5859
  * @param markerData 마커 데이터
5848
5860
  * @returns 변환된 좌표 또는 null
5849
5861
  */
@@ -6314,11 +6326,11 @@ var WoongCanvasLayerComponent = function (props) {
6314
6326
 
6315
6327
  try {
6316
6328
  var clickedOffset = controller.positionToOffset(event.param.position);
6317
- var data = findData(clickedOffset);
6329
+ var data_1 = findData(clickedOffset);
6318
6330
 
6319
- if (data) {
6320
- handleLocalClick(data);
6321
- onClick(data, selectedIdsRef.current);
6331
+ if (data_1) {
6332
+ handleLocalClick(data_1);
6333
+ onClick(data_1, selectedIdsRef.current);
6322
6334
  }
6323
6335
  } catch (error) {
6324
6336
  console.error('[WoongKonvaMarker] handleClick error:', error);
@@ -6595,13 +6607,13 @@ var WoongCanvasLayerComponent = function (props) {
6595
6607
 
6596
6608
  doRenderEvent();
6597
6609
  }, [externalSelectedItem]); // --------------------------------------------------------------------------
6598
- // Lifecycle: 마커 데이터 변경 시 렌더링
6610
+ // Lifecycle: 데이터 변경 시 렌더링
6599
6611
  // --------------------------------------------------------------------------
6600
6612
 
6601
6613
  useEffect(function () {
6602
6614
  if (!stageRef.current) return; // markersRef 동기화
6603
6615
 
6604
- markersRef.current = markers; // 데이터 변경 시 즉시 transform 제거 및 캐시 정리 (겹침 방지)
6616
+ markersRef.current = data; // 데이터 변경 시 즉시 transform 제거 및 캐시 정리 (겹침 방지)
6605
6617
 
6606
6618
  if (containerRef.current) {
6607
6619
  containerRef.current.style.transform = '';
@@ -6618,18 +6630,18 @@ var WoongCanvasLayerComponent = function (props) {
6618
6630
  /**
6619
6631
  * 선택 상태 동기화 (최적화 버전)
6620
6632
  *
6621
- * markers가 변경되면 selectedItemsMapRef도 업데이트 필요
6633
+ * data가 변경되면 selectedItemsMapRef도 업데이트 필요
6622
6634
  * (참조가 바뀌므로 기존 Map의 데이터는 stale 상태)
6623
6635
  *
6624
- * 🔥 중요: 화면 밖 마커도 선택 상태 유지!
6625
- * - 현재 markers에 있으면 최신 데이터로 업데이트
6636
+ * 🔥 중요: 화면 밖 데이터도 선택 상태 유지!
6637
+ * - 현재 data에 있으면 최신 데이터로 업데이트
6626
6638
  * - 없으면 기존 selectedItemsMapRef의 데이터 유지
6627
6639
  *
6628
- * 최적화: markers를 Map으로 먼저 변환하여 find() 순회 제거
6629
- * - O(전체 마커 수 + 선택된 개수) - 매우 효율적
6640
+ * 최적화: data를 Map으로 먼저 변환하여 find() 순회 제거
6641
+ * - O(전체 데이터 수 + 선택된 개수) - 매우 효율적
6630
6642
  */
6631
6643
 
6632
- var markersMap = new Map(markers.map(function (m) {
6644
+ var markersMap = new Map(data.map(function (m) {
6633
6645
  return [m.id, m];
6634
6646
  }));
6635
6647
  var newSelectedItemsMap = new Map();
@@ -6652,7 +6664,7 @@ var WoongCanvasLayerComponent = function (props) {
6652
6664
  selectedItemsMapRef.current = newSelectedItemsMap; // 즉시 렌더링
6653
6665
 
6654
6666
  renderAllImmediate();
6655
- }, [markers]);
6667
+ }, [data]);
6656
6668
  return createPortal(React.createElement("div", {
6657
6669
  ref: containerRef,
6658
6670
  style: {
@@ -6663,27 +6675,96 @@ var WoongCanvasLayerComponent = function (props) {
6663
6675
  }), divElement);
6664
6676
  };
6665
6677
  /**
6666
- * 🔥 React.memo 최적화: 마커 배열과 selectedItems 변경 체크
6678
+ * 🚀 WoongCanvasLayer - Konva 기반 초고성능 마커/폴리곤 렌더링 컴포넌트
6679
+ *
6680
+ * ## 📌 주요 특징
6681
+ * - **30,000개 이상의 폴리곤/마커를 60fps로 렌더링**
6682
+ * - **Multi-Layer 아키텍처**: Base/Animation/Event 레이어 분리
6683
+ * - **Spatial Hash Grid**: O(1) 수준의 빠른 Hit Test
6684
+ * - **LRU 캐시**: 좌표 변환 결과 캐싱으로 성능 최적화
6685
+ * - **Viewport Culling**: 화면에 보이는 영역만 렌더링
6686
+ * - **Discriminated Union Props**: 타입 안전한 MARKER/POLYGON 모드
6687
+ *
6688
+ * ## 🎯 사용 방법
6689
+ *
6690
+ * ### 1️⃣ POLYGON 모드 (자동 렌더링)
6691
+ * ```tsx
6692
+ * <WoongCanvasLayer
6693
+ * dataType={CanvasDataType.POLYGON}
6694
+ * data={polygons}
6695
+ * baseFillColor="rgba(255, 100, 100, 0.5)"
6696
+ * baseStrokeColor="rgba(200, 50, 50, 0.8)"
6697
+ * baseLineWidth={2}
6698
+ * selectedFillColor="rgba(255, 193, 7, 0.7)"
6699
+ * selectedStrokeColor="rgba(255, 152, 0, 1)"
6700
+ * selectedLineWidth={4}
6701
+ * hoveredFillColor="rgba(100, 150, 255, 0.8)" // optional
6702
+ * hoveredStrokeColor="rgba(0, 100, 200, 1)" // optional
6703
+ * hoveredLineWidth={3} // optional
6704
+ * enableMultiSelect={true}
6705
+ * onClick={handleClick}
6706
+ * />
6707
+ * ```
6667
6708
  *
6668
- * 비교 전략:
6669
- * 1. markers 배열 비교
6670
- * 2. selectedItems 배열 비교 (외부 제어)
6709
+ * ### 2️⃣ MARKER 모드 (커스텀 렌더링)
6710
+ * ```tsx
6711
+ * <WoongCanvasLayer
6712
+ * dataType={CanvasDataType.MARKER}
6713
+ * data={markers}
6714
+ * renderBase={renderMarkerBase} // required
6715
+ * renderAnimation={renderMarkerAnimation} // optional
6716
+ * renderEvent={renderMarkerEvent} // optional
6717
+ * topOnHover={true}
6718
+ * onClick={handleClick}
6719
+ * />
6720
+ * ```
6671
6721
  *
6672
- * 주의: JSON.stringify() 사용 금지! (매우 느림)
6722
+ * ## 📊 데이터 형식
6723
+ * ```typescript
6724
+ * const data: KonvaCanvasMarkerData<T>[] = [
6725
+ * {
6726
+ * id: 'unique-id',
6727
+ * position: new Position(lat, lng),
6728
+ * // POLYGON: paths 필수
6729
+ * paths: [[[lat, lng], [lat, lng], ...]],
6730
+ * // MARKER: boxWidth/boxHeight 권장 (Hit Test 정확도)
6731
+ * boxWidth: 60,
6732
+ * boxHeight: 75,
6733
+ * // 커스텀 데이터
6734
+ * ...customData
6735
+ * }
6736
+ * ];
6737
+ * ```
6738
+ *
6739
+ * ## ⚡ 성능 최적화 팁
6740
+ * 1. **동적 boxWidth 계산**: `measureText()`로 실제 너비 계산 후 전달
6741
+ * 2. **enableViewportCulling**: 대량 데이터 시 필수 (기본 true)
6742
+ * 3. **selectedItems 외부 관리**: 상태를 외부에서 관리하여 리렌더링 최소화
6743
+ * 4. **React.memo 최적화**: 컴포넌트가 자동으로 불필요한 리렌더링 방지
6744
+ *
6745
+ * @template T 마커/폴리곤 데이터의 추가 속성 타입
6746
+ *
6747
+ * @example
6748
+ * // 동적 boxWidth 계산 예시
6749
+ * const tempCtx = document.createElement('canvas').getContext('2d');
6750
+ * tempCtx.font = 'bold 15px Arial';
6751
+ * const boxWidth = Math.max(60, tempCtx.measureText(text).width + 20);
6752
+ *
6753
+ * @see {@link https://github.com/your-repo/docs/WoongCanvasLayer.md} 전체 문서
6673
6754
  */
6674
6755
 
6675
6756
 
6676
6757
  var WoongCanvasLayer = React.memo(WoongCanvasLayerComponent, function (prevProps, nextProps) {
6677
- // 1. markers 비교
6678
- var prevMarkers = prevProps.markers;
6679
- var nextMarkers = nextProps.markers; // 참조가 같으면 스킵
6758
+ // 1. data 비교
6759
+ var prevData = prevProps.data;
6760
+ var nextData = nextProps.data; // 참조가 같으면 스킵
6680
6761
 
6681
- if (prevMarkers !== nextMarkers) {
6762
+ if (prevData !== nextData) {
6682
6763
  // 길이가 다르면 변경됨
6683
- if (prevMarkers.length !== nextMarkers.length) return false; // 각 마커의 ID 비교
6764
+ if (prevData.length !== nextData.length) return false; // 각 데이터의 ID 비교
6684
6765
 
6685
- for (var i = 0; i < prevMarkers.length; i++) {
6686
- if (prevMarkers[i].id !== nextMarkers[i].id) {
6766
+ for (var i = 0; i < prevData.length; i++) {
6767
+ if (prevData[i].id !== nextData[i].id) {
6687
6768
  return false; // 변경됨 → 리렌더링
6688
6769
  }
6689
6770
  }
@@ -6698,18 +6779,6 @@ var WoongCanvasLayer = React.memo(WoongCanvasLayerComponent, function (prevProps
6698
6779
  return false; // 변경됨 → 리렌더링
6699
6780
  }
6700
6781
 
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
6782
  if (prevProps.disableInteraction !== nextProps.disableInteraction) {
6714
6783
  return false; // 변경됨 → 리렌더링
6715
6784
  }
@@ -9548,4 +9617,4 @@ function MintMap(_a) {
9548
9617
  }), loading));
9549
9618
  }
9550
9619
 
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 };
9620
+ 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,7 +5660,7 @@
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"]); // --------------------------------------------------------------------------
5653
5664
  // Hooks & Context
5654
5665
  // --------------------------------------------------------------------------
5655
5666
 
@@ -5674,9 +5685,9 @@
5674
5685
  // Data Refs - 선택 및 Hover 상태 관리
5675
5686
  // --------------------------------------------------------------------------
5676
5687
 
5677
- /** markers prop을 ref로 추적 (stale closure 방지, useEffect에서 동기화) */
5688
+ /** data prop을 ref로 추적 (stale closure 방지, useEffect에서 동기화) */
5678
5689
 
5679
- var markersRef = React.useRef(markers); // --------------------------------------------------------------------------
5690
+ var markersRef = React.useRef(data); // --------------------------------------------------------------------------
5680
5691
  // State Refs - 선택 및 Hover 상태 관리
5681
5692
  // --------------------------------------------------------------------------
5682
5693
 
@@ -5848,6 +5859,7 @@
5848
5859
  };
5849
5860
  /**
5850
5861
  * 마커 좌표 변환 결과를 캐시하고 반환
5862
+ *
5851
5863
  * @param markerData 마커 데이터
5852
5864
  * @returns 변환된 좌표 또는 null
5853
5865
  */
@@ -6318,11 +6330,11 @@
6318
6330
 
6319
6331
  try {
6320
6332
  var clickedOffset = controller.positionToOffset(event.param.position);
6321
- var data = findData(clickedOffset);
6333
+ var data_1 = findData(clickedOffset);
6322
6334
 
6323
- if (data) {
6324
- handleLocalClick(data);
6325
- onClick(data, selectedIdsRef.current);
6335
+ if (data_1) {
6336
+ handleLocalClick(data_1);
6337
+ onClick(data_1, selectedIdsRef.current);
6326
6338
  }
6327
6339
  } catch (error) {
6328
6340
  console.error('[WoongKonvaMarker] handleClick error:', error);
@@ -6599,13 +6611,13 @@
6599
6611
 
6600
6612
  doRenderEvent();
6601
6613
  }, [externalSelectedItem]); // --------------------------------------------------------------------------
6602
- // Lifecycle: 마커 데이터 변경 시 렌더링
6614
+ // Lifecycle: 데이터 변경 시 렌더링
6603
6615
  // --------------------------------------------------------------------------
6604
6616
 
6605
6617
  React.useEffect(function () {
6606
6618
  if (!stageRef.current) return; // markersRef 동기화
6607
6619
 
6608
- markersRef.current = markers; // 데이터 변경 시 즉시 transform 제거 및 캐시 정리 (겹침 방지)
6620
+ markersRef.current = data; // 데이터 변경 시 즉시 transform 제거 및 캐시 정리 (겹침 방지)
6609
6621
 
6610
6622
  if (containerRef.current) {
6611
6623
  containerRef.current.style.transform = '';
@@ -6622,18 +6634,18 @@
6622
6634
  /**
6623
6635
  * 선택 상태 동기화 (최적화 버전)
6624
6636
  *
6625
- * markers가 변경되면 selectedItemsMapRef도 업데이트 필요
6637
+ * data가 변경되면 selectedItemsMapRef도 업데이트 필요
6626
6638
  * (참조가 바뀌므로 기존 Map의 데이터는 stale 상태)
6627
6639
  *
6628
- * 🔥 중요: 화면 밖 마커도 선택 상태 유지!
6629
- * - 현재 markers에 있으면 최신 데이터로 업데이트
6640
+ * 🔥 중요: 화면 밖 데이터도 선택 상태 유지!
6641
+ * - 현재 data에 있으면 최신 데이터로 업데이트
6630
6642
  * - 없으면 기존 selectedItemsMapRef의 데이터 유지
6631
6643
  *
6632
- * 최적화: markers를 Map으로 먼저 변환하여 find() 순회 제거
6633
- * - O(전체 마커 수 + 선택된 개수) - 매우 효율적
6644
+ * 최적화: data를 Map으로 먼저 변환하여 find() 순회 제거
6645
+ * - O(전체 데이터 수 + 선택된 개수) - 매우 효율적
6634
6646
  */
6635
6647
 
6636
- var markersMap = new Map(markers.map(function (m) {
6648
+ var markersMap = new Map(data.map(function (m) {
6637
6649
  return [m.id, m];
6638
6650
  }));
6639
6651
  var newSelectedItemsMap = new Map();
@@ -6656,7 +6668,7 @@
6656
6668
  selectedItemsMapRef.current = newSelectedItemsMap; // 즉시 렌더링
6657
6669
 
6658
6670
  renderAllImmediate();
6659
- }, [markers]);
6671
+ }, [data]);
6660
6672
  return reactDom.createPortal(React__default["default"].createElement("div", {
6661
6673
  ref: containerRef,
6662
6674
  style: {
@@ -6667,27 +6679,96 @@
6667
6679
  }), divElement);
6668
6680
  };
6669
6681
  /**
6670
- * 🔥 React.memo 최적화: 마커 배열과 selectedItems 변경 체크
6682
+ * 🚀 WoongCanvasLayer - Konva 기반 초고성능 마커/폴리곤 렌더링 컴포넌트
6683
+ *
6684
+ * ## 📌 주요 특징
6685
+ * - **30,000개 이상의 폴리곤/마커를 60fps로 렌더링**
6686
+ * - **Multi-Layer 아키텍처**: Base/Animation/Event 레이어 분리
6687
+ * - **Spatial Hash Grid**: O(1) 수준의 빠른 Hit Test
6688
+ * - **LRU 캐시**: 좌표 변환 결과 캐싱으로 성능 최적화
6689
+ * - **Viewport Culling**: 화면에 보이는 영역만 렌더링
6690
+ * - **Discriminated Union Props**: 타입 안전한 MARKER/POLYGON 모드
6691
+ *
6692
+ * ## 🎯 사용 방법
6693
+ *
6694
+ * ### 1️⃣ POLYGON 모드 (자동 렌더링)
6695
+ * ```tsx
6696
+ * <WoongCanvasLayer
6697
+ * dataType={CanvasDataType.POLYGON}
6698
+ * data={polygons}
6699
+ * baseFillColor="rgba(255, 100, 100, 0.5)"
6700
+ * baseStrokeColor="rgba(200, 50, 50, 0.8)"
6701
+ * baseLineWidth={2}
6702
+ * selectedFillColor="rgba(255, 193, 7, 0.7)"
6703
+ * selectedStrokeColor="rgba(255, 152, 0, 1)"
6704
+ * selectedLineWidth={4}
6705
+ * hoveredFillColor="rgba(100, 150, 255, 0.8)" // optional
6706
+ * hoveredStrokeColor="rgba(0, 100, 200, 1)" // optional
6707
+ * hoveredLineWidth={3} // optional
6708
+ * enableMultiSelect={true}
6709
+ * onClick={handleClick}
6710
+ * />
6711
+ * ```
6671
6712
  *
6672
- * 비교 전략:
6673
- * 1. markers 배열 비교
6674
- * 2. selectedItems 배열 비교 (외부 제어)
6713
+ * ### 2️⃣ MARKER 모드 (커스텀 렌더링)
6714
+ * ```tsx
6715
+ * <WoongCanvasLayer
6716
+ * dataType={CanvasDataType.MARKER}
6717
+ * data={markers}
6718
+ * renderBase={renderMarkerBase} // required
6719
+ * renderAnimation={renderMarkerAnimation} // optional
6720
+ * renderEvent={renderMarkerEvent} // optional
6721
+ * topOnHover={true}
6722
+ * onClick={handleClick}
6723
+ * />
6724
+ * ```
6675
6725
  *
6676
- * 주의: JSON.stringify() 사용 금지! (매우 느림)
6726
+ * ## 📊 데이터 형식
6727
+ * ```typescript
6728
+ * const data: KonvaCanvasMarkerData<T>[] = [
6729
+ * {
6730
+ * id: 'unique-id',
6731
+ * position: new Position(lat, lng),
6732
+ * // POLYGON: paths 필수
6733
+ * paths: [[[lat, lng], [lat, lng], ...]],
6734
+ * // MARKER: boxWidth/boxHeight 권장 (Hit Test 정확도)
6735
+ * boxWidth: 60,
6736
+ * boxHeight: 75,
6737
+ * // 커스텀 데이터
6738
+ * ...customData
6739
+ * }
6740
+ * ];
6741
+ * ```
6742
+ *
6743
+ * ## ⚡ 성능 최적화 팁
6744
+ * 1. **동적 boxWidth 계산**: `measureText()`로 실제 너비 계산 후 전달
6745
+ * 2. **enableViewportCulling**: 대량 데이터 시 필수 (기본 true)
6746
+ * 3. **selectedItems 외부 관리**: 상태를 외부에서 관리하여 리렌더링 최소화
6747
+ * 4. **React.memo 최적화**: 컴포넌트가 자동으로 불필요한 리렌더링 방지
6748
+ *
6749
+ * @template T 마커/폴리곤 데이터의 추가 속성 타입
6750
+ *
6751
+ * @example
6752
+ * // 동적 boxWidth 계산 예시
6753
+ * const tempCtx = document.createElement('canvas').getContext('2d');
6754
+ * tempCtx.font = 'bold 15px Arial';
6755
+ * const boxWidth = Math.max(60, tempCtx.measureText(text).width + 20);
6756
+ *
6757
+ * @see {@link https://github.com/your-repo/docs/WoongCanvasLayer.md} 전체 문서
6677
6758
  */
6678
6759
 
6679
6760
 
6680
6761
  var WoongCanvasLayer = React__default["default"].memo(WoongCanvasLayerComponent, function (prevProps, nextProps) {
6681
- // 1. markers 비교
6682
- var prevMarkers = prevProps.markers;
6683
- var nextMarkers = nextProps.markers; // 참조가 같으면 스킵
6762
+ // 1. data 비교
6763
+ var prevData = prevProps.data;
6764
+ var nextData = nextProps.data; // 참조가 같으면 스킵
6684
6765
 
6685
- if (prevMarkers !== nextMarkers) {
6766
+ if (prevData !== nextData) {
6686
6767
  // 길이가 다르면 변경됨
6687
- if (prevMarkers.length !== nextMarkers.length) return false; // 각 마커의 ID 비교
6768
+ if (prevData.length !== nextData.length) return false; // 각 데이터의 ID 비교
6688
6769
 
6689
- for (var i = 0; i < prevMarkers.length; i++) {
6690
- if (prevMarkers[i].id !== nextMarkers[i].id) {
6770
+ for (var i = 0; i < prevData.length; i++) {
6771
+ if (prevData[i].id !== nextData[i].id) {
6691
6772
  return false; // 변경됨 → 리렌더링
6692
6773
  }
6693
6774
  }
@@ -6702,18 +6783,6 @@
6702
6783
  return false; // 변경됨 → 리렌더링
6703
6784
  }
6704
6785
 
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
6786
  if (prevProps.disableInteraction !== nextProps.disableInteraction) {
6718
6787
  return false; // 변경됨 → 리렌더링
6719
6788
  }
@@ -9597,6 +9666,7 @@
9597
9666
  exports.SpatialHashGrid = SpatialHashGrid;
9598
9667
  exports.Status = Status;
9599
9668
  exports.WoongCanvasLayer = WoongCanvasLayer;
9669
+ exports.calculateTextBoxWidth = calculateTextBoxWidth;
9600
9670
  exports.computeMarkerOffset = computeMarkerOffset;
9601
9671
  exports.computePolygonOffsets = computePolygonOffsets;
9602
9672
  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.12",
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;