@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.
Files changed (28) hide show
  1. package/.claude/settings.local.json +16 -0
  2. package/.vscode/settings.json +11 -0
  3. package/CLAUDE.md +100 -0
  4. package/README.md +190 -0
  5. package/dist/components/mint-map/core/MintMapCore.js +4 -1
  6. package/dist/components/mint-map/core/advanced/canvas/CanvasMarkerClaude.d.ts +63 -0
  7. package/dist/components/mint-map/core/advanced/canvas/CanvasMarkerClaude.js +1084 -0
  8. package/dist/components/mint-map/core/advanced/canvas/CanvasMarkerHanquf.d.ts +22 -0
  9. package/dist/components/mint-map/core/advanced/canvas/CanvasMarkerHanquf.js +413 -0
  10. package/dist/components/mint-map/core/advanced/canvas/index.d.ts +2 -0
  11. package/dist/components/mint-map/core/advanced/woongCanvas/ClusterMarker.d.ts +11 -0
  12. package/dist/components/mint-map/core/advanced/woongCanvas/WoongKonvaMarker.d.ts +48 -0
  13. package/dist/components/mint-map/core/advanced/woongCanvas/shared/context.d.ts +31 -0
  14. package/dist/components/mint-map/core/advanced/woongCanvas/shared/context.js +159 -0
  15. package/dist/components/mint-map/core/advanced/woongCanvas/shared/index.d.ts +4 -0
  16. package/dist/components/mint-map/core/advanced/woongCanvas/shared/performance.d.ts +161 -0
  17. package/dist/components/mint-map/core/advanced/woongCanvas/shared/types.d.ts +121 -0
  18. package/dist/components/mint-map/core/advanced/woongCanvas/shared/utils.d.ts +23 -0
  19. package/dist/components/mint-map/core/util/geohash.d.ts +2 -0
  20. package/dist/components/mint-map/core/util/geohash.js +125 -0
  21. package/dist/components/mint-map/google/GoogleMintMapController.js +1 -0
  22. package/dist/components/mint-map/kakao/KakaoMintMapController.js +1 -0
  23. package/dist/components/mint-map/naver/NaverMintMapController.js +1 -0
  24. package/dist/index.es.js +1863 -137
  25. package/dist/index.js +4 -0
  26. package/dist/index.umd.js +1862 -134
  27. package/dist/mock.d.ts +133 -0
  28. package/package.json +17 -4
package/dist/index.umd.js CHANGED
@@ -653,6 +653,286 @@
653
653
  var styles$1 = {"mint-map-root":"MintMapCore-module_mint-map-root__SMfwn","mint-map-container":"MintMapCore-module_mint-map-container__8MIIr"};
654
654
  styleInject__default["default"](css_248z$1);
655
655
 
656
+ var Drawable =
657
+ /** @class */
658
+ function () {
659
+ function Drawable() {}
660
+
661
+ return Drawable;
662
+ }();
663
+
664
+ var Marker =
665
+ /** @class */
666
+ function (_super) {
667
+ tslib.__extends(Marker, _super);
668
+ /**
669
+ * 지도에 표시할 마커정보
670
+ */
671
+
672
+
673
+ function Marker(options) {
674
+ var _this = _super.call(this) || this;
675
+
676
+ _this.options = options;
677
+ return _this;
678
+ }
679
+
680
+ return Marker;
681
+ }(Drawable);
682
+
683
+ var Polyline =
684
+ /** @class */
685
+ function (_super) {
686
+ tslib.__extends(Polyline, _super);
687
+ /**
688
+ * 지도에 표시할 폴리곤정보
689
+ */
690
+
691
+
692
+ function Polyline(options) {
693
+ var _this = _super.call(this) || this;
694
+
695
+ _this.options = options;
696
+ return _this;
697
+ }
698
+
699
+ return Polyline;
700
+ }(Drawable);
701
+
702
+ var Polygon =
703
+ /** @class */
704
+ function (_super) {
705
+ tslib.__extends(Polygon, _super);
706
+ /**
707
+ * 지도에 표시할 폴리곤정보
708
+ */
709
+
710
+
711
+ function Polygon(options) {
712
+ var _this = _super.call(this) || this;
713
+
714
+ _this.options = options;
715
+ return _this;
716
+ }
717
+ /**
718
+ * 폴리곤의 중점을 구한다.
719
+ */
720
+
721
+
722
+ Polygon.prototype.getCenter = function () {
723
+ if (Array.isArray(this.options.position) && this.options.position.length > 0) {
724
+ var paths = this.options.position.map(function (elem) {
725
+ return elem instanceof Position ? elem : new Position(elem[0], elem[1]);
726
+ });
727
+ return PolygonCalculator.getCenter(paths);
728
+ }
729
+
730
+ throw new Error('center 를 찾을 수 없습니다.');
731
+ };
732
+
733
+ return Polygon;
734
+ }(Drawable);
735
+
736
+ // export type MapEvent = 'bounds_changed'|'center_changed'|'idle'|'zoom_changed'|'zoomstart'
737
+ // export type MapUIEvent = 'click'|'dblclick'|''
738
+ var MapEvent =
739
+ /** @class */
740
+ function () {
741
+ function MapEvent() {
742
+ this.BOUNDS_CHANGED = 'bounds_changed';
743
+ this.CENTER_CHANGED = 'center_changed';
744
+ this.IDLE = 'idle';
745
+ this.ZOOM_CHANGED = 'zoom_changed';
746
+ this.ZOOMSTART = 'zoomstart';
747
+ }
748
+
749
+ MapEvent.prototype.get = function (eventName) {
750
+ var value = this[eventName];
751
+
752
+ if (typeof value === 'string') {
753
+ return value;
754
+ }
755
+ };
756
+
757
+ return MapEvent;
758
+ }();
759
+
760
+ var MapUIEvent =
761
+ /** @class */
762
+ function () {
763
+ function MapUIEvent() {
764
+ this.CLICK = 'click';
765
+ this.DBLCLICK = 'dblclick';
766
+ this.MOUSEDOWN = 'mousedown';
767
+ this.MOUSEUP = 'mouseup';
768
+ this.MOUSEOUT = 'mouseout';
769
+ this.MOUSEMOVE = 'mousemove';
770
+ this.MOUSEOVER = 'mouseover';
771
+ this.DRAG = 'drag';
772
+ this.DRAGSTART = 'dragstart';
773
+ this.DRAGEND = 'dragend';
774
+ this.RIGHTCLICK = 'rightclick';
775
+ this.CONTEXTMENU = 'contextmenu';
776
+ }
777
+
778
+ MapUIEvent.prototype.get = function (eventName) {
779
+ var value = this[eventName];
780
+
781
+ if (typeof value === 'string') {
782
+ return value;
783
+ }
784
+ };
785
+
786
+ return MapUIEvent;
787
+ }();
788
+
789
+ var KonvaMarkerContext = React.createContext(null);
790
+ var KonvaMarkerProvider = function (_a) {
791
+ var children = _a.children;
792
+ var controller = useMintMapController(); // Refs
793
+
794
+ var componentsRef = React.useRef([]);
795
+ var currentHoveredRef = React.useRef(null);
796
+ var currentHoveredDataRef = React.useRef(null);
797
+ var draggingRef = React.useRef(false);
798
+ /**
799
+ * 컴포넌트 등록 (zIndex 내림차순 정렬)
800
+ * 높은 zIndex가 먼저 처리됨
801
+ */
802
+
803
+ var registerComponent = React.useCallback(function (instance) {
804
+ componentsRef.current.push(instance);
805
+ componentsRef.current.sort(function (a, b) {
806
+ return b.zIndex - a.zIndex;
807
+ });
808
+ }, []);
809
+ /**
810
+ * 컴포넌트 등록 해제
811
+ */
812
+
813
+ var unregisterComponent = React.useCallback(function (instance) {
814
+ // Hover 중이던 컴포넌트면 초기화
815
+ if (currentHoveredRef.current === instance) {
816
+ currentHoveredRef.current = null;
817
+ currentHoveredDataRef.current = null;
818
+ }
819
+
820
+ componentsRef.current = componentsRef.current.filter(function (c) {
821
+ return c !== instance;
822
+ });
823
+ }, []);
824
+ /**
825
+ * 전역 클릭 핸들러 (zIndex 우선순위)
826
+ */
827
+
828
+ var handleGlobalClick = React.useCallback(function (event) {
829
+ var _a;
830
+
831
+ if (!((_a = event === null || event === void 0 ? void 0 : event.param) === null || _a === void 0 ? void 0 : _a.position)) return;
832
+ var clickedOffset = controller.positionToOffset(event.param.position); // zIndex 순서대로 순회 (높은 것부터)
833
+
834
+ for (var _i = 0, _b = componentsRef.current; _i < _b.length; _i++) {
835
+ var component = _b[_i];
836
+ var data = component.findData(clickedOffset);
837
+
838
+ if (data) {
839
+ component.handleLocalClick(data);
840
+
841
+ if (component.onClick) {
842
+ component.onClick(data, component.getSelectedIds());
843
+ }
844
+
845
+ return; // 첫 번째 히트만 처리
846
+ }
847
+ }
848
+ }, [controller]);
849
+ /**
850
+ * 전역 마우스 이동 핸들러 (zIndex 우선순위)
851
+ */
852
+
853
+ var handleGlobalMouseMove = React.useCallback(function (event) {
854
+ var _a;
855
+
856
+ if (draggingRef.current || !((_a = event === null || event === void 0 ? void 0 : event.param) === null || _a === void 0 ? void 0 : _a.position)) return;
857
+ var mouseOffset = controller.positionToOffset(event.param.position); // zIndex 순서대로 순회하여 Hover 대상 찾기
858
+
859
+ var newHoveredComponent = null;
860
+ var newHoveredData = null;
861
+
862
+ for (var _i = 0, _b = componentsRef.current; _i < _b.length; _i++) {
863
+ var component = _b[_i];
864
+ var data = component.findData(mouseOffset);
865
+
866
+ if (data) {
867
+ newHoveredComponent = component;
868
+ newHoveredData = data;
869
+ break; // 첫 번째 히트만 처리
870
+ }
871
+ } // Hover 상태 변경 감지 (최적화: 별도 ref로 직접 비교)
872
+
873
+
874
+ if (currentHoveredRef.current !== newHoveredComponent || currentHoveredDataRef.current !== newHoveredData) {
875
+ // 이전 hover 해제
876
+ if (currentHoveredRef.current) {
877
+ currentHoveredRef.current.setHovered(null);
878
+
879
+ if (currentHoveredRef.current.onMouseOut && currentHoveredDataRef.current) {
880
+ currentHoveredRef.current.onMouseOut(currentHoveredDataRef.current);
881
+ }
882
+ } // 새 hover 설정
883
+
884
+
885
+ if (newHoveredComponent && newHoveredData) {
886
+ newHoveredComponent.setHovered(newHoveredData);
887
+
888
+ if (newHoveredComponent.onMouseOver) {
889
+ newHoveredComponent.onMouseOver(newHoveredData);
890
+ }
891
+ }
892
+
893
+ currentHoveredRef.current = newHoveredComponent;
894
+ currentHoveredDataRef.current = newHoveredData;
895
+ }
896
+ }, [controller]);
897
+ /**
898
+ * 줌/드래그 시작 (마우스 이동 이벤트 무시)
899
+ */
900
+
901
+ var handleZoomStart = React.useCallback(function () {
902
+ draggingRef.current = true;
903
+ }, []);
904
+ /**
905
+ * 지도 idle (마우스 이동 이벤트 재개)
906
+ */
907
+
908
+ var handleIdle = React.useCallback(function () {
909
+ draggingRef.current = false;
910
+ }, []); // 이벤트 리스너 등록
911
+
912
+ React.useEffect(function () {
913
+ controller.addEventListener('CLICK', handleGlobalClick);
914
+ controller.addEventListener('MOUSEMOVE', handleGlobalMouseMove);
915
+ controller.addEventListener('ZOOMSTART', handleZoomStart);
916
+ controller.addEventListener('IDLE', handleIdle);
917
+ return function () {
918
+ controller.removeEventListener('CLICK', handleGlobalClick);
919
+ controller.removeEventListener('MOUSEMOVE', handleGlobalMouseMove);
920
+ controller.removeEventListener('ZOOMSTART', handleZoomStart);
921
+ controller.removeEventListener('IDLE', handleIdle);
922
+ };
923
+ }, [controller, handleGlobalClick, handleGlobalMouseMove, handleZoomStart, handleIdle]); // Context value 메모이제이션
924
+
925
+ var contextValue = React.useMemo(function () {
926
+ return {
927
+ registerComponent: registerComponent,
928
+ unregisterComponent: unregisterComponent
929
+ };
930
+ }, [registerComponent, unregisterComponent]);
931
+ return React__default["default"].createElement(KonvaMarkerContext.Provider, {
932
+ value: contextValue
933
+ }, children);
934
+ };
935
+
656
936
  var cn$3 = classNames__default["default"].bind(styles$1);
657
937
  function MintMapCore(_a) {
658
938
  var _this = this;
@@ -735,7 +1015,7 @@
735
1015
  }, [center]);
736
1016
  return React__default["default"].createElement("div", {
737
1017
  className: cn$3('mint-map-root')
738
- }, mapInitialized && children, React__default["default"].createElement("div", {
1018
+ }, mapInitialized && React__default["default"].createElement(KonvaMarkerProvider, null, children), React__default["default"].createElement("div", {
739
1019
  className: cn$3('mint-map-container'),
740
1020
  style: {
741
1021
  visibility: visible ? 'inherit' : 'hidden'
@@ -1432,59 +1712,6 @@
1432
1712
  return MapZoomInfo;
1433
1713
  }();
1434
1714
 
1435
- // export type MapEvent = 'bounds_changed'|'center_changed'|'idle'|'zoom_changed'|'zoomstart'
1436
- // export type MapUIEvent = 'click'|'dblclick'|''
1437
- var MapEvent =
1438
- /** @class */
1439
- function () {
1440
- function MapEvent() {
1441
- this.BOUNDS_CHANGED = 'bounds_changed';
1442
- this.CENTER_CHANGED = 'center_changed';
1443
- this.IDLE = 'idle';
1444
- this.ZOOM_CHANGED = 'zoom_changed';
1445
- this.ZOOMSTART = 'zoomstart';
1446
- }
1447
-
1448
- MapEvent.prototype.get = function (eventName) {
1449
- var value = this[eventName];
1450
-
1451
- if (typeof value === 'string') {
1452
- return value;
1453
- }
1454
- };
1455
-
1456
- return MapEvent;
1457
- }();
1458
-
1459
- var MapUIEvent =
1460
- /** @class */
1461
- function () {
1462
- function MapUIEvent() {
1463
- this.CLICK = 'click';
1464
- this.DBLCLICK = 'dblclick';
1465
- this.MOUSEDOWN = 'mousedown';
1466
- this.MOUSEUP = 'mouseup';
1467
- this.MOUSEOUT = 'mouseout';
1468
- this.MOUSEMOVE = 'mousemove';
1469
- this.MOUSEOVER = 'mouseover';
1470
- this.DRAG = 'drag';
1471
- this.DRAGSTART = 'dragstart';
1472
- this.DRAGEND = 'dragend';
1473
- this.RIGHTCLICK = 'rightclick';
1474
- this.CONTEXTMENU = 'contextmenu';
1475
- }
1476
-
1477
- MapUIEvent.prototype.get = function (eventName) {
1478
- var value = this[eventName];
1479
-
1480
- if (typeof value === 'string') {
1481
- return value;
1482
- }
1483
- };
1484
-
1485
- return MapUIEvent;
1486
- }();
1487
-
1488
1715
  var MintMapCanvasRenderer =
1489
1716
  /** @class */
1490
1717
  function () {
@@ -1538,86 +1765,6 @@
1538
1765
  }, children));
1539
1766
  }
1540
1767
 
1541
- var Drawable =
1542
- /** @class */
1543
- function () {
1544
- function Drawable() {}
1545
-
1546
- return Drawable;
1547
- }();
1548
-
1549
- var Marker =
1550
- /** @class */
1551
- function (_super) {
1552
- tslib.__extends(Marker, _super);
1553
- /**
1554
- * 지도에 표시할 마커정보
1555
- */
1556
-
1557
-
1558
- function Marker(options) {
1559
- var _this = _super.call(this) || this;
1560
-
1561
- _this.options = options;
1562
- return _this;
1563
- }
1564
-
1565
- return Marker;
1566
- }(Drawable);
1567
-
1568
- var Polyline =
1569
- /** @class */
1570
- function (_super) {
1571
- tslib.__extends(Polyline, _super);
1572
- /**
1573
- * 지도에 표시할 폴리곤정보
1574
- */
1575
-
1576
-
1577
- function Polyline(options) {
1578
- var _this = _super.call(this) || this;
1579
-
1580
- _this.options = options;
1581
- return _this;
1582
- }
1583
-
1584
- return Polyline;
1585
- }(Drawable);
1586
-
1587
- var Polygon =
1588
- /** @class */
1589
- function (_super) {
1590
- tslib.__extends(Polygon, _super);
1591
- /**
1592
- * 지도에 표시할 폴리곤정보
1593
- */
1594
-
1595
-
1596
- function Polygon(options) {
1597
- var _this = _super.call(this) || this;
1598
-
1599
- _this.options = options;
1600
- return _this;
1601
- }
1602
- /**
1603
- * 폴리곤의 중점을 구한다.
1604
- */
1605
-
1606
-
1607
- Polygon.prototype.getCenter = function () {
1608
- if (Array.isArray(this.options.position) && this.options.position.length > 0) {
1609
- var paths = this.options.position.map(function (elem) {
1610
- return elem instanceof Position ? elem : new Position(elem[0], elem[1]);
1611
- });
1612
- return PolygonCalculator.getCenter(paths);
1613
- }
1614
-
1615
- throw new Error('center 를 찾을 수 없습니다.');
1616
- };
1617
-
1618
- return Polygon;
1619
- }(Drawable);
1620
-
1621
1768
  function SVGPolygon(_a) {
1622
1769
  var path = _a.path,
1623
1770
  _b = _a.innerPath,
@@ -2680,6 +2827,1585 @@
2680
2827
  })), divElement);
2681
2828
  }
2682
2829
 
2830
+ // Lightweight Geohash encoder and neighbor utilities
2831
+ var BASE32 = '0123456789bcdefghjkmnpqrstuvwxyz';
2832
+ var NEIGHBORS = {
2833
+ n: {
2834
+ even: {
2835
+ border: 'prxz',
2836
+ neighbor: 'bc01fg45238967deuvhjyznpkmstqrwx'
2837
+ },
2838
+ odd: {
2839
+ border: 'bcfguvyz',
2840
+ neighbor: 'p0r21436x8zb9dcf5h7kjnmqesgutwvy'
2841
+ }
2842
+ },
2843
+ s: {
2844
+ even: {
2845
+ border: '028b',
2846
+ neighbor: '238967debc01fg45kmstqrwxuvhjyznp'
2847
+ },
2848
+ odd: {
2849
+ border: '0145hjnp',
2850
+ neighbor: '14365h7k9dcfesgujnmqp0r2twvyx8zb'
2851
+ }
2852
+ },
2853
+ e: {
2854
+ even: {
2855
+ border: 'bcfguvyz',
2856
+ neighbor: '14365h7k9dcfesgujnmqp0r2twvyx8zb'
2857
+ },
2858
+ odd: {
2859
+ border: 'prxz',
2860
+ neighbor: 'bc01fg45238967deuvhjyznpkmstqrwx'
2861
+ }
2862
+ },
2863
+ w: {
2864
+ even: {
2865
+ border: '0145hjnp',
2866
+ neighbor: '238967debc01fg45kmstqrwxuvhjyznp'
2867
+ },
2868
+ odd: {
2869
+ border: '028b',
2870
+ neighbor: 'p0r21436x8zb9dcf5h7kjnmqesgutwvy'
2871
+ }
2872
+ }
2873
+ };
2874
+ function geohashEncode(lat, lon, precision) {
2875
+ if (precision === void 0) {
2876
+ precision = 6;
2877
+ }
2878
+
2879
+ var idx = 0;
2880
+ var bit = 0;
2881
+ var evenBit = true;
2882
+ var geohash = '';
2883
+ var latMin = -90,
2884
+ latMax = 90;
2885
+ var lonMin = -180,
2886
+ lonMax = 180;
2887
+
2888
+ while (geohash.length < precision) {
2889
+ if (evenBit) {
2890
+ var lonMid = (lonMin + lonMax) / 2;
2891
+
2892
+ if (lon >= lonMid) {
2893
+ idx = idx * 2 + 1;
2894
+ lonMin = lonMid;
2895
+ } else {
2896
+ idx = idx * 2;
2897
+ lonMax = lonMid;
2898
+ }
2899
+ } else {
2900
+ var latMid = (latMin + latMax) / 2;
2901
+
2902
+ if (lat >= latMid) {
2903
+ idx = idx * 2 + 1;
2904
+ latMin = latMid;
2905
+ } else {
2906
+ idx = idx * 2;
2907
+ latMax = latMid;
2908
+ }
2909
+ }
2910
+
2911
+ evenBit = !evenBit;
2912
+
2913
+ if (++bit == 5) {
2914
+ geohash += BASE32.charAt(idx);
2915
+ bit = 0;
2916
+ idx = 0;
2917
+ }
2918
+ }
2919
+
2920
+ return geohash;
2921
+ }
2922
+
2923
+ function adjacent(hash, dir) {
2924
+ var lastChr = hash[hash.length - 1];
2925
+ var type = hash.length % 2 ? 'odd' : 'even'; // @ts-ignore
2926
+
2927
+ var border = NEIGHBORS[dir][type].border; // @ts-ignore
2928
+
2929
+ var neighbor = NEIGHBORS[dir][type].neighbor;
2930
+ var base = hash.substring(0, hash.length - 1);
2931
+
2932
+ if (border.indexOf(lastChr) !== -1) {
2933
+ base = adjacent(base, dir);
2934
+ }
2935
+
2936
+ var pos = neighbor.indexOf(lastChr);
2937
+ var nextChr = BASE32.charAt(pos);
2938
+ return base + nextChr;
2939
+ }
2940
+
2941
+ function geohashNeighbors(hash) {
2942
+ var n = adjacent(hash, 'n');
2943
+ var s = adjacent(hash, 's');
2944
+ var e = adjacent(hash, 'e');
2945
+ var w = adjacent(hash, 'w');
2946
+ return [hash, n, s, e, w, adjacent(n, 'e'), adjacent(n, 'w'), adjacent(s, 'e'), adjacent(s, 'w')];
2947
+ }
2948
+
2949
+ function CanvasMarkerHanquf(_a) {
2950
+ var renderer = _a.renderer,
2951
+ data = _a.data,
2952
+ onMouseOver = _a.onMouseOver,
2953
+ onMouseOut = _a.onMouseOut,
2954
+ onClick = _a.onClick,
2955
+ options = tslib.__rest(_a, ["renderer", "data", "onMouseOver", "onMouseOut", "onClick"]); //controller
2956
+
2957
+
2958
+ var controller = useMintMapController(); //element
2959
+
2960
+ var divRef = React.useRef(document.createElement('div'));
2961
+ var divElement = divRef.current; //canvas container ref
2962
+
2963
+ var containerRef = React.useRef(null); //canvas ref
2964
+
2965
+ var canvasRef = React.useRef(null); //canvas context
2966
+
2967
+ var contextRef = React.useRef(); //marker
2968
+
2969
+ var markerRef = React.useRef(); // interaction states
2970
+
2971
+ var hoveredRef = React.useRef(null);
2972
+ var clickedRef = React.useRef(null); // geohash index: hash -> items
2973
+
2974
+ var geoIndexRef = React.useRef(new Map());
2975
+ var geoPrecisionRef = React.useRef(6); // drag translate state
2976
+
2977
+ var prevCenterOffsetRef = React.useRef(null);
2978
+ var accumTranslateRef = React.useRef({
2979
+ x: 0,
2980
+ y: 0
2981
+ });
2982
+ var draggingRef = React.useRef(false); //create object
2983
+
2984
+ React.useEffect(function () {
2985
+ divElement.style.width = 'fit-content';
2986
+ divElement.style.pointerEvents = 'none';
2987
+ return function () {
2988
+ if (markerRef.current) {
2989
+ controller.clearDrawable(markerRef.current);
2990
+ markerRef.current = undefined;
2991
+ }
2992
+ };
2993
+ }, []); //create / update object
2994
+
2995
+ React.useEffect(function () {
2996
+ if (options) {
2997
+ var bounds = controller.getCurrBounds();
2998
+
2999
+ var markerOptions = tslib.__assign({
3000
+ position: bounds.nw
3001
+ }, options);
3002
+
3003
+ if (markerRef.current) {
3004
+ controller.updateMarker(markerRef.current, markerOptions);
3005
+ } else {
3006
+ markerRef.current = new Marker(markerOptions);
3007
+ markerRef.current.element = divElement;
3008
+ controller.createMarker(markerRef.current); //disablePointerEvent 처리
3009
+
3010
+ if (divElement.parentElement) {
3011
+ divElement.style.pointerEvents = 'none';
3012
+ divElement.parentElement.style.pointerEvents = 'none';
3013
+ } //z-index 처리
3014
+
3015
+
3016
+ if (options.zIndex !== undefined) {
3017
+ controller.setMarkerZIndex(markerRef.current, options.zIndex);
3018
+ }
3019
+ }
3020
+ }
3021
+ }, [options]);
3022
+
3023
+ var handleIdle = function () {
3024
+ // 클리어
3025
+ clearRect(canvasRef.current, contextRef.current); // 표시 복구 (드래그/줌 중 숨김 처리 복원)
3026
+
3027
+ containerRef.current && (containerRef.current.style.visibility = ''); // 드래그 종료 처리: 변환 초기화 및 기준 위치 갱신
3028
+
3029
+ draggingRef.current = false;
3030
+ prevCenterOffsetRef.current = null;
3031
+ accumTranslateRef.current = {
3032
+ x: 0,
3033
+ y: 0
3034
+ };
3035
+
3036
+ if (containerRef.current) {
3037
+ containerRef.current.style.transform = '';
3038
+ } // 마커 이동
3039
+
3040
+
3041
+ var bounds = controller.getCurrBounds();
3042
+
3043
+ var markerOptions = tslib.__assign({
3044
+ position: bounds.nw
3045
+ }, options);
3046
+
3047
+ markerRef.current && controller.updateMarker(markerRef.current, markerOptions); // 렌더링 (hover/click item last)
3048
+
3049
+ renderMain(controller, rendererRef.current, containerRef.current, canvasRef.current, contextRef.current, buildOrderedData());
3050
+ };
3051
+
3052
+ var handleZoomStart = function () {
3053
+ containerRef.current && (containerRef.current.style.visibility = 'hidden');
3054
+ };
3055
+
3056
+ var handleZoomEnd = function () {
3057
+ containerRef.current && (containerRef.current.style.visibility = '');
3058
+ }; //initialize
3059
+
3060
+
3061
+ React.useEffect(function () {
3062
+ var resizeObserver; // hover/out 공통 처리기 (mouse 좌표 기반)
3063
+
3064
+ var updateHoverByMouseXY = function (mouseX, mouseY) {
3065
+ var hit = hitTest(mouseX, mouseY);
3066
+
3067
+ if ((hit === null || hit === void 0 ? void 0 : hit.item) !== hoveredRef.current) {
3068
+ if (hoveredRef.current && onMouseOut) {
3069
+ onMouseOut(hoveredRef.current);
3070
+ }
3071
+
3072
+ hoveredRef.current = (hit === null || hit === void 0 ? void 0 : hit.item) || null;
3073
+
3074
+ if ((hit === null || hit === void 0 ? void 0 : hit.item) && onMouseOver) {
3075
+ onMouseOver(hit.item);
3076
+ }
3077
+
3078
+ clearRect(canvasRef.current, contextRef.current);
3079
+ renderMain(controller, rendererRef.current, containerRef.current, canvasRef.current, contextRef.current, buildOrderedData());
3080
+ }
3081
+ };
3082
+
3083
+ var handleMouseLeave = function () {
3084
+ console.log('handleMouseLeave');
3085
+
3086
+ if (hoveredRef.current && onMouseOut) {
3087
+ onMouseOut(hoveredRef.current);
3088
+ }
3089
+
3090
+ hoveredRef.current = null;
3091
+ clearRect(canvasRef.current, contextRef.current);
3092
+ renderMain(controller, rendererRef.current, containerRef.current, canvasRef.current, contextRef.current, buildOrderedData());
3093
+ }; // 지도 이벤트 → 화면 좌표 변환
3094
+
3095
+
3096
+ var parseEventToOffset = function (e) {
3097
+ var mapType = controller.getMapType();
3098
+ var latlng = (e === null || e === void 0 ? void 0 : e.latlng) || (e === null || e === void 0 ? void 0 : e.latLng);
3099
+
3100
+ if (!latlng) {
3101
+ return null;
3102
+ }
3103
+
3104
+ var pos = {
3105
+ lat: 0,
3106
+ lng: 0
3107
+ }; //@ts-ignore
3108
+
3109
+ if (mapType === 'naver') {
3110
+ pos.lat = latlng._lat;
3111
+ pos.lng = latlng._lng; //@ts-ignore
3112
+ } else if (mapType === 'google') {
3113
+ pos.lat = latlng.lat();
3114
+ pos.lng = latlng.lng(); //@ts-ignore
3115
+ } else if (mapType === 'kakao') {
3116
+ pos.lat = latlng.Ma;
3117
+ pos.lng = latlng.La;
3118
+ } else {
3119
+ return null;
3120
+ }
3121
+
3122
+ var offset = controller.positionToOffset(pos);
3123
+ return offset;
3124
+ };
3125
+
3126
+ var handleMapMouseMove = function (e) {
3127
+ if (draggingRef.current) {
3128
+ return;
3129
+ }
3130
+
3131
+ var offset = parseEventToOffset(e);
3132
+ if (!offset) return;
3133
+ updateHoverByMouseXY(offset.x, offset.y);
3134
+ };
3135
+
3136
+ var handleMapClick = function (e) {
3137
+ console.log('handleClick');
3138
+ var offset = parseEventToOffset(e);
3139
+ if (!offset) return;
3140
+ var hit = hitTest(offset.x, offset.y);
3141
+
3142
+ if (hit === null || hit === void 0 ? void 0 : hit.item) {
3143
+ clickedRef.current = hit.item;
3144
+ onClick && onClick(hit.item);
3145
+ clearRect(canvasRef.current, contextRef.current);
3146
+ renderMain(controller, rendererRef.current, containerRef.current, canvasRef.current, contextRef.current, buildOrderedData());
3147
+ }
3148
+ };
3149
+
3150
+ if (canvasRef.current && containerRef.current) {
3151
+ // 리사이즈 처리
3152
+ resizeObserver = new ResizeObserver(function () {
3153
+ // 클리어
3154
+ clearRect(canvasRef.current, contextRef.current); // 스케일링
3155
+
3156
+ canvasRef.current && contextRef.current && scaleCanvas(controller, canvasRef.current, contextRef.current); // 렌더링 (respect hover/click ordering)
3157
+
3158
+ renderMain(controller, rendererRef.current, containerRef.current, canvasRef.current, contextRef.current, buildOrderedData());
3159
+ });
3160
+ resizeObserver.observe(controller.mapDivElement); // IDLE 이벤트 등록
3161
+
3162
+ controller.addEventListener('IDLE', handleIdle); // 줌 처리
3163
+
3164
+ controller.addEventListener('ZOOMSTART', handleZoomStart);
3165
+ controller.addEventListener('ZOOM_CHANGED', handleZoomEnd); // 2d 컨텍스트
3166
+
3167
+ contextRef.current = canvasRef.current.getContext('2d'); // 스케일링
3168
+
3169
+ if (contextRef.current) {
3170
+ scaleCanvas(controller, canvasRef.current, contextRef.current);
3171
+ } // 지도 이벤트 구독 (부모 pointer-events:none 이어도 동작)
3172
+
3173
+
3174
+ var map = controller.getMap();
3175
+
3176
+ if (map) {
3177
+ //@ts-ignore
3178
+ map.addListener('mousemove', handleMapMouseMove); //@ts-ignore
3179
+
3180
+ map.addListener('click', handleMapClick); //@ts-ignore
3181
+
3182
+ map.addListener('center_changed', function () {
3183
+ // 드래그 중에는 리렌더 대신 transform 으로만 추종
3184
+ var center = controller.getCurrBounds().getCenter();
3185
+ var curr = controller.positionToOffset(center);
3186
+ var prev = prevCenterOffsetRef.current;
3187
+
3188
+ if (!prev) {
3189
+ prevCenterOffsetRef.current = {
3190
+ x: curr.x,
3191
+ y: curr.y
3192
+ };
3193
+ draggingRef.current = true;
3194
+ return;
3195
+ }
3196
+
3197
+ var dx = prev.x - curr.x;
3198
+ var dy = prev.y - curr.y;
3199
+ accumTranslateRef.current = {
3200
+ x: accumTranslateRef.current.x + dx,
3201
+ y: accumTranslateRef.current.y + dy
3202
+ };
3203
+ prevCenterOffsetRef.current = {
3204
+ x: curr.x,
3205
+ y: curr.y
3206
+ };
3207
+ draggingRef.current = true;
3208
+
3209
+ if (containerRef.current) {
3210
+ containerRef.current.style.transform = "translate(".concat(accumTranslateRef.current.x, "px, ").concat(accumTranslateRef.current.y, "px)");
3211
+ }
3212
+ }); //@ts-ignore
3213
+
3214
+ map.addListener('idle', handleIdle);
3215
+ } // 마우스가 지도 영역을 벗어나는 경우 처리
3216
+
3217
+
3218
+ controller.mapDivElement.addEventListener('mouseleave', handleMouseLeave);
3219
+ }
3220
+
3221
+ return function () {
3222
+ resizeObserver && resizeObserver.disconnect();
3223
+ controller.mapDivElement && controller.mapDivElement.removeEventListener('mouseleave', handleMouseLeave);
3224
+ controller.removeEventListener('IDLE', handleIdle);
3225
+ controller.removeEventListener('ZOOMSTART', handleZoomStart);
3226
+ controller.removeEventListener('ZOOM_CHANGED', handleZoomEnd);
3227
+ };
3228
+ }, []); // data ref
3229
+
3230
+ var dataRef = React.useRef(data);
3231
+ React.useEffect(function () {
3232
+ dataRef.current = data; // rebuild geohash index
3233
+
3234
+ var map = new Map();
3235
+ var precision = geoPrecisionRef.current;
3236
+
3237
+ for (var _i = 0, _a = data || []; _i < _a.length; _i++) {
3238
+ var item = _a[_i];
3239
+ var pos = item.position && item.position[0];
3240
+ if (!pos) continue;
3241
+ var hash = geohashEncode(pos.lat, pos.lng, precision);
3242
+ var list = map.get(hash) || [];
3243
+ list.push(item);
3244
+ map.set(hash, list);
3245
+ }
3246
+
3247
+ geoIndexRef.current = map;
3248
+ }, [data]); // renderer ref
3249
+
3250
+ var rendererRef = React.useRef(renderer);
3251
+ React.useEffect(function () {
3252
+ rendererRef.current = renderer;
3253
+ }, [renderer]); // Build ordered list so hovered/clicked items render last (on top) within this canvas layer
3254
+
3255
+ var buildOrderedData = function () {
3256
+ var base = dataRef.current || [];
3257
+ var result = [];
3258
+ var hoverItem;
3259
+ var clickItem;
3260
+
3261
+ for (var _i = 0, base_1 = base; _i < base_1.length; _i++) {
3262
+ var item = base_1[_i];
3263
+
3264
+ if (clickedRef.current && item === clickedRef.current) {
3265
+ clickItem = item;
3266
+ } else if (hoveredRef.current && item === hoveredRef.current) {
3267
+ hoverItem = item;
3268
+ } else {
3269
+ result.push(item);
3270
+ }
3271
+ }
3272
+
3273
+ if (hoverItem) result.push(hoverItem);
3274
+ if (clickItem) result.push(clickItem);
3275
+ return result;
3276
+ }; // Geohash-accelerated hit-test
3277
+
3278
+
3279
+ var hitTest = function (mouseX, mouseY) {
3280
+ // convert mouse offset -> lat/lng then geohash it
3281
+ var pos = controller.offsetToPosition({
3282
+ x: mouseX,
3283
+ y: mouseY
3284
+ });
3285
+ var precision = geoPrecisionRef.current;
3286
+ var baseHash = geohashEncode(pos.lat, pos.lng, precision);
3287
+ var buckets = geohashNeighbors(baseHash); // collect candidates from buckets (preserve ordering for top-most first)
3288
+
3289
+ var set = new Set();
3290
+ var ordered = buildOrderedData();
3291
+
3292
+ for (var _i = 0, buckets_1 = buckets; _i < buckets_1.length; _i++) {
3293
+ var b = buckets_1[_i];
3294
+ var list = geoIndexRef.current.get(b);
3295
+ if (!list) continue;
3296
+
3297
+ for (var _a = 0, list_1 = list; _a < list_1.length; _a++) {
3298
+ var it = list_1[_a];
3299
+ set.add(it);
3300
+ }
3301
+ } // iterate from top-most
3302
+
3303
+
3304
+ for (var i = ordered.length - 1; i >= 0; i--) {
3305
+ var item = ordered[i];
3306
+ if (!set.has(item)) continue;
3307
+ if (item.visible === false) continue;
3308
+ var p = item.position && item.position[0];
3309
+ if (!p) continue;
3310
+ var off = controller.positionToOffset(p);
3311
+ var r = Math.max(2, item.hitRadius || 20);
3312
+ var dx = mouseX - off.x;
3313
+ var dy = mouseY - off.y;
3314
+
3315
+ if (dx * dx + dy * dy <= r * r) {
3316
+ return {
3317
+ item: item
3318
+ };
3319
+ }
3320
+ }
3321
+
3322
+ return null;
3323
+ }; // Initial render
3324
+
3325
+
3326
+ renderMain(controller, renderer, containerRef.current, canvasRef.current, contextRef.current, buildOrderedData());
3327
+ return reactDom.createPortal(React__default["default"].createElement("div", {
3328
+ ref: containerRef,
3329
+ style: {
3330
+ position: 'absolute',
3331
+ width: '100%',
3332
+ height: '100%',
3333
+ pointerEvents: 'none'
3334
+ }
3335
+ }, React__default["default"].createElement("canvas", {
3336
+ ref: canvasRef,
3337
+ style: {
3338
+ pointerEvents: 'revert-layer'
3339
+ }
3340
+ })), divElement);
3341
+ }
3342
+
3343
+ var defaultPolygonRenderer = function (_a) {
3344
+ var context = _a.context,
3345
+ offsets = _a.offsets,
3346
+ innerOffsets = _a.innerOffsets,
3347
+ polygon = _a.polygon,
3348
+ isHovered = _a.isHovered,
3349
+ defaultBackground = _a.defaultBackground,
3350
+ defaultStrokeColor = _a.defaultStrokeColor,
3351
+ defaultStrokeWidth = _a.defaultStrokeWidth,
3352
+ hoverBackground = _a.hoverBackground,
3353
+ hoverStrokeColor = _a.hoverStrokeColor;
3354
+ if (offsets.length < 3) return; // 외부 폴리곤 그리기
3355
+
3356
+ context.beginPath();
3357
+ context.moveTo(offsets[0].x, offsets[0].y);
3358
+
3359
+ for (var i = 1; i < offsets.length; i++) {
3360
+ context.lineTo(offsets[i].x, offsets[i].y);
3361
+ }
3362
+
3363
+ context.closePath(); // 내부 폴리곤 (구멍) 그리기
3364
+
3365
+ if (innerOffsets) {
3366
+ innerOffsets.forEach(function (innerOffset) {
3367
+ if (innerOffset.length < 3) return;
3368
+ context.moveTo(innerOffset[0].x, innerOffset[0].y);
3369
+
3370
+ for (var i = 1; i < innerOffset.length; i++) {
3371
+ context.lineTo(innerOffset[i].x, innerOffset[i].y);
3372
+ }
3373
+
3374
+ context.closePath();
3375
+ });
3376
+ } // 스타일 설정
3377
+
3378
+
3379
+ var fillColor = isHovered ? hoverBackground : polygon.background || defaultBackground;
3380
+ var lineColor = isHovered ? hoverStrokeColor : polygon.strokeColor || defaultStrokeColor;
3381
+ var lineWidth = polygon.strokeWidth || defaultStrokeWidth;
3382
+ context.fillStyle = fillColor;
3383
+ context.strokeStyle = lineColor;
3384
+ context.lineWidth = lineWidth;
3385
+ context.fill('evenodd');
3386
+ context.stroke();
3387
+ };
3388
+
3389
+ var CanvasMarkerClaude = React__default["default"].memo(function CanvasMarkerClaude(_a) {
3390
+ var _b = _a.polygons,
3391
+ polygons = _b === void 0 ? [] : _b,
3392
+ _c = _a.markers,
3393
+ markers = _c === void 0 ? [] : _c,
3394
+ _d = _a.background,
3395
+ background = _d === void 0 ? 'rgba(0, 100, 255, 0.3)' : _d,
3396
+ _e = _a.strokeColor,
3397
+ strokeColor = _e === void 0 ? 'blue' : _e,
3398
+ _f = _a.strokeWidth,
3399
+ strokeWidth = _f === void 0 ? 1 : _f,
3400
+ onPolygonClick = _a.onPolygonClick,
3401
+ onPolygonHover = _a.onPolygonHover,
3402
+ onPolygonLeave = _a.onPolygonLeave;
3403
+ _a.onMarkerClick;
3404
+ var onMarkerHover = _a.onMarkerHover,
3405
+ onMarkerLeave = _a.onMarkerLeave,
3406
+ onRenderComplete = _a.onRenderComplete,
3407
+ markerRenderer = _a.markerRenderer,
3408
+ options = tslib.__rest(_a, ["polygons", "markers", "background", "strokeColor", "strokeWidth", "onPolygonClick", "onPolygonHover", "onPolygonLeave", "onMarkerClick", "onMarkerHover", "onMarkerLeave", "onRenderComplete", "markerRenderer"]); // controller
3409
+
3410
+
3411
+ var controller = useMintMapController(); // element (CanvasMarker 스타일)
3412
+
3413
+ var divRef = React.useRef(document.createElement('div'));
3414
+ var divElement = divRef.current; // canvas container ref
3415
+
3416
+ var containerRef = React.useRef(null); // base canvas (모든 폴리곤 - IDLE 시에만 업데이트)
3417
+
3418
+ var baseCanvasRef = React.useRef(null);
3419
+ var baseContextRef = React.useRef(); // hover canvas (호버된 폴리곤만 - 마우스 이동 시 업데이트)
3420
+
3421
+ var hoverCanvasRef = React.useRef(null);
3422
+ var hoverContextRef = React.useRef(); // base marker canvas (기본 상태 마커 - IDLE 시 업데이트)
3423
+
3424
+ var baseMarkerCanvasRef = React.useRef(null);
3425
+ var baseMarkerContextRef = React.useRef(); // interactive marker canvas (호버/클릭 마커 - 마우스 이동 시 업데이트)
3426
+
3427
+ var interactiveMarkerCanvasRef = React.useRef(null);
3428
+ var interactiveMarkerContextRef = React.useRef(); // marker
3429
+
3430
+ var markerRef = React.useRef(); // interaction states
3431
+
3432
+ var hoveredPolygonRef = React.useRef(null);
3433
+ var clickedPolygonRef = React.useRef(null);
3434
+ var hoveredMarkerRef = React.useRef(null);
3435
+ var clickedMarkerRef = React.useRef(null); // 🚀 WoongCanvasMarker 방식: 단순한 data ref (geohash 제거)
3436
+
3437
+ var polygonsRef = React.useRef([]);
3438
+ var markersRef = React.useRef([]); // 마커 경계 정보 저장 (hit-test용)
3439
+
3440
+ var markerBoundsRef = React.useRef(new Map()); // 🚀 드래그 추적용 (WoongCanvasMarker 방식)
3441
+
3442
+ var draggingRef = React.useRef(false);
3443
+ var prevCenterOffsetRef = React.useRef(null);
3444
+ var accumTranslateRef = React.useRef({
3445
+ x: 0,
3446
+ y: 0
3447
+ }); // 🚀 Offset 캐싱 (ID 문자열 기반으로 변경하여 메모리 누수 방지)
3448
+
3449
+ var MAX_CACHE_SIZE = 50000; // 최대 캐시 크기 제한 (3만개 + 여유분)
3450
+
3451
+ var polygonOffsetCacheRef = React.useRef(new Map());
3452
+ var polygonInnerOffsetCacheRef = React.useRef(new Map()); // innerOffsets 캐시 추가
3453
+
3454
+ var markerOffsetCacheRef = React.useRef(new Map()); // create object (CanvasMarker 스타일)
3455
+
3456
+ React.useEffect(function () {
3457
+ divElement.style.width = 'fit-content';
3458
+ divElement.style.pointerEvents = 'none'; // CanvasMarkerHanquf 방식
3459
+
3460
+ return function () {
3461
+ if (markerRef.current) {
3462
+ controller.clearDrawable(markerRef.current);
3463
+ markerRef.current = undefined;
3464
+ }
3465
+ };
3466
+ }, []); // create / update object (CanvasMarker 스타일)
3467
+
3468
+ React.useEffect(function () {
3469
+ if (options) {
3470
+ var bounds = controller.getCurrBounds();
3471
+
3472
+ var markerOptions = tslib.__assign({
3473
+ position: bounds.nw
3474
+ }, options);
3475
+
3476
+ if (markerRef.current) {
3477
+ controller.updateMarker(markerRef.current, markerOptions);
3478
+ } else {
3479
+ markerRef.current = new Marker(markerOptions);
3480
+ markerRef.current.element = divElement;
3481
+ controller.createMarker(markerRef.current); // CanvasMarkerHanquf 방식: pointer events 비활성화
3482
+
3483
+ if (divElement.parentElement) {
3484
+ divElement.style.pointerEvents = 'none';
3485
+ divElement.parentElement.style.pointerEvents = 'none';
3486
+ } // z-index 처리
3487
+
3488
+
3489
+ if (options.zIndex !== undefined) {
3490
+ controller.setMarkerZIndex(markerRef.current, options.zIndex);
3491
+ }
3492
+ }
3493
+ }
3494
+ }, [options]); // 베이스 폴리곤 렌더링 (모든 폴리곤, 호버 효과 없음)
3495
+
3496
+ var renderBasePolygons = function () {
3497
+ if (!baseContextRef.current || !polygonsRef.current || polygonsRef.current.length === 0) return;
3498
+ clearRect(baseCanvasRef.current, baseContextRef.current);
3499
+ var cacheMissCount = 0; // 캐시 미스 카운트
3500
+ // 🚀 WoongCanvasMarker 방식: 전체 데이터 순회 (필터링 제거)
3501
+
3502
+ polygonsRef.current.forEach(function (polygon) {
3503
+ if (polygon.visible === false) return; // 🚀 캐시 확인 (ID 기반으로 변경)
3504
+
3505
+ var offsets = polygonOffsetCacheRef.current.get(polygon.id);
3506
+
3507
+ if (!offsets) {
3508
+ cacheMissCount++;
3509
+ offsets = polygon.positions.map(function (pos) {
3510
+ return controller.positionToOffset(pos);
3511
+ }); // LRU: 캐시 크기 제한
3512
+
3513
+ if (polygonOffsetCacheRef.current.size >= MAX_CACHE_SIZE) {
3514
+ var firstKey = polygonOffsetCacheRef.current.keys().next().value;
3515
+
3516
+ if (firstKey) {
3517
+ polygonOffsetCacheRef.current.delete(firstKey);
3518
+ }
3519
+ }
3520
+
3521
+ polygonOffsetCacheRef.current.set(polygon.id, offsets);
3522
+ }
3523
+
3524
+ var innerOffsets;
3525
+
3526
+ if (polygon.innerPositions) {
3527
+ // innerOffsets도 캐시 사용
3528
+ innerOffsets = polygonInnerOffsetCacheRef.current.get(polygon.id);
3529
+
3530
+ if (!innerOffsets) {
3531
+ innerOffsets = polygon.innerPositions.map(function (innerPositions) {
3532
+ return innerPositions.map(function (pos) {
3533
+ return controller.positionToOffset(pos);
3534
+ });
3535
+ });
3536
+ polygonInnerOffsetCacheRef.current.set(polygon.id, innerOffsets);
3537
+ }
3538
+ } // 렌더러 호출 (항상 기본 스타일)
3539
+
3540
+
3541
+ defaultPolygonRenderer({
3542
+ context: baseContextRef.current,
3543
+ offsets: offsets,
3544
+ innerOffsets: innerOffsets,
3545
+ polygon: polygon,
3546
+ isHovered: false,
3547
+ defaultBackground: background,
3548
+ defaultStrokeColor: strokeColor,
3549
+ defaultStrokeWidth: strokeWidth,
3550
+ hoverBackground: background,
3551
+ hoverStrokeColor: strokeColor
3552
+ });
3553
+ }); // 요약 로그만 출력 (캐시 미스가 있을 때만)
3554
+
3555
+ if (cacheMissCount > 0) {
3556
+ console.log("\uD83C\uDFA8 [renderBasePolygons] \uCE90\uC2DC \uBBF8\uC2A4: ".concat(cacheMissCount, "\uAC1C, \uD604\uC7AC \uCE90\uC2DC \uD06C\uAE30: ").concat(polygonOffsetCacheRef.current.size));
3557
+ } // 렌더링 완료 콜백 호출
3558
+
3559
+
3560
+ if (onRenderComplete) {
3561
+ onRenderComplete();
3562
+ }
3563
+ }; // 베이스 마커 렌더링 (기본 상태 마커만, IDLE 시 업데이트)
3564
+
3565
+
3566
+ var renderBaseMarkers = function () {
3567
+ if (!baseMarkerContextRef.current || !markerRenderer) return;
3568
+ if (!markersRef.current || markersRef.current.length === 0) return;
3569
+ clearRect(baseMarkerCanvasRef.current, baseMarkerContextRef.current); // 마커 경계 정보 초기화
3570
+
3571
+ markerBoundsRef.current.clear(); // 🚀 WoongCanvasMarker 방식: 전체 데이터 순회 (필터링 제거)
3572
+
3573
+ markersRef.current.forEach(function (marker) {
3574
+ var _a, _b;
3575
+
3576
+ if (marker.visible === false) return; // 호버/클릭 상태인 마커는 스킵 (인터랙션 레이어에서 렌더링)
3577
+
3578
+ if (marker.id === ((_a = hoveredMarkerRef.current) === null || _a === void 0 ? void 0 : _a.id)) return;
3579
+ if (marker.id === ((_b = clickedMarkerRef.current) === null || _b === void 0 ? void 0 : _b.id)) return; // 🚀 캐시 확인 (ID 기반으로 변경)
3580
+
3581
+ var offset = markerOffsetCacheRef.current.get(marker.id);
3582
+
3583
+ if (!offset) {
3584
+ offset = controller.positionToOffset(new Position(marker.lat, marker.lng));
3585
+ markerOffsetCacheRef.current.set(marker.id, offset);
3586
+ } // 커스텀 마커 렌더러 호출 (기본 상태)
3587
+
3588
+
3589
+ var markerBounds = markerRenderer({
3590
+ context: baseMarkerContextRef.current,
3591
+ x: offset.x,
3592
+ y: offset.y,
3593
+ marker: marker,
3594
+ isHovered: false,
3595
+ isClicked: false
3596
+ }); // 마커 경계 정보 저장 (hit-test용)
3597
+
3598
+ markerBoundsRef.current.set(marker.id, {
3599
+ marker: marker,
3600
+ bounds: markerBounds
3601
+ });
3602
+ });
3603
+ }; // 인터랙션 마커 렌더링 (호버/클릭 마커만, 마우스 이동 시 업데이트)
3604
+
3605
+
3606
+ var renderInteractionMarkers = function () {
3607
+ if (!interactiveMarkerContextRef.current || !markerRenderer) return;
3608
+ clearRect(interactiveMarkerCanvasRef.current, interactiveMarkerContextRef.current); // 클릭된 마커 먼저 그리기 (하단 레이어)
3609
+
3610
+ if (clickedMarkerRef.current) {
3611
+ var marker = clickedMarkerRef.current;
3612
+
3613
+ if (marker.visible !== false) {
3614
+ // 🚀 캐시 확인 (ID 기반으로 변경)
3615
+ var offset = markerOffsetCacheRef.current.get(marker.id);
3616
+
3617
+ if (!offset) {
3618
+ offset = controller.positionToOffset(new Position(marker.lat, marker.lng));
3619
+ markerOffsetCacheRef.current.set(marker.id, offset);
3620
+ }
3621
+
3622
+ var markerBounds = markerRenderer({
3623
+ context: interactiveMarkerContextRef.current,
3624
+ x: offset.x,
3625
+ y: offset.y,
3626
+ marker: marker,
3627
+ isHovered: false,
3628
+ isClicked: true
3629
+ }); // 마커 경계 정보 업데이트
3630
+
3631
+ markerBoundsRef.current.set(marker.id, {
3632
+ marker: marker,
3633
+ bounds: markerBounds
3634
+ });
3635
+ }
3636
+ } // 호버된 마커 나중에 그리기 (최상단 레이어)
3637
+
3638
+
3639
+ if (hoveredMarkerRef.current) {
3640
+ var marker = hoveredMarkerRef.current;
3641
+
3642
+ if (marker.visible !== false) {
3643
+ // 🚀 캐시 확인 (ID 기반으로 변경)
3644
+ var offset = markerOffsetCacheRef.current.get(marker.id);
3645
+
3646
+ if (!offset) {
3647
+ offset = controller.positionToOffset(new Position(marker.lat, marker.lng));
3648
+ markerOffsetCacheRef.current.set(marker.id, offset);
3649
+ }
3650
+
3651
+ var markerBounds = markerRenderer({
3652
+ context: interactiveMarkerContextRef.current,
3653
+ x: offset.x,
3654
+ y: offset.y,
3655
+ marker: marker,
3656
+ isHovered: true,
3657
+ isClicked: false
3658
+ }); // 마커 경계 정보 업데이트
3659
+
3660
+ markerBoundsRef.current.set(marker.id, {
3661
+ marker: marker,
3662
+ bounds: markerBounds
3663
+ });
3664
+ }
3665
+ }
3666
+ }; // 인터랙션 폴리곤 렌더링 (호버 + 클릭)
3667
+
3668
+
3669
+ var renderInteractionPolygons = function () {
3670
+ if (!hoverContextRef.current) return; // 인터랙션 캔버스 클리어
3671
+
3672
+ clearRect(hoverCanvasRef.current, hoverContextRef.current); // 1. 클릭된 폴리곤 먼저 그리기 (하단 레이어)
3673
+
3674
+ if (clickedPolygonRef.current) {
3675
+ var polygon = clickedPolygonRef.current;
3676
+
3677
+ if (polygon.visible !== false) {
3678
+ // 🚀 캐시 확인 (ID 기반으로 변경)
3679
+ var offsets = polygonOffsetCacheRef.current.get(polygon.id);
3680
+
3681
+ if (!offsets) {
3682
+ offsets = polygon.positions.map(function (pos) {
3683
+ return controller.positionToOffset(pos);
3684
+ });
3685
+ polygonOffsetCacheRef.current.set(polygon.id, offsets);
3686
+ }
3687
+
3688
+ var innerOffsets = void 0;
3689
+
3690
+ if (polygon.innerPositions) {
3691
+ // innerOffsets도 캐시 사용
3692
+ innerOffsets = polygonInnerOffsetCacheRef.current.get(polygon.id);
3693
+
3694
+ if (!innerOffsets) {
3695
+ innerOffsets = polygon.innerPositions.map(function (innerPositions) {
3696
+ return innerPositions.map(function (pos) {
3697
+ return controller.positionToOffset(pos);
3698
+ });
3699
+ });
3700
+ polygonInnerOffsetCacheRef.current.set(polygon.id, innerOffsets);
3701
+ }
3702
+ } // 클릭 스타일 (노란색)
3703
+
3704
+
3705
+ defaultPolygonRenderer({
3706
+ context: hoverContextRef.current,
3707
+ offsets: offsets,
3708
+ innerOffsets: innerOffsets,
3709
+ polygon: polygon,
3710
+ isHovered: true,
3711
+ defaultBackground: background,
3712
+ defaultStrokeColor: strokeColor,
3713
+ defaultStrokeWidth: strokeWidth,
3714
+ hoverBackground: 'rgba(255, 200, 0, 0.6)',
3715
+ hoverStrokeColor: 'orange' // 주황색 테두리
3716
+
3717
+ });
3718
+ }
3719
+ } // 2. 호버된 폴리곤 나중에 그리기 (최상단 레이어)
3720
+
3721
+
3722
+ if (hoveredPolygonRef.current) {
3723
+ var polygon = hoveredPolygonRef.current;
3724
+
3725
+ if (polygon.visible !== false) {
3726
+ // 🚀 캐시 확인 (ID 기반으로 변경)
3727
+ var offsets = polygonOffsetCacheRef.current.get(polygon.id);
3728
+
3729
+ if (!offsets) {
3730
+ offsets = polygon.positions.map(function (pos) {
3731
+ return controller.positionToOffset(pos);
3732
+ });
3733
+ polygonOffsetCacheRef.current.set(polygon.id, offsets);
3734
+ }
3735
+
3736
+ var innerOffsets = void 0;
3737
+
3738
+ if (polygon.innerPositions) {
3739
+ // innerOffsets도 캐시 사용
3740
+ innerOffsets = polygonInnerOffsetCacheRef.current.get(polygon.id);
3741
+
3742
+ if (!innerOffsets) {
3743
+ innerOffsets = polygon.innerPositions.map(function (innerPositions) {
3744
+ return innerPositions.map(function (pos) {
3745
+ return controller.positionToOffset(pos);
3746
+ });
3747
+ });
3748
+ polygonInnerOffsetCacheRef.current.set(polygon.id, innerOffsets);
3749
+ }
3750
+ } // 호버 스타일 (빨간색)
3751
+
3752
+
3753
+ defaultPolygonRenderer({
3754
+ context: hoverContextRef.current,
3755
+ offsets: offsets,
3756
+ innerOffsets: innerOffsets,
3757
+ polygon: polygon,
3758
+ isHovered: true,
3759
+ defaultBackground: background,
3760
+ defaultStrokeColor: strokeColor,
3761
+ defaultStrokeWidth: strokeWidth,
3762
+ hoverBackground: 'rgba(255, 0, 0, 0.5)',
3763
+ hoverStrokeColor: 'red' // 빨간색 테두리
3764
+
3765
+ });
3766
+ }
3767
+ }
3768
+ }; // point-in-polygon 테스트 (ray casting algorithm)
3769
+
3770
+
3771
+ var pointInPolygon = function (x, y, polygon) {
3772
+ if (polygon.length < 3) return false;
3773
+ var inside = false;
3774
+
3775
+ for (var i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
3776
+ var xi = polygon[i].x,
3777
+ yi = polygon[i].y;
3778
+ var xj = polygon[j].x,
3779
+ yj = polygon[j].y;
3780
+
3781
+ if (yi > y !== yj > y && x < (xj - xi) * (y - yi) / (yj - yi) + xi) {
3782
+ inside = !inside;
3783
+ }
3784
+ }
3785
+
3786
+ return inside;
3787
+ }; // 히트 테스트 함수 (마커 우선)
3788
+
3789
+
3790
+ var findPolygonAt = function (x, y) {
3791
+ var _a, _b;
3792
+
3793
+ if (!polygonsRef.current || polygonsRef.current.length === 0) return null; // 1. 마커 먼저 체크 (우선순위: hover 중 → clicked 중 → 배열 역순)
3794
+
3795
+ if (markersRef.current) {
3796
+ // 🎯 최우선: 현재 hover 중인 마커가 있으면 그것부터 체크
3797
+ if (hoveredMarkerRef.current && hoveredMarkerRef.current.visible !== false) {
3798
+ var markerData = markerBoundsRef.current.get(hoveredMarkerRef.current.id);
3799
+
3800
+ if (markerData) {
3801
+ var bounds = markerData.bounds;
3802
+
3803
+ if (x >= bounds.x && x <= bounds.x + bounds.width && y >= bounds.y && y <= bounds.y + bounds.height) {
3804
+ return null; // 마커 히트 시 null 반환 (폴리곤이 아니므로)
3805
+ }
3806
+ }
3807
+ } // 🎯 두 번째: hover 중인 마커에 히트 안 되고, clicked 마커가 있으면 체크
3808
+
3809
+
3810
+ if (clickedMarkerRef.current && clickedMarkerRef.current.visible !== false) {
3811
+ var markerData = markerBoundsRef.current.get(clickedMarkerRef.current.id);
3812
+
3813
+ if (markerData) {
3814
+ var bounds = markerData.bounds;
3815
+
3816
+ if (x >= bounds.x && x <= bounds.x + bounds.width && y >= bounds.y && y <= bounds.y + bounds.height) {
3817
+ return null; // 마커 히트 시 null 반환 (폴리곤이 아니므로)
3818
+ }
3819
+ }
3820
+ } // 🎯 세 번째: 나머지 마커들을 역순으로 체크 (위에 그려진 것부터)
3821
+
3822
+
3823
+ for (var i = markersRef.current.length - 1; i >= 0; i--) {
3824
+ var marker = markersRef.current[i];
3825
+ if (marker.visible === false) continue; // 이미 체크한 hover/clicked 마커는 스킵
3826
+
3827
+ if (marker.id === ((_a = hoveredMarkerRef.current) === null || _a === void 0 ? void 0 : _a.id)) continue;
3828
+ if (marker.id === ((_b = clickedMarkerRef.current) === null || _b === void 0 ? void 0 : _b.id)) continue;
3829
+ var markerData = markerBoundsRef.current.get(marker.id);
3830
+ if (!markerData) continue;
3831
+ var bounds = markerData.bounds; // 사각형 경계 체크
3832
+
3833
+ if (x >= bounds.x && x <= bounds.x + bounds.width && y >= bounds.y && y <= bounds.y + bounds.height) {
3834
+ // 마커 히트 시 null 반환 (폴리곤이 아니므로)
3835
+ return null;
3836
+ }
3837
+ }
3838
+ } // 2. 마커에 히트 안 되면 폴리곤 체크 - 🚀 전체 폴리곤 순회
3839
+ // 역순으로 체크 (위에 그려진 것부터)
3840
+
3841
+
3842
+ for (var i = polygonsRef.current.length - 1; i >= 0; i--) {
3843
+ var polygon = polygonsRef.current[i];
3844
+ if (polygon.visible === false) continue; // 🚀 캐시 확인 (ID 기반으로 변경)
3845
+
3846
+ var offsets = polygonOffsetCacheRef.current.get(polygon.id);
3847
+
3848
+ if (!offsets) {
3849
+ offsets = polygon.positions.map(function (pos) {
3850
+ return controller.positionToOffset(pos);
3851
+ });
3852
+ polygonOffsetCacheRef.current.set(polygon.id, offsets);
3853
+ } // 외부 폴리곤 체크
3854
+
3855
+
3856
+ if (pointInPolygon(x, y, offsets)) {
3857
+ // 도넛 폴리곤인 경우 내부 구멍도 체크
3858
+ if (polygon.innerPositions && polygon.innerPositions.length > 0) {
3859
+ var innerOffsets = polygonInnerOffsetCacheRef.current.get(polygon.id);
3860
+
3861
+ if (!innerOffsets) {
3862
+ innerOffsets = polygon.innerPositions.map(function (innerPositions) {
3863
+ return innerPositions.map(function (pos) {
3864
+ return controller.positionToOffset(pos);
3865
+ });
3866
+ });
3867
+ polygonInnerOffsetCacheRef.current.set(polygon.id, innerOffsets);
3868
+ } // 내부 구멍 중 하나라도 포함되면 이 폴리곤은 스킵하고 다음 폴리곤 체크
3869
+
3870
+
3871
+ var isInHole = false;
3872
+
3873
+ for (var _i = 0, innerOffsets_1 = innerOffsets; _i < innerOffsets_1.length; _i++) {
3874
+ var innerOffset = innerOffsets_1[_i];
3875
+
3876
+ if (pointInPolygon(x, y, innerOffset)) {
3877
+ isInHole = true;
3878
+ break;
3879
+ }
3880
+ } // 구멍 안에 있으면 이 폴리곤은 스킵하고 다음 폴리곤 계속 검색
3881
+
3882
+
3883
+ if (isInHole) {
3884
+ continue;
3885
+ }
3886
+ } // 외부 폴리곤 안에 있고, 구멍 안에는 없으면 히트
3887
+
3888
+
3889
+ return polygon;
3890
+ }
3891
+ }
3892
+
3893
+ return null;
3894
+ }; // IDLE 이벤트 핸들러 (WoongCanvasMarker 최적화 적용)
3895
+
3896
+
3897
+ var handleIdle = function () {
3898
+ var beforeCacheSize = polygonOffsetCacheRef.current.size;
3899
+ clearRect(baseCanvasRef.current, baseContextRef.current); // 표시 복구 (드래그/줌 중 숨김 처리 복원)
3900
+
3901
+ containerRef.current && (containerRef.current.style.visibility = '');
3902
+ draggingRef.current = false;
3903
+ prevCenterOffsetRef.current = null;
3904
+ accumTranslateRef.current = {
3905
+ x: 0,
3906
+ y: 0
3907
+ };
3908
+
3909
+ if (containerRef.current) {
3910
+ containerRef.current.style.transform = '';
3911
+ } // 🚀 캐시 무효화 (지도 이동/줌으로 좌표가 바뀌므로 전체 clear 필요)
3912
+
3913
+
3914
+ polygonOffsetCacheRef.current.clear();
3915
+ polygonInnerOffsetCacheRef.current.clear();
3916
+ markerOffsetCacheRef.current.clear();
3917
+ markerBoundsRef.current.clear(); // 캐시 clear 로그
3918
+
3919
+ if (beforeCacheSize > 0) {
3920
+ console.log("\uD83E\uDDF9 [IDLE] \uCE90\uC2DC \uCD08\uAE30\uD654: ".concat(beforeCacheSize, "\uAC1C \uC81C\uAC70 (\uC88C\uD45C \uAC31\uC2E0 \uD544\uC694)"));
3921
+ } // 마커 이동
3922
+
3923
+
3924
+ var bounds = controller.getCurrBounds();
3925
+
3926
+ var markerOptions = tslib.__assign({
3927
+ position: bounds.nw
3928
+ }, options);
3929
+
3930
+ markerRef.current && controller.updateMarker(markerRef.current, markerOptions); // 🚀 폴리곤 렌더링 (독립)
3931
+
3932
+ if (polygonsRef.current.length > 0) {
3933
+ renderBasePolygons();
3934
+ renderInteractionPolygons();
3935
+ } // 🚀 마커 렌더링 (독립)
3936
+
3937
+
3938
+ if (markersRef.current.length > 0 && markerRenderer) {
3939
+ renderBaseMarkers();
3940
+ renderInteractionMarkers();
3941
+ }
3942
+ };
3943
+
3944
+ var handleZoomStart = function () {
3945
+ containerRef.current && (containerRef.current.style.visibility = 'hidden');
3946
+ };
3947
+
3948
+ var handleZoomEnd = function () {
3949
+ containerRef.current && (containerRef.current.style.visibility = '');
3950
+ }; // 지도 이벤트를 화면 좌표로 변환 (CanvasMarkerHanquf 방식)
3951
+
3952
+
3953
+ var parseEventToOffset = function (event) {
3954
+ var mapDiv = controller.mapDivElement;
3955
+ var rect = mapDiv.getBoundingClientRect();
3956
+ return {
3957
+ x: event.clientX - rect.left,
3958
+ y: event.clientY - rect.top
3959
+ };
3960
+ }; // 🚀 지도 클릭 이벤트 핸들러 (WoongCanvasMarker 방식 - controller.addEventListener)
3961
+
3962
+
3963
+ var handleMapClick = function (event) {
3964
+ var _a, _b, _c;
3965
+
3966
+ var clickedOffset = controller.positionToOffset(event.param.position); // 1. 마커 먼저 체크 (우선순위: hover 중 → clicked 중 → 배열 역순)
3967
+
3968
+ var hitMarker = null;
3969
+
3970
+ if (markersRef.current) {
3971
+ // 🎯 최우선: 현재 hover 중인 마커가 있으면 그것부터 체크
3972
+ if (hoveredMarkerRef.current && hoveredMarkerRef.current.visible !== false) {
3973
+ var markerData = markerBoundsRef.current.get(hoveredMarkerRef.current.id);
3974
+
3975
+ if (markerData) {
3976
+ var bounds = markerData.bounds;
3977
+
3978
+ if (clickedOffset.x >= bounds.x && clickedOffset.x <= bounds.x + bounds.width && clickedOffset.y >= bounds.y && clickedOffset.y <= bounds.y + bounds.height) {
3979
+ hitMarker = hoveredMarkerRef.current;
3980
+ }
3981
+ }
3982
+ } // 🎯 두 번째: hover 중인 마커에 히트 안 되고, clicked 마커가 있으면 체크
3983
+
3984
+
3985
+ if (!hitMarker && clickedMarkerRef.current && clickedMarkerRef.current.visible !== false) {
3986
+ var markerData = markerBoundsRef.current.get(clickedMarkerRef.current.id);
3987
+
3988
+ if (markerData) {
3989
+ var bounds = markerData.bounds;
3990
+
3991
+ if (clickedOffset.x >= bounds.x && clickedOffset.x <= bounds.x + bounds.width && clickedOffset.y >= bounds.y && clickedOffset.y <= bounds.y + bounds.height) {
3992
+ hitMarker = clickedMarkerRef.current;
3993
+ }
3994
+ }
3995
+ } // 🎯 세 번째: 나머지 마커들을 역순으로 체크 (위에 그려진 것부터)
3996
+
3997
+
3998
+ if (!hitMarker) {
3999
+ for (var i = markersRef.current.length - 1; i >= 0; i--) {
4000
+ var marker = markersRef.current[i];
4001
+ if (marker.visible === false) continue; // 이미 체크한 hover/clicked 마커는 스킵
4002
+
4003
+ if (marker.id === ((_a = hoveredMarkerRef.current) === null || _a === void 0 ? void 0 : _a.id)) continue;
4004
+ if (marker.id === ((_b = clickedMarkerRef.current) === null || _b === void 0 ? void 0 : _b.id)) continue;
4005
+ var markerData = markerBoundsRef.current.get(marker.id);
4006
+ if (!markerData) continue;
4007
+ var bounds = markerData.bounds;
4008
+
4009
+ if (clickedOffset.x >= bounds.x && clickedOffset.x <= bounds.x + bounds.width && clickedOffset.y >= bounds.y && clickedOffset.y <= bounds.y + bounds.height) {
4010
+ hitMarker = marker;
4011
+ break;
4012
+ }
4013
+ }
4014
+ }
4015
+ } // 2. 마커에 히트 안 되면 폴리곤 체크
4016
+
4017
+
4018
+ var hitPolygon = findPolygonAt(clickedOffset.x, clickedOffset.y); // 마커 클릭 해제
4019
+
4020
+ if (clickedMarkerRef.current) {
4021
+ clickedMarkerRef.current = null;
4022
+ renderInteractionMarkers();
4023
+ }
4024
+
4025
+ if (hitPolygon) {
4026
+ // 같은 폴리곤 다시 클릭하면 선택 해제 (토글)
4027
+ if (((_c = clickedPolygonRef.current) === null || _c === void 0 ? void 0 : _c.id) === hitPolygon.id) {
4028
+ console.log('🖱️ Polygon deselected:', hitPolygon.id);
4029
+ clickedPolygonRef.current = null; // 선택 해제 시에도 콜백 호출 (null 전달)
4030
+
4031
+ if (onPolygonClick) {
4032
+ onPolygonClick(null);
4033
+ }
4034
+ } else {
4035
+ console.log('🖱️ Polygon selected:', hitPolygon.id);
4036
+ clickedPolygonRef.current = hitPolygon; // 선택 시 콜백 호출
4037
+
4038
+ if (onPolygonClick) {
4039
+ onPolygonClick(hitPolygon);
4040
+ }
4041
+ } // 인터랙션 레이어 재렌더링
4042
+
4043
+
4044
+ renderInteractionPolygons();
4045
+ }
4046
+ }; // 마우스 이벤트 RAF 스로틀링용
4047
+
4048
+
4049
+ var rafIdRef = React.useRef(); // 마우스 이벤트 핸들러 (requestAnimationFrame으로 최적화)
4050
+
4051
+ var handleMouseMove = function (e) {
4052
+ // 🚀 드래그 중이면 호버 처리 스킵 (WoongCanvasMarker 방식)
4053
+ if (draggingRef.current) return; // 이미 RAF가 대기 중이면 스킵
4054
+
4055
+ if (rafIdRef.current) return;
4056
+ rafIdRef.current = requestAnimationFrame(function () {
4057
+ var _a, _b, _c;
4058
+
4059
+ rafIdRef.current = undefined;
4060
+
4061
+ var _d = parseEventToOffset(e),
4062
+ x = _d.x,
4063
+ y = _d.y; // 1. 마커 먼저 체크 (우선순위: hover 중 → clicked 중 → 배열 역순)
4064
+
4065
+
4066
+ var hitMarker = null;
4067
+
4068
+ if (markersRef.current) {
4069
+ // 🎯 최우선: 현재 hover 중인 마커가 있으면 그것부터 체크
4070
+ if (hoveredMarkerRef.current && hoveredMarkerRef.current.visible !== false) {
4071
+ var markerData = markerBoundsRef.current.get(hoveredMarkerRef.current.id);
4072
+
4073
+ if (markerData) {
4074
+ var bounds = markerData.bounds;
4075
+
4076
+ if (x >= bounds.x && x <= bounds.x + bounds.width && y >= bounds.y && y <= bounds.y + bounds.height) {
4077
+ hitMarker = hoveredMarkerRef.current;
4078
+ }
4079
+ }
4080
+ } // 🎯 두 번째: hover 중인 마커에 히트 안 되고, clicked 마커가 있으면 체크
4081
+
4082
+
4083
+ if (!hitMarker && clickedMarkerRef.current && clickedMarkerRef.current.visible !== false) {
4084
+ var markerData = markerBoundsRef.current.get(clickedMarkerRef.current.id);
4085
+
4086
+ if (markerData) {
4087
+ var bounds = markerData.bounds;
4088
+
4089
+ if (x >= bounds.x && x <= bounds.x + bounds.width && y >= bounds.y && y <= bounds.y + bounds.height) {
4090
+ hitMarker = clickedMarkerRef.current;
4091
+ }
4092
+ }
4093
+ } // 🎯 세 번째: 나머지 마커들을 역순으로 체크 (위에 그려진 것부터)
4094
+
4095
+
4096
+ if (!hitMarker) {
4097
+ for (var i = markersRef.current.length - 1; i >= 0; i--) {
4098
+ var marker = markersRef.current[i];
4099
+ if (marker.visible === false) continue; // 이미 체크한 hover/clicked 마커는 스킵
4100
+
4101
+ if (marker.id === ((_a = hoveredMarkerRef.current) === null || _a === void 0 ? void 0 : _a.id)) continue;
4102
+ if (marker.id === ((_b = clickedMarkerRef.current) === null || _b === void 0 ? void 0 : _b.id)) continue;
4103
+ var markerData = markerBoundsRef.current.get(marker.id);
4104
+ if (!markerData) continue;
4105
+ var bounds = markerData.bounds;
4106
+
4107
+ if (x >= bounds.x && x <= bounds.x + bounds.width && y >= bounds.y && y <= bounds.y + bounds.height) {
4108
+ hitMarker = marker;
4109
+ break;
4110
+ }
4111
+ }
4112
+ }
4113
+ } // 마커가 호버되었을 때
4114
+
4115
+
4116
+ if (hitMarker) {
4117
+ // 폴리곤 호버 해제
4118
+ if (hoveredPolygonRef.current) {
4119
+ hoveredPolygonRef.current = null;
4120
+
4121
+ if (onPolygonLeave) {
4122
+ onPolygonLeave();
4123
+ }
4124
+ } // 마커 호버 상태 업데이트
4125
+
4126
+
4127
+ if (hitMarker.id !== ((_c = hoveredMarkerRef.current) === null || _c === void 0 ? void 0 : _c.id)) {
4128
+ hoveredMarkerRef.current = hitMarker;
4129
+ console.log('🎯 Marker hovered:', hitMarker.id);
4130
+
4131
+ if (onMarkerHover) {
4132
+ onMarkerHover(hitMarker);
4133
+ }
4134
+
4135
+ renderInteractionMarkers();
4136
+ renderInteractionPolygons();
4137
+ }
4138
+
4139
+ return;
4140
+ } // 2. 마커에 히트 안 되면 폴리곤 체크
4141
+
4142
+
4143
+ var hitPolygon = findPolygonAt(x, y); // 마커 호버 해제
4144
+
4145
+ if (hoveredMarkerRef.current) {
4146
+ if (onMarkerLeave) {
4147
+ onMarkerLeave();
4148
+ }
4149
+
4150
+ hoveredMarkerRef.current = null;
4151
+ renderInteractionMarkers();
4152
+ }
4153
+
4154
+ if (hitPolygon !== hoveredPolygonRef.current) {
4155
+ // leave 이벤트
4156
+ if (hoveredPolygonRef.current && onPolygonLeave) {
4157
+ onPolygonLeave();
4158
+ } // hover 이벤트
4159
+
4160
+
4161
+ hoveredPolygonRef.current = hitPolygon;
4162
+
4163
+ if (hitPolygon && onPolygonHover) {
4164
+ onPolygonHover(hitPolygon);
4165
+ } // 인터랙션 레이어 재렌더링 (호버 + 클릭)
4166
+
4167
+
4168
+ renderInteractionPolygons();
4169
+ }
4170
+ });
4171
+ };
4172
+
4173
+ var handleCenterChanged = function () {
4174
+ // 드래그 중에는 리렌더 대신 transform 으로만 추종
4175
+ var center = controller.getCurrBounds().getCenter();
4176
+ var curr = controller.positionToOffset(center);
4177
+ var prev = prevCenterOffsetRef.current;
4178
+
4179
+ if (!prev) {
4180
+ prevCenterOffsetRef.current = {
4181
+ x: curr.x,
4182
+ y: curr.y
4183
+ };
4184
+ draggingRef.current = true;
4185
+ return;
4186
+ }
4187
+
4188
+ var dx = prev.x - curr.x;
4189
+ var dy = prev.y - curr.y;
4190
+ accumTranslateRef.current = {
4191
+ x: accumTranslateRef.current.x + dx,
4192
+ y: accumTranslateRef.current.y + dy
4193
+ };
4194
+ prevCenterOffsetRef.current = {
4195
+ x: curr.x,
4196
+ y: curr.y
4197
+ };
4198
+ draggingRef.current = true;
4199
+
4200
+ if (containerRef.current) {
4201
+ containerRef.current.style.transform = "translate(".concat(accumTranslateRef.current.x, "px, ").concat(accumTranslateRef.current.y, "px)");
4202
+ }
4203
+ };
4204
+
4205
+ var handleMouseLeave = function () {
4206
+ // RAF 취소
4207
+ if (rafIdRef.current) {
4208
+ cancelAnimationFrame(rafIdRef.current);
4209
+ rafIdRef.current = undefined;
4210
+ }
4211
+
4212
+ if (hoveredPolygonRef.current && onPolygonLeave) {
4213
+ onPolygonLeave();
4214
+ }
4215
+
4216
+ hoveredPolygonRef.current = null;
4217
+ hoveredMarkerRef.current = null;
4218
+ renderInteractionMarkers();
4219
+ renderInteractionPolygons();
4220
+ }; // 🚀 initialize - 폴리곤 캔버스 초기화 (독립)
4221
+
4222
+
4223
+ React.useEffect(function () {
4224
+ if (baseCanvasRef.current && hoverCanvasRef.current && containerRef.current) {
4225
+ // 2d 컨텍스트 설정 (폴리곤 캔버스만)
4226
+ baseContextRef.current = baseCanvasRef.current.getContext('2d');
4227
+ hoverContextRef.current = hoverCanvasRef.current.getContext('2d'); // 스케일링 (폴리곤 캔버스만)
4228
+
4229
+ if (baseContextRef.current) {
4230
+ scaleCanvas(controller, baseCanvasRef.current, baseContextRef.current);
4231
+ }
4232
+
4233
+ if (hoverContextRef.current) {
4234
+ scaleCanvas(controller, hoverCanvasRef.current, hoverContextRef.current);
4235
+ } // 폴리곤 데이터가 있으면 렌더링
4236
+
4237
+
4238
+ if (polygonsRef.current.length > 0) {
4239
+ renderBasePolygons();
4240
+ renderInteractionPolygons();
4241
+ }
4242
+ }
4243
+ }, []); // 🚀 initialize - 마커 캔버스 초기화 (독립)
4244
+
4245
+ React.useEffect(function () {
4246
+ if (baseMarkerCanvasRef.current && interactiveMarkerCanvasRef.current) {
4247
+ // 2d 컨텍스트 설정 (마커 캔버스만)
4248
+ baseMarkerContextRef.current = baseMarkerCanvasRef.current.getContext('2d');
4249
+ interactiveMarkerContextRef.current = interactiveMarkerCanvasRef.current.getContext('2d'); // 스케일링 (마커 캔버스만)
4250
+
4251
+ if (baseMarkerContextRef.current) {
4252
+ scaleCanvas(controller, baseMarkerCanvasRef.current, baseMarkerContextRef.current);
4253
+ }
4254
+
4255
+ if (interactiveMarkerContextRef.current) {
4256
+ scaleCanvas(controller, interactiveMarkerCanvasRef.current, interactiveMarkerContextRef.current);
4257
+ } // 마커 데이터가 있으면 렌더링
4258
+
4259
+
4260
+ if (markersRef.current.length > 0 && markerRenderer) {
4261
+ renderBaseMarkers();
4262
+ renderInteractionMarkers();
4263
+ }
4264
+ }
4265
+ }, [markerRenderer]); // 🚀 공통 초기화 - 이벤트 등록 및 리사이즈
4266
+
4267
+ React.useEffect(function () {
4268
+ var resizeObserver;
4269
+
4270
+ if (containerRef.current) {
4271
+ // 리사이즈 처리
4272
+ resizeObserver = new ResizeObserver(function () {
4273
+ // 폴리곤 캔버스 리사이즈
4274
+ if (baseCanvasRef.current && baseContextRef.current) {
4275
+ clearRect(baseCanvasRef.current, baseContextRef.current);
4276
+ scaleCanvas(controller, baseCanvasRef.current, baseContextRef.current);
4277
+ }
4278
+
4279
+ if (hoverCanvasRef.current && hoverContextRef.current) {
4280
+ clearRect(hoverCanvasRef.current, hoverContextRef.current);
4281
+ scaleCanvas(controller, hoverCanvasRef.current, hoverContextRef.current);
4282
+ } // 마커 캔버스 리사이즈
4283
+
4284
+
4285
+ if (baseMarkerCanvasRef.current && baseMarkerContextRef.current) {
4286
+ clearRect(baseMarkerCanvasRef.current, baseMarkerContextRef.current);
4287
+ scaleCanvas(controller, baseMarkerCanvasRef.current, baseMarkerContextRef.current);
4288
+ }
4289
+
4290
+ if (interactiveMarkerCanvasRef.current && interactiveMarkerContextRef.current) {
4291
+ clearRect(interactiveMarkerCanvasRef.current, interactiveMarkerContextRef.current);
4292
+ scaleCanvas(controller, interactiveMarkerCanvasRef.current, interactiveMarkerContextRef.current);
4293
+ } // 폴리곤 렌더링
4294
+
4295
+
4296
+ if (polygonsRef.current.length > 0) {
4297
+ renderBasePolygons();
4298
+ renderInteractionPolygons();
4299
+ } // 마커 렌더링
4300
+
4301
+
4302
+ if (markersRef.current.length > 0 && markerRenderer) {
4303
+ renderBaseMarkers();
4304
+ renderInteractionMarkers();
4305
+ }
4306
+ });
4307
+ resizeObserver.observe(controller.mapDivElement); // 🚀 이벤트 등록 (한 번만)
4308
+
4309
+ controller.addEventListener('IDLE', handleIdle);
4310
+ controller.addEventListener('ZOOMSTART', handleZoomStart);
4311
+ controller.addEventListener('ZOOM_CHANGED', handleZoomEnd);
4312
+ controller.addEventListener('CLICK', handleMapClick);
4313
+ controller.addEventListener('CENTER_CHANGED', handleCenterChanged); // 지도 div에 마우스 이벤트 등록
4314
+
4315
+ controller.mapDivElement.addEventListener('mousemove', handleMouseMove);
4316
+ controller.mapDivElement.addEventListener('mouseleave', handleMouseLeave);
4317
+ }
4318
+
4319
+ return function () {
4320
+ // RAF 정리
4321
+ if (rafIdRef.current) {
4322
+ cancelAnimationFrame(rafIdRef.current);
4323
+ }
4324
+
4325
+ resizeObserver && resizeObserver.disconnect();
4326
+ controller.removeEventListener('IDLE', handleIdle);
4327
+ controller.removeEventListener('ZOOMSTART', handleZoomStart);
4328
+ controller.removeEventListener('ZOOM_CHANGED', handleZoomEnd);
4329
+ controller.removeEventListener('CLICK', handleMapClick);
4330
+ controller.removeEventListener('CENTER_CHANGED', handleCenterChanged);
4331
+ controller.mapDivElement.removeEventListener('mousemove', handleMouseMove);
4332
+ controller.mapDivElement.removeEventListener('mouseleave', handleMouseLeave);
4333
+ };
4334
+ }, []); // 🚀 polygons 데이터 업데이트 (독립)
4335
+
4336
+ React.useEffect(function () {
4337
+ console.log('📦 [useEffect] polygons 변경:', polygons === null || polygons === void 0 ? void 0 : polygons.length, '개'); // refs 업데이트
4338
+
4339
+ polygonsRef.current = polygons || []; // 🚀 폴리곤만 렌더링 (마커와 독립)
4340
+
4341
+ if (baseContextRef.current) {
4342
+ renderBasePolygons();
4343
+ renderInteractionPolygons();
4344
+ }
4345
+ }, [polygons]); // 🚀 markers 데이터 업데이트 (독립)
4346
+
4347
+ React.useEffect(function () {
4348
+ console.log('📦 [useEffect] markers 변경:', markers === null || markers === void 0 ? void 0 : markers.length, '개'); // refs 업데이트
4349
+
4350
+ markersRef.current = markers || []; // 🚀 마커만 렌더링 (폴리곤과 독립)
4351
+
4352
+ if (baseMarkerContextRef.current && markerRenderer) {
4353
+ renderBaseMarkers();
4354
+ renderInteractionMarkers();
4355
+ }
4356
+ }, [markers, markerRenderer]); // CanvasMarker 스타일의 createPortal return
4357
+
4358
+ return reactDom.createPortal(React__default["default"].createElement("div", {
4359
+ ref: containerRef,
4360
+ style: {
4361
+ position: 'absolute',
4362
+ width: '100%',
4363
+ height: '100%',
4364
+ pointerEvents: 'none'
4365
+ }
4366
+ }, React__default["default"].createElement("canvas", {
4367
+ ref: baseCanvasRef,
4368
+ style: {
4369
+ position: 'absolute',
4370
+ top: 0,
4371
+ left: 0,
4372
+ pointerEvents: 'revert-layer',
4373
+ width: '100%',
4374
+ height: '100%'
4375
+ }
4376
+ }), React__default["default"].createElement("canvas", {
4377
+ ref: hoverCanvasRef,
4378
+ style: {
4379
+ position: 'absolute',
4380
+ top: 0,
4381
+ left: 0,
4382
+ pointerEvents: 'none',
4383
+ width: '100%',
4384
+ height: '100%'
4385
+ }
4386
+ }), React__default["default"].createElement("canvas", {
4387
+ ref: baseMarkerCanvasRef,
4388
+ style: {
4389
+ position: 'absolute',
4390
+ top: 0,
4391
+ left: 0,
4392
+ pointerEvents: 'none',
4393
+ width: '100%',
4394
+ height: '100%'
4395
+ }
4396
+ }), React__default["default"].createElement("canvas", {
4397
+ ref: interactiveMarkerCanvasRef,
4398
+ style: {
4399
+ position: 'absolute',
4400
+ top: 0,
4401
+ left: 0,
4402
+ pointerEvents: 'none',
4403
+ width: '100%',
4404
+ height: '100%'
4405
+ }
4406
+ })), divElement);
4407
+ });
4408
+
2683
4409
  /**
2684
4410
  * Mint Map 컴포넌트
2685
4411
  *
@@ -5918,6 +7644,8 @@
5918
7644
  exports.AnimationPlayer = AnimationPlayer;
5919
7645
  exports.Bounds = Bounds;
5920
7646
  exports.CanvasMarker = CanvasMarker;
7647
+ exports.CanvasMarkerClaude = CanvasMarkerClaude;
7648
+ exports.CanvasMarkerHanquf = CanvasMarkerHanquf;
5921
7649
  exports.CircleMarker = CircleMarker;
5922
7650
  exports.Drawable = Drawable;
5923
7651
  exports.GeoCalulator = GeoCalulator;