mehdi-akbari-map 1.0.0 → 1.0.2

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.
@@ -1 +1 @@
1
- {"version":3,"file":"Map.d.ts","sourceRoot":"","sources":["../../src/components/Map.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAmD,MAAM,OAAO,CAAC;AACxE,OAAO,KAAK,EAAoB,QAAQ,EAAE,MAAM,UAAU,CAAC;AAO3D,QAAA,MAAM,GAAG,EAAE,KAAK,CAAC,EAAE,CAAC,QAAQ,CAkI3B,CAAC;AAEF,eAAe,GAAG,CAAC"}
1
+ {"version":3,"file":"Map.d.ts","sourceRoot":"","sources":["../../src/components/Map.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAmD,MAAM,OAAO,CAAC;AACxE,OAAO,KAAK,EAAoB,QAAQ,EAAE,MAAM,UAAU,CAAC;AAO3D,QAAA,MAAM,GAAG,EAAE,KAAK,CAAC,EAAE,CAAC,QAAQ,CAsJ3B,CAAC;AAEF,eAAe,GAAG,CAAC"}
package/dist/index.cjs CHANGED
@@ -61,10 +61,8 @@ function createCustomMarkerElement({
61
61
  });
62
62
  const iconBox = document.createElement("div");
63
63
  Object.assign(iconBox.style, {
64
- width: "40px",
65
- height: "40px",
66
- borderRadius: "50%",
67
- backgroundColor: "white",
64
+ width: "32px",
65
+ height: "32px",
68
66
  border: isSelected ? "3px solid #3b82f6" : "2px solid #ef4444",
69
67
  boxShadow: "0 4px 8px rgba(0,0,0,0.2)",
70
68
  display: "flex",
@@ -95,10 +93,13 @@ function createCustomMarkerElement({
95
93
 
96
94
  // src/utils/createClusterElement.ts
97
95
  function createClusterElement(count) {
98
- const el = document.createElement("div");
99
- el.className = "neshan-cluster-marker";
100
- el.innerHTML = `<span class="neshan-cluster-count">${count}</span>`;
101
- return el;
96
+ const container = document.createElement("div");
97
+ container.className = "neshan-cluster-marker";
98
+ const countSpan = document.createElement("span");
99
+ countSpan.className = "neshan-cluster-count";
100
+ countSpan.innerText = count.toString();
101
+ container.appendChild(countSpan);
102
+ return container;
102
103
  }
103
104
 
104
105
  // src/components/Map.tsx
@@ -147,7 +148,6 @@ var Map = ({
147
148
  mapInstance.flyTo({
148
149
  center: [selectedStore.lng, selectedStore.lat],
149
150
  zoom: 16,
150
- // زوم نزدیک هنگام انتخاب
151
151
  essential: true,
152
152
  duration: 1500
153
153
  });
@@ -169,12 +169,21 @@ var Map = ({
169
169
  let el;
170
170
  if (isCluster) {
171
171
  el = createClusterElement(count);
172
- el.onclick = () => {
172
+ const newMarker2 = new mapLib.Marker({
173
+ element: el,
174
+ anchor: "center"
175
+ }).setLngLat([lng, lat]).addTo(mapInstance);
176
+ el.onclick = (e) => {
177
+ e.stopPropagation();
173
178
  const expansionZoom = Math.min(
174
179
  clusterIndex.current.getClusterExpansionZoom(cluster.id),
175
180
  18
176
181
  );
177
- mapInstance.easeTo({ center: [lng, lat], zoom: expansionZoom });
182
+ mapInstance.easeTo({
183
+ center: [lng, lat],
184
+ zoom: expansionZoom,
185
+ duration: 800
186
+ });
178
187
  };
179
188
  } else {
180
189
  const storeData = cluster.properties;
@@ -205,7 +214,17 @@ var Map = ({
205
214
  mapInstance.off("moveend", updateMarkers);
206
215
  };
207
216
  }, [mapInstance, updateMarkers]);
208
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "relative", ...style }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref: mapContainerRef, style: { width: "100%", height: "100%" } }) });
217
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "relative", ...style }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
218
+ "div",
219
+ {
220
+ ref: mapContainerRef,
221
+ style: {
222
+ width: "100%",
223
+ height: "100%",
224
+ backfaceVisibility: "hidden"
225
+ }
226
+ }
227
+ ) });
209
228
  };
210
229
  var Map_default = Map;
211
230
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/components/InteractiveMap.tsx","../src/components/Map.tsx","../src/utils/createCustomMarkerElement.ts","../src/utils/createClusterElement.ts","../src/components/StoreCard.tsx"],"sourcesContent":["\r\nimport './styles.css';\r\n\r\n\r\nexport { InteractiveMap as NeshanMap } from './components/InteractiveMap';\r\n\r\n\r\nexport { default as RawMap } from './components/Map';\r\n\r\n\r\nexport type {\r\n MapProps,\r\n MapOptions,\r\n MarkerData,\r\n Store,\r\n MapboxMap,\r\n LngLatLike,\r\n} from './types';","\"use client\";\r\n\r\nimport React, { useState, useEffect } from 'react';\r\nimport Map from './Map';\r\nimport { StoreCard } from './StoreCard';\r\nimport { Store, MapProps } from '../types';\r\n\r\nexport const InteractiveMap: React.FC<MapProps> = (props) => {\r\n const [isMobile, setIsMobile] = useState(false);\r\n // ✅ ذخیره نمونه نقشه برای پاس دادن به کلیک مارکر\r\n const [mapInstance, setMapInstance] = useState<any>(null);\r\n\r\n useEffect(() => {\r\n const checkMobile = () => setIsMobile(window.innerWidth < 1024);\r\n checkMobile();\r\n window.addEventListener('resize', checkMobile);\r\n return () => window.removeEventListener('resize', checkMobile);\r\n }, []);\r\n\r\n const handleStoreSelect = (store: Store, index: number) => {\r\n // ✅ حالا دیگر null نمی‌فرستیم، نمونه واقعی نقشه را می‌فرستیم\r\n props.onMarkerClick?.(store, index, mapInstance);\r\n };\r\n\r\n // اطمینان از اینکه markers همیشه یک آرایه است\r\n const safeMarkers = props.markers || [];\r\n\r\n return (\r\n <div className={`map-layout-root ${isMobile ? 'is-mobile' : 'is-desktop'}`}>\r\n \r\n {!isMobile && (\r\n <aside className=\"sidebar\">\r\n <header className=\"sidebar-header\">لیست فروشگاه‌ها</header>\r\n <div className=\"sidebar-list\">\r\n {/* ✅ استفاده از safeMarkers برای جلوگیری از خطای undefined */}\r\n {safeMarkers.map((store, idx) => (\r\n <StoreCard \r\n key={store.id} \r\n store={store} \r\n isSelected={props.selectedMarkerId === store.id}\r\n onSelect={() => handleStoreSelect(store, idx)}\r\n />\r\n ))}\r\n </div>\r\n </aside>\r\n )}\r\n\r\n <main className=\"map-container\">\r\n {/* ✅ دریافت نمونه نقشه وقتی لود شد */}\r\n <Map \r\n {...props} \r\n onMapLoad={(map) => {\r\n setMapInstance(map);\r\n props.onMapLoad?.(map);\r\n }} \r\n />\r\n \r\n {props.productName && (\r\n <div className=\"product-badge\">\r\n <span>{props.productName}</span>\r\n </div>\r\n )}\r\n\r\n {isMobile && (\r\n <div className=\"mobile-slider\">\r\n {safeMarkers.map((store, idx) => (\r\n <StoreCard \r\n key={store.id} \r\n store={store} \r\n isSelected={props.selectedMarkerId === store.id}\r\n onSelect={() => handleStoreSelect(store, idx)}\r\n />\r\n ))}\r\n </div>\r\n )}\r\n </main>\r\n </div>\r\n );\r\n};","\"use client\";\r\n\r\nimport React, { useEffect, useRef, useState, useCallback } from 'react';\r\nimport type { MapboxMap, Store, MapProps } from '../types';\r\nimport { createCustomMarkerElement } from '../utils/createCustomMarkerElement';\r\nimport { createClusterElement } from '../utils/createClusterElement';\r\nimport Supercluster from 'supercluster';\r\n\r\n\r\n\r\nconst Map: React.FC<MapProps> = ({\r\n options,\r\n markers = [],\r\n markerLogoUrl = '',\r\n selectedMarkerId = null,\r\n onMarkerClick,\r\n onMapLoad,\r\n style = { width: '100%', height: '100%' },\r\n}) => {\r\n const mapContainerRef = useRef<HTMLDivElement>(null);\r\n const [mapInstance, setMapInstance] = useState<MapboxMap | null>(null);\r\n const [mapLib, setMapLib] = useState<any>(null);\r\n const markersRef = useRef<any[]>([]);\r\n \r\n // سیستم کلاسترینگ\r\n const clusterIndex = useRef(new Supercluster({\r\n radius: 60,\r\n maxZoom: 16\r\n }));\r\n\r\n // ۱. لود کتابخانه\r\n useEffect(() => {\r\n import('@neshan-maps-platform/mapbox-gl').then((mod) => {\r\n setMapLib(mod.default || mod);\r\n });\r\n }, []);\r\n\r\n // ۲. ساخت نقشه\r\n useEffect(() => {\r\n if (!mapLib || !mapContainerRef.current || mapInstance) return;\r\n const map = new mapLib.Map({\r\n container: mapContainerRef.current,\r\n ...options,\r\n });\r\n map.on('load', () => {\r\n setMapInstance(map);\r\n onMapLoad?.(map);\r\n });\r\n return () => { if (map) map.remove(); };\r\n }, [mapLib]);\r\n\r\n // ۳. منطق زوم خودکار (FlyTo) وقتی مارکری انتخاب می‌شود\r\n useEffect(() => {\r\n if (!mapInstance || !selectedMarkerId) return;\r\n const selectedStore = markers.find(m => m.id === selectedMarkerId);\r\n if (selectedStore) {\r\n mapInstance.flyTo({\r\n center: [selectedStore.lng, selectedStore.lat],\r\n zoom: 16, // زوم نزدیک هنگام انتخاب\r\n essential: true,\r\n duration: 1500\r\n });\r\n }\r\n }, [selectedMarkerId, mapInstance, markers]);\r\n\r\n // ۴. تابع رندر کردن مارکرها و کلاسترها\r\n const updateMarkers = useCallback(() => {\r\n if (!mapInstance || !mapLib) return;\r\n\r\n const bounds = mapInstance.getBounds();\r\n const zoom = Math.floor(mapInstance.getZoom());\r\n\r\n // دریافت داده‌های کلاستر شده برای محدوده فعلی نقشه\r\n const clusters = clusterIndex.current.getClusters(\r\n [bounds.getWest(), bounds.getSouth(), bounds.getEast(), bounds.getNorth()],\r\n zoom\r\n );\r\n\r\n // پاک کردن مارکرهای قبلی\r\n markersRef.current.forEach(m => m.remove());\r\n markersRef.current = [];\r\n\r\n clusters.forEach(cluster => {\r\n const [lng, lat] = cluster.geometry.coordinates;\r\n const { cluster: isCluster, point_count: count } = cluster.properties;\r\n\r\n let el: HTMLElement;\r\n\r\n if (isCluster) {\r\n // اگر کلاستر بود\r\n el = createClusterElement(count);\r\n el.onclick = () => {\r\n const expansionZoom = Math.min(\r\n clusterIndex.current.getClusterExpansionZoom(cluster.id as number),\r\n 18\r\n );\r\n mapInstance.easeTo({ center: [lng, lat], zoom: expansionZoom });\r\n };\r\n } else {\r\n // اگر مارکر تکی بود\r\n const storeData = cluster.properties as Store;\r\n el = createCustomMarkerElement({\r\n marker: storeData,\r\n isSelected: storeData.id === selectedMarkerId,\r\n logoSrc: markerLogoUrl,\r\n });\r\n el.onclick = () => onMarkerClick?.(storeData, 0, mapInstance);\r\n }\r\n\r\n const newMarker = new mapLib.Marker({ element: el, anchor: isCluster ? 'center' : 'bottom' })\r\n .setLngLat([lng, lat])\r\n .addTo(mapInstance);\r\n \r\n markersRef.current.push(newMarker);\r\n });\r\n }, [mapInstance, mapLib, selectedMarkerId, markerLogoUrl, onMarkerClick]);\r\n\r\n // ۵. آماده‌سازی داده‌ها برای کلاسترینگ\r\n useEffect(() => {\r\n const points: any = markers.map(s => ({\r\n type: 'Feature',\r\n properties: { ...s },\r\n geometry: { type: 'Point', coordinates: [s.lng, s.lat] }\r\n }));\r\n clusterIndex.current.load(points);\r\n updateMarkers();\r\n }, [markers, updateMarkers]);\r\n\r\n // ۶. گوش دادن به تغییرات نقشه برای بروزرسانی کلاسترها\r\n useEffect(() => {\r\n if (!mapInstance) return;\r\n mapInstance.on('moveend', updateMarkers);\r\n return () => { mapInstance.off('moveend', updateMarkers); };\r\n }, [mapInstance, updateMarkers]);\r\n\r\n return (\r\n <div style={{ position: 'relative', ...style }}>\r\n <div ref={mapContainerRef} style={{ width: '100%', height: '100%' }} />\r\n </div>\r\n );\r\n};\r\n\r\nexport default Map;","import type { MarkerData } from '../types';\r\n\r\ninterface CreateMarkerOptions {\r\n marker: MarkerData;\r\n isSelected: boolean;\r\n logoSrc?: string;\r\n showPrice?: boolean;\r\n}\r\n\r\nexport function createCustomMarkerElement({\r\n marker,\r\n isSelected,\r\n logoSrc = '',\r\n showPrice = true,\r\n}: CreateMarkerOptions): HTMLElement {\r\n \r\n const container = document.createElement('div');\r\n container.className = 'neshan-marker-container';\r\n\r\n \r\n const body = document.createElement('div');\r\n body.className = `neshan-marker-body ${isSelected ? 'neshan-marker-selected' : ''}`;\r\n \r\n \r\n Object.assign(body.style, {\r\n display: 'flex',\r\n flexDirection: 'column',\r\n alignItems: 'center',\r\n transition: 'transform 0.3s ease',\r\n transform: isSelected ? 'scale(1.2)' : 'scale(1)',\r\n });\r\n\r\n \r\n const iconBox = document.createElement('div');\r\n Object.assign(iconBox.style, {\r\n width: '40px',\r\n height: '40px',\r\n borderRadius: '50%',\r\n backgroundColor: 'white',\r\n border: isSelected ? '3px solid #3b82f6' : '2px solid #ef4444',\r\n boxShadow: '0 4px 8px rgba(0,0,0,0.2)',\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n overflow: 'hidden'\r\n });\r\n\r\n if (logoSrc) {\r\n const img = document.createElement('img');\r\n img.src = logoSrc;\r\n img.style.width = '100%';\r\n img.style.height = '100%';\r\n img.style.objectFit = 'cover';\r\n iconBox.appendChild(img);\r\n } else {\r\n iconBox.style.backgroundColor = '#ef4444';\r\n }\r\n\r\n body.appendChild(iconBox);\r\n\r\n \r\n if (showPrice && (marker.price || marker.name)) {\r\n const label = document.createElement('div');\r\n label.className = 'neshan-marker-label'; // استفاده از کلاس CSS جدید\r\n label.textContent = marker.price ? `${marker.price} ت` : (marker.name ?? '');\r\n body.appendChild(label);\r\n}\r\n\r\n container.appendChild(body);\r\n return container;\r\n}","export function createClusterElement(count: number): HTMLElement {\r\n const el = document.createElement('div');\r\n el.className = 'neshan-cluster-marker';\r\n el.innerHTML = `<span class=\"neshan-cluster-count\">${count}</span>`;\r\n return el;\r\n}","import React from 'react';\r\nimport { Store } from '../types';\r\n\r\ninterface StoreCardProps {\r\n store: Store;\r\n isSelected: boolean;\r\n onSelect: () => void;\r\n}\r\n\r\nexport const StoreCard: React.FC<StoreCardProps> = ({ store, isSelected, onSelect }) => {\r\n const googleMapUrl = `https://www.google.com/maps/dir/?api=1&destination=${store.lat},${store.lng}`;\r\n\r\n return (\r\n <div \r\n className={`store-card ${isSelected ? 'active' : ''}`}\r\n onClick={onSelect}\r\n >\r\n <div className=\"store-card-header\">\r\n <div>\r\n <h4 className=\"store-name\">{store.name}</h4>\r\n <span className=\"store-city\">{store.city}</span>\r\n </div>\r\n <div className=\"store-price\">{store.price} تومان</div>\r\n </div>\r\n\r\n <p className=\"store-desc\">{store.desc}</p>\r\n\r\n {/* بخش آکاردئونی با CSS Animation */}\r\n <div className=\"store-details-wrapper\">\r\n <div className=\"store-details-content\">\r\n <div className=\"action-buttons\">\r\n {store.phone && (\r\n <a href={`tel:${store.phone}`} className=\"btn btn-call\">\r\n <span>تماس</span>\r\n </a>\r\n )}\r\n {store.whatsapp && (\r\n <a href={`https://wa.me/${store.whatsapp}`} target=\"_blank\" className=\"btn btn-wa\">\r\n <span>واتساپ</span>\r\n </a>\r\n )}\r\n <a href={googleMapUrl} target=\"_blank\" className=\"btn btn-route\">\r\n <span>مسیریابی</span>\r\n </a>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n );\r\n};"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACEA,IAAAA,gBAA2C;;;ACA3C,mBAAgE;;;ACOzD,SAAS,0BAA0B;AAAA,EACxC;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV,YAAY;AACd,GAAqC;AAEnC,QAAM,YAAY,SAAS,cAAc,KAAK;AAC9C,YAAU,YAAY;AAGtB,QAAM,OAAO,SAAS,cAAc,KAAK;AACzC,OAAK,YAAY,sBAAsB,aAAa,2BAA2B,EAAE;AAGjF,SAAO,OAAO,KAAK,OAAO;AAAA,IACxB,SAAS;AAAA,IACT,eAAe;AAAA,IACf,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,WAAW,aAAa,eAAe;AAAA,EACzC,CAAC;AAGD,QAAM,UAAU,SAAS,cAAc,KAAK;AAC5C,SAAO,OAAO,QAAQ,OAAO;AAAA,IAC3B,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,cAAc;AAAA,IACd,iBAAiB;AAAA,IACjB,QAAQ,aAAa,sBAAsB;AAAA,IAC3C,WAAW;AAAA,IACX,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,gBAAgB;AAAA,IAChB,UAAU;AAAA,EACZ,CAAC;AAED,MAAI,SAAS;AACX,UAAM,MAAM,SAAS,cAAc,KAAK;AACxC,QAAI,MAAM;AACV,QAAI,MAAM,QAAQ;AAClB,QAAI,MAAM,SAAS;AACnB,QAAI,MAAM,YAAY;AACtB,YAAQ,YAAY,GAAG;AAAA,EACzB,OAAO;AACL,YAAQ,MAAM,kBAAkB;AAAA,EAClC;AAEA,OAAK,YAAY,OAAO;AAGzB,MAAI,cAAc,OAAO,SAAS,OAAO,OAAO;AAC7C,UAAM,QAAQ,SAAS,cAAc,KAAK;AAC1C,UAAM,YAAY;AAClB,UAAM,cAAc,OAAO,QAAQ,GAAG,OAAO,KAAK,YAAQ,OAAO,QAAQ;AACzE,SAAK,YAAY,KAAK;AAAA,EAC1B;AAEE,YAAU,YAAY,IAAI;AAC1B,SAAO;AACT;;;ACtEO,SAAS,qBAAqB,OAA4B;AAC/D,QAAM,KAAK,SAAS,cAAc,KAAK;AACvC,KAAG,YAAY;AACf,KAAG,YAAY,sCAAsC,KAAK;AAC1D,SAAO;AACT;;;AFCA,0BAAyB;AAmInB;AA/HN,IAAM,MAA0B,CAAC;AAAA,EAC/B;AAAA,EACA,UAAU,CAAC;AAAA,EACX,gBAAgB;AAAA,EAChB,mBAAmB;AAAA,EACnB;AAAA,EACA;AAAA,EACA,QAAQ,EAAE,OAAO,QAAQ,QAAQ,OAAO;AAC1C,MAAM;AACJ,QAAM,sBAAkB,qBAAuB,IAAI;AACnD,QAAM,CAAC,aAAa,cAAc,QAAI,uBAA2B,IAAI;AACrE,QAAM,CAAC,QAAQ,SAAS,QAAI,uBAAc,IAAI;AAC9C,QAAM,iBAAa,qBAAc,CAAC,CAAC;AAGnC,QAAM,mBAAe,qBAAO,IAAI,oBAAAC,QAAa;AAAA,IAC3C,QAAQ;AAAA,IACR,SAAS;AAAA,EACX,CAAC,CAAC;AAGF,8BAAU,MAAM;AACd,WAAO,iCAAiC,EAAE,KAAK,CAAC,QAAQ;AACtD,gBAAU,IAAI,WAAW,GAAG;AAAA,IAC9B,CAAC;AAAA,EACH,GAAG,CAAC,CAAC;AAGL,8BAAU,MAAM;AACd,QAAI,CAAC,UAAU,CAAC,gBAAgB,WAAW,YAAa;AACxD,UAAM,MAAM,IAAI,OAAO,IAAI;AAAA,MACzB,WAAW,gBAAgB;AAAA,MAC3B,GAAG;AAAA,IACL,CAAC;AACD,QAAI,GAAG,QAAQ,MAAM;AACnB,qBAAe,GAAG;AAClB,kBAAY,GAAG;AAAA,IACjB,CAAC;AACD,WAAO,MAAM;AAAE,UAAI,IAAK,KAAI,OAAO;AAAA,IAAG;AAAA,EACxC,GAAG,CAAC,MAAM,CAAC;AAGX,8BAAU,MAAM;AACd,QAAI,CAAC,eAAe,CAAC,iBAAkB;AACvC,UAAM,gBAAgB,QAAQ,KAAK,OAAK,EAAE,OAAO,gBAAgB;AACjE,QAAI,eAAe;AACjB,kBAAY,MAAM;AAAA,QAChB,QAAQ,CAAC,cAAc,KAAK,cAAc,GAAG;AAAA,QAC7C,MAAM;AAAA;AAAA,QACN,WAAW;AAAA,QACX,UAAU;AAAA,MACZ,CAAC;AAAA,IACH;AAAA,EACF,GAAG,CAAC,kBAAkB,aAAa,OAAO,CAAC;AAG3C,QAAM,oBAAgB,0BAAY,MAAM;AACtC,QAAI,CAAC,eAAe,CAAC,OAAQ;AAE7B,UAAM,SAAS,YAAY,UAAU;AACrC,UAAM,OAAO,KAAK,MAAM,YAAY,QAAQ,CAAC;AAG7C,UAAM,WAAW,aAAa,QAAQ;AAAA,MACpC,CAAC,OAAO,QAAQ,GAAG,OAAO,SAAS,GAAG,OAAO,QAAQ,GAAG,OAAO,SAAS,CAAC;AAAA,MACzE;AAAA,IACF;AAGA,eAAW,QAAQ,QAAQ,OAAK,EAAE,OAAO,CAAC;AAC1C,eAAW,UAAU,CAAC;AAEtB,aAAS,QAAQ,aAAW;AAC1B,YAAM,CAAC,KAAK,GAAG,IAAI,QAAQ,SAAS;AACpC,YAAM,EAAE,SAAS,WAAW,aAAa,MAAM,IAAI,QAAQ;AAE3D,UAAI;AAEJ,UAAI,WAAW;AAEb,aAAK,qBAAqB,KAAK;AAC/B,WAAG,UAAU,MAAM;AACjB,gBAAM,gBAAgB,KAAK;AAAA,YACzB,aAAa,QAAQ,wBAAwB,QAAQ,EAAY;AAAA,YACjE;AAAA,UACF;AACA,sBAAY,OAAO,EAAE,QAAQ,CAAC,KAAK,GAAG,GAAG,MAAM,cAAc,CAAC;AAAA,QAChE;AAAA,MACF,OAAO;AAEL,cAAM,YAAY,QAAQ;AAC1B,aAAK,0BAA0B;AAAA,UAC7B,QAAQ;AAAA,UACR,YAAY,UAAU,OAAO;AAAA,UAC7B,SAAS;AAAA,QACX,CAAC;AACD,WAAG,UAAU,MAAM,gBAAgB,WAAW,GAAG,WAAW;AAAA,MAC9D;AAEA,YAAM,YAAY,IAAI,OAAO,OAAO,EAAE,SAAS,IAAI,QAAQ,YAAY,WAAW,SAAS,CAAC,EACzF,UAAU,CAAC,KAAK,GAAG,CAAC,EACpB,MAAM,WAAW;AAEpB,iBAAW,QAAQ,KAAK,SAAS;AAAA,IACnC,CAAC;AAAA,EACH,GAAG,CAAC,aAAa,QAAQ,kBAAkB,eAAe,aAAa,CAAC;AAGxE,8BAAU,MAAM;AACd,UAAM,SAAc,QAAQ,IAAI,QAAM;AAAA,MACpC,MAAM;AAAA,MACN,YAAY,EAAE,GAAG,EAAE;AAAA,MACnB,UAAU,EAAE,MAAM,SAAS,aAAa,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE;AAAA,IACzD,EAAE;AACF,iBAAa,QAAQ,KAAK,MAAM;AAChC,kBAAc;AAAA,EAChB,GAAG,CAAC,SAAS,aAAa,CAAC;AAG3B,8BAAU,MAAM;AACd,QAAI,CAAC,YAAa;AAClB,gBAAY,GAAG,WAAW,aAAa;AACvC,WAAO,MAAM;AAAE,kBAAY,IAAI,WAAW,aAAa;AAAA,IAAG;AAAA,EAC5D,GAAG,CAAC,aAAa,aAAa,CAAC;AAE/B,SACE,4CAAC,SAAI,OAAO,EAAE,UAAU,YAAY,GAAG,MAAM,GAC3C,sDAAC,SAAI,KAAK,iBAAiB,OAAO,EAAE,OAAO,QAAQ,QAAQ,OAAO,GAAG,GACvE;AAEJ;AAEA,IAAO,cAAQ;;;AG5HP,IAAAC,sBAAA;AATD,IAAM,YAAsC,CAAC,EAAE,OAAO,YAAY,SAAS,MAAM;AACtF,QAAM,eAAe,sDAAsD,MAAM,GAAG,IAAI,MAAM,GAAG;AAEjG,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,cAAc,aAAa,WAAW,EAAE;AAAA,MACnD,SAAS;AAAA,MAET;AAAA,sDAAC,SAAI,WAAU,qBACb;AAAA,wDAAC,SACC;AAAA,yDAAC,QAAG,WAAU,cAAc,gBAAM,MAAK;AAAA,YACvC,6CAAC,UAAK,WAAU,cAAc,gBAAM,MAAK;AAAA,aAC3C;AAAA,UACA,8CAAC,SAAI,WAAU,eAAe;AAAA,kBAAM;AAAA,YAAM;AAAA,aAAM;AAAA,WAClD;AAAA,QAEA,6CAAC,OAAE,WAAU,cAAc,gBAAM,MAAK;AAAA,QAGtC,6CAAC,SAAI,WAAU,yBACb,uDAAC,SAAI,WAAU,yBACb,wDAAC,SAAI,WAAU,kBACZ;AAAA,gBAAM,SACL,6CAAC,OAAE,MAAM,OAAO,MAAM,KAAK,IAAI,WAAU,gBACvC,uDAAC,UAAK,sCAAI,GACZ;AAAA,UAED,MAAM,YACL,6CAAC,OAAE,MAAM,iBAAiB,MAAM,QAAQ,IAAI,QAAO,UAAS,WAAU,cACpE,uDAAC,UAAK,kDAAM,GACd;AAAA,UAEF,6CAAC,OAAE,MAAM,cAAc,QAAO,UAAS,WAAU,iBAC/C,uDAAC,UAAK,8DAAQ,GAChB;AAAA,WACF,GACF,GACF;AAAA;AAAA;AAAA,EACF;AAEJ;;;AJlBQ,IAAAC,sBAAA;AAxBD,IAAM,iBAAqC,CAAC,UAAU;AAC3D,QAAM,CAAC,UAAU,WAAW,QAAI,wBAAS,KAAK;AAE9C,QAAM,CAAC,aAAa,cAAc,QAAI,wBAAc,IAAI;AAExD,+BAAU,MAAM;AACd,UAAM,cAAc,MAAM,YAAY,OAAO,aAAa,IAAI;AAC9D,gBAAY;AACZ,WAAO,iBAAiB,UAAU,WAAW;AAC7C,WAAO,MAAM,OAAO,oBAAoB,UAAU,WAAW;AAAA,EAC/D,GAAG,CAAC,CAAC;AAEL,QAAM,oBAAoB,CAAC,OAAc,UAAkB;AAEzD,UAAM,gBAAgB,OAAO,OAAO,WAAW;AAAA,EACjD;AAGA,QAAM,cAAc,MAAM,WAAW,CAAC;AAEtC,SACE,8CAAC,SAAI,WAAW,mBAAmB,WAAW,cAAc,YAAY,IAErE;AAAA,KAAC,YACA,8CAAC,WAAM,WAAU,WACf;AAAA,mDAAC,YAAO,WAAU,kBAAiB,mGAAe;AAAA,MAClD,6CAAC,SAAI,WAAU,gBAEZ,sBAAY,IAAI,CAAC,OAAO,QACvB;AAAA,QAAC;AAAA;AAAA,UAEC;AAAA,UACA,YAAY,MAAM,qBAAqB,MAAM;AAAA,UAC7C,UAAU,MAAM,kBAAkB,OAAO,GAAG;AAAA;AAAA,QAHvC,MAAM;AAAA,MAIb,CACD,GACH;AAAA,OACF;AAAA,IAGF,8CAAC,UAAK,WAAU,iBAEd;AAAA;AAAA,QAAC;AAAA;AAAA,UACE,GAAG;AAAA,UACJ,WAAW,CAAC,QAAQ;AAClB,2BAAe,GAAG;AAClB,kBAAM,YAAY,GAAG;AAAA,UACvB;AAAA;AAAA,MACF;AAAA,MAEC,MAAM,eACL,6CAAC,SAAI,WAAU,iBACZ,uDAAC,UAAM,gBAAM,aAAY,GAC5B;AAAA,MAGD,YACC,6CAAC,SAAI,WAAU,iBACZ,sBAAY,IAAI,CAAC,OAAO,QACvB;AAAA,QAAC;AAAA;AAAA,UAEC;AAAA,UACA,YAAY,MAAM,qBAAqB,MAAM;AAAA,UAC7C,UAAU,MAAM,kBAAkB,OAAO,GAAG;AAAA;AAAA,QAHvC,MAAM;AAAA,MAIb,CACD,GACH;AAAA,OAEJ;AAAA,KACF;AAEJ;","names":["import_react","Supercluster","import_jsx_runtime","import_jsx_runtime"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/components/InteractiveMap.tsx","../src/components/Map.tsx","../src/utils/createCustomMarkerElement.ts","../src/utils/createClusterElement.ts","../src/components/StoreCard.tsx"],"sourcesContent":["\r\nimport './styles.css';\r\n\r\n\r\nexport { InteractiveMap as NeshanMap } from './components/InteractiveMap';\r\n\r\n\r\nexport { default as RawMap } from './components/Map';\r\n\r\n\r\nexport type {\r\n MapProps,\r\n MapOptions,\r\n MarkerData,\r\n Store,\r\n MapboxMap,\r\n LngLatLike,\r\n} from './types';","\"use client\";\r\n\r\nimport React, { useState, useEffect } from 'react';\r\nimport Map from './Map';\r\nimport { StoreCard } from './StoreCard';\r\nimport { Store, MapProps } from '../types';\r\n\r\nexport const InteractiveMap: React.FC<MapProps> = (props) => {\r\n const [isMobile, setIsMobile] = useState(false);\r\n // ✅ ذخیره نمونه نقشه برای پاس دادن به کلیک مارکر\r\n const [mapInstance, setMapInstance] = useState<any>(null);\r\n\r\n useEffect(() => {\r\n const checkMobile = () => setIsMobile(window.innerWidth < 1024);\r\n checkMobile();\r\n window.addEventListener('resize', checkMobile);\r\n return () => window.removeEventListener('resize', checkMobile);\r\n }, []);\r\n\r\n const handleStoreSelect = (store: Store, index: number) => {\r\n // ✅ حالا دیگر null نمی‌فرستیم، نمونه واقعی نقشه را می‌فرستیم\r\n props.onMarkerClick?.(store, index, mapInstance);\r\n };\r\n\r\n // اطمینان از اینکه markers همیشه یک آرایه است\r\n const safeMarkers = props.markers || [];\r\n\r\n return (\r\n <div className={`map-layout-root ${isMobile ? 'is-mobile' : 'is-desktop'}`}>\r\n \r\n {!isMobile && (\r\n <aside className=\"sidebar\">\r\n <header className=\"sidebar-header\">لیست فروشگاه‌ها</header>\r\n <div className=\"sidebar-list\">\r\n {/* ✅ استفاده از safeMarkers برای جلوگیری از خطای undefined */}\r\n {safeMarkers.map((store, idx) => (\r\n <StoreCard \r\n key={store.id} \r\n store={store} \r\n isSelected={props.selectedMarkerId === store.id}\r\n onSelect={() => handleStoreSelect(store, idx)}\r\n />\r\n ))}\r\n </div>\r\n </aside>\r\n )}\r\n\r\n <main className=\"map-container\">\r\n {/* ✅ دریافت نمونه نقشه وقتی لود شد */}\r\n <Map \r\n {...props} \r\n onMapLoad={(map) => {\r\n setMapInstance(map);\r\n props.onMapLoad?.(map);\r\n }} \r\n />\r\n \r\n {props.productName && (\r\n <div className=\"product-badge\">\r\n <span>{props.productName}</span>\r\n </div>\r\n )}\r\n\r\n {isMobile && (\r\n <div className=\"mobile-slider\">\r\n {safeMarkers.map((store, idx) => (\r\n <StoreCard \r\n key={store.id} \r\n store={store} \r\n isSelected={props.selectedMarkerId === store.id}\r\n onSelect={() => handleStoreSelect(store, idx)}\r\n />\r\n ))}\r\n </div>\r\n )}\r\n </main>\r\n </div>\r\n );\r\n};","\"use client\";\r\n\r\nimport React, { useEffect, useRef, useState, useCallback } from 'react';\r\nimport type { MapboxMap, Store, MapProps } from '../types';\r\nimport { createCustomMarkerElement } from '../utils/createCustomMarkerElement';\r\nimport { createClusterElement } from '../utils/createClusterElement';\r\nimport Supercluster from 'supercluster';\r\n\r\n\r\n\r\nconst Map: React.FC<MapProps> = ({\r\n options,\r\n markers = [],\r\n markerLogoUrl = '',\r\n selectedMarkerId = null,\r\n onMarkerClick,\r\n onMapLoad,\r\n style = { width: '100%', height: '100%' },\r\n}) => {\r\n const mapContainerRef = useRef<HTMLDivElement>(null);\r\n const [mapInstance, setMapInstance] = useState<MapboxMap | null>(null);\r\n const [mapLib, setMapLib] = useState<any>(null);\r\n const markersRef = useRef<any[]>([]);\r\n \r\n \r\n const clusterIndex = useRef(new Supercluster({\r\n radius: 60,\r\n maxZoom: 16\r\n }));\r\n\r\n \r\n useEffect(() => {\r\n import('@neshan-maps-platform/mapbox-gl').then((mod) => {\r\n setMapLib(mod.default || mod);\r\n });\r\n }, []);\r\n\r\n \r\n useEffect(() => {\r\n if (!mapLib || !mapContainerRef.current || mapInstance) return;\r\n const map = new mapLib.Map({\r\n container: mapContainerRef.current,\r\n ...options,\r\n });\r\n map.on('load', () => {\r\n setMapInstance(map);\r\n onMapLoad?.(map);\r\n });\r\n return () => { if (map) map.remove(); };\r\n }, [mapLib]);\r\n\r\n \r\n useEffect(() => {\r\n if (!mapInstance || !selectedMarkerId) return;\r\n const selectedStore = markers.find(m => m.id === selectedMarkerId);\r\n if (selectedStore) {\r\n mapInstance.flyTo({\r\n center: [selectedStore.lng, selectedStore.lat],\r\n zoom: 16, \r\n essential: true,\r\n duration: 1500\r\n });\r\n }\r\n }, [selectedMarkerId, mapInstance, markers]);\r\n\r\n \r\n const updateMarkers = useCallback(() => {\r\n if (!mapInstance || !mapLib) return;\r\n\r\n const bounds = mapInstance.getBounds();\r\n const zoom = Math.floor(mapInstance.getZoom());\r\n\r\n \r\n const clusters = clusterIndex.current.getClusters(\r\n [bounds.getWest(), bounds.getSouth(), bounds.getEast(), bounds.getNorth()],\r\n zoom\r\n );\r\n\r\n \r\n markersRef.current.forEach(m => m.remove());\r\n markersRef.current = [];\r\n\r\n clusters.forEach(cluster => {\r\n const [lng, lat] = cluster.geometry.coordinates;\r\n const { cluster: isCluster, point_count: count } = cluster.properties;\r\n\r\n let el: HTMLElement;\r\n\r\n if (isCluster) {\r\n el = createClusterElement(count);\r\n \r\n const newMarker = new mapLib.Marker({ \r\n element: el, \r\n anchor: 'center' \r\n })\r\n .setLngLat([lng, lat])\r\n .addTo(mapInstance);\r\n\r\n el.onclick = (e) => {\r\n e.stopPropagation(); \r\n const expansionZoom = Math.min(\r\n clusterIndex.current.getClusterExpansionZoom(cluster.id as number),\r\n 18\r\n );\r\n mapInstance.easeTo({ \r\n center: [lng, lat], \r\n zoom: expansionZoom,\r\n duration: 800 \r\n });\r\n };\r\n } else {\r\n \r\n const storeData = cluster.properties as Store;\r\n el = createCustomMarkerElement({\r\n marker: storeData,\r\n isSelected: storeData.id === selectedMarkerId,\r\n logoSrc: markerLogoUrl,\r\n });\r\n el.onclick = () => onMarkerClick?.(storeData, 0, mapInstance);\r\n }\r\n\r\n const newMarker = new mapLib.Marker({ element: el, anchor: isCluster ? 'center' : 'bottom' })\r\n .setLngLat([lng, lat])\r\n .addTo(mapInstance);\r\n \r\n markersRef.current.push(newMarker);\r\n });\r\n }, [mapInstance, mapLib, selectedMarkerId, markerLogoUrl, onMarkerClick]);\r\n\r\n \r\n useEffect(() => {\r\n const points: any = markers.map(s => ({\r\n type: 'Feature',\r\n properties: { ...s },\r\n geometry: { type: 'Point', coordinates: [s.lng, s.lat] }\r\n }));\r\n clusterIndex.current.load(points);\r\n updateMarkers();\r\n }, [markers, updateMarkers]);\r\n\r\n \r\n useEffect(() => {\r\n if (!mapInstance) return;\r\n mapInstance.on('moveend', updateMarkers);\r\n return () => { mapInstance.off('moveend', updateMarkers); };\r\n }, [mapInstance, updateMarkers]);\r\n\r\n return (\r\n <div style={{ position: 'relative', ...style }}>\r\n <div \r\n ref={mapContainerRef} \r\n style={{ \r\n width: '100%', \r\n height: '100%',\r\n \r\n backfaceVisibility: 'hidden' \r\n }} \r\n />\r\n </div>\r\n);\r\n};\r\n\r\nexport default Map;","import type { MarkerData } from '../types';\r\n\r\ninterface CreateMarkerOptions {\r\n marker: MarkerData;\r\n isSelected: boolean;\r\n logoSrc?: string;\r\n showPrice?: boolean;\r\n}\r\n\r\nexport function createCustomMarkerElement({\r\n marker,\r\n isSelected,\r\n logoSrc = '',\r\n showPrice = true,\r\n}: CreateMarkerOptions): HTMLElement {\r\n \r\n const container = document.createElement('div');\r\n container.className = 'neshan-marker-container';\r\n\r\n \r\n const body = document.createElement('div');\r\n body.className = `neshan-marker-body ${isSelected ? 'neshan-marker-selected' : ''}`;\r\n \r\n \r\n Object.assign(body.style, {\r\n display: 'flex',\r\n flexDirection: 'column',\r\n alignItems: 'center',\r\n transition: 'transform 0.3s ease',\r\n transform: isSelected ? 'scale(1.2)' : 'scale(1)',\r\n });\r\n\r\n \r\n const iconBox = document.createElement('div');\r\n Object.assign(iconBox.style, {\r\n width: '32px',\r\n height: '32px',\r\n\r\n \r\n border: isSelected ? '3px solid #3b82f6' : '2px solid #ef4444',\r\n boxShadow: '0 4px 8px rgba(0,0,0,0.2)',\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n overflow: 'hidden'\r\n });\r\n\r\n if (logoSrc) {\r\n const img = document.createElement('img');\r\n img.src = logoSrc;\r\n img.style.width = '100%';\r\n img.style.height = '100%';\r\n img.style.objectFit = 'cover';\r\n iconBox.appendChild(img);\r\n } else {\r\n iconBox.style.backgroundColor = '#ef4444';\r\n }\r\n\r\n body.appendChild(iconBox);\r\n\r\n \r\n if (showPrice && (marker.price || marker.name)) {\r\n const label = document.createElement('div');\r\n label.className = 'neshan-marker-label'; // استفاده از کلاس CSS جدید\r\n label.textContent = marker.price ? `${marker.price} ت` : (marker.name ?? '');\r\n body.appendChild(label);\r\n}\r\n\r\n container.appendChild(body);\r\n return container;\r\n}","export function createClusterElement(count: number): HTMLElement {\r\n const container = document.createElement('div');\r\n container.className = 'neshan-cluster-marker';\r\n \r\n \r\n const countSpan = document.createElement('span');\r\n countSpan.className = 'neshan-cluster-count';\r\n countSpan.innerText = count.toString();\r\n \r\n container.appendChild(countSpan);\r\n return container;\r\n}","import React from 'react';\r\nimport { Store } from '../types';\r\n\r\ninterface StoreCardProps {\r\n store: Store;\r\n isSelected: boolean;\r\n onSelect: () => void;\r\n}\r\n\r\nexport const StoreCard: React.FC<StoreCardProps> = ({ store, isSelected, onSelect }) => {\r\n const googleMapUrl = `https://www.google.com/maps/dir/?api=1&destination=${store.lat},${store.lng}`;\r\n\r\n return (\r\n <div \r\n className={`store-card ${isSelected ? 'active' : ''}`}\r\n onClick={onSelect}\r\n >\r\n <div className=\"store-card-header\">\r\n <div>\r\n <h4 className=\"store-name\">{store.name}</h4>\r\n <span className=\"store-city\">{store.city}</span>\r\n </div>\r\n <div className=\"store-price\">{store.price} تومان</div>\r\n </div>\r\n\r\n <p className=\"store-desc\">{store.desc}</p>\r\n\r\n {/* بخش آکاردئونی با CSS Animation */}\r\n <div className=\"store-details-wrapper\">\r\n <div className=\"store-details-content\">\r\n <div className=\"action-buttons\">\r\n {store.phone && (\r\n <a href={`tel:${store.phone}`} className=\"btn btn-call\">\r\n <span>تماس</span>\r\n </a>\r\n )}\r\n {store.whatsapp && (\r\n <a href={`https://wa.me/${store.whatsapp}`} target=\"_blank\" className=\"btn btn-wa\">\r\n <span>واتساپ</span>\r\n </a>\r\n )}\r\n <a href={googleMapUrl} target=\"_blank\" className=\"btn btn-route\">\r\n <span>مسیریابی</span>\r\n </a>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n );\r\n};"],"mappings":"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","names":["import_react","Supercluster","newMarker","import_jsx_runtime","import_jsx_runtime"]}
package/dist/index.css CHANGED
@@ -156,19 +156,21 @@
156
156
  background: #ffffff;
157
157
  border: 3px solid #2563eb;
158
158
  border-radius: 50%;
159
- width: 46px;
160
- height: 46px;
159
+ width: 36px;
160
+ height: 36px;
161
161
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.25);
162
162
  cursor: pointer;
163
- transition: transform 0.2s ease;
163
+ transition: none;
164
164
  }
165
165
  .neshan-cluster-marker:hover {
166
- transform: scale(1.1);
166
+ border-color: #1d4ed8;
167
+ box-shadow: 0 6px 25px rgba(0, 0, 0, 0.3);
167
168
  }
168
169
  .neshan-cluster-count {
169
170
  color: #2563eb;
170
171
  font-weight: 900;
171
172
  font-size: 16px;
173
+ pointer-events: none;
172
174
  }
173
175
  .neshan-marker-selected .neshan-marker-body {
174
176
  animation: marker-bounce 0.8s infinite alternate cubic-bezier(0.45, 0.05, 0.55, 0.95);
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/styles.css"],"sourcesContent":["\r\n@import \"@neshan-maps-platform/mapbox-gl/dist/NeshanMapboxGl.css\";\r\n\r\n\r\n.map-layout-root {\r\n display: flex;\r\n width: 100%;\r\n height: 100%;\r\n direction: rtl;\r\n font-family: inherit;\r\n overflow: hidden;\r\n}\r\n\r\n\r\n.sidebar {\r\n width: 350px;\r\n background: #ffffff;\r\n border-left: 1px solid #e5e7eb;\r\n display: flex;\r\n flex-direction: column;\r\n z-index: 10;\r\n box-shadow: 2px 0 10px rgba(0, 0, 0, 0.05);\r\n}\r\n\r\n.sidebar-header {\r\n padding: 20px;\r\n font-weight: 800;\r\n font-size: 18px;\r\n color: #111827;\r\n border-bottom: 1px solid #f3f4f6;\r\n background: #fafafa;\r\n}\r\n\r\n.sidebar-list {\r\n flex: 1;\r\n overflow-y: auto;\r\n scrollbar-width: thin;\r\n}\r\n\r\n\r\n.map-container {\r\n flex: 1;\r\n position: relative;\r\n background: #f3f4f6;\r\n}\r\n\r\n\r\n.store-card {\r\n padding: 16px;\r\n border-bottom: 1px solid #f3f4f6;\r\n cursor: pointer;\r\n transition: all 0.2s ease;\r\n border-right: 4px solid transparent;\r\n}\r\n\r\n.store-card:hover {\r\n background: #f9fafb;\r\n}\r\n\r\n.store-card.active {\r\n background: #eff6ff;\r\n border-right-color: #2563eb;\r\n}\r\n\r\n.store-card-header {\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: flex-start;\r\n margin-bottom: 8px;\r\n}\r\n\r\n.store-name {\r\n margin: 0;\r\n font-size: 16px;\r\n font-weight: 700;\r\n color: #111827;\r\n}\r\n\r\n.store-city {\r\n display: block;\r\n font-size: 12px;\r\n color: #6b7280;\r\n margin-top: 2px;\r\n}\r\n\r\n.store-price {\r\n color: #059669;\r\n font-weight: 800;\r\n font-size: 15px;\r\n background: #ecfdf5;\r\n padding: 2px 8px;\r\n border-radius: 6px;\r\n}\r\n\r\n.store-desc {\r\n font-size: 12.5px;\r\n color: #4b5563;\r\n line-height: 1.6;\r\n margin: 8px 0;\r\n display: -webkit-box;\r\n -webkit-line-clamp: 2;\r\n -webkit-box-orient: vertical;\r\n overflow: hidden;\r\n}\r\n\r\n\r\n.store-details-wrapper {\r\n display: grid;\r\n grid-template-rows: 0fr;\r\n transition: grid-template-rows 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n overflow: hidden;\r\n}\r\n\r\n.active .store-details-wrapper {\r\n grid-template-rows: 1fr;\r\n margin-top: 14px;\r\n}\r\n\r\n.store-details-content {\r\n min-height: 0;\r\n}\r\n\r\n.action-buttons {\r\n display: flex;\r\n gap: 8px;\r\n padding-bottom: 4px;\r\n}\r\n\r\n.btn {\r\n flex: 1;\r\n padding: 10px 5px;\r\n border-radius: 8px;\r\n text-align: center;\r\n text-decoration: none;\r\n font-size: 13px;\r\n font-weight: 700;\r\n color: #fff;\r\n transition: transform 0.1s, opacity 0.2s;\r\n}\r\n\r\n.btn:active { transform: scale(0.95); }\r\n.btn-call { background: #2563eb; }\r\n.btn-wa { background: #10b981; }\r\n.btn-route { background: #7c3aed; }\r\n\r\n\r\n.neshan-marker-container {\r\n pointer-events: auto;\r\n}\r\n\r\n.neshan-marker-body {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n transform-origin: bottom center;\r\n}\r\n\r\n.neshan-marker-label {\r\n background: #2563eb;\r\n color: #ffffff !important;\r\n font-size: 12px;\r\n font-weight: 900;\r\n padding: 4px 12px;\r\n border-radius: 20px;\r\n margin-top: 6px;\r\n box-shadow: 0 4px 12px rgba(37, 99, 235, 0.4);\r\n border: 2px solid #ffffff;\r\n white-space: nowrap;\r\n pointer-events: none;\r\n}\r\n\r\n\r\n.neshan-cluster-marker {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n background: #ffffff;\r\n border: 3px solid #2563eb;\r\n border-radius: 50%;\r\n width: 46px;\r\n height: 46px;\r\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.25);\r\n cursor: pointer;\r\n transition: transform 0.2s ease;\r\n}\r\n\r\n.neshan-cluster-marker:hover {\r\n transform: scale(1.1);\r\n}\r\n\r\n.neshan-cluster-count {\r\n color: #2563eb;\r\n font-weight: 900;\r\n font-size: 16px;\r\n}\r\n\r\n\r\n.neshan-marker-selected .neshan-marker-body {\r\n animation: marker-bounce 0.8s infinite alternate cubic-bezier(0.45, 0.05, 0.55, 0.95);\r\n z-index: 100;\r\n}\r\n\r\n@keyframes marker-bounce {\r\n from { transform: translateY(0) scale(1.1); }\r\n to { transform: translateY(-12px) scale(1.15); }\r\n}\r\n\r\n\r\n.is-mobile .mobile-slider {\r\n position: absolute;\r\n bottom: 24px;\r\n left: 0;\r\n right: 0;\r\n display: flex;\r\n gap: 12px;\r\n padding: 0 20px;\r\n overflow-x: auto;\r\n scroll-snap-type: x mandatory;\r\n pointer-events: none;\r\n z-index: 20;\r\n}\r\n\r\n.is-mobile .mobile-slider::-webkit-scrollbar { display: none; }\r\n\r\n.is-mobile .store-card {\r\n min-width: 300px;\r\n max-width: 85vw;\r\n background: #ffffff;\r\n border-radius: 16px;\r\n box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);\r\n pointer-events: auto;\r\n scroll-snap-align: center;\r\n border: none;\r\n}\r\n\r\n\r\n.product-badge {\r\n position: absolute;\r\n top: 20px;\r\n right: 20px;\r\n background: rgba(255, 255, 255, 0.95);\r\n backdrop-filter: blur(4px);\r\n padding: 10px 18px;\r\n border-radius: 12px;\r\n box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);\r\n font-weight: 800;\r\n font-size: 14px;\r\n color: #1f2937;\r\n z-index: 5;\r\n border: 1px solid rgba(0, 0, 0, 0.05);\r\n}"],"mappings":";;;AAIA,CAAC;AACC,WAAS;AACT,SAAO;AACP,UAAQ;AACR,aAAW;AACX,eAAa;AACb,YAAU;AACZ;AAGA,CAAC;AACC,SAAO;AACP,cAAY;AACZ,eAAa,IAAI,MAAM;AACvB,WAAS;AACT,kBAAgB;AAChB,WAAS;AACT,cAAY,IAAI,EAAE,KAAK,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;AACvC;AAEA,CAAC;AACC,WAAS;AACT,eAAa;AACb,aAAW;AACX,SAAO;AACP,iBAAe,IAAI,MAAM;AACzB,cAAY;AACd;AAEA,CAAC;AACC,QAAM;AACN,cAAY;AACZ,mBAAiB;AACnB;AAGA,CAAC;AACC,QAAM;AACN,YAAU;AACV,cAAY;AACd;AAGA,CAAC;AACC,WAAS;AACT,iBAAe,IAAI,MAAM;AACzB,UAAQ;AACR,cAAY,IAAI,KAAK;AACrB,gBAAc,IAAI,MAAM;AAC1B;AAEA,CARC,UAQU;AACT,cAAY;AACd;AAEA,CAZC,UAYU,CAAC;AACV,cAAY;AACZ,sBAAoB;AACtB;AAEA,CAAC;AACC,WAAS;AACT,mBAAiB;AACjB,eAAa;AACb,iBAAe;AACjB;AAEA,CAAC;AACC,UAAQ;AACR,aAAW;AACX,eAAa;AACb,SAAO;AACT;AAEA,CAAC;AACC,WAAS;AACT,aAAW;AACX,SAAO;AACP,cAAY;AACd;AAEA,CAAC;AACC,SAAO;AACP,eAAa;AACb,aAAW;AACX,cAAY;AACZ,WAAS,IAAI;AACb,iBAAe;AACjB;AAEA,CAAC;AACC,aAAW;AACX,SAAO;AACP,eAAa;AACb,UAAQ,IAAI;AACZ,WAAS;AACT,sBAAoB;AACpB,sBAAoB;AACpB,YAAU;AACZ;AAGA,CAAC;AACC,WAAS;AACT,sBAAoB;AACpB,cAAY,mBAAmB,KAAK,aAAa,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE;AAC9D,YAAU;AACZ;AAEA,CAtDY,OAsDJ,CAPP;AAQC,sBAAoB;AACpB,cAAY;AACd;AAEA,CAAC;AACC,cAAY;AACd;AAEA,CAAC;AACC,WAAS;AACT,OAAK;AACL,kBAAgB;AAClB;AAEA,CAAC;AACC,QAAM;AACN,WAAS,KAAK;AACd,iBAAe;AACf,cAAY;AACZ,mBAAiB;AACjB,aAAW;AACX,eAAa;AACb,SAAO;AACP,cAAY,UAAU,IAAI,EAAE,QAAQ;AACtC;AAEA,CAZC,GAYG;AAAU,aAAW,MAAM;AAAO;AACtC,CAAC;AAAW,cAAY;AAAS;AACjC,CAAC;AAAS,cAAY;AAAS;AAC/B,CAAC;AAAY,cAAY;AAAS;AAGlC,CAAC;AACC,kBAAgB;AAClB;AAEA,CAAC;AACC,WAAS;AACT,kBAAgB;AAChB,eAAa;AACb,oBAAkB,OAAO;AAC3B;AAEA,CAAC;AACC,cAAY;AACZ,SAAO;AACP,aAAW;AACX,eAAa;AACb,WAAS,IAAI;AACb,iBAAe;AACf,cAAY;AACZ,cAAY,EAAE,IAAI,KAAK,KAAK,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE;AACzC,UAAQ,IAAI,MAAM;AAClB,eAAa;AACb,kBAAgB;AAClB;AAGA,CAAC;AACC,WAAS;AACT,eAAa;AACb,mBAAiB;AACjB,cAAY;AACZ,UAAQ,IAAI,MAAM;AAClB,iBAAe;AACf,SAAO;AACP,UAAQ;AACR,cAAY,EAAE,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;AACrC,UAAQ;AACR,cAAY,UAAU,KAAK;AAC7B;AAEA,CAdC,qBAcqB;AACpB,aAAW,MAAM;AACnB;AAEA,CAAC;AACC,SAAO;AACP,eAAa;AACb,aAAW;AACb;AAGA,CAAC,uBAAuB,CA/CvB;AAgDC,aAAW,cAAc,KAAK,SAAS,UAAU,aAAa,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE;AAChF,WAAS;AACX;AAEA,WAJa;AAKX;AAAO,eAAW,WAAW,GAAG,MAAM;AAAM;AAC5C;AAAK,eAAW,WAAW,OAAO,MAAM;AAAO;AACjD;AAGA,CAAC,UAAU,CAAC;AACV,YAAU;AACV,UAAQ;AACR,QAAM;AACN,SAAO;AACP,WAAS;AACT,OAAK;AACL,WAAS,EAAE;AACX,cAAY;AACZ,oBAAkB,EAAE;AACpB,kBAAgB;AAChB,WAAS;AACX;AAEA,CAdC,UAcU,CAdC,aAca;AAAsB,WAAS;AAAM;AAE9D,CAhBC,UAgBU,CAjLV;AAkLC,aAAW;AACX,aAAW;AACX,cAAY;AACZ,iBAAe;AACf,cAAY,EAAE,KAAK,KAAK,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;AACtC,kBAAgB;AAChB,qBAAmB;AACnB,UAAQ;AACV;AAGA,CAAC;AACC,YAAU;AACV,OAAK;AACL,SAAO;AACP,cAAY,KAAK,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE;AAChC,mBAAiB,KAAK;AACtB,WAAS,KAAK;AACd,iBAAe;AACf,cAAY,EAAE,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;AACrC,eAAa;AACb,aAAW;AACX,SAAO;AACP,WAAS;AACT,UAAQ,IAAI,MAAM,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;AAClC;","names":[]}
1
+ {"version":3,"sources":["../src/styles.css"],"sourcesContent":["\r\n@import \"@neshan-maps-platform/mapbox-gl/dist/NeshanMapboxGl.css\";\r\n\r\n\r\n.map-layout-root {\r\n display: flex;\r\n width: 100%;\r\n height: 100%;\r\n direction: rtl;\r\n font-family: inherit;\r\n overflow: hidden;\r\n}\r\n\r\n\r\n.sidebar {\r\n width: 350px;\r\n background: #ffffff;\r\n border-left: 1px solid #e5e7eb;\r\n display: flex;\r\n flex-direction: column;\r\n z-index: 10;\r\n box-shadow: 2px 0 10px rgba(0, 0, 0, 0.05);\r\n}\r\n\r\n.sidebar-header {\r\n padding: 20px;\r\n font-weight: 800;\r\n font-size: 18px;\r\n color: #111827;\r\n border-bottom: 1px solid #f3f4f6;\r\n background: #fafafa;\r\n}\r\n\r\n.sidebar-list {\r\n flex: 1;\r\n overflow-y: auto;\r\n scrollbar-width: thin;\r\n}\r\n\r\n\r\n.map-container {\r\n flex: 1;\r\n position: relative;\r\n background: #f3f4f6;\r\n}\r\n\r\n\r\n.store-card {\r\n padding: 16px;\r\n border-bottom: 1px solid #f3f4f6;\r\n cursor: pointer;\r\n transition: all 0.2s ease;\r\n border-right: 4px solid transparent;\r\n}\r\n\r\n.store-card:hover {\r\n background: #f9fafb;\r\n}\r\n\r\n.store-card.active {\r\n background: #eff6ff;\r\n border-right-color: #2563eb;\r\n}\r\n\r\n.store-card-header {\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: flex-start;\r\n margin-bottom: 8px;\r\n}\r\n\r\n.store-name {\r\n margin: 0;\r\n font-size: 16px;\r\n font-weight: 700;\r\n color: #111827;\r\n}\r\n\r\n.store-city {\r\n display: block;\r\n font-size: 12px;\r\n color: #6b7280;\r\n margin-top: 2px;\r\n}\r\n\r\n.store-price {\r\n color: #059669;\r\n font-weight: 800;\r\n font-size: 15px;\r\n background: #ecfdf5;\r\n padding: 2px 8px;\r\n border-radius: 6px;\r\n}\r\n\r\n.store-desc {\r\n font-size: 12.5px;\r\n color: #4b5563;\r\n line-height: 1.6;\r\n margin: 8px 0;\r\n display: -webkit-box;\r\n -webkit-line-clamp: 2;\r\n -webkit-box-orient: vertical;\r\n overflow: hidden;\r\n}\r\n\r\n\r\n.store-details-wrapper {\r\n display: grid;\r\n grid-template-rows: 0fr;\r\n transition: grid-template-rows 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n overflow: hidden;\r\n}\r\n\r\n.active .store-details-wrapper {\r\n grid-template-rows: 1fr;\r\n margin-top: 14px;\r\n}\r\n\r\n.store-details-content {\r\n min-height: 0;\r\n}\r\n\r\n.action-buttons {\r\n display: flex;\r\n gap: 8px;\r\n padding-bottom: 4px;\r\n}\r\n\r\n.btn {\r\n flex: 1;\r\n padding: 10px 5px;\r\n border-radius: 8px;\r\n text-align: center;\r\n text-decoration: none;\r\n font-size: 13px;\r\n font-weight: 700;\r\n color: #fff;\r\n transition: transform 0.1s, opacity 0.2s;\r\n}\r\n\r\n.btn:active { transform: scale(0.95); }\r\n.btn-call { background: #2563eb; }\r\n.btn-wa { background: #10b981; }\r\n.btn-route { background: #7c3aed; }\r\n\r\n\r\n.neshan-marker-container {\r\n pointer-events: auto;\r\n}\r\n\r\n.neshan-marker-body {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n transform-origin: bottom center;\r\n}\r\n\r\n.neshan-marker-label {\r\n background: #2563eb;\r\n color: #ffffff !important;\r\n font-size: 12px;\r\n font-weight: 900;\r\n padding: 4px 12px;\r\n border-radius: 20px;\r\n margin-top: 6px;\r\n box-shadow: 0 4px 12px rgba(37, 99, 235, 0.4);\r\n border: 2px solid #ffffff;\r\n white-space: nowrap;\r\n pointer-events: none;\r\n}\r\n\r\n\r\n.neshan-cluster-marker {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n background: #ffffff;\r\n border: 3px solid #2563eb;\r\n border-radius: 50%;\r\n width: 36px;\r\n height: 36px;\r\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.25);\r\n cursor: pointer;\r\n transition: none;\r\n}\r\n\r\n.neshan-cluster-marker:hover {\r\n border-color: #1d4ed8;\r\n box-shadow: 0 6px 25px rgba(0, 0, 0, 0.3);\r\n}\r\n\r\n.neshan-cluster-count {\r\n color: #2563eb;\r\n font-weight: 900;\r\n font-size: 16px;\r\n pointer-events: none; /* جلوگیری از تداخل کلیک با عدد */\r\n}\r\n\r\n\r\n.neshan-marker-selected .neshan-marker-body {\r\n animation: marker-bounce 0.8s infinite alternate cubic-bezier(0.45, 0.05, 0.55, 0.95);\r\n z-index: 100;\r\n}\r\n\r\n@keyframes marker-bounce {\r\n from { transform: translateY(0) scale(1.1); }\r\n to { transform: translateY(-12px) scale(1.15); }\r\n}\r\n\r\n\r\n.is-mobile .mobile-slider {\r\n position: absolute;\r\n bottom: 24px;\r\n left: 0;\r\n right: 0;\r\n display: flex;\r\n gap: 12px;\r\n padding: 0 20px;\r\n overflow-x: auto;\r\n scroll-snap-type: x mandatory;\r\n pointer-events: none;\r\n z-index: 20;\r\n}\r\n\r\n.is-mobile .mobile-slider::-webkit-scrollbar { display: none; }\r\n\r\n.is-mobile .store-card {\r\n min-width: 300px;\r\n max-width: 85vw;\r\n background: #ffffff;\r\n border-radius: 16px;\r\n box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);\r\n pointer-events: auto;\r\n scroll-snap-align: center;\r\n border: none;\r\n}\r\n\r\n\r\n.product-badge {\r\n position: absolute;\r\n top: 20px;\r\n right: 20px;\r\n background: rgba(255, 255, 255, 0.95);\r\n backdrop-filter: blur(4px);\r\n padding: 10px 18px;\r\n border-radius: 12px;\r\n box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);\r\n font-weight: 800;\r\n font-size: 14px;\r\n color: #1f2937;\r\n z-index: 5;\r\n border: 1px solid rgba(0, 0, 0, 0.05);\r\n}"],"mappings":"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","names":[]}
package/dist/index.js CHANGED
@@ -24,10 +24,8 @@ function createCustomMarkerElement({
24
24
  });
25
25
  const iconBox = document.createElement("div");
26
26
  Object.assign(iconBox.style, {
27
- width: "40px",
28
- height: "40px",
29
- borderRadius: "50%",
30
- backgroundColor: "white",
27
+ width: "32px",
28
+ height: "32px",
31
29
  border: isSelected ? "3px solid #3b82f6" : "2px solid #ef4444",
32
30
  boxShadow: "0 4px 8px rgba(0,0,0,0.2)",
33
31
  display: "flex",
@@ -58,10 +56,13 @@ function createCustomMarkerElement({
58
56
 
59
57
  // src/utils/createClusterElement.ts
60
58
  function createClusterElement(count) {
61
- const el = document.createElement("div");
62
- el.className = "neshan-cluster-marker";
63
- el.innerHTML = `<span class="neshan-cluster-count">${count}</span>`;
64
- return el;
59
+ const container = document.createElement("div");
60
+ container.className = "neshan-cluster-marker";
61
+ const countSpan = document.createElement("span");
62
+ countSpan.className = "neshan-cluster-count";
63
+ countSpan.innerText = count.toString();
64
+ container.appendChild(countSpan);
65
+ return container;
65
66
  }
66
67
 
67
68
  // src/components/Map.tsx
@@ -110,7 +111,6 @@ var Map = ({
110
111
  mapInstance.flyTo({
111
112
  center: [selectedStore.lng, selectedStore.lat],
112
113
  zoom: 16,
113
- // زوم نزدیک هنگام انتخاب
114
114
  essential: true,
115
115
  duration: 1500
116
116
  });
@@ -132,12 +132,21 @@ var Map = ({
132
132
  let el;
133
133
  if (isCluster) {
134
134
  el = createClusterElement(count);
135
- el.onclick = () => {
135
+ const newMarker2 = new mapLib.Marker({
136
+ element: el,
137
+ anchor: "center"
138
+ }).setLngLat([lng, lat]).addTo(mapInstance);
139
+ el.onclick = (e) => {
140
+ e.stopPropagation();
136
141
  const expansionZoom = Math.min(
137
142
  clusterIndex.current.getClusterExpansionZoom(cluster.id),
138
143
  18
139
144
  );
140
- mapInstance.easeTo({ center: [lng, lat], zoom: expansionZoom });
145
+ mapInstance.easeTo({
146
+ center: [lng, lat],
147
+ zoom: expansionZoom,
148
+ duration: 800
149
+ });
141
150
  };
142
151
  } else {
143
152
  const storeData = cluster.properties;
@@ -168,7 +177,17 @@ var Map = ({
168
177
  mapInstance.off("moveend", updateMarkers);
169
178
  };
170
179
  }, [mapInstance, updateMarkers]);
171
- return /* @__PURE__ */ jsx("div", { style: { position: "relative", ...style }, children: /* @__PURE__ */ jsx("div", { ref: mapContainerRef, style: { width: "100%", height: "100%" } }) });
180
+ return /* @__PURE__ */ jsx("div", { style: { position: "relative", ...style }, children: /* @__PURE__ */ jsx(
181
+ "div",
182
+ {
183
+ ref: mapContainerRef,
184
+ style: {
185
+ width: "100%",
186
+ height: "100%",
187
+ backfaceVisibility: "hidden"
188
+ }
189
+ }
190
+ ) });
172
191
  };
173
192
  var Map_default = Map;
174
193
 
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/components/InteractiveMap.tsx","../src/components/Map.tsx","../src/utils/createCustomMarkerElement.ts","../src/utils/createClusterElement.ts","../src/components/StoreCard.tsx"],"sourcesContent":["\"use client\";\r\n\r\nimport React, { useState, useEffect } from 'react';\r\nimport Map from './Map';\r\nimport { StoreCard } from './StoreCard';\r\nimport { Store, MapProps } from '../types';\r\n\r\nexport const InteractiveMap: React.FC<MapProps> = (props) => {\r\n const [isMobile, setIsMobile] = useState(false);\r\n // ✅ ذخیره نمونه نقشه برای پاس دادن به کلیک مارکر\r\n const [mapInstance, setMapInstance] = useState<any>(null);\r\n\r\n useEffect(() => {\r\n const checkMobile = () => setIsMobile(window.innerWidth < 1024);\r\n checkMobile();\r\n window.addEventListener('resize', checkMobile);\r\n return () => window.removeEventListener('resize', checkMobile);\r\n }, []);\r\n\r\n const handleStoreSelect = (store: Store, index: number) => {\r\n // ✅ حالا دیگر null نمی‌فرستیم، نمونه واقعی نقشه را می‌فرستیم\r\n props.onMarkerClick?.(store, index, mapInstance);\r\n };\r\n\r\n // اطمینان از اینکه markers همیشه یک آرایه است\r\n const safeMarkers = props.markers || [];\r\n\r\n return (\r\n <div className={`map-layout-root ${isMobile ? 'is-mobile' : 'is-desktop'}`}>\r\n \r\n {!isMobile && (\r\n <aside className=\"sidebar\">\r\n <header className=\"sidebar-header\">لیست فروشگاه‌ها</header>\r\n <div className=\"sidebar-list\">\r\n {/* ✅ استفاده از safeMarkers برای جلوگیری از خطای undefined */}\r\n {safeMarkers.map((store, idx) => (\r\n <StoreCard \r\n key={store.id} \r\n store={store} \r\n isSelected={props.selectedMarkerId === store.id}\r\n onSelect={() => handleStoreSelect(store, idx)}\r\n />\r\n ))}\r\n </div>\r\n </aside>\r\n )}\r\n\r\n <main className=\"map-container\">\r\n {/* ✅ دریافت نمونه نقشه وقتی لود شد */}\r\n <Map \r\n {...props} \r\n onMapLoad={(map) => {\r\n setMapInstance(map);\r\n props.onMapLoad?.(map);\r\n }} \r\n />\r\n \r\n {props.productName && (\r\n <div className=\"product-badge\">\r\n <span>{props.productName}</span>\r\n </div>\r\n )}\r\n\r\n {isMobile && (\r\n <div className=\"mobile-slider\">\r\n {safeMarkers.map((store, idx) => (\r\n <StoreCard \r\n key={store.id} \r\n store={store} \r\n isSelected={props.selectedMarkerId === store.id}\r\n onSelect={() => handleStoreSelect(store, idx)}\r\n />\r\n ))}\r\n </div>\r\n )}\r\n </main>\r\n </div>\r\n );\r\n};","\"use client\";\r\n\r\nimport React, { useEffect, useRef, useState, useCallback } from 'react';\r\nimport type { MapboxMap, Store, MapProps } from '../types';\r\nimport { createCustomMarkerElement } from '../utils/createCustomMarkerElement';\r\nimport { createClusterElement } from '../utils/createClusterElement';\r\nimport Supercluster from 'supercluster';\r\n\r\n\r\n\r\nconst Map: React.FC<MapProps> = ({\r\n options,\r\n markers = [],\r\n markerLogoUrl = '',\r\n selectedMarkerId = null,\r\n onMarkerClick,\r\n onMapLoad,\r\n style = { width: '100%', height: '100%' },\r\n}) => {\r\n const mapContainerRef = useRef<HTMLDivElement>(null);\r\n const [mapInstance, setMapInstance] = useState<MapboxMap | null>(null);\r\n const [mapLib, setMapLib] = useState<any>(null);\r\n const markersRef = useRef<any[]>([]);\r\n \r\n // سیستم کلاسترینگ\r\n const clusterIndex = useRef(new Supercluster({\r\n radius: 60,\r\n maxZoom: 16\r\n }));\r\n\r\n // ۱. لود کتابخانه\r\n useEffect(() => {\r\n import('@neshan-maps-platform/mapbox-gl').then((mod) => {\r\n setMapLib(mod.default || mod);\r\n });\r\n }, []);\r\n\r\n // ۲. ساخت نقشه\r\n useEffect(() => {\r\n if (!mapLib || !mapContainerRef.current || mapInstance) return;\r\n const map = new mapLib.Map({\r\n container: mapContainerRef.current,\r\n ...options,\r\n });\r\n map.on('load', () => {\r\n setMapInstance(map);\r\n onMapLoad?.(map);\r\n });\r\n return () => { if (map) map.remove(); };\r\n }, [mapLib]);\r\n\r\n // ۳. منطق زوم خودکار (FlyTo) وقتی مارکری انتخاب می‌شود\r\n useEffect(() => {\r\n if (!mapInstance || !selectedMarkerId) return;\r\n const selectedStore = markers.find(m => m.id === selectedMarkerId);\r\n if (selectedStore) {\r\n mapInstance.flyTo({\r\n center: [selectedStore.lng, selectedStore.lat],\r\n zoom: 16, // زوم نزدیک هنگام انتخاب\r\n essential: true,\r\n duration: 1500\r\n });\r\n }\r\n }, [selectedMarkerId, mapInstance, markers]);\r\n\r\n // ۴. تابع رندر کردن مارکرها و کلاسترها\r\n const updateMarkers = useCallback(() => {\r\n if (!mapInstance || !mapLib) return;\r\n\r\n const bounds = mapInstance.getBounds();\r\n const zoom = Math.floor(mapInstance.getZoom());\r\n\r\n // دریافت داده‌های کلاستر شده برای محدوده فعلی نقشه\r\n const clusters = clusterIndex.current.getClusters(\r\n [bounds.getWest(), bounds.getSouth(), bounds.getEast(), bounds.getNorth()],\r\n zoom\r\n );\r\n\r\n // پاک کردن مارکرهای قبلی\r\n markersRef.current.forEach(m => m.remove());\r\n markersRef.current = [];\r\n\r\n clusters.forEach(cluster => {\r\n const [lng, lat] = cluster.geometry.coordinates;\r\n const { cluster: isCluster, point_count: count } = cluster.properties;\r\n\r\n let el: HTMLElement;\r\n\r\n if (isCluster) {\r\n // اگر کلاستر بود\r\n el = createClusterElement(count);\r\n el.onclick = () => {\r\n const expansionZoom = Math.min(\r\n clusterIndex.current.getClusterExpansionZoom(cluster.id as number),\r\n 18\r\n );\r\n mapInstance.easeTo({ center: [lng, lat], zoom: expansionZoom });\r\n };\r\n } else {\r\n // اگر مارکر تکی بود\r\n const storeData = cluster.properties as Store;\r\n el = createCustomMarkerElement({\r\n marker: storeData,\r\n isSelected: storeData.id === selectedMarkerId,\r\n logoSrc: markerLogoUrl,\r\n });\r\n el.onclick = () => onMarkerClick?.(storeData, 0, mapInstance);\r\n }\r\n\r\n const newMarker = new mapLib.Marker({ element: el, anchor: isCluster ? 'center' : 'bottom' })\r\n .setLngLat([lng, lat])\r\n .addTo(mapInstance);\r\n \r\n markersRef.current.push(newMarker);\r\n });\r\n }, [mapInstance, mapLib, selectedMarkerId, markerLogoUrl, onMarkerClick]);\r\n\r\n // ۵. آماده‌سازی داده‌ها برای کلاسترینگ\r\n useEffect(() => {\r\n const points: any = markers.map(s => ({\r\n type: 'Feature',\r\n properties: { ...s },\r\n geometry: { type: 'Point', coordinates: [s.lng, s.lat] }\r\n }));\r\n clusterIndex.current.load(points);\r\n updateMarkers();\r\n }, [markers, updateMarkers]);\r\n\r\n // ۶. گوش دادن به تغییرات نقشه برای بروزرسانی کلاسترها\r\n useEffect(() => {\r\n if (!mapInstance) return;\r\n mapInstance.on('moveend', updateMarkers);\r\n return () => { mapInstance.off('moveend', updateMarkers); };\r\n }, [mapInstance, updateMarkers]);\r\n\r\n return (\r\n <div style={{ position: 'relative', ...style }}>\r\n <div ref={mapContainerRef} style={{ width: '100%', height: '100%' }} />\r\n </div>\r\n );\r\n};\r\n\r\nexport default Map;","import type { MarkerData } from '../types';\r\n\r\ninterface CreateMarkerOptions {\r\n marker: MarkerData;\r\n isSelected: boolean;\r\n logoSrc?: string;\r\n showPrice?: boolean;\r\n}\r\n\r\nexport function createCustomMarkerElement({\r\n marker,\r\n isSelected,\r\n logoSrc = '',\r\n showPrice = true,\r\n}: CreateMarkerOptions): HTMLElement {\r\n \r\n const container = document.createElement('div');\r\n container.className = 'neshan-marker-container';\r\n\r\n \r\n const body = document.createElement('div');\r\n body.className = `neshan-marker-body ${isSelected ? 'neshan-marker-selected' : ''}`;\r\n \r\n \r\n Object.assign(body.style, {\r\n display: 'flex',\r\n flexDirection: 'column',\r\n alignItems: 'center',\r\n transition: 'transform 0.3s ease',\r\n transform: isSelected ? 'scale(1.2)' : 'scale(1)',\r\n });\r\n\r\n \r\n const iconBox = document.createElement('div');\r\n Object.assign(iconBox.style, {\r\n width: '40px',\r\n height: '40px',\r\n borderRadius: '50%',\r\n backgroundColor: 'white',\r\n border: isSelected ? '3px solid #3b82f6' : '2px solid #ef4444',\r\n boxShadow: '0 4px 8px rgba(0,0,0,0.2)',\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n overflow: 'hidden'\r\n });\r\n\r\n if (logoSrc) {\r\n const img = document.createElement('img');\r\n img.src = logoSrc;\r\n img.style.width = '100%';\r\n img.style.height = '100%';\r\n img.style.objectFit = 'cover';\r\n iconBox.appendChild(img);\r\n } else {\r\n iconBox.style.backgroundColor = '#ef4444';\r\n }\r\n\r\n body.appendChild(iconBox);\r\n\r\n \r\n if (showPrice && (marker.price || marker.name)) {\r\n const label = document.createElement('div');\r\n label.className = 'neshan-marker-label'; // استفاده از کلاس CSS جدید\r\n label.textContent = marker.price ? `${marker.price} ت` : (marker.name ?? '');\r\n body.appendChild(label);\r\n}\r\n\r\n container.appendChild(body);\r\n return container;\r\n}","export function createClusterElement(count: number): HTMLElement {\r\n const el = document.createElement('div');\r\n el.className = 'neshan-cluster-marker';\r\n el.innerHTML = `<span class=\"neshan-cluster-count\">${count}</span>`;\r\n return el;\r\n}","import React from 'react';\r\nimport { Store } from '../types';\r\n\r\ninterface StoreCardProps {\r\n store: Store;\r\n isSelected: boolean;\r\n onSelect: () => void;\r\n}\r\n\r\nexport const StoreCard: React.FC<StoreCardProps> = ({ store, isSelected, onSelect }) => {\r\n const googleMapUrl = `https://www.google.com/maps/dir/?api=1&destination=${store.lat},${store.lng}`;\r\n\r\n return (\r\n <div \r\n className={`store-card ${isSelected ? 'active' : ''}`}\r\n onClick={onSelect}\r\n >\r\n <div className=\"store-card-header\">\r\n <div>\r\n <h4 className=\"store-name\">{store.name}</h4>\r\n <span className=\"store-city\">{store.city}</span>\r\n </div>\r\n <div className=\"store-price\">{store.price} تومان</div>\r\n </div>\r\n\r\n <p className=\"store-desc\">{store.desc}</p>\r\n\r\n {/* بخش آکاردئونی با CSS Animation */}\r\n <div className=\"store-details-wrapper\">\r\n <div className=\"store-details-content\">\r\n <div className=\"action-buttons\">\r\n {store.phone && (\r\n <a href={`tel:${store.phone}`} className=\"btn btn-call\">\r\n <span>تماس</span>\r\n </a>\r\n )}\r\n {store.whatsapp && (\r\n <a href={`https://wa.me/${store.whatsapp}`} target=\"_blank\" className=\"btn btn-wa\">\r\n <span>واتساپ</span>\r\n </a>\r\n )}\r\n <a href={googleMapUrl} target=\"_blank\" className=\"btn btn-route\">\r\n <span>مسیریابی</span>\r\n </a>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n );\r\n};"],"mappings":"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","names":["useState","useEffect","jsx","jsx","jsxs","useState","useEffect"]}
1
+ {"version":3,"sources":["../src/components/InteractiveMap.tsx","../src/components/Map.tsx","../src/utils/createCustomMarkerElement.ts","../src/utils/createClusterElement.ts","../src/components/StoreCard.tsx"],"sourcesContent":["\"use client\";\r\n\r\nimport React, { useState, useEffect } from 'react';\r\nimport Map from './Map';\r\nimport { StoreCard } from './StoreCard';\r\nimport { Store, MapProps } from '../types';\r\n\r\nexport const InteractiveMap: React.FC<MapProps> = (props) => {\r\n const [isMobile, setIsMobile] = useState(false);\r\n // ✅ ذخیره نمونه نقشه برای پاس دادن به کلیک مارکر\r\n const [mapInstance, setMapInstance] = useState<any>(null);\r\n\r\n useEffect(() => {\r\n const checkMobile = () => setIsMobile(window.innerWidth < 1024);\r\n checkMobile();\r\n window.addEventListener('resize', checkMobile);\r\n return () => window.removeEventListener('resize', checkMobile);\r\n }, []);\r\n\r\n const handleStoreSelect = (store: Store, index: number) => {\r\n // ✅ حالا دیگر null نمی‌فرستیم، نمونه واقعی نقشه را می‌فرستیم\r\n props.onMarkerClick?.(store, index, mapInstance);\r\n };\r\n\r\n // اطمینان از اینکه markers همیشه یک آرایه است\r\n const safeMarkers = props.markers || [];\r\n\r\n return (\r\n <div className={`map-layout-root ${isMobile ? 'is-mobile' : 'is-desktop'}`}>\r\n \r\n {!isMobile && (\r\n <aside className=\"sidebar\">\r\n <header className=\"sidebar-header\">لیست فروشگاه‌ها</header>\r\n <div className=\"sidebar-list\">\r\n {/* ✅ استفاده از safeMarkers برای جلوگیری از خطای undefined */}\r\n {safeMarkers.map((store, idx) => (\r\n <StoreCard \r\n key={store.id} \r\n store={store} \r\n isSelected={props.selectedMarkerId === store.id}\r\n onSelect={() => handleStoreSelect(store, idx)}\r\n />\r\n ))}\r\n </div>\r\n </aside>\r\n )}\r\n\r\n <main className=\"map-container\">\r\n {/* ✅ دریافت نمونه نقشه وقتی لود شد */}\r\n <Map \r\n {...props} \r\n onMapLoad={(map) => {\r\n setMapInstance(map);\r\n props.onMapLoad?.(map);\r\n }} \r\n />\r\n \r\n {props.productName && (\r\n <div className=\"product-badge\">\r\n <span>{props.productName}</span>\r\n </div>\r\n )}\r\n\r\n {isMobile && (\r\n <div className=\"mobile-slider\">\r\n {safeMarkers.map((store, idx) => (\r\n <StoreCard \r\n key={store.id} \r\n store={store} \r\n isSelected={props.selectedMarkerId === store.id}\r\n onSelect={() => handleStoreSelect(store, idx)}\r\n />\r\n ))}\r\n </div>\r\n )}\r\n </main>\r\n </div>\r\n );\r\n};","\"use client\";\r\n\r\nimport React, { useEffect, useRef, useState, useCallback } from 'react';\r\nimport type { MapboxMap, Store, MapProps } from '../types';\r\nimport { createCustomMarkerElement } from '../utils/createCustomMarkerElement';\r\nimport { createClusterElement } from '../utils/createClusterElement';\r\nimport Supercluster from 'supercluster';\r\n\r\n\r\n\r\nconst Map: React.FC<MapProps> = ({\r\n options,\r\n markers = [],\r\n markerLogoUrl = '',\r\n selectedMarkerId = null,\r\n onMarkerClick,\r\n onMapLoad,\r\n style = { width: '100%', height: '100%' },\r\n}) => {\r\n const mapContainerRef = useRef<HTMLDivElement>(null);\r\n const [mapInstance, setMapInstance] = useState<MapboxMap | null>(null);\r\n const [mapLib, setMapLib] = useState<any>(null);\r\n const markersRef = useRef<any[]>([]);\r\n \r\n \r\n const clusterIndex = useRef(new Supercluster({\r\n radius: 60,\r\n maxZoom: 16\r\n }));\r\n\r\n \r\n useEffect(() => {\r\n import('@neshan-maps-platform/mapbox-gl').then((mod) => {\r\n setMapLib(mod.default || mod);\r\n });\r\n }, []);\r\n\r\n \r\n useEffect(() => {\r\n if (!mapLib || !mapContainerRef.current || mapInstance) return;\r\n const map = new mapLib.Map({\r\n container: mapContainerRef.current,\r\n ...options,\r\n });\r\n map.on('load', () => {\r\n setMapInstance(map);\r\n onMapLoad?.(map);\r\n });\r\n return () => { if (map) map.remove(); };\r\n }, [mapLib]);\r\n\r\n \r\n useEffect(() => {\r\n if (!mapInstance || !selectedMarkerId) return;\r\n const selectedStore = markers.find(m => m.id === selectedMarkerId);\r\n if (selectedStore) {\r\n mapInstance.flyTo({\r\n center: [selectedStore.lng, selectedStore.lat],\r\n zoom: 16, \r\n essential: true,\r\n duration: 1500\r\n });\r\n }\r\n }, [selectedMarkerId, mapInstance, markers]);\r\n\r\n \r\n const updateMarkers = useCallback(() => {\r\n if (!mapInstance || !mapLib) return;\r\n\r\n const bounds = mapInstance.getBounds();\r\n const zoom = Math.floor(mapInstance.getZoom());\r\n\r\n \r\n const clusters = clusterIndex.current.getClusters(\r\n [bounds.getWest(), bounds.getSouth(), bounds.getEast(), bounds.getNorth()],\r\n zoom\r\n );\r\n\r\n \r\n markersRef.current.forEach(m => m.remove());\r\n markersRef.current = [];\r\n\r\n clusters.forEach(cluster => {\r\n const [lng, lat] = cluster.geometry.coordinates;\r\n const { cluster: isCluster, point_count: count } = cluster.properties;\r\n\r\n let el: HTMLElement;\r\n\r\n if (isCluster) {\r\n el = createClusterElement(count);\r\n \r\n const newMarker = new mapLib.Marker({ \r\n element: el, \r\n anchor: 'center' \r\n })\r\n .setLngLat([lng, lat])\r\n .addTo(mapInstance);\r\n\r\n el.onclick = (e) => {\r\n e.stopPropagation(); \r\n const expansionZoom = Math.min(\r\n clusterIndex.current.getClusterExpansionZoom(cluster.id as number),\r\n 18\r\n );\r\n mapInstance.easeTo({ \r\n center: [lng, lat], \r\n zoom: expansionZoom,\r\n duration: 800 \r\n });\r\n };\r\n } else {\r\n \r\n const storeData = cluster.properties as Store;\r\n el = createCustomMarkerElement({\r\n marker: storeData,\r\n isSelected: storeData.id === selectedMarkerId,\r\n logoSrc: markerLogoUrl,\r\n });\r\n el.onclick = () => onMarkerClick?.(storeData, 0, mapInstance);\r\n }\r\n\r\n const newMarker = new mapLib.Marker({ element: el, anchor: isCluster ? 'center' : 'bottom' })\r\n .setLngLat([lng, lat])\r\n .addTo(mapInstance);\r\n \r\n markersRef.current.push(newMarker);\r\n });\r\n }, [mapInstance, mapLib, selectedMarkerId, markerLogoUrl, onMarkerClick]);\r\n\r\n \r\n useEffect(() => {\r\n const points: any = markers.map(s => ({\r\n type: 'Feature',\r\n properties: { ...s },\r\n geometry: { type: 'Point', coordinates: [s.lng, s.lat] }\r\n }));\r\n clusterIndex.current.load(points);\r\n updateMarkers();\r\n }, [markers, updateMarkers]);\r\n\r\n \r\n useEffect(() => {\r\n if (!mapInstance) return;\r\n mapInstance.on('moveend', updateMarkers);\r\n return () => { mapInstance.off('moveend', updateMarkers); };\r\n }, [mapInstance, updateMarkers]);\r\n\r\n return (\r\n <div style={{ position: 'relative', ...style }}>\r\n <div \r\n ref={mapContainerRef} \r\n style={{ \r\n width: '100%', \r\n height: '100%',\r\n \r\n backfaceVisibility: 'hidden' \r\n }} \r\n />\r\n </div>\r\n);\r\n};\r\n\r\nexport default Map;","import type { MarkerData } from '../types';\r\n\r\ninterface CreateMarkerOptions {\r\n marker: MarkerData;\r\n isSelected: boolean;\r\n logoSrc?: string;\r\n showPrice?: boolean;\r\n}\r\n\r\nexport function createCustomMarkerElement({\r\n marker,\r\n isSelected,\r\n logoSrc = '',\r\n showPrice = true,\r\n}: CreateMarkerOptions): HTMLElement {\r\n \r\n const container = document.createElement('div');\r\n container.className = 'neshan-marker-container';\r\n\r\n \r\n const body = document.createElement('div');\r\n body.className = `neshan-marker-body ${isSelected ? 'neshan-marker-selected' : ''}`;\r\n \r\n \r\n Object.assign(body.style, {\r\n display: 'flex',\r\n flexDirection: 'column',\r\n alignItems: 'center',\r\n transition: 'transform 0.3s ease',\r\n transform: isSelected ? 'scale(1.2)' : 'scale(1)',\r\n });\r\n\r\n \r\n const iconBox = document.createElement('div');\r\n Object.assign(iconBox.style, {\r\n width: '32px',\r\n height: '32px',\r\n\r\n \r\n border: isSelected ? '3px solid #3b82f6' : '2px solid #ef4444',\r\n boxShadow: '0 4px 8px rgba(0,0,0,0.2)',\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n overflow: 'hidden'\r\n });\r\n\r\n if (logoSrc) {\r\n const img = document.createElement('img');\r\n img.src = logoSrc;\r\n img.style.width = '100%';\r\n img.style.height = '100%';\r\n img.style.objectFit = 'cover';\r\n iconBox.appendChild(img);\r\n } else {\r\n iconBox.style.backgroundColor = '#ef4444';\r\n }\r\n\r\n body.appendChild(iconBox);\r\n\r\n \r\n if (showPrice && (marker.price || marker.name)) {\r\n const label = document.createElement('div');\r\n label.className = 'neshan-marker-label'; // استفاده از کلاس CSS جدید\r\n label.textContent = marker.price ? `${marker.price} ت` : (marker.name ?? '');\r\n body.appendChild(label);\r\n}\r\n\r\n container.appendChild(body);\r\n return container;\r\n}","export function createClusterElement(count: number): HTMLElement {\r\n const container = document.createElement('div');\r\n container.className = 'neshan-cluster-marker';\r\n \r\n \r\n const countSpan = document.createElement('span');\r\n countSpan.className = 'neshan-cluster-count';\r\n countSpan.innerText = count.toString();\r\n \r\n container.appendChild(countSpan);\r\n return container;\r\n}","import React from 'react';\r\nimport { Store } from '../types';\r\n\r\ninterface StoreCardProps {\r\n store: Store;\r\n isSelected: boolean;\r\n onSelect: () => void;\r\n}\r\n\r\nexport const StoreCard: React.FC<StoreCardProps> = ({ store, isSelected, onSelect }) => {\r\n const googleMapUrl = `https://www.google.com/maps/dir/?api=1&destination=${store.lat},${store.lng}`;\r\n\r\n return (\r\n <div \r\n className={`store-card ${isSelected ? 'active' : ''}`}\r\n onClick={onSelect}\r\n >\r\n <div className=\"store-card-header\">\r\n <div>\r\n <h4 className=\"store-name\">{store.name}</h4>\r\n <span className=\"store-city\">{store.city}</span>\r\n </div>\r\n <div className=\"store-price\">{store.price} تومان</div>\r\n </div>\r\n\r\n <p className=\"store-desc\">{store.desc}</p>\r\n\r\n {/* بخش آکاردئونی با CSS Animation */}\r\n <div className=\"store-details-wrapper\">\r\n <div className=\"store-details-content\">\r\n <div className=\"action-buttons\">\r\n {store.phone && (\r\n <a href={`tel:${store.phone}`} className=\"btn btn-call\">\r\n <span>تماس</span>\r\n </a>\r\n )}\r\n {store.whatsapp && (\r\n <a href={`https://wa.me/${store.whatsapp}`} target=\"_blank\" className=\"btn btn-wa\">\r\n <span>واتساپ</span>\r\n </a>\r\n )}\r\n <a href={googleMapUrl} target=\"_blank\" className=\"btn btn-route\">\r\n <span>مسیریابی</span>\r\n </a>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n );\r\n};"],"mappings":"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","names":["useState","useEffect","newMarker","jsx","jsx","jsxs","useState","useEffect"]}
package/dist/react.cjs CHANGED
@@ -61,10 +61,8 @@ function createCustomMarkerElement({
61
61
  });
62
62
  const iconBox = document.createElement("div");
63
63
  Object.assign(iconBox.style, {
64
- width: "40px",
65
- height: "40px",
66
- borderRadius: "50%",
67
- backgroundColor: "white",
64
+ width: "32px",
65
+ height: "32px",
68
66
  border: isSelected ? "3px solid #3b82f6" : "2px solid #ef4444",
69
67
  boxShadow: "0 4px 8px rgba(0,0,0,0.2)",
70
68
  display: "flex",
@@ -95,10 +93,13 @@ function createCustomMarkerElement({
95
93
 
96
94
  // src/utils/createClusterElement.ts
97
95
  function createClusterElement(count) {
98
- const el = document.createElement("div");
99
- el.className = "neshan-cluster-marker";
100
- el.innerHTML = `<span class="neshan-cluster-count">${count}</span>`;
101
- return el;
96
+ const container = document.createElement("div");
97
+ container.className = "neshan-cluster-marker";
98
+ const countSpan = document.createElement("span");
99
+ countSpan.className = "neshan-cluster-count";
100
+ countSpan.innerText = count.toString();
101
+ container.appendChild(countSpan);
102
+ return container;
102
103
  }
103
104
 
104
105
  // src/components/Map.tsx
@@ -147,7 +148,6 @@ var Map = ({
147
148
  mapInstance.flyTo({
148
149
  center: [selectedStore.lng, selectedStore.lat],
149
150
  zoom: 16,
150
- // زوم نزدیک هنگام انتخاب
151
151
  essential: true,
152
152
  duration: 1500
153
153
  });
@@ -169,12 +169,21 @@ var Map = ({
169
169
  let el;
170
170
  if (isCluster) {
171
171
  el = createClusterElement(count);
172
- el.onclick = () => {
172
+ const newMarker2 = new mapLib.Marker({
173
+ element: el,
174
+ anchor: "center"
175
+ }).setLngLat([lng, lat]).addTo(mapInstance);
176
+ el.onclick = (e) => {
177
+ e.stopPropagation();
173
178
  const expansionZoom = Math.min(
174
179
  clusterIndex.current.getClusterExpansionZoom(cluster.id),
175
180
  18
176
181
  );
177
- mapInstance.easeTo({ center: [lng, lat], zoom: expansionZoom });
182
+ mapInstance.easeTo({
183
+ center: [lng, lat],
184
+ zoom: expansionZoom,
185
+ duration: 800
186
+ });
178
187
  };
179
188
  } else {
180
189
  const storeData = cluster.properties;
@@ -205,7 +214,17 @@ var Map = ({
205
214
  mapInstance.off("moveend", updateMarkers);
206
215
  };
207
216
  }, [mapInstance, updateMarkers]);
208
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "relative", ...style }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref: mapContainerRef, style: { width: "100%", height: "100%" } }) });
217
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "relative", ...style }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
218
+ "div",
219
+ {
220
+ ref: mapContainerRef,
221
+ style: {
222
+ width: "100%",
223
+ height: "100%",
224
+ backfaceVisibility: "hidden"
225
+ }
226
+ }
227
+ ) });
209
228
  };
210
229
  var Map_default = Map;
211
230
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/react.ts","../src/components/InteractiveMap.tsx","../src/components/Map.tsx","../src/utils/createCustomMarkerElement.ts","../src/utils/createClusterElement.ts","../src/components/StoreCard.tsx"],"sourcesContent":["\"use client\";\r\n\r\n\r\nexport { InteractiveMap as NeshanMap } from './components/InteractiveMap';\r\n\r\n\r\nexport type {\r\n MapProps,\r\n MapOptions,\r\n MarkerData,\r\n Store,\r\n} from './types';","\"use client\";\r\n\r\nimport React, { useState, useEffect } from 'react';\r\nimport Map from './Map';\r\nimport { StoreCard } from './StoreCard';\r\nimport { Store, MapProps } from '../types';\r\n\r\nexport const InteractiveMap: React.FC<MapProps> = (props) => {\r\n const [isMobile, setIsMobile] = useState(false);\r\n // ✅ ذخیره نمونه نقشه برای پاس دادن به کلیک مارکر\r\n const [mapInstance, setMapInstance] = useState<any>(null);\r\n\r\n useEffect(() => {\r\n const checkMobile = () => setIsMobile(window.innerWidth < 1024);\r\n checkMobile();\r\n window.addEventListener('resize', checkMobile);\r\n return () => window.removeEventListener('resize', checkMobile);\r\n }, []);\r\n\r\n const handleStoreSelect = (store: Store, index: number) => {\r\n // ✅ حالا دیگر null نمی‌فرستیم، نمونه واقعی نقشه را می‌فرستیم\r\n props.onMarkerClick?.(store, index, mapInstance);\r\n };\r\n\r\n // اطمینان از اینکه markers همیشه یک آرایه است\r\n const safeMarkers = props.markers || [];\r\n\r\n return (\r\n <div className={`map-layout-root ${isMobile ? 'is-mobile' : 'is-desktop'}`}>\r\n \r\n {!isMobile && (\r\n <aside className=\"sidebar\">\r\n <header className=\"sidebar-header\">لیست فروشگاه‌ها</header>\r\n <div className=\"sidebar-list\">\r\n {/* ✅ استفاده از safeMarkers برای جلوگیری از خطای undefined */}\r\n {safeMarkers.map((store, idx) => (\r\n <StoreCard \r\n key={store.id} \r\n store={store} \r\n isSelected={props.selectedMarkerId === store.id}\r\n onSelect={() => handleStoreSelect(store, idx)}\r\n />\r\n ))}\r\n </div>\r\n </aside>\r\n )}\r\n\r\n <main className=\"map-container\">\r\n {/* ✅ دریافت نمونه نقشه وقتی لود شد */}\r\n <Map \r\n {...props} \r\n onMapLoad={(map) => {\r\n setMapInstance(map);\r\n props.onMapLoad?.(map);\r\n }} \r\n />\r\n \r\n {props.productName && (\r\n <div className=\"product-badge\">\r\n <span>{props.productName}</span>\r\n </div>\r\n )}\r\n\r\n {isMobile && (\r\n <div className=\"mobile-slider\">\r\n {safeMarkers.map((store, idx) => (\r\n <StoreCard \r\n key={store.id} \r\n store={store} \r\n isSelected={props.selectedMarkerId === store.id}\r\n onSelect={() => handleStoreSelect(store, idx)}\r\n />\r\n ))}\r\n </div>\r\n )}\r\n </main>\r\n </div>\r\n );\r\n};","\"use client\";\r\n\r\nimport React, { useEffect, useRef, useState, useCallback } from 'react';\r\nimport type { MapboxMap, Store, MapProps } from '../types';\r\nimport { createCustomMarkerElement } from '../utils/createCustomMarkerElement';\r\nimport { createClusterElement } from '../utils/createClusterElement';\r\nimport Supercluster from 'supercluster';\r\n\r\n\r\n\r\nconst Map: React.FC<MapProps> = ({\r\n options,\r\n markers = [],\r\n markerLogoUrl = '',\r\n selectedMarkerId = null,\r\n onMarkerClick,\r\n onMapLoad,\r\n style = { width: '100%', height: '100%' },\r\n}) => {\r\n const mapContainerRef = useRef<HTMLDivElement>(null);\r\n const [mapInstance, setMapInstance] = useState<MapboxMap | null>(null);\r\n const [mapLib, setMapLib] = useState<any>(null);\r\n const markersRef = useRef<any[]>([]);\r\n \r\n // سیستم کلاسترینگ\r\n const clusterIndex = useRef(new Supercluster({\r\n radius: 60,\r\n maxZoom: 16\r\n }));\r\n\r\n // ۱. لود کتابخانه\r\n useEffect(() => {\r\n import('@neshan-maps-platform/mapbox-gl').then((mod) => {\r\n setMapLib(mod.default || mod);\r\n });\r\n }, []);\r\n\r\n // ۲. ساخت نقشه\r\n useEffect(() => {\r\n if (!mapLib || !mapContainerRef.current || mapInstance) return;\r\n const map = new mapLib.Map({\r\n container: mapContainerRef.current,\r\n ...options,\r\n });\r\n map.on('load', () => {\r\n setMapInstance(map);\r\n onMapLoad?.(map);\r\n });\r\n return () => { if (map) map.remove(); };\r\n }, [mapLib]);\r\n\r\n // ۳. منطق زوم خودکار (FlyTo) وقتی مارکری انتخاب می‌شود\r\n useEffect(() => {\r\n if (!mapInstance || !selectedMarkerId) return;\r\n const selectedStore = markers.find(m => m.id === selectedMarkerId);\r\n if (selectedStore) {\r\n mapInstance.flyTo({\r\n center: [selectedStore.lng, selectedStore.lat],\r\n zoom: 16, // زوم نزدیک هنگام انتخاب\r\n essential: true,\r\n duration: 1500\r\n });\r\n }\r\n }, [selectedMarkerId, mapInstance, markers]);\r\n\r\n // ۴. تابع رندر کردن مارکرها و کلاسترها\r\n const updateMarkers = useCallback(() => {\r\n if (!mapInstance || !mapLib) return;\r\n\r\n const bounds = mapInstance.getBounds();\r\n const zoom = Math.floor(mapInstance.getZoom());\r\n\r\n // دریافت داده‌های کلاستر شده برای محدوده فعلی نقشه\r\n const clusters = clusterIndex.current.getClusters(\r\n [bounds.getWest(), bounds.getSouth(), bounds.getEast(), bounds.getNorth()],\r\n zoom\r\n );\r\n\r\n // پاک کردن مارکرهای قبلی\r\n markersRef.current.forEach(m => m.remove());\r\n markersRef.current = [];\r\n\r\n clusters.forEach(cluster => {\r\n const [lng, lat] = cluster.geometry.coordinates;\r\n const { cluster: isCluster, point_count: count } = cluster.properties;\r\n\r\n let el: HTMLElement;\r\n\r\n if (isCluster) {\r\n // اگر کلاستر بود\r\n el = createClusterElement(count);\r\n el.onclick = () => {\r\n const expansionZoom = Math.min(\r\n clusterIndex.current.getClusterExpansionZoom(cluster.id as number),\r\n 18\r\n );\r\n mapInstance.easeTo({ center: [lng, lat], zoom: expansionZoom });\r\n };\r\n } else {\r\n // اگر مارکر تکی بود\r\n const storeData = cluster.properties as Store;\r\n el = createCustomMarkerElement({\r\n marker: storeData,\r\n isSelected: storeData.id === selectedMarkerId,\r\n logoSrc: markerLogoUrl,\r\n });\r\n el.onclick = () => onMarkerClick?.(storeData, 0, mapInstance);\r\n }\r\n\r\n const newMarker = new mapLib.Marker({ element: el, anchor: isCluster ? 'center' : 'bottom' })\r\n .setLngLat([lng, lat])\r\n .addTo(mapInstance);\r\n \r\n markersRef.current.push(newMarker);\r\n });\r\n }, [mapInstance, mapLib, selectedMarkerId, markerLogoUrl, onMarkerClick]);\r\n\r\n // ۵. آماده‌سازی داده‌ها برای کلاسترینگ\r\n useEffect(() => {\r\n const points: any = markers.map(s => ({\r\n type: 'Feature',\r\n properties: { ...s },\r\n geometry: { type: 'Point', coordinates: [s.lng, s.lat] }\r\n }));\r\n clusterIndex.current.load(points);\r\n updateMarkers();\r\n }, [markers, updateMarkers]);\r\n\r\n // ۶. گوش دادن به تغییرات نقشه برای بروزرسانی کلاسترها\r\n useEffect(() => {\r\n if (!mapInstance) return;\r\n mapInstance.on('moveend', updateMarkers);\r\n return () => { mapInstance.off('moveend', updateMarkers); };\r\n }, [mapInstance, updateMarkers]);\r\n\r\n return (\r\n <div style={{ position: 'relative', ...style }}>\r\n <div ref={mapContainerRef} style={{ width: '100%', height: '100%' }} />\r\n </div>\r\n );\r\n};\r\n\r\nexport default Map;","import type { MarkerData } from '../types';\r\n\r\ninterface CreateMarkerOptions {\r\n marker: MarkerData;\r\n isSelected: boolean;\r\n logoSrc?: string;\r\n showPrice?: boolean;\r\n}\r\n\r\nexport function createCustomMarkerElement({\r\n marker,\r\n isSelected,\r\n logoSrc = '',\r\n showPrice = true,\r\n}: CreateMarkerOptions): HTMLElement {\r\n \r\n const container = document.createElement('div');\r\n container.className = 'neshan-marker-container';\r\n\r\n \r\n const body = document.createElement('div');\r\n body.className = `neshan-marker-body ${isSelected ? 'neshan-marker-selected' : ''}`;\r\n \r\n \r\n Object.assign(body.style, {\r\n display: 'flex',\r\n flexDirection: 'column',\r\n alignItems: 'center',\r\n transition: 'transform 0.3s ease',\r\n transform: isSelected ? 'scale(1.2)' : 'scale(1)',\r\n });\r\n\r\n \r\n const iconBox = document.createElement('div');\r\n Object.assign(iconBox.style, {\r\n width: '40px',\r\n height: '40px',\r\n borderRadius: '50%',\r\n backgroundColor: 'white',\r\n border: isSelected ? '3px solid #3b82f6' : '2px solid #ef4444',\r\n boxShadow: '0 4px 8px rgba(0,0,0,0.2)',\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n overflow: 'hidden'\r\n });\r\n\r\n if (logoSrc) {\r\n const img = document.createElement('img');\r\n img.src = logoSrc;\r\n img.style.width = '100%';\r\n img.style.height = '100%';\r\n img.style.objectFit = 'cover';\r\n iconBox.appendChild(img);\r\n } else {\r\n iconBox.style.backgroundColor = '#ef4444';\r\n }\r\n\r\n body.appendChild(iconBox);\r\n\r\n \r\n if (showPrice && (marker.price || marker.name)) {\r\n const label = document.createElement('div');\r\n label.className = 'neshan-marker-label'; // استفاده از کلاس CSS جدید\r\n label.textContent = marker.price ? `${marker.price} ت` : (marker.name ?? '');\r\n body.appendChild(label);\r\n}\r\n\r\n container.appendChild(body);\r\n return container;\r\n}","export function createClusterElement(count: number): HTMLElement {\r\n const el = document.createElement('div');\r\n el.className = 'neshan-cluster-marker';\r\n el.innerHTML = `<span class=\"neshan-cluster-count\">${count}</span>`;\r\n return el;\r\n}","import React from 'react';\r\nimport { Store } from '../types';\r\n\r\ninterface StoreCardProps {\r\n store: Store;\r\n isSelected: boolean;\r\n onSelect: () => void;\r\n}\r\n\r\nexport const StoreCard: React.FC<StoreCardProps> = ({ store, isSelected, onSelect }) => {\r\n const googleMapUrl = `https://www.google.com/maps/dir/?api=1&destination=${store.lat},${store.lng}`;\r\n\r\n return (\r\n <div \r\n className={`store-card ${isSelected ? 'active' : ''}`}\r\n onClick={onSelect}\r\n >\r\n <div className=\"store-card-header\">\r\n <div>\r\n <h4 className=\"store-name\">{store.name}</h4>\r\n <span className=\"store-city\">{store.city}</span>\r\n </div>\r\n <div className=\"store-price\">{store.price} تومان</div>\r\n </div>\r\n\r\n <p className=\"store-desc\">{store.desc}</p>\r\n\r\n {/* بخش آکاردئونی با CSS Animation */}\r\n <div className=\"store-details-wrapper\">\r\n <div className=\"store-details-content\">\r\n <div className=\"action-buttons\">\r\n {store.phone && (\r\n <a href={`tel:${store.phone}`} className=\"btn btn-call\">\r\n <span>تماس</span>\r\n </a>\r\n )}\r\n {store.whatsapp && (\r\n <a href={`https://wa.me/${store.whatsapp}`} target=\"_blank\" className=\"btn btn-wa\">\r\n <span>واتساپ</span>\r\n </a>\r\n )}\r\n <a href={googleMapUrl} target=\"_blank\" className=\"btn btn-route\">\r\n <span>مسیریابی</span>\r\n </a>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n );\r\n};"],"mappings":"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","names":["import_react","Supercluster","import_jsx_runtime","import_jsx_runtime"]}
1
+ {"version":3,"sources":["../src/react.ts","../src/components/InteractiveMap.tsx","../src/components/Map.tsx","../src/utils/createCustomMarkerElement.ts","../src/utils/createClusterElement.ts","../src/components/StoreCard.tsx"],"sourcesContent":["\"use client\";\r\n\r\n\r\nexport { InteractiveMap as NeshanMap } from './components/InteractiveMap';\r\n\r\n\r\nexport type {\r\n MapProps,\r\n MapOptions,\r\n MarkerData,\r\n Store,\r\n} from './types';","\"use client\";\r\n\r\nimport React, { useState, useEffect } from 'react';\r\nimport Map from './Map';\r\nimport { StoreCard } from './StoreCard';\r\nimport { Store, MapProps } from '../types';\r\n\r\nexport const InteractiveMap: React.FC<MapProps> = (props) => {\r\n const [isMobile, setIsMobile] = useState(false);\r\n // ✅ ذخیره نمونه نقشه برای پاس دادن به کلیک مارکر\r\n const [mapInstance, setMapInstance] = useState<any>(null);\r\n\r\n useEffect(() => {\r\n const checkMobile = () => setIsMobile(window.innerWidth < 1024);\r\n checkMobile();\r\n window.addEventListener('resize', checkMobile);\r\n return () => window.removeEventListener('resize', checkMobile);\r\n }, []);\r\n\r\n const handleStoreSelect = (store: Store, index: number) => {\r\n // ✅ حالا دیگر null نمی‌فرستیم، نمونه واقعی نقشه را می‌فرستیم\r\n props.onMarkerClick?.(store, index, mapInstance);\r\n };\r\n\r\n // اطمینان از اینکه markers همیشه یک آرایه است\r\n const safeMarkers = props.markers || [];\r\n\r\n return (\r\n <div className={`map-layout-root ${isMobile ? 'is-mobile' : 'is-desktop'}`}>\r\n \r\n {!isMobile && (\r\n <aside className=\"sidebar\">\r\n <header className=\"sidebar-header\">لیست فروشگاه‌ها</header>\r\n <div className=\"sidebar-list\">\r\n {/* ✅ استفاده از safeMarkers برای جلوگیری از خطای undefined */}\r\n {safeMarkers.map((store, idx) => (\r\n <StoreCard \r\n key={store.id} \r\n store={store} \r\n isSelected={props.selectedMarkerId === store.id}\r\n onSelect={() => handleStoreSelect(store, idx)}\r\n />\r\n ))}\r\n </div>\r\n </aside>\r\n )}\r\n\r\n <main className=\"map-container\">\r\n {/* ✅ دریافت نمونه نقشه وقتی لود شد */}\r\n <Map \r\n {...props} \r\n onMapLoad={(map) => {\r\n setMapInstance(map);\r\n props.onMapLoad?.(map);\r\n }} \r\n />\r\n \r\n {props.productName && (\r\n <div className=\"product-badge\">\r\n <span>{props.productName}</span>\r\n </div>\r\n )}\r\n\r\n {isMobile && (\r\n <div className=\"mobile-slider\">\r\n {safeMarkers.map((store, idx) => (\r\n <StoreCard \r\n key={store.id} \r\n store={store} \r\n isSelected={props.selectedMarkerId === store.id}\r\n onSelect={() => handleStoreSelect(store, idx)}\r\n />\r\n ))}\r\n </div>\r\n )}\r\n </main>\r\n </div>\r\n );\r\n};","\"use client\";\r\n\r\nimport React, { useEffect, useRef, useState, useCallback } from 'react';\r\nimport type { MapboxMap, Store, MapProps } from '../types';\r\nimport { createCustomMarkerElement } from '../utils/createCustomMarkerElement';\r\nimport { createClusterElement } from '../utils/createClusterElement';\r\nimport Supercluster from 'supercluster';\r\n\r\n\r\n\r\nconst Map: React.FC<MapProps> = ({\r\n options,\r\n markers = [],\r\n markerLogoUrl = '',\r\n selectedMarkerId = null,\r\n onMarkerClick,\r\n onMapLoad,\r\n style = { width: '100%', height: '100%' },\r\n}) => {\r\n const mapContainerRef = useRef<HTMLDivElement>(null);\r\n const [mapInstance, setMapInstance] = useState<MapboxMap | null>(null);\r\n const [mapLib, setMapLib] = useState<any>(null);\r\n const markersRef = useRef<any[]>([]);\r\n \r\n \r\n const clusterIndex = useRef(new Supercluster({\r\n radius: 60,\r\n maxZoom: 16\r\n }));\r\n\r\n \r\n useEffect(() => {\r\n import('@neshan-maps-platform/mapbox-gl').then((mod) => {\r\n setMapLib(mod.default || mod);\r\n });\r\n }, []);\r\n\r\n \r\n useEffect(() => {\r\n if (!mapLib || !mapContainerRef.current || mapInstance) return;\r\n const map = new mapLib.Map({\r\n container: mapContainerRef.current,\r\n ...options,\r\n });\r\n map.on('load', () => {\r\n setMapInstance(map);\r\n onMapLoad?.(map);\r\n });\r\n return () => { if (map) map.remove(); };\r\n }, [mapLib]);\r\n\r\n \r\n useEffect(() => {\r\n if (!mapInstance || !selectedMarkerId) return;\r\n const selectedStore = markers.find(m => m.id === selectedMarkerId);\r\n if (selectedStore) {\r\n mapInstance.flyTo({\r\n center: [selectedStore.lng, selectedStore.lat],\r\n zoom: 16, \r\n essential: true,\r\n duration: 1500\r\n });\r\n }\r\n }, [selectedMarkerId, mapInstance, markers]);\r\n\r\n \r\n const updateMarkers = useCallback(() => {\r\n if (!mapInstance || !mapLib) return;\r\n\r\n const bounds = mapInstance.getBounds();\r\n const zoom = Math.floor(mapInstance.getZoom());\r\n\r\n \r\n const clusters = clusterIndex.current.getClusters(\r\n [bounds.getWest(), bounds.getSouth(), bounds.getEast(), bounds.getNorth()],\r\n zoom\r\n );\r\n\r\n \r\n markersRef.current.forEach(m => m.remove());\r\n markersRef.current = [];\r\n\r\n clusters.forEach(cluster => {\r\n const [lng, lat] = cluster.geometry.coordinates;\r\n const { cluster: isCluster, point_count: count } = cluster.properties;\r\n\r\n let el: HTMLElement;\r\n\r\n if (isCluster) {\r\n el = createClusterElement(count);\r\n \r\n const newMarker = new mapLib.Marker({ \r\n element: el, \r\n anchor: 'center' \r\n })\r\n .setLngLat([lng, lat])\r\n .addTo(mapInstance);\r\n\r\n el.onclick = (e) => {\r\n e.stopPropagation(); \r\n const expansionZoom = Math.min(\r\n clusterIndex.current.getClusterExpansionZoom(cluster.id as number),\r\n 18\r\n );\r\n mapInstance.easeTo({ \r\n center: [lng, lat], \r\n zoom: expansionZoom,\r\n duration: 800 \r\n });\r\n };\r\n } else {\r\n \r\n const storeData = cluster.properties as Store;\r\n el = createCustomMarkerElement({\r\n marker: storeData,\r\n isSelected: storeData.id === selectedMarkerId,\r\n logoSrc: markerLogoUrl,\r\n });\r\n el.onclick = () => onMarkerClick?.(storeData, 0, mapInstance);\r\n }\r\n\r\n const newMarker = new mapLib.Marker({ element: el, anchor: isCluster ? 'center' : 'bottom' })\r\n .setLngLat([lng, lat])\r\n .addTo(mapInstance);\r\n \r\n markersRef.current.push(newMarker);\r\n });\r\n }, [mapInstance, mapLib, selectedMarkerId, markerLogoUrl, onMarkerClick]);\r\n\r\n \r\n useEffect(() => {\r\n const points: any = markers.map(s => ({\r\n type: 'Feature',\r\n properties: { ...s },\r\n geometry: { type: 'Point', coordinates: [s.lng, s.lat] }\r\n }));\r\n clusterIndex.current.load(points);\r\n updateMarkers();\r\n }, [markers, updateMarkers]);\r\n\r\n \r\n useEffect(() => {\r\n if (!mapInstance) return;\r\n mapInstance.on('moveend', updateMarkers);\r\n return () => { mapInstance.off('moveend', updateMarkers); };\r\n }, [mapInstance, updateMarkers]);\r\n\r\n return (\r\n <div style={{ position: 'relative', ...style }}>\r\n <div \r\n ref={mapContainerRef} \r\n style={{ \r\n width: '100%', \r\n height: '100%',\r\n \r\n backfaceVisibility: 'hidden' \r\n }} \r\n />\r\n </div>\r\n);\r\n};\r\n\r\nexport default Map;","import type { MarkerData } from '../types';\r\n\r\ninterface CreateMarkerOptions {\r\n marker: MarkerData;\r\n isSelected: boolean;\r\n logoSrc?: string;\r\n showPrice?: boolean;\r\n}\r\n\r\nexport function createCustomMarkerElement({\r\n marker,\r\n isSelected,\r\n logoSrc = '',\r\n showPrice = true,\r\n}: CreateMarkerOptions): HTMLElement {\r\n \r\n const container = document.createElement('div');\r\n container.className = 'neshan-marker-container';\r\n\r\n \r\n const body = document.createElement('div');\r\n body.className = `neshan-marker-body ${isSelected ? 'neshan-marker-selected' : ''}`;\r\n \r\n \r\n Object.assign(body.style, {\r\n display: 'flex',\r\n flexDirection: 'column',\r\n alignItems: 'center',\r\n transition: 'transform 0.3s ease',\r\n transform: isSelected ? 'scale(1.2)' : 'scale(1)',\r\n });\r\n\r\n \r\n const iconBox = document.createElement('div');\r\n Object.assign(iconBox.style, {\r\n width: '32px',\r\n height: '32px',\r\n\r\n \r\n border: isSelected ? '3px solid #3b82f6' : '2px solid #ef4444',\r\n boxShadow: '0 4px 8px rgba(0,0,0,0.2)',\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n overflow: 'hidden'\r\n });\r\n\r\n if (logoSrc) {\r\n const img = document.createElement('img');\r\n img.src = logoSrc;\r\n img.style.width = '100%';\r\n img.style.height = '100%';\r\n img.style.objectFit = 'cover';\r\n iconBox.appendChild(img);\r\n } else {\r\n iconBox.style.backgroundColor = '#ef4444';\r\n }\r\n\r\n body.appendChild(iconBox);\r\n\r\n \r\n if (showPrice && (marker.price || marker.name)) {\r\n const label = document.createElement('div');\r\n label.className = 'neshan-marker-label'; // استفاده از کلاس CSS جدید\r\n label.textContent = marker.price ? `${marker.price} ت` : (marker.name ?? '');\r\n body.appendChild(label);\r\n}\r\n\r\n container.appendChild(body);\r\n return container;\r\n}","export function createClusterElement(count: number): HTMLElement {\r\n const container = document.createElement('div');\r\n container.className = 'neshan-cluster-marker';\r\n \r\n \r\n const countSpan = document.createElement('span');\r\n countSpan.className = 'neshan-cluster-count';\r\n countSpan.innerText = count.toString();\r\n \r\n container.appendChild(countSpan);\r\n return container;\r\n}","import React from 'react';\r\nimport { Store } from '../types';\r\n\r\ninterface StoreCardProps {\r\n store: Store;\r\n isSelected: boolean;\r\n onSelect: () => void;\r\n}\r\n\r\nexport const StoreCard: React.FC<StoreCardProps> = ({ store, isSelected, onSelect }) => {\r\n const googleMapUrl = `https://www.google.com/maps/dir/?api=1&destination=${store.lat},${store.lng}`;\r\n\r\n return (\r\n <div \r\n className={`store-card ${isSelected ? 'active' : ''}`}\r\n onClick={onSelect}\r\n >\r\n <div className=\"store-card-header\">\r\n <div>\r\n <h4 className=\"store-name\">{store.name}</h4>\r\n <span className=\"store-city\">{store.city}</span>\r\n </div>\r\n <div className=\"store-price\">{store.price} تومان</div>\r\n </div>\r\n\r\n <p className=\"store-desc\">{store.desc}</p>\r\n\r\n {/* بخش آکاردئونی با CSS Animation */}\r\n <div className=\"store-details-wrapper\">\r\n <div className=\"store-details-content\">\r\n <div className=\"action-buttons\">\r\n {store.phone && (\r\n <a href={`tel:${store.phone}`} className=\"btn btn-call\">\r\n <span>تماس</span>\r\n </a>\r\n )}\r\n {store.whatsapp && (\r\n <a href={`https://wa.me/${store.whatsapp}`} target=\"_blank\" className=\"btn btn-wa\">\r\n <span>واتساپ</span>\r\n </a>\r\n )}\r\n <a href={googleMapUrl} target=\"_blank\" className=\"btn btn-route\">\r\n <span>مسیریابی</span>\r\n </a>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n );\r\n};"],"mappings":"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","names":["import_react","Supercluster","newMarker","import_jsx_runtime","import_jsx_runtime"]}
package/dist/react.js CHANGED
@@ -26,10 +26,8 @@ function createCustomMarkerElement({
26
26
  });
27
27
  const iconBox = document.createElement("div");
28
28
  Object.assign(iconBox.style, {
29
- width: "40px",
30
- height: "40px",
31
- borderRadius: "50%",
32
- backgroundColor: "white",
29
+ width: "32px",
30
+ height: "32px",
33
31
  border: isSelected ? "3px solid #3b82f6" : "2px solid #ef4444",
34
32
  boxShadow: "0 4px 8px rgba(0,0,0,0.2)",
35
33
  display: "flex",
@@ -60,10 +58,13 @@ function createCustomMarkerElement({
60
58
 
61
59
  // src/utils/createClusterElement.ts
62
60
  function createClusterElement(count) {
63
- const el = document.createElement("div");
64
- el.className = "neshan-cluster-marker";
65
- el.innerHTML = `<span class="neshan-cluster-count">${count}</span>`;
66
- return el;
61
+ const container = document.createElement("div");
62
+ container.className = "neshan-cluster-marker";
63
+ const countSpan = document.createElement("span");
64
+ countSpan.className = "neshan-cluster-count";
65
+ countSpan.innerText = count.toString();
66
+ container.appendChild(countSpan);
67
+ return container;
67
68
  }
68
69
 
69
70
  // src/components/Map.tsx
@@ -112,7 +113,6 @@ var Map = ({
112
113
  mapInstance.flyTo({
113
114
  center: [selectedStore.lng, selectedStore.lat],
114
115
  zoom: 16,
115
- // زوم نزدیک هنگام انتخاب
116
116
  essential: true,
117
117
  duration: 1500
118
118
  });
@@ -134,12 +134,21 @@ var Map = ({
134
134
  let el;
135
135
  if (isCluster) {
136
136
  el = createClusterElement(count);
137
- el.onclick = () => {
137
+ const newMarker2 = new mapLib.Marker({
138
+ element: el,
139
+ anchor: "center"
140
+ }).setLngLat([lng, lat]).addTo(mapInstance);
141
+ el.onclick = (e) => {
142
+ e.stopPropagation();
138
143
  const expansionZoom = Math.min(
139
144
  clusterIndex.current.getClusterExpansionZoom(cluster.id),
140
145
  18
141
146
  );
142
- mapInstance.easeTo({ center: [lng, lat], zoom: expansionZoom });
147
+ mapInstance.easeTo({
148
+ center: [lng, lat],
149
+ zoom: expansionZoom,
150
+ duration: 800
151
+ });
143
152
  };
144
153
  } else {
145
154
  const storeData = cluster.properties;
@@ -170,7 +179,17 @@ var Map = ({
170
179
  mapInstance.off("moveend", updateMarkers);
171
180
  };
172
181
  }, [mapInstance, updateMarkers]);
173
- return /* @__PURE__ */ jsx("div", { style: { position: "relative", ...style }, children: /* @__PURE__ */ jsx("div", { ref: mapContainerRef, style: { width: "100%", height: "100%" } }) });
182
+ return /* @__PURE__ */ jsx("div", { style: { position: "relative", ...style }, children: /* @__PURE__ */ jsx(
183
+ "div",
184
+ {
185
+ ref: mapContainerRef,
186
+ style: {
187
+ width: "100%",
188
+ height: "100%",
189
+ backfaceVisibility: "hidden"
190
+ }
191
+ }
192
+ ) });
174
193
  };
175
194
  var Map_default = Map;
176
195
 
package/dist/react.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/components/InteractiveMap.tsx","../src/components/Map.tsx","../src/utils/createCustomMarkerElement.ts","../src/utils/createClusterElement.ts","../src/components/StoreCard.tsx"],"sourcesContent":["\"use client\";\r\n\r\nimport React, { useState, useEffect } from 'react';\r\nimport Map from './Map';\r\nimport { StoreCard } from './StoreCard';\r\nimport { Store, MapProps } from '../types';\r\n\r\nexport const InteractiveMap: React.FC<MapProps> = (props) => {\r\n const [isMobile, setIsMobile] = useState(false);\r\n // ✅ ذخیره نمونه نقشه برای پاس دادن به کلیک مارکر\r\n const [mapInstance, setMapInstance] = useState<any>(null);\r\n\r\n useEffect(() => {\r\n const checkMobile = () => setIsMobile(window.innerWidth < 1024);\r\n checkMobile();\r\n window.addEventListener('resize', checkMobile);\r\n return () => window.removeEventListener('resize', checkMobile);\r\n }, []);\r\n\r\n const handleStoreSelect = (store: Store, index: number) => {\r\n // ✅ حالا دیگر null نمی‌فرستیم، نمونه واقعی نقشه را می‌فرستیم\r\n props.onMarkerClick?.(store, index, mapInstance);\r\n };\r\n\r\n // اطمینان از اینکه markers همیشه یک آرایه است\r\n const safeMarkers = props.markers || [];\r\n\r\n return (\r\n <div className={`map-layout-root ${isMobile ? 'is-mobile' : 'is-desktop'}`}>\r\n \r\n {!isMobile && (\r\n <aside className=\"sidebar\">\r\n <header className=\"sidebar-header\">لیست فروشگاه‌ها</header>\r\n <div className=\"sidebar-list\">\r\n {/* ✅ استفاده از safeMarkers برای جلوگیری از خطای undefined */}\r\n {safeMarkers.map((store, idx) => (\r\n <StoreCard \r\n key={store.id} \r\n store={store} \r\n isSelected={props.selectedMarkerId === store.id}\r\n onSelect={() => handleStoreSelect(store, idx)}\r\n />\r\n ))}\r\n </div>\r\n </aside>\r\n )}\r\n\r\n <main className=\"map-container\">\r\n {/* ✅ دریافت نمونه نقشه وقتی لود شد */}\r\n <Map \r\n {...props} \r\n onMapLoad={(map) => {\r\n setMapInstance(map);\r\n props.onMapLoad?.(map);\r\n }} \r\n />\r\n \r\n {props.productName && (\r\n <div className=\"product-badge\">\r\n <span>{props.productName}</span>\r\n </div>\r\n )}\r\n\r\n {isMobile && (\r\n <div className=\"mobile-slider\">\r\n {safeMarkers.map((store, idx) => (\r\n <StoreCard \r\n key={store.id} \r\n store={store} \r\n isSelected={props.selectedMarkerId === store.id}\r\n onSelect={() => handleStoreSelect(store, idx)}\r\n />\r\n ))}\r\n </div>\r\n )}\r\n </main>\r\n </div>\r\n );\r\n};","\"use client\";\r\n\r\nimport React, { useEffect, useRef, useState, useCallback } from 'react';\r\nimport type { MapboxMap, Store, MapProps } from '../types';\r\nimport { createCustomMarkerElement } from '../utils/createCustomMarkerElement';\r\nimport { createClusterElement } from '../utils/createClusterElement';\r\nimport Supercluster from 'supercluster';\r\n\r\n\r\n\r\nconst Map: React.FC<MapProps> = ({\r\n options,\r\n markers = [],\r\n markerLogoUrl = '',\r\n selectedMarkerId = null,\r\n onMarkerClick,\r\n onMapLoad,\r\n style = { width: '100%', height: '100%' },\r\n}) => {\r\n const mapContainerRef = useRef<HTMLDivElement>(null);\r\n const [mapInstance, setMapInstance] = useState<MapboxMap | null>(null);\r\n const [mapLib, setMapLib] = useState<any>(null);\r\n const markersRef = useRef<any[]>([]);\r\n \r\n // سیستم کلاسترینگ\r\n const clusterIndex = useRef(new Supercluster({\r\n radius: 60,\r\n maxZoom: 16\r\n }));\r\n\r\n // ۱. لود کتابخانه\r\n useEffect(() => {\r\n import('@neshan-maps-platform/mapbox-gl').then((mod) => {\r\n setMapLib(mod.default || mod);\r\n });\r\n }, []);\r\n\r\n // ۲. ساخت نقشه\r\n useEffect(() => {\r\n if (!mapLib || !mapContainerRef.current || mapInstance) return;\r\n const map = new mapLib.Map({\r\n container: mapContainerRef.current,\r\n ...options,\r\n });\r\n map.on('load', () => {\r\n setMapInstance(map);\r\n onMapLoad?.(map);\r\n });\r\n return () => { if (map) map.remove(); };\r\n }, [mapLib]);\r\n\r\n // ۳. منطق زوم خودکار (FlyTo) وقتی مارکری انتخاب می‌شود\r\n useEffect(() => {\r\n if (!mapInstance || !selectedMarkerId) return;\r\n const selectedStore = markers.find(m => m.id === selectedMarkerId);\r\n if (selectedStore) {\r\n mapInstance.flyTo({\r\n center: [selectedStore.lng, selectedStore.lat],\r\n zoom: 16, // زوم نزدیک هنگام انتخاب\r\n essential: true,\r\n duration: 1500\r\n });\r\n }\r\n }, [selectedMarkerId, mapInstance, markers]);\r\n\r\n // ۴. تابع رندر کردن مارکرها و کلاسترها\r\n const updateMarkers = useCallback(() => {\r\n if (!mapInstance || !mapLib) return;\r\n\r\n const bounds = mapInstance.getBounds();\r\n const zoom = Math.floor(mapInstance.getZoom());\r\n\r\n // دریافت داده‌های کلاستر شده برای محدوده فعلی نقشه\r\n const clusters = clusterIndex.current.getClusters(\r\n [bounds.getWest(), bounds.getSouth(), bounds.getEast(), bounds.getNorth()],\r\n zoom\r\n );\r\n\r\n // پاک کردن مارکرهای قبلی\r\n markersRef.current.forEach(m => m.remove());\r\n markersRef.current = [];\r\n\r\n clusters.forEach(cluster => {\r\n const [lng, lat] = cluster.geometry.coordinates;\r\n const { cluster: isCluster, point_count: count } = cluster.properties;\r\n\r\n let el: HTMLElement;\r\n\r\n if (isCluster) {\r\n // اگر کلاستر بود\r\n el = createClusterElement(count);\r\n el.onclick = () => {\r\n const expansionZoom = Math.min(\r\n clusterIndex.current.getClusterExpansionZoom(cluster.id as number),\r\n 18\r\n );\r\n mapInstance.easeTo({ center: [lng, lat], zoom: expansionZoom });\r\n };\r\n } else {\r\n // اگر مارکر تکی بود\r\n const storeData = cluster.properties as Store;\r\n el = createCustomMarkerElement({\r\n marker: storeData,\r\n isSelected: storeData.id === selectedMarkerId,\r\n logoSrc: markerLogoUrl,\r\n });\r\n el.onclick = () => onMarkerClick?.(storeData, 0, mapInstance);\r\n }\r\n\r\n const newMarker = new mapLib.Marker({ element: el, anchor: isCluster ? 'center' : 'bottom' })\r\n .setLngLat([lng, lat])\r\n .addTo(mapInstance);\r\n \r\n markersRef.current.push(newMarker);\r\n });\r\n }, [mapInstance, mapLib, selectedMarkerId, markerLogoUrl, onMarkerClick]);\r\n\r\n // ۵. آماده‌سازی داده‌ها برای کلاسترینگ\r\n useEffect(() => {\r\n const points: any = markers.map(s => ({\r\n type: 'Feature',\r\n properties: { ...s },\r\n geometry: { type: 'Point', coordinates: [s.lng, s.lat] }\r\n }));\r\n clusterIndex.current.load(points);\r\n updateMarkers();\r\n }, [markers, updateMarkers]);\r\n\r\n // ۶. گوش دادن به تغییرات نقشه برای بروزرسانی کلاسترها\r\n useEffect(() => {\r\n if (!mapInstance) return;\r\n mapInstance.on('moveend', updateMarkers);\r\n return () => { mapInstance.off('moveend', updateMarkers); };\r\n }, [mapInstance, updateMarkers]);\r\n\r\n return (\r\n <div style={{ position: 'relative', ...style }}>\r\n <div ref={mapContainerRef} style={{ width: '100%', height: '100%' }} />\r\n </div>\r\n );\r\n};\r\n\r\nexport default Map;","import type { MarkerData } from '../types';\r\n\r\ninterface CreateMarkerOptions {\r\n marker: MarkerData;\r\n isSelected: boolean;\r\n logoSrc?: string;\r\n showPrice?: boolean;\r\n}\r\n\r\nexport function createCustomMarkerElement({\r\n marker,\r\n isSelected,\r\n logoSrc = '',\r\n showPrice = true,\r\n}: CreateMarkerOptions): HTMLElement {\r\n \r\n const container = document.createElement('div');\r\n container.className = 'neshan-marker-container';\r\n\r\n \r\n const body = document.createElement('div');\r\n body.className = `neshan-marker-body ${isSelected ? 'neshan-marker-selected' : ''}`;\r\n \r\n \r\n Object.assign(body.style, {\r\n display: 'flex',\r\n flexDirection: 'column',\r\n alignItems: 'center',\r\n transition: 'transform 0.3s ease',\r\n transform: isSelected ? 'scale(1.2)' : 'scale(1)',\r\n });\r\n\r\n \r\n const iconBox = document.createElement('div');\r\n Object.assign(iconBox.style, {\r\n width: '40px',\r\n height: '40px',\r\n borderRadius: '50%',\r\n backgroundColor: 'white',\r\n border: isSelected ? '3px solid #3b82f6' : '2px solid #ef4444',\r\n boxShadow: '0 4px 8px rgba(0,0,0,0.2)',\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n overflow: 'hidden'\r\n });\r\n\r\n if (logoSrc) {\r\n const img = document.createElement('img');\r\n img.src = logoSrc;\r\n img.style.width = '100%';\r\n img.style.height = '100%';\r\n img.style.objectFit = 'cover';\r\n iconBox.appendChild(img);\r\n } else {\r\n iconBox.style.backgroundColor = '#ef4444';\r\n }\r\n\r\n body.appendChild(iconBox);\r\n\r\n \r\n if (showPrice && (marker.price || marker.name)) {\r\n const label = document.createElement('div');\r\n label.className = 'neshan-marker-label'; // استفاده از کلاس CSS جدید\r\n label.textContent = marker.price ? `${marker.price} ت` : (marker.name ?? '');\r\n body.appendChild(label);\r\n}\r\n\r\n container.appendChild(body);\r\n return container;\r\n}","export function createClusterElement(count: number): HTMLElement {\r\n const el = document.createElement('div');\r\n el.className = 'neshan-cluster-marker';\r\n el.innerHTML = `<span class=\"neshan-cluster-count\">${count}</span>`;\r\n return el;\r\n}","import React from 'react';\r\nimport { Store } from '../types';\r\n\r\ninterface StoreCardProps {\r\n store: Store;\r\n isSelected: boolean;\r\n onSelect: () => void;\r\n}\r\n\r\nexport const StoreCard: React.FC<StoreCardProps> = ({ store, isSelected, onSelect }) => {\r\n const googleMapUrl = `https://www.google.com/maps/dir/?api=1&destination=${store.lat},${store.lng}`;\r\n\r\n return (\r\n <div \r\n className={`store-card ${isSelected ? 'active' : ''}`}\r\n onClick={onSelect}\r\n >\r\n <div className=\"store-card-header\">\r\n <div>\r\n <h4 className=\"store-name\">{store.name}</h4>\r\n <span className=\"store-city\">{store.city}</span>\r\n </div>\r\n <div className=\"store-price\">{store.price} تومان</div>\r\n </div>\r\n\r\n <p className=\"store-desc\">{store.desc}</p>\r\n\r\n {/* بخش آکاردئونی با CSS Animation */}\r\n <div className=\"store-details-wrapper\">\r\n <div className=\"store-details-content\">\r\n <div className=\"action-buttons\">\r\n {store.phone && (\r\n <a href={`tel:${store.phone}`} className=\"btn btn-call\">\r\n <span>تماس</span>\r\n </a>\r\n )}\r\n {store.whatsapp && (\r\n <a href={`https://wa.me/${store.whatsapp}`} target=\"_blank\" className=\"btn btn-wa\">\r\n <span>واتساپ</span>\r\n </a>\r\n )}\r\n <a href={googleMapUrl} target=\"_blank\" className=\"btn btn-route\">\r\n <span>مسیریابی</span>\r\n </a>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n );\r\n};"],"mappings":"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","names":["useState","useEffect","jsx","jsx","jsxs","useState","useEffect"]}
1
+ {"version":3,"sources":["../src/components/InteractiveMap.tsx","../src/components/Map.tsx","../src/utils/createCustomMarkerElement.ts","../src/utils/createClusterElement.ts","../src/components/StoreCard.tsx"],"sourcesContent":["\"use client\";\r\n\r\nimport React, { useState, useEffect } from 'react';\r\nimport Map from './Map';\r\nimport { StoreCard } from './StoreCard';\r\nimport { Store, MapProps } from '../types';\r\n\r\nexport const InteractiveMap: React.FC<MapProps> = (props) => {\r\n const [isMobile, setIsMobile] = useState(false);\r\n // ✅ ذخیره نمونه نقشه برای پاس دادن به کلیک مارکر\r\n const [mapInstance, setMapInstance] = useState<any>(null);\r\n\r\n useEffect(() => {\r\n const checkMobile = () => setIsMobile(window.innerWidth < 1024);\r\n checkMobile();\r\n window.addEventListener('resize', checkMobile);\r\n return () => window.removeEventListener('resize', checkMobile);\r\n }, []);\r\n\r\n const handleStoreSelect = (store: Store, index: number) => {\r\n // ✅ حالا دیگر null نمی‌فرستیم، نمونه واقعی نقشه را می‌فرستیم\r\n props.onMarkerClick?.(store, index, mapInstance);\r\n };\r\n\r\n // اطمینان از اینکه markers همیشه یک آرایه است\r\n const safeMarkers = props.markers || [];\r\n\r\n return (\r\n <div className={`map-layout-root ${isMobile ? 'is-mobile' : 'is-desktop'}`}>\r\n \r\n {!isMobile && (\r\n <aside className=\"sidebar\">\r\n <header className=\"sidebar-header\">لیست فروشگاه‌ها</header>\r\n <div className=\"sidebar-list\">\r\n {/* ✅ استفاده از safeMarkers برای جلوگیری از خطای undefined */}\r\n {safeMarkers.map((store, idx) => (\r\n <StoreCard \r\n key={store.id} \r\n store={store} \r\n isSelected={props.selectedMarkerId === store.id}\r\n onSelect={() => handleStoreSelect(store, idx)}\r\n />\r\n ))}\r\n </div>\r\n </aside>\r\n )}\r\n\r\n <main className=\"map-container\">\r\n {/* ✅ دریافت نمونه نقشه وقتی لود شد */}\r\n <Map \r\n {...props} \r\n onMapLoad={(map) => {\r\n setMapInstance(map);\r\n props.onMapLoad?.(map);\r\n }} \r\n />\r\n \r\n {props.productName && (\r\n <div className=\"product-badge\">\r\n <span>{props.productName}</span>\r\n </div>\r\n )}\r\n\r\n {isMobile && (\r\n <div className=\"mobile-slider\">\r\n {safeMarkers.map((store, idx) => (\r\n <StoreCard \r\n key={store.id} \r\n store={store} \r\n isSelected={props.selectedMarkerId === store.id}\r\n onSelect={() => handleStoreSelect(store, idx)}\r\n />\r\n ))}\r\n </div>\r\n )}\r\n </main>\r\n </div>\r\n );\r\n};","\"use client\";\r\n\r\nimport React, { useEffect, useRef, useState, useCallback } from 'react';\r\nimport type { MapboxMap, Store, MapProps } from '../types';\r\nimport { createCustomMarkerElement } from '../utils/createCustomMarkerElement';\r\nimport { createClusterElement } from '../utils/createClusterElement';\r\nimport Supercluster from 'supercluster';\r\n\r\n\r\n\r\nconst Map: React.FC<MapProps> = ({\r\n options,\r\n markers = [],\r\n markerLogoUrl = '',\r\n selectedMarkerId = null,\r\n onMarkerClick,\r\n onMapLoad,\r\n style = { width: '100%', height: '100%' },\r\n}) => {\r\n const mapContainerRef = useRef<HTMLDivElement>(null);\r\n const [mapInstance, setMapInstance] = useState<MapboxMap | null>(null);\r\n const [mapLib, setMapLib] = useState<any>(null);\r\n const markersRef = useRef<any[]>([]);\r\n \r\n \r\n const clusterIndex = useRef(new Supercluster({\r\n radius: 60,\r\n maxZoom: 16\r\n }));\r\n\r\n \r\n useEffect(() => {\r\n import('@neshan-maps-platform/mapbox-gl').then((mod) => {\r\n setMapLib(mod.default || mod);\r\n });\r\n }, []);\r\n\r\n \r\n useEffect(() => {\r\n if (!mapLib || !mapContainerRef.current || mapInstance) return;\r\n const map = new mapLib.Map({\r\n container: mapContainerRef.current,\r\n ...options,\r\n });\r\n map.on('load', () => {\r\n setMapInstance(map);\r\n onMapLoad?.(map);\r\n });\r\n return () => { if (map) map.remove(); };\r\n }, [mapLib]);\r\n\r\n \r\n useEffect(() => {\r\n if (!mapInstance || !selectedMarkerId) return;\r\n const selectedStore = markers.find(m => m.id === selectedMarkerId);\r\n if (selectedStore) {\r\n mapInstance.flyTo({\r\n center: [selectedStore.lng, selectedStore.lat],\r\n zoom: 16, \r\n essential: true,\r\n duration: 1500\r\n });\r\n }\r\n }, [selectedMarkerId, mapInstance, markers]);\r\n\r\n \r\n const updateMarkers = useCallback(() => {\r\n if (!mapInstance || !mapLib) return;\r\n\r\n const bounds = mapInstance.getBounds();\r\n const zoom = Math.floor(mapInstance.getZoom());\r\n\r\n \r\n const clusters = clusterIndex.current.getClusters(\r\n [bounds.getWest(), bounds.getSouth(), bounds.getEast(), bounds.getNorth()],\r\n zoom\r\n );\r\n\r\n \r\n markersRef.current.forEach(m => m.remove());\r\n markersRef.current = [];\r\n\r\n clusters.forEach(cluster => {\r\n const [lng, lat] = cluster.geometry.coordinates;\r\n const { cluster: isCluster, point_count: count } = cluster.properties;\r\n\r\n let el: HTMLElement;\r\n\r\n if (isCluster) {\r\n el = createClusterElement(count);\r\n \r\n const newMarker = new mapLib.Marker({ \r\n element: el, \r\n anchor: 'center' \r\n })\r\n .setLngLat([lng, lat])\r\n .addTo(mapInstance);\r\n\r\n el.onclick = (e) => {\r\n e.stopPropagation(); \r\n const expansionZoom = Math.min(\r\n clusterIndex.current.getClusterExpansionZoom(cluster.id as number),\r\n 18\r\n );\r\n mapInstance.easeTo({ \r\n center: [lng, lat], \r\n zoom: expansionZoom,\r\n duration: 800 \r\n });\r\n };\r\n } else {\r\n \r\n const storeData = cluster.properties as Store;\r\n el = createCustomMarkerElement({\r\n marker: storeData,\r\n isSelected: storeData.id === selectedMarkerId,\r\n logoSrc: markerLogoUrl,\r\n });\r\n el.onclick = () => onMarkerClick?.(storeData, 0, mapInstance);\r\n }\r\n\r\n const newMarker = new mapLib.Marker({ element: el, anchor: isCluster ? 'center' : 'bottom' })\r\n .setLngLat([lng, lat])\r\n .addTo(mapInstance);\r\n \r\n markersRef.current.push(newMarker);\r\n });\r\n }, [mapInstance, mapLib, selectedMarkerId, markerLogoUrl, onMarkerClick]);\r\n\r\n \r\n useEffect(() => {\r\n const points: any = markers.map(s => ({\r\n type: 'Feature',\r\n properties: { ...s },\r\n geometry: { type: 'Point', coordinates: [s.lng, s.lat] }\r\n }));\r\n clusterIndex.current.load(points);\r\n updateMarkers();\r\n }, [markers, updateMarkers]);\r\n\r\n \r\n useEffect(() => {\r\n if (!mapInstance) return;\r\n mapInstance.on('moveend', updateMarkers);\r\n return () => { mapInstance.off('moveend', updateMarkers); };\r\n }, [mapInstance, updateMarkers]);\r\n\r\n return (\r\n <div style={{ position: 'relative', ...style }}>\r\n <div \r\n ref={mapContainerRef} \r\n style={{ \r\n width: '100%', \r\n height: '100%',\r\n \r\n backfaceVisibility: 'hidden' \r\n }} \r\n />\r\n </div>\r\n);\r\n};\r\n\r\nexport default Map;","import type { MarkerData } from '../types';\r\n\r\ninterface CreateMarkerOptions {\r\n marker: MarkerData;\r\n isSelected: boolean;\r\n logoSrc?: string;\r\n showPrice?: boolean;\r\n}\r\n\r\nexport function createCustomMarkerElement({\r\n marker,\r\n isSelected,\r\n logoSrc = '',\r\n showPrice = true,\r\n}: CreateMarkerOptions): HTMLElement {\r\n \r\n const container = document.createElement('div');\r\n container.className = 'neshan-marker-container';\r\n\r\n \r\n const body = document.createElement('div');\r\n body.className = `neshan-marker-body ${isSelected ? 'neshan-marker-selected' : ''}`;\r\n \r\n \r\n Object.assign(body.style, {\r\n display: 'flex',\r\n flexDirection: 'column',\r\n alignItems: 'center',\r\n transition: 'transform 0.3s ease',\r\n transform: isSelected ? 'scale(1.2)' : 'scale(1)',\r\n });\r\n\r\n \r\n const iconBox = document.createElement('div');\r\n Object.assign(iconBox.style, {\r\n width: '32px',\r\n height: '32px',\r\n\r\n \r\n border: isSelected ? '3px solid #3b82f6' : '2px solid #ef4444',\r\n boxShadow: '0 4px 8px rgba(0,0,0,0.2)',\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n overflow: 'hidden'\r\n });\r\n\r\n if (logoSrc) {\r\n const img = document.createElement('img');\r\n img.src = logoSrc;\r\n img.style.width = '100%';\r\n img.style.height = '100%';\r\n img.style.objectFit = 'cover';\r\n iconBox.appendChild(img);\r\n } else {\r\n iconBox.style.backgroundColor = '#ef4444';\r\n }\r\n\r\n body.appendChild(iconBox);\r\n\r\n \r\n if (showPrice && (marker.price || marker.name)) {\r\n const label = document.createElement('div');\r\n label.className = 'neshan-marker-label'; // استفاده از کلاس CSS جدید\r\n label.textContent = marker.price ? `${marker.price} ت` : (marker.name ?? '');\r\n body.appendChild(label);\r\n}\r\n\r\n container.appendChild(body);\r\n return container;\r\n}","export function createClusterElement(count: number): HTMLElement {\r\n const container = document.createElement('div');\r\n container.className = 'neshan-cluster-marker';\r\n \r\n \r\n const countSpan = document.createElement('span');\r\n countSpan.className = 'neshan-cluster-count';\r\n countSpan.innerText = count.toString();\r\n \r\n container.appendChild(countSpan);\r\n return container;\r\n}","import React from 'react';\r\nimport { Store } from '../types';\r\n\r\ninterface StoreCardProps {\r\n store: Store;\r\n isSelected: boolean;\r\n onSelect: () => void;\r\n}\r\n\r\nexport const StoreCard: React.FC<StoreCardProps> = ({ store, isSelected, onSelect }) => {\r\n const googleMapUrl = `https://www.google.com/maps/dir/?api=1&destination=${store.lat},${store.lng}`;\r\n\r\n return (\r\n <div \r\n className={`store-card ${isSelected ? 'active' : ''}`}\r\n onClick={onSelect}\r\n >\r\n <div className=\"store-card-header\">\r\n <div>\r\n <h4 className=\"store-name\">{store.name}</h4>\r\n <span className=\"store-city\">{store.city}</span>\r\n </div>\r\n <div className=\"store-price\">{store.price} تومان</div>\r\n </div>\r\n\r\n <p className=\"store-desc\">{store.desc}</p>\r\n\r\n {/* بخش آکاردئونی با CSS Animation */}\r\n <div className=\"store-details-wrapper\">\r\n <div className=\"store-details-content\">\r\n <div className=\"action-buttons\">\r\n {store.phone && (\r\n <a href={`tel:${store.phone}`} className=\"btn btn-call\">\r\n <span>تماس</span>\r\n </a>\r\n )}\r\n {store.whatsapp && (\r\n <a href={`https://wa.me/${store.whatsapp}`} target=\"_blank\" className=\"btn btn-wa\">\r\n <span>واتساپ</span>\r\n </a>\r\n )}\r\n <a href={googleMapUrl} target=\"_blank\" className=\"btn btn-route\">\r\n <span>مسیریابی</span>\r\n </a>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n );\r\n};"],"mappings":";;;AAEA,SAAgB,YAAAA,WAAU,aAAAC,kBAAiB;;;ACA3C,SAAgB,WAAW,QAAQ,UAAU,mBAAmB;;;ACOzD,SAAS,0BAA0B;AAAA,EACxC;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV,YAAY;AACd,GAAqC;AAEnC,QAAM,YAAY,SAAS,cAAc,KAAK;AAC9C,YAAU,YAAY;AAGtB,QAAM,OAAO,SAAS,cAAc,KAAK;AACzC,OAAK,YAAY,sBAAsB,aAAa,2BAA2B,EAAE;AAGjF,SAAO,OAAO,KAAK,OAAO;AAAA,IACxB,SAAS;AAAA,IACT,eAAe;AAAA,IACf,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,WAAW,aAAa,eAAe;AAAA,EACzC,CAAC;AAGD,QAAM,UAAU,SAAS,cAAc,KAAK;AAC5C,SAAO,OAAO,QAAQ,OAAO;AAAA,IAC3B,OAAO;AAAA,IACP,QAAQ;AAAA,IAGR,QAAQ,aAAa,sBAAsB;AAAA,IAC3C,WAAW;AAAA,IACX,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,gBAAgB;AAAA,IAChB,UAAU;AAAA,EACZ,CAAC;AAED,MAAI,SAAS;AACX,UAAM,MAAM,SAAS,cAAc,KAAK;AACxC,QAAI,MAAM;AACV,QAAI,MAAM,QAAQ;AAClB,QAAI,MAAM,SAAS;AACnB,QAAI,MAAM,YAAY;AACtB,YAAQ,YAAY,GAAG;AAAA,EACzB,OAAO;AACL,YAAQ,MAAM,kBAAkB;AAAA,EAClC;AAEA,OAAK,YAAY,OAAO;AAGzB,MAAI,cAAc,OAAO,SAAS,OAAO,OAAO;AAC7C,UAAM,QAAQ,SAAS,cAAc,KAAK;AAC1C,UAAM,YAAY;AAClB,UAAM,cAAc,OAAO,QAAQ,GAAG,OAAO,KAAK,YAAQ,OAAO,QAAQ;AACzE,SAAK,YAAY,KAAK;AAAA,EAC1B;AAEE,YAAU,YAAY,IAAI;AAC1B,SAAO;AACT;;;ACtEO,SAAS,qBAAqB,OAA4B;AAC/D,QAAM,YAAY,SAAS,cAAc,KAAK;AAC9C,YAAU,YAAY;AAGtB,QAAM,YAAY,SAAS,cAAc,MAAM;AAC/C,YAAU,YAAY;AACtB,YAAU,YAAY,MAAM,SAAS;AAErC,YAAU,YAAY,SAAS;AAC/B,SAAO;AACT;;;AFLA,OAAO,kBAAkB;AA+IrB;AA3IJ,IAAM,MAA0B,CAAC;AAAA,EAC/B;AAAA,EACA,UAAU,CAAC;AAAA,EACX,gBAAgB;AAAA,EAChB,mBAAmB;AAAA,EACnB;AAAA,EACA;AAAA,EACA,QAAQ,EAAE,OAAO,QAAQ,QAAQ,OAAO;AAC1C,MAAM;AACJ,QAAM,kBAAkB,OAAuB,IAAI;AACnD,QAAM,CAAC,aAAa,cAAc,IAAI,SAA2B,IAAI;AACrE,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAc,IAAI;AAC9C,QAAM,aAAa,OAAc,CAAC,CAAC;AAGnC,QAAM,eAAe,OAAO,IAAI,aAAa;AAAA,IAC3C,QAAQ;AAAA,IACR,SAAS;AAAA,EACX,CAAC,CAAC;AAGF,YAAU,MAAM;AACd,WAAO,iCAAiC,EAAE,KAAK,CAAC,QAAQ;AACtD,gBAAU,IAAI,WAAW,GAAG;AAAA,IAC9B,CAAC;AAAA,EACH,GAAG,CAAC,CAAC;AAGL,YAAU,MAAM;AACd,QAAI,CAAC,UAAU,CAAC,gBAAgB,WAAW,YAAa;AACxD,UAAM,MAAM,IAAI,OAAO,IAAI;AAAA,MACzB,WAAW,gBAAgB;AAAA,MAC3B,GAAG;AAAA,IACL,CAAC;AACD,QAAI,GAAG,QAAQ,MAAM;AACnB,qBAAe,GAAG;AAClB,kBAAY,GAAG;AAAA,IACjB,CAAC;AACD,WAAO,MAAM;AAAE,UAAI,IAAK,KAAI,OAAO;AAAA,IAAG;AAAA,EACxC,GAAG,CAAC,MAAM,CAAC;AAGX,YAAU,MAAM;AACd,QAAI,CAAC,eAAe,CAAC,iBAAkB;AACvC,UAAM,gBAAgB,QAAQ,KAAK,OAAK,EAAE,OAAO,gBAAgB;AACjE,QAAI,eAAe;AACjB,kBAAY,MAAM;AAAA,QAChB,QAAQ,CAAC,cAAc,KAAK,cAAc,GAAG;AAAA,QAC7C,MAAM;AAAA,QACN,WAAW;AAAA,QACX,UAAU;AAAA,MACZ,CAAC;AAAA,IACH;AAAA,EACF,GAAG,CAAC,kBAAkB,aAAa,OAAO,CAAC;AAG3C,QAAM,gBAAgB,YAAY,MAAM;AACtC,QAAI,CAAC,eAAe,CAAC,OAAQ;AAE7B,UAAM,SAAS,YAAY,UAAU;AACrC,UAAM,OAAO,KAAK,MAAM,YAAY,QAAQ,CAAC;AAG7C,UAAM,WAAW,aAAa,QAAQ;AAAA,MACpC,CAAC,OAAO,QAAQ,GAAG,OAAO,SAAS,GAAG,OAAO,QAAQ,GAAG,OAAO,SAAS,CAAC;AAAA,MACzE;AAAA,IACF;AAGA,eAAW,QAAQ,QAAQ,OAAK,EAAE,OAAO,CAAC;AAC1C,eAAW,UAAU,CAAC;AAEtB,aAAS,QAAQ,aAAW;AAC1B,YAAM,CAAC,KAAK,GAAG,IAAI,QAAQ,SAAS;AACpC,YAAM,EAAE,SAAS,WAAW,aAAa,MAAM,IAAI,QAAQ;AAE3D,UAAI;AAEN,UAAI,WAAW;AACjB,aAAK,qBAAqB,KAAK;AAE/B,cAAMC,aAAY,IAAI,OAAO,OAAO;AAAA,UAClC,SAAS;AAAA,UACT,QAAQ;AAAA,QACV,CAAC,EACE,UAAU,CAAC,KAAK,GAAG,CAAC,EACpB,MAAM,WAAW;AAEpB,WAAG,UAAU,CAAC,MAAM;AAClB,YAAE,gBAAgB;AAClB,gBAAM,gBAAgB,KAAK;AAAA,YACzB,aAAa,QAAQ,wBAAwB,QAAQ,EAAY;AAAA,YACjE;AAAA,UACF;AACA,sBAAY,OAAO;AAAA,YACjB,QAAQ,CAAC,KAAK,GAAG;AAAA,YACjB,MAAM;AAAA,YACN,UAAU;AAAA,UACZ,CAAC;AAAA,QACH;AAAA,MACI,OAAO;AAEL,cAAM,YAAY,QAAQ;AAC1B,aAAK,0BAA0B;AAAA,UAC7B,QAAQ;AAAA,UACR,YAAY,UAAU,OAAO;AAAA,UAC7B,SAAS;AAAA,QACX,CAAC;AACD,WAAG,UAAU,MAAM,gBAAgB,WAAW,GAAG,WAAW;AAAA,MAC9D;AAEA,YAAM,YAAY,IAAI,OAAO,OAAO,EAAE,SAAS,IAAI,QAAQ,YAAY,WAAW,SAAS,CAAC,EACzF,UAAU,CAAC,KAAK,GAAG,CAAC,EACpB,MAAM,WAAW;AAEpB,iBAAW,QAAQ,KAAK,SAAS;AAAA,IACnC,CAAC;AAAA,EACH,GAAG,CAAC,aAAa,QAAQ,kBAAkB,eAAe,aAAa,CAAC;AAGxE,YAAU,MAAM;AACd,UAAM,SAAc,QAAQ,IAAI,QAAM;AAAA,MACpC,MAAM;AAAA,MACN,YAAY,EAAE,GAAG,EAAE;AAAA,MACnB,UAAU,EAAE,MAAM,SAAS,aAAa,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE;AAAA,IACzD,EAAE;AACF,iBAAa,QAAQ,KAAK,MAAM;AAChC,kBAAc;AAAA,EAChB,GAAG,CAAC,SAAS,aAAa,CAAC;AAG3B,YAAU,MAAM;AACd,QAAI,CAAC,YAAa;AAClB,gBAAY,GAAG,WAAW,aAAa;AACvC,WAAO,MAAM;AAAE,kBAAY,IAAI,WAAW,aAAa;AAAA,IAAG;AAAA,EAC5D,GAAG,CAAC,aAAa,aAAa,CAAC;AAEhC,SACC,oBAAC,SAAI,OAAO,EAAE,UAAU,YAAY,GAAG,MAAM,GAC3C;AAAA,IAAC;AAAA;AAAA,MACC,KAAK;AAAA,MACL,OAAO;AAAA,QACL,OAAO;AAAA,QACP,QAAQ;AAAA,QAER,oBAAoB;AAAA,MACtB;AAAA;AAAA,EACF,GACF;AAEF;AAEA,IAAO,cAAQ;;;AGhJP,SACE,OAAAC,MADF;AATD,IAAM,YAAsC,CAAC,EAAE,OAAO,YAAY,SAAS,MAAM;AACtF,QAAM,eAAe,sDAAsD,MAAM,GAAG,IAAI,MAAM,GAAG;AAEjG,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,cAAc,aAAa,WAAW,EAAE;AAAA,MACnD,SAAS;AAAA,MAET;AAAA,6BAAC,SAAI,WAAU,qBACb;AAAA,+BAAC,SACC;AAAA,4BAAAA,KAAC,QAAG,WAAU,cAAc,gBAAM,MAAK;AAAA,YACvC,gBAAAA,KAAC,UAAK,WAAU,cAAc,gBAAM,MAAK;AAAA,aAC3C;AAAA,UACA,qBAAC,SAAI,WAAU,eAAe;AAAA,kBAAM;AAAA,YAAM;AAAA,aAAM;AAAA,WAClD;AAAA,QAEA,gBAAAA,KAAC,OAAE,WAAU,cAAc,gBAAM,MAAK;AAAA,QAGtC,gBAAAA,KAAC,SAAI,WAAU,yBACb,0BAAAA,KAAC,SAAI,WAAU,yBACb,+BAAC,SAAI,WAAU,kBACZ;AAAA,gBAAM,SACL,gBAAAA,KAAC,OAAE,MAAM,OAAO,MAAM,KAAK,IAAI,WAAU,gBACvC,0BAAAA,KAAC,UAAK,sCAAI,GACZ;AAAA,UAED,MAAM,YACL,gBAAAA,KAAC,OAAE,MAAM,iBAAiB,MAAM,QAAQ,IAAI,QAAO,UAAS,WAAU,cACpE,0BAAAA,KAAC,UAAK,kDAAM,GACd;AAAA,UAEF,gBAAAA,KAAC,OAAE,MAAM,cAAc,QAAO,UAAS,WAAU,iBAC/C,0BAAAA,KAAC,UAAK,8DAAQ,GAChB;AAAA,WACF,GACF,GACF;AAAA;AAAA;AAAA,EACF;AAEJ;;;AJlBQ,SACE,OAAAC,MADF,QAAAC,aAAA;AAxBD,IAAM,iBAAqC,CAAC,UAAU;AAC3D,QAAM,CAAC,UAAU,WAAW,IAAIC,UAAS,KAAK;AAE9C,QAAM,CAAC,aAAa,cAAc,IAAIA,UAAc,IAAI;AAExD,EAAAC,WAAU,MAAM;AACd,UAAM,cAAc,MAAM,YAAY,OAAO,aAAa,IAAI;AAC9D,gBAAY;AACZ,WAAO,iBAAiB,UAAU,WAAW;AAC7C,WAAO,MAAM,OAAO,oBAAoB,UAAU,WAAW;AAAA,EAC/D,GAAG,CAAC,CAAC;AAEL,QAAM,oBAAoB,CAAC,OAAc,UAAkB;AAEzD,UAAM,gBAAgB,OAAO,OAAO,WAAW;AAAA,EACjD;AAGA,QAAM,cAAc,MAAM,WAAW,CAAC;AAEtC,SACE,gBAAAF,MAAC,SAAI,WAAW,mBAAmB,WAAW,cAAc,YAAY,IAErE;AAAA,KAAC,YACA,gBAAAA,MAAC,WAAM,WAAU,WACf;AAAA,sBAAAD,KAAC,YAAO,WAAU,kBAAiB,mGAAe;AAAA,MAClD,gBAAAA,KAAC,SAAI,WAAU,gBAEZ,sBAAY,IAAI,CAAC,OAAO,QACvB,gBAAAA;AAAA,QAAC;AAAA;AAAA,UAEC;AAAA,UACA,YAAY,MAAM,qBAAqB,MAAM;AAAA,UAC7C,UAAU,MAAM,kBAAkB,OAAO,GAAG;AAAA;AAAA,QAHvC,MAAM;AAAA,MAIb,CACD,GACH;AAAA,OACF;AAAA,IAGF,gBAAAC,MAAC,UAAK,WAAU,iBAEd;AAAA,sBAAAD;AAAA,QAAC;AAAA;AAAA,UACE,GAAG;AAAA,UACJ,WAAW,CAAC,QAAQ;AAClB,2BAAe,GAAG;AAClB,kBAAM,YAAY,GAAG;AAAA,UACvB;AAAA;AAAA,MACF;AAAA,MAEC,MAAM,eACL,gBAAAA,KAAC,SAAI,WAAU,iBACZ,0BAAAA,KAAC,UAAM,gBAAM,aAAY,GAC5B;AAAA,MAGD,YACC,gBAAAA,KAAC,SAAI,WAAU,iBACZ,sBAAY,IAAI,CAAC,OAAO,QACvB,gBAAAA;AAAA,QAAC;AAAA;AAAA,UAEC;AAAA,UACA,YAAY,MAAM,qBAAqB,MAAM;AAAA,UAC7C,UAAU,MAAM,kBAAkB,OAAO,GAAG;AAAA;AAAA,QAHvC,MAAM;AAAA,MAIb,CACD,GACH;AAAA,OAEJ;AAAA,KACF;AAEJ;","names":["useState","useEffect","newMarker","jsx","jsx","jsxs","useState","useEffect"]}
package/dist/styles.css CHANGED
@@ -177,21 +177,23 @@
177
177
  background: #ffffff;
178
178
  border: 3px solid #2563eb;
179
179
  border-radius: 50%;
180
- width: 46px;
181
- height: 46px;
180
+ width: 36px;
181
+ height: 36px;
182
182
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.25);
183
183
  cursor: pointer;
184
- transition: transform 0.2s ease;
184
+ transition: none;
185
185
  }
186
186
 
187
187
  .neshan-cluster-marker:hover {
188
- transform: scale(1.1);
188
+ border-color: #1d4ed8;
189
+ box-shadow: 0 6px 25px rgba(0, 0, 0, 0.3);
189
190
  }
190
191
 
191
192
  .neshan-cluster-count {
192
193
  color: #2563eb;
193
194
  font-weight: 900;
194
195
  font-size: 16px;
196
+ pointer-events: none; /* جلوگیری از تداخل کلیک با عدد */
195
197
  }
196
198
 
197
199
 
@@ -1 +1 @@
1
- {"version":3,"file":"createClusterElement.d.ts","sourceRoot":"","sources":["../../src/utils/createClusterElement.ts"],"names":[],"mappings":"AAAA,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,CAK/D"}
1
+ {"version":3,"file":"createClusterElement.d.ts","sourceRoot":"","sources":["../../src/utils/createClusterElement.ts"],"names":[],"mappings":"AAAA,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,CAW/D"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mehdi-akbari-map",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "A professional Map",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",