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