@mint-ui/map 1.1.2 → 1.2.0-test.1
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/.claude/settings.local.json +16 -0
- package/.vscode/settings.json +11 -0
- package/CLAUDE.md +100 -0
- package/README.md +190 -0
- package/dist/components/mint-map/core/MintMapCore.js +4 -1
- package/dist/components/mint-map/core/advanced/canvas/CanvasMarkerClaude.d.ts +63 -0
- package/dist/components/mint-map/core/advanced/canvas/CanvasMarkerClaude.js +1084 -0
- package/dist/components/mint-map/core/advanced/canvas/CanvasMarkerHanquf.d.ts +22 -0
- package/dist/components/mint-map/core/advanced/canvas/CanvasMarkerHanquf.js +413 -0
- package/dist/components/mint-map/core/advanced/canvas/index.d.ts +2 -0
- package/dist/components/mint-map/core/advanced/woongCanvas/ClusterMarker.d.ts +11 -0
- package/dist/components/mint-map/core/advanced/woongCanvas/WoongKonvaMarker.d.ts +48 -0
- package/dist/components/mint-map/core/advanced/woongCanvas/shared/context.d.ts +31 -0
- package/dist/components/mint-map/core/advanced/woongCanvas/shared/context.js +159 -0
- package/dist/components/mint-map/core/advanced/woongCanvas/shared/index.d.ts +4 -0
- package/dist/components/mint-map/core/advanced/woongCanvas/shared/performance.d.ts +161 -0
- package/dist/components/mint-map/core/advanced/woongCanvas/shared/types.d.ts +121 -0
- package/dist/components/mint-map/core/advanced/woongCanvas/shared/utils.d.ts +23 -0
- package/dist/components/mint-map/core/util/geohash.d.ts +2 -0
- package/dist/components/mint-map/core/util/geohash.js +125 -0
- package/dist/components/mint-map/google/GoogleMintMapController.js +1 -0
- package/dist/components/mint-map/kakao/KakaoMintMapController.js +1 -0
- package/dist/components/mint-map/naver/NaverMintMapController.js +1 -0
- package/dist/index.es.js +1863 -137
- package/dist/index.js +4 -0
- package/dist/index.umd.js +1862 -134
- package/dist/mock.d.ts +133 -0
- package/package.json +17 -4
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { MarkerOptions, Offset, Position } from "../../../types";
|
|
3
|
+
export interface CanvasMarkerRendererParamsHanquf<T> {
|
|
4
|
+
context: CanvasRenderingContext2D;
|
|
5
|
+
offset: Offset[];
|
|
6
|
+
payload?: CanvasMarkerDataHanquf<T>;
|
|
7
|
+
}
|
|
8
|
+
export interface CanvasMarkerOptionHanquf {
|
|
9
|
+
position?: Position[];
|
|
10
|
+
visible?: boolean;
|
|
11
|
+
hitRadius?: number;
|
|
12
|
+
}
|
|
13
|
+
export declare type CanvasMarkerDataHanquf<T> = T & CanvasMarkerOptionHanquf;
|
|
14
|
+
export declare type CanvasMarkerRenderFunctionHanquf<T> = (params: CanvasMarkerRendererParamsHanquf<T>) => void;
|
|
15
|
+
export interface CanvasMarkerPropsHanquf<T> extends Pick<MarkerOptions, 'zIndex' | 'anchor' | 'visible'> {
|
|
16
|
+
renderer: CanvasMarkerRenderFunctionHanquf<T>;
|
|
17
|
+
data: CanvasMarkerDataHanquf<T>[];
|
|
18
|
+
onMouseOver?: (item: CanvasMarkerDataHanquf<T>) => void;
|
|
19
|
+
onMouseOut?: (item: CanvasMarkerDataHanquf<T>) => void;
|
|
20
|
+
onClick?: (item: CanvasMarkerDataHanquf<T>) => void;
|
|
21
|
+
}
|
|
22
|
+
export declare function CanvasMarkerHanquf<T>({ renderer, data, onMouseOver, onMouseOut, onClick, ...options }: CanvasMarkerPropsHanquf<T>): React.ReactPortal;
|
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var tslib = require('tslib');
|
|
6
|
+
var React = require('react');
|
|
7
|
+
var MintMapProvider = require('../../provider/MintMapProvider.js');
|
|
8
|
+
var MapDrawables = require('../../../types/MapDrawables.js');
|
|
9
|
+
require('../../../types/MapTypes.js');
|
|
10
|
+
require('../../../types/MapEventTypes.js');
|
|
11
|
+
var reactDom = require('react-dom');
|
|
12
|
+
var canvasUtil = require('./draw/canvas-util.js');
|
|
13
|
+
var geohash = require('../../util/geohash.js');
|
|
14
|
+
|
|
15
|
+
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
16
|
+
|
|
17
|
+
var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
|
|
18
|
+
|
|
19
|
+
function CanvasMarkerHanquf(_a) {
|
|
20
|
+
var renderer = _a.renderer,
|
|
21
|
+
data = _a.data,
|
|
22
|
+
onMouseOver = _a.onMouseOver,
|
|
23
|
+
onMouseOut = _a.onMouseOut,
|
|
24
|
+
onClick = _a.onClick,
|
|
25
|
+
options = tslib.__rest(_a, ["renderer", "data", "onMouseOver", "onMouseOut", "onClick"]); //controller
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
var controller = MintMapProvider.useMintMapController(); //element
|
|
29
|
+
|
|
30
|
+
var divRef = React.useRef(document.createElement('div'));
|
|
31
|
+
var divElement = divRef.current; //canvas container ref
|
|
32
|
+
|
|
33
|
+
var containerRef = React.useRef(null); //canvas ref
|
|
34
|
+
|
|
35
|
+
var canvasRef = React.useRef(null); //canvas context
|
|
36
|
+
|
|
37
|
+
var contextRef = React.useRef(); //marker
|
|
38
|
+
|
|
39
|
+
var markerRef = React.useRef(); // interaction states
|
|
40
|
+
|
|
41
|
+
var hoveredRef = React.useRef(null);
|
|
42
|
+
var clickedRef = React.useRef(null); // geohash index: hash -> items
|
|
43
|
+
|
|
44
|
+
var geoIndexRef = React.useRef(new Map());
|
|
45
|
+
var geoPrecisionRef = React.useRef(6); // drag translate state
|
|
46
|
+
|
|
47
|
+
var prevCenterOffsetRef = React.useRef(null);
|
|
48
|
+
var accumTranslateRef = React.useRef({
|
|
49
|
+
x: 0,
|
|
50
|
+
y: 0
|
|
51
|
+
});
|
|
52
|
+
var draggingRef = React.useRef(false); //create object
|
|
53
|
+
|
|
54
|
+
React.useEffect(function () {
|
|
55
|
+
divElement.style.width = 'fit-content';
|
|
56
|
+
divElement.style.pointerEvents = 'none';
|
|
57
|
+
return function () {
|
|
58
|
+
if (markerRef.current) {
|
|
59
|
+
controller.clearDrawable(markerRef.current);
|
|
60
|
+
markerRef.current = undefined;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}, []); //create / update object
|
|
64
|
+
|
|
65
|
+
React.useEffect(function () {
|
|
66
|
+
if (options) {
|
|
67
|
+
var bounds = controller.getCurrBounds();
|
|
68
|
+
|
|
69
|
+
var markerOptions = tslib.__assign({
|
|
70
|
+
position: bounds.nw
|
|
71
|
+
}, options);
|
|
72
|
+
|
|
73
|
+
if (markerRef.current) {
|
|
74
|
+
controller.updateMarker(markerRef.current, markerOptions);
|
|
75
|
+
} else {
|
|
76
|
+
markerRef.current = new MapDrawables.Marker(markerOptions);
|
|
77
|
+
markerRef.current.element = divElement;
|
|
78
|
+
controller.createMarker(markerRef.current); //disablePointerEvent 처리
|
|
79
|
+
|
|
80
|
+
if (divElement.parentElement) {
|
|
81
|
+
divElement.style.pointerEvents = 'none';
|
|
82
|
+
divElement.parentElement.style.pointerEvents = 'none';
|
|
83
|
+
} //z-index 처리
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
if (options.zIndex !== undefined) {
|
|
87
|
+
controller.setMarkerZIndex(markerRef.current, options.zIndex);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}, [options]);
|
|
92
|
+
|
|
93
|
+
var handleIdle = function () {
|
|
94
|
+
// 클리어
|
|
95
|
+
canvasUtil.clearRect(canvasRef.current, contextRef.current); // 표시 복구 (드래그/줌 중 숨김 처리 복원)
|
|
96
|
+
|
|
97
|
+
containerRef.current && (containerRef.current.style.visibility = ''); // 드래그 종료 처리: 변환 초기화 및 기준 위치 갱신
|
|
98
|
+
|
|
99
|
+
draggingRef.current = false;
|
|
100
|
+
prevCenterOffsetRef.current = null;
|
|
101
|
+
accumTranslateRef.current = {
|
|
102
|
+
x: 0,
|
|
103
|
+
y: 0
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
if (containerRef.current) {
|
|
107
|
+
containerRef.current.style.transform = '';
|
|
108
|
+
} // 마커 이동
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
var bounds = controller.getCurrBounds();
|
|
112
|
+
|
|
113
|
+
var markerOptions = tslib.__assign({
|
|
114
|
+
position: bounds.nw
|
|
115
|
+
}, options);
|
|
116
|
+
|
|
117
|
+
markerRef.current && controller.updateMarker(markerRef.current, markerOptions); // 렌더링 (hover/click item last)
|
|
118
|
+
|
|
119
|
+
canvasUtil.renderMain(controller, rendererRef.current, containerRef.current, canvasRef.current, contextRef.current, buildOrderedData());
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
var handleZoomStart = function () {
|
|
123
|
+
containerRef.current && (containerRef.current.style.visibility = 'hidden');
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
var handleZoomEnd = function () {
|
|
127
|
+
containerRef.current && (containerRef.current.style.visibility = '');
|
|
128
|
+
}; //initialize
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
React.useEffect(function () {
|
|
132
|
+
var resizeObserver; // hover/out 공통 처리기 (mouse 좌표 기반)
|
|
133
|
+
|
|
134
|
+
var updateHoverByMouseXY = function (mouseX, mouseY) {
|
|
135
|
+
var hit = hitTest(mouseX, mouseY);
|
|
136
|
+
|
|
137
|
+
if ((hit === null || hit === void 0 ? void 0 : hit.item) !== hoveredRef.current) {
|
|
138
|
+
if (hoveredRef.current && onMouseOut) {
|
|
139
|
+
onMouseOut(hoveredRef.current);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
hoveredRef.current = (hit === null || hit === void 0 ? void 0 : hit.item) || null;
|
|
143
|
+
|
|
144
|
+
if ((hit === null || hit === void 0 ? void 0 : hit.item) && onMouseOver) {
|
|
145
|
+
onMouseOver(hit.item);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
canvasUtil.clearRect(canvasRef.current, contextRef.current);
|
|
149
|
+
canvasUtil.renderMain(controller, rendererRef.current, containerRef.current, canvasRef.current, contextRef.current, buildOrderedData());
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
var handleMouseLeave = function () {
|
|
154
|
+
console.log('handleMouseLeave');
|
|
155
|
+
|
|
156
|
+
if (hoveredRef.current && onMouseOut) {
|
|
157
|
+
onMouseOut(hoveredRef.current);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
hoveredRef.current = null;
|
|
161
|
+
canvasUtil.clearRect(canvasRef.current, contextRef.current);
|
|
162
|
+
canvasUtil.renderMain(controller, rendererRef.current, containerRef.current, canvasRef.current, contextRef.current, buildOrderedData());
|
|
163
|
+
}; // 지도 이벤트 → 화면 좌표 변환
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
var parseEventToOffset = function (e) {
|
|
167
|
+
var mapType = controller.getMapType();
|
|
168
|
+
var latlng = (e === null || e === void 0 ? void 0 : e.latlng) || (e === null || e === void 0 ? void 0 : e.latLng);
|
|
169
|
+
|
|
170
|
+
if (!latlng) {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
var pos = {
|
|
175
|
+
lat: 0,
|
|
176
|
+
lng: 0
|
|
177
|
+
}; //@ts-ignore
|
|
178
|
+
|
|
179
|
+
if (mapType === 'naver') {
|
|
180
|
+
pos.lat = latlng._lat;
|
|
181
|
+
pos.lng = latlng._lng; //@ts-ignore
|
|
182
|
+
} else if (mapType === 'google') {
|
|
183
|
+
pos.lat = latlng.lat();
|
|
184
|
+
pos.lng = latlng.lng(); //@ts-ignore
|
|
185
|
+
} else if (mapType === 'kakao') {
|
|
186
|
+
pos.lat = latlng.Ma;
|
|
187
|
+
pos.lng = latlng.La;
|
|
188
|
+
} else {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
var offset = controller.positionToOffset(pos);
|
|
193
|
+
return offset;
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
var handleMapMouseMove = function (e) {
|
|
197
|
+
if (draggingRef.current) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
var offset = parseEventToOffset(e);
|
|
202
|
+
if (!offset) return;
|
|
203
|
+
updateHoverByMouseXY(offset.x, offset.y);
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
var handleMapClick = function (e) {
|
|
207
|
+
console.log('handleClick');
|
|
208
|
+
var offset = parseEventToOffset(e);
|
|
209
|
+
if (!offset) return;
|
|
210
|
+
var hit = hitTest(offset.x, offset.y);
|
|
211
|
+
|
|
212
|
+
if (hit === null || hit === void 0 ? void 0 : hit.item) {
|
|
213
|
+
clickedRef.current = hit.item;
|
|
214
|
+
onClick && onClick(hit.item);
|
|
215
|
+
canvasUtil.clearRect(canvasRef.current, contextRef.current);
|
|
216
|
+
canvasUtil.renderMain(controller, rendererRef.current, containerRef.current, canvasRef.current, contextRef.current, buildOrderedData());
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
if (canvasRef.current && containerRef.current) {
|
|
221
|
+
// 리사이즈 처리
|
|
222
|
+
resizeObserver = new ResizeObserver(function () {
|
|
223
|
+
// 클리어
|
|
224
|
+
canvasUtil.clearRect(canvasRef.current, contextRef.current); // 스케일링
|
|
225
|
+
|
|
226
|
+
canvasRef.current && contextRef.current && canvasUtil.scaleCanvas(controller, canvasRef.current, contextRef.current); // 렌더링 (respect hover/click ordering)
|
|
227
|
+
|
|
228
|
+
canvasUtil.renderMain(controller, rendererRef.current, containerRef.current, canvasRef.current, contextRef.current, buildOrderedData());
|
|
229
|
+
});
|
|
230
|
+
resizeObserver.observe(controller.mapDivElement); // IDLE 이벤트 등록
|
|
231
|
+
|
|
232
|
+
controller.addEventListener('IDLE', handleIdle); // 줌 처리
|
|
233
|
+
|
|
234
|
+
controller.addEventListener('ZOOMSTART', handleZoomStart);
|
|
235
|
+
controller.addEventListener('ZOOM_CHANGED', handleZoomEnd); // 2d 컨텍스트
|
|
236
|
+
|
|
237
|
+
contextRef.current = canvasRef.current.getContext('2d'); // 스케일링
|
|
238
|
+
|
|
239
|
+
if (contextRef.current) {
|
|
240
|
+
canvasUtil.scaleCanvas(controller, canvasRef.current, contextRef.current);
|
|
241
|
+
} // 지도 이벤트 구독 (부모 pointer-events:none 이어도 동작)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
var map = controller.getMap();
|
|
245
|
+
|
|
246
|
+
if (map) {
|
|
247
|
+
//@ts-ignore
|
|
248
|
+
map.addListener('mousemove', handleMapMouseMove); //@ts-ignore
|
|
249
|
+
|
|
250
|
+
map.addListener('click', handleMapClick); //@ts-ignore
|
|
251
|
+
|
|
252
|
+
map.addListener('center_changed', function () {
|
|
253
|
+
// 드래그 중에는 리렌더 대신 transform 으로만 추종
|
|
254
|
+
var center = controller.getCurrBounds().getCenter();
|
|
255
|
+
var curr = controller.positionToOffset(center);
|
|
256
|
+
var prev = prevCenterOffsetRef.current;
|
|
257
|
+
|
|
258
|
+
if (!prev) {
|
|
259
|
+
prevCenterOffsetRef.current = {
|
|
260
|
+
x: curr.x,
|
|
261
|
+
y: curr.y
|
|
262
|
+
};
|
|
263
|
+
draggingRef.current = true;
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
var dx = prev.x - curr.x;
|
|
268
|
+
var dy = prev.y - curr.y;
|
|
269
|
+
accumTranslateRef.current = {
|
|
270
|
+
x: accumTranslateRef.current.x + dx,
|
|
271
|
+
y: accumTranslateRef.current.y + dy
|
|
272
|
+
};
|
|
273
|
+
prevCenterOffsetRef.current = {
|
|
274
|
+
x: curr.x,
|
|
275
|
+
y: curr.y
|
|
276
|
+
};
|
|
277
|
+
draggingRef.current = true;
|
|
278
|
+
|
|
279
|
+
if (containerRef.current) {
|
|
280
|
+
containerRef.current.style.transform = "translate(".concat(accumTranslateRef.current.x, "px, ").concat(accumTranslateRef.current.y, "px)");
|
|
281
|
+
}
|
|
282
|
+
}); //@ts-ignore
|
|
283
|
+
|
|
284
|
+
map.addListener('idle', handleIdle);
|
|
285
|
+
} // 마우스가 지도 영역을 벗어나는 경우 처리
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
controller.mapDivElement.addEventListener('mouseleave', handleMouseLeave);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return function () {
|
|
292
|
+
resizeObserver && resizeObserver.disconnect();
|
|
293
|
+
controller.mapDivElement && controller.mapDivElement.removeEventListener('mouseleave', handleMouseLeave);
|
|
294
|
+
controller.removeEventListener('IDLE', handleIdle);
|
|
295
|
+
controller.removeEventListener('ZOOMSTART', handleZoomStart);
|
|
296
|
+
controller.removeEventListener('ZOOM_CHANGED', handleZoomEnd);
|
|
297
|
+
};
|
|
298
|
+
}, []); // data ref
|
|
299
|
+
|
|
300
|
+
var dataRef = React.useRef(data);
|
|
301
|
+
React.useEffect(function () {
|
|
302
|
+
dataRef.current = data; // rebuild geohash index
|
|
303
|
+
|
|
304
|
+
var map = new Map();
|
|
305
|
+
var precision = geoPrecisionRef.current;
|
|
306
|
+
|
|
307
|
+
for (var _i = 0, _a = data || []; _i < _a.length; _i++) {
|
|
308
|
+
var item = _a[_i];
|
|
309
|
+
var pos = item.position && item.position[0];
|
|
310
|
+
if (!pos) continue;
|
|
311
|
+
var hash = geohash.geohashEncode(pos.lat, pos.lng, precision);
|
|
312
|
+
var list = map.get(hash) || [];
|
|
313
|
+
list.push(item);
|
|
314
|
+
map.set(hash, list);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
geoIndexRef.current = map;
|
|
318
|
+
}, [data]); // renderer ref
|
|
319
|
+
|
|
320
|
+
var rendererRef = React.useRef(renderer);
|
|
321
|
+
React.useEffect(function () {
|
|
322
|
+
rendererRef.current = renderer;
|
|
323
|
+
}, [renderer]); // Build ordered list so hovered/clicked items render last (on top) within this canvas layer
|
|
324
|
+
|
|
325
|
+
var buildOrderedData = function () {
|
|
326
|
+
var base = dataRef.current || [];
|
|
327
|
+
var result = [];
|
|
328
|
+
var hoverItem;
|
|
329
|
+
var clickItem;
|
|
330
|
+
|
|
331
|
+
for (var _i = 0, base_1 = base; _i < base_1.length; _i++) {
|
|
332
|
+
var item = base_1[_i];
|
|
333
|
+
|
|
334
|
+
if (clickedRef.current && item === clickedRef.current) {
|
|
335
|
+
clickItem = item;
|
|
336
|
+
} else if (hoveredRef.current && item === hoveredRef.current) {
|
|
337
|
+
hoverItem = item;
|
|
338
|
+
} else {
|
|
339
|
+
result.push(item);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (hoverItem) result.push(hoverItem);
|
|
344
|
+
if (clickItem) result.push(clickItem);
|
|
345
|
+
return result;
|
|
346
|
+
}; // Geohash-accelerated hit-test
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
var hitTest = function (mouseX, mouseY) {
|
|
350
|
+
// convert mouse offset -> lat/lng then geohash it
|
|
351
|
+
var pos = controller.offsetToPosition({
|
|
352
|
+
x: mouseX,
|
|
353
|
+
y: mouseY
|
|
354
|
+
});
|
|
355
|
+
var precision = geoPrecisionRef.current;
|
|
356
|
+
var baseHash = geohash.geohashEncode(pos.lat, pos.lng, precision);
|
|
357
|
+
var buckets = geohash.geohashNeighbors(baseHash); // collect candidates from buckets (preserve ordering for top-most first)
|
|
358
|
+
|
|
359
|
+
var set = new Set();
|
|
360
|
+
var ordered = buildOrderedData();
|
|
361
|
+
|
|
362
|
+
for (var _i = 0, buckets_1 = buckets; _i < buckets_1.length; _i++) {
|
|
363
|
+
var b = buckets_1[_i];
|
|
364
|
+
var list = geoIndexRef.current.get(b);
|
|
365
|
+
if (!list) continue;
|
|
366
|
+
|
|
367
|
+
for (var _a = 0, list_1 = list; _a < list_1.length; _a++) {
|
|
368
|
+
var it = list_1[_a];
|
|
369
|
+
set.add(it);
|
|
370
|
+
}
|
|
371
|
+
} // iterate from top-most
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
for (var i = ordered.length - 1; i >= 0; i--) {
|
|
375
|
+
var item = ordered[i];
|
|
376
|
+
if (!set.has(item)) continue;
|
|
377
|
+
if (item.visible === false) continue;
|
|
378
|
+
var p = item.position && item.position[0];
|
|
379
|
+
if (!p) continue;
|
|
380
|
+
var off = controller.positionToOffset(p);
|
|
381
|
+
var r = Math.max(2, item.hitRadius || 20);
|
|
382
|
+
var dx = mouseX - off.x;
|
|
383
|
+
var dy = mouseY - off.y;
|
|
384
|
+
|
|
385
|
+
if (dx * dx + dy * dy <= r * r) {
|
|
386
|
+
return {
|
|
387
|
+
item: item
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return null;
|
|
393
|
+
}; // Initial render
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
canvasUtil.renderMain(controller, renderer, containerRef.current, canvasRef.current, contextRef.current, buildOrderedData());
|
|
397
|
+
return reactDom.createPortal(React__default["default"].createElement("div", {
|
|
398
|
+
ref: containerRef,
|
|
399
|
+
style: {
|
|
400
|
+
position: 'absolute',
|
|
401
|
+
width: '100%',
|
|
402
|
+
height: '100%',
|
|
403
|
+
pointerEvents: 'none'
|
|
404
|
+
}
|
|
405
|
+
}, React__default["default"].createElement("canvas", {
|
|
406
|
+
ref: canvasRef,
|
|
407
|
+
style: {
|
|
408
|
+
pointerEvents: 'revert-layer'
|
|
409
|
+
}
|
|
410
|
+
})), divElement);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
exports.CanvasMarkerHanquf = CanvasMarkerHanquf;
|
|
@@ -0,0 +1,11 @@
|
|
|
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;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { MarkerOptions } from "../../../types";
|
|
3
|
+
import { CanvasMarkerData, CustomRenderBase, CustomRenderAnimation, CustomRenderEvent } from "./shared";
|
|
4
|
+
export { KonvaMarkerProvider, LRUCache, SpatialHashGrid } from "./shared";
|
|
5
|
+
export type { CanvasMarkerOption, Paths, CanvasMarkerData, CustomRenderBase, CustomRenderAnimation, CustomRenderEvent, RenderUtils, RenderBaseParams, RenderAnimationParams, RenderEventParams } from "./shared";
|
|
6
|
+
/**
|
|
7
|
+
* WoongKonvaMarker 컴포넌트 Props
|
|
8
|
+
* @template T 마커 데이터의 추가 속성 타입
|
|
9
|
+
*/
|
|
10
|
+
export interface WoongKonvaMarkerProps<T> extends Pick<MarkerOptions, 'zIndex' | 'anchor' | 'visible'> {
|
|
11
|
+
/** 렌더링할 마커 데이터 배열 */
|
|
12
|
+
markers: CanvasMarkerData<T>[];
|
|
13
|
+
/** 마커 클릭 시 호출되는 콜백 */
|
|
14
|
+
onClick?: (payload: CanvasMarkerData<T>, selectedIds: Set<string>) => void;
|
|
15
|
+
/** 마커에 마우스 오버 시 호출되는 콜백 */
|
|
16
|
+
onMouseOver?: (payload: CanvasMarkerData<T>) => void;
|
|
17
|
+
/** 마커에서 마우스 아웃 시 호출되는 콜백 */
|
|
18
|
+
onMouseOut?: (payload: CanvasMarkerData<T>) => void;
|
|
19
|
+
/** Base Layer 렌더링 함수 (필수) */
|
|
20
|
+
renderBase: CustomRenderBase<T>;
|
|
21
|
+
/** Animation Layer 렌더링 함수 (선택, 애니메이션용) */
|
|
22
|
+
renderAnimation?: CustomRenderAnimation<T>;
|
|
23
|
+
/** Event Layer 렌더링 함수 (필수) */
|
|
24
|
+
renderEvent: CustomRenderEvent<T>;
|
|
25
|
+
/** 다중 선택 활성화 여부 (기본: false) */
|
|
26
|
+
enableMultiSelect?: boolean;
|
|
27
|
+
/** hover 시 마커를 최상단으로 표시 (기본: false) */
|
|
28
|
+
topOnHover?: boolean;
|
|
29
|
+
/** 뷰포트 컬링 활성화 여부 (기본: true) */
|
|
30
|
+
enableViewportCulling?: boolean;
|
|
31
|
+
/** 뷰포트 컬링 여유 공간 (기본: 100px) */
|
|
32
|
+
cullingMargin?: number;
|
|
33
|
+
/** LRU 캐시 최대 크기 (기본: 10000) */
|
|
34
|
+
maxCacheSize?: number;
|
|
35
|
+
/** 외부에서 제어하는 선택된 항목 배열 (선택) */
|
|
36
|
+
selectedItems?: CanvasMarkerData<T>[];
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* 🔥 React.memo 최적화: 마커 배열과 selectedItems 변경 체크
|
|
40
|
+
*
|
|
41
|
+
* 비교 전략:
|
|
42
|
+
* 1. markers 배열 비교
|
|
43
|
+
* 2. selectedItems 배열 비교 (외부 제어)
|
|
44
|
+
*
|
|
45
|
+
* 주의: JSON.stringify() 사용 금지! (매우 느림)
|
|
46
|
+
*/
|
|
47
|
+
declare const WoongKonvaMarker: <T>({ markers, onClick, onMouseOver, onMouseOut, renderBase, renderAnimation, renderEvent, enableMultiSelect, topOnHover, enableViewportCulling, cullingMargin, maxCacheSize, selectedItems: externalSelectedItems, ...options }: WoongKonvaMarkerProps<T>) => React.ReactPortal;
|
|
48
|
+
export default WoongKonvaMarker;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Offset } from "../../../../types";
|
|
3
|
+
import { CanvasMarkerData } from "./types";
|
|
4
|
+
/**
|
|
5
|
+
* 다중 WoongKonvaMarker 인스턴스를 관리하기 위한 Context
|
|
6
|
+
*
|
|
7
|
+
* 용도:
|
|
8
|
+
* - zIndex 기반 이벤트 우선순위 처리
|
|
9
|
+
* - 전역 클릭/호버 이벤트 조정
|
|
10
|
+
* - 여러 레이어 간 상호작용 관리
|
|
11
|
+
*/
|
|
12
|
+
export interface ComponentInstance<T> {
|
|
13
|
+
zIndex: number;
|
|
14
|
+
hitTest: (offset: Offset) => boolean;
|
|
15
|
+
onClick?: (payload: CanvasMarkerData<T>, selectedIds: Set<string>) => void;
|
|
16
|
+
onMouseOver?: (payload: CanvasMarkerData<T>) => void;
|
|
17
|
+
onMouseOut?: (payload: CanvasMarkerData<T>) => void;
|
|
18
|
+
findData: (offset: Offset) => CanvasMarkerData<T> | null;
|
|
19
|
+
setHovered: (data: CanvasMarkerData<T> | null) => void;
|
|
20
|
+
handleLocalClick: (data: CanvasMarkerData<T>) => void;
|
|
21
|
+
getSelectedIds: () => Set<string>;
|
|
22
|
+
}
|
|
23
|
+
interface KonvaMarkerContextValue {
|
|
24
|
+
registerComponent: <T>(instance: ComponentInstance<T>) => void;
|
|
25
|
+
unregisterComponent: <T>(instance: ComponentInstance<T>) => void;
|
|
26
|
+
}
|
|
27
|
+
export declare const KonvaMarkerProvider: React.FC<{
|
|
28
|
+
children: React.ReactNode;
|
|
29
|
+
}>;
|
|
30
|
+
export declare const useKonvaMarkerContext: () => KonvaMarkerContextValue | null;
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var React = require('react');
|
|
6
|
+
var MintMapProvider = require('../../../provider/MintMapProvider.js');
|
|
7
|
+
|
|
8
|
+
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
9
|
+
|
|
10
|
+
var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
|
|
11
|
+
|
|
12
|
+
var KonvaMarkerContext = React.createContext(null);
|
|
13
|
+
var KonvaMarkerProvider = function (_a) {
|
|
14
|
+
var children = _a.children;
|
|
15
|
+
var controller = MintMapProvider.useMintMapController(); // Refs
|
|
16
|
+
|
|
17
|
+
var componentsRef = React.useRef([]);
|
|
18
|
+
var currentHoveredRef = React.useRef(null);
|
|
19
|
+
var currentHoveredDataRef = React.useRef(null);
|
|
20
|
+
var draggingRef = React.useRef(false);
|
|
21
|
+
/**
|
|
22
|
+
* 컴포넌트 등록 (zIndex 내림차순 정렬)
|
|
23
|
+
* 높은 zIndex가 먼저 처리됨
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
var registerComponent = React.useCallback(function (instance) {
|
|
27
|
+
componentsRef.current.push(instance);
|
|
28
|
+
componentsRef.current.sort(function (a, b) {
|
|
29
|
+
return b.zIndex - a.zIndex;
|
|
30
|
+
});
|
|
31
|
+
}, []);
|
|
32
|
+
/**
|
|
33
|
+
* 컴포넌트 등록 해제
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
var unregisterComponent = React.useCallback(function (instance) {
|
|
37
|
+
// Hover 중이던 컴포넌트면 초기화
|
|
38
|
+
if (currentHoveredRef.current === instance) {
|
|
39
|
+
currentHoveredRef.current = null;
|
|
40
|
+
currentHoveredDataRef.current = null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
componentsRef.current = componentsRef.current.filter(function (c) {
|
|
44
|
+
return c !== instance;
|
|
45
|
+
});
|
|
46
|
+
}, []);
|
|
47
|
+
/**
|
|
48
|
+
* 전역 클릭 핸들러 (zIndex 우선순위)
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
var handleGlobalClick = React.useCallback(function (event) {
|
|
52
|
+
var _a;
|
|
53
|
+
|
|
54
|
+
if (!((_a = event === null || event === void 0 ? void 0 : event.param) === null || _a === void 0 ? void 0 : _a.position)) return;
|
|
55
|
+
var clickedOffset = controller.positionToOffset(event.param.position); // zIndex 순서대로 순회 (높은 것부터)
|
|
56
|
+
|
|
57
|
+
for (var _i = 0, _b = componentsRef.current; _i < _b.length; _i++) {
|
|
58
|
+
var component = _b[_i];
|
|
59
|
+
var data = component.findData(clickedOffset);
|
|
60
|
+
|
|
61
|
+
if (data) {
|
|
62
|
+
component.handleLocalClick(data);
|
|
63
|
+
|
|
64
|
+
if (component.onClick) {
|
|
65
|
+
component.onClick(data, component.getSelectedIds());
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return; // 첫 번째 히트만 처리
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}, [controller]);
|
|
72
|
+
/**
|
|
73
|
+
* 전역 마우스 이동 핸들러 (zIndex 우선순위)
|
|
74
|
+
*/
|
|
75
|
+
|
|
76
|
+
var handleGlobalMouseMove = React.useCallback(function (event) {
|
|
77
|
+
var _a;
|
|
78
|
+
|
|
79
|
+
if (draggingRef.current || !((_a = event === null || event === void 0 ? void 0 : event.param) === null || _a === void 0 ? void 0 : _a.position)) return;
|
|
80
|
+
var mouseOffset = controller.positionToOffset(event.param.position); // zIndex 순서대로 순회하여 Hover 대상 찾기
|
|
81
|
+
|
|
82
|
+
var newHoveredComponent = null;
|
|
83
|
+
var newHoveredData = null;
|
|
84
|
+
|
|
85
|
+
for (var _i = 0, _b = componentsRef.current; _i < _b.length; _i++) {
|
|
86
|
+
var component = _b[_i];
|
|
87
|
+
var data = component.findData(mouseOffset);
|
|
88
|
+
|
|
89
|
+
if (data) {
|
|
90
|
+
newHoveredComponent = component;
|
|
91
|
+
newHoveredData = data;
|
|
92
|
+
break; // 첫 번째 히트만 처리
|
|
93
|
+
}
|
|
94
|
+
} // Hover 상태 변경 감지 (최적화: 별도 ref로 직접 비교)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
if (currentHoveredRef.current !== newHoveredComponent || currentHoveredDataRef.current !== newHoveredData) {
|
|
98
|
+
// 이전 hover 해제
|
|
99
|
+
if (currentHoveredRef.current) {
|
|
100
|
+
currentHoveredRef.current.setHovered(null);
|
|
101
|
+
|
|
102
|
+
if (currentHoveredRef.current.onMouseOut && currentHoveredDataRef.current) {
|
|
103
|
+
currentHoveredRef.current.onMouseOut(currentHoveredDataRef.current);
|
|
104
|
+
}
|
|
105
|
+
} // 새 hover 설정
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
if (newHoveredComponent && newHoveredData) {
|
|
109
|
+
newHoveredComponent.setHovered(newHoveredData);
|
|
110
|
+
|
|
111
|
+
if (newHoveredComponent.onMouseOver) {
|
|
112
|
+
newHoveredComponent.onMouseOver(newHoveredData);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
currentHoveredRef.current = newHoveredComponent;
|
|
117
|
+
currentHoveredDataRef.current = newHoveredData;
|
|
118
|
+
}
|
|
119
|
+
}, [controller]);
|
|
120
|
+
/**
|
|
121
|
+
* 줌/드래그 시작 (마우스 이동 이벤트 무시)
|
|
122
|
+
*/
|
|
123
|
+
|
|
124
|
+
var handleZoomStart = React.useCallback(function () {
|
|
125
|
+
draggingRef.current = true;
|
|
126
|
+
}, []);
|
|
127
|
+
/**
|
|
128
|
+
* 지도 idle (마우스 이동 이벤트 재개)
|
|
129
|
+
*/
|
|
130
|
+
|
|
131
|
+
var handleIdle = React.useCallback(function () {
|
|
132
|
+
draggingRef.current = false;
|
|
133
|
+
}, []); // 이벤트 리스너 등록
|
|
134
|
+
|
|
135
|
+
React.useEffect(function () {
|
|
136
|
+
controller.addEventListener('CLICK', handleGlobalClick);
|
|
137
|
+
controller.addEventListener('MOUSEMOVE', handleGlobalMouseMove);
|
|
138
|
+
controller.addEventListener('ZOOMSTART', handleZoomStart);
|
|
139
|
+
controller.addEventListener('IDLE', handleIdle);
|
|
140
|
+
return function () {
|
|
141
|
+
controller.removeEventListener('CLICK', handleGlobalClick);
|
|
142
|
+
controller.removeEventListener('MOUSEMOVE', handleGlobalMouseMove);
|
|
143
|
+
controller.removeEventListener('ZOOMSTART', handleZoomStart);
|
|
144
|
+
controller.removeEventListener('IDLE', handleIdle);
|
|
145
|
+
};
|
|
146
|
+
}, [controller, handleGlobalClick, handleGlobalMouseMove, handleZoomStart, handleIdle]); // Context value 메모이제이션
|
|
147
|
+
|
|
148
|
+
var contextValue = React.useMemo(function () {
|
|
149
|
+
return {
|
|
150
|
+
registerComponent: registerComponent,
|
|
151
|
+
unregisterComponent: unregisterComponent
|
|
152
|
+
};
|
|
153
|
+
}, [registerComponent, unregisterComponent]);
|
|
154
|
+
return React__default["default"].createElement(KonvaMarkerContext.Provider, {
|
|
155
|
+
value: contextValue
|
|
156
|
+
}, children);
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
exports.KonvaMarkerProvider = KonvaMarkerProvider;
|