mehdi-akbari-map 1.0.5 → 1.0.7

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;AAK3D,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;AAM3D,QAAA,MAAM,GAAG,EAAE,KAAK,CAAC,EAAE,CAAC,QAAQ,CA2J3B,CAAC;AAEF,eAAe,GAAG,CAAC"}
package/dist/index.cjs CHANGED
@@ -119,14 +119,19 @@ var Map = ({
119
119
  const [mapInstance, setMapInstance] = (0, import_react.useState)(null);
120
120
  const [mapLib, setMapLib] = (0, import_react.useState)(null);
121
121
  const markersRef = (0, import_react.useRef)([]);
122
+ const isRemoving = (0, import_react.useRef)(false);
122
123
  const clusterIndex = (0, import_react.useRef)(new import_supercluster.default({
123
124
  radius: 60,
124
125
  maxZoom: 16
125
126
  }));
126
127
  (0, import_react.useEffect)(() => {
128
+ let mounted = true;
127
129
  import("@neshan-maps-platform/mapbox-gl").then((mod) => {
128
- setMapLib(mod.default || mod);
130
+ if (mounted) setMapLib(mod.default || mod);
129
131
  });
132
+ return () => {
133
+ mounted = false;
134
+ };
130
135
  }, []);
131
136
  (0, import_react.useEffect)(() => {
132
137
  if (!mapLib || !mapContainerRef.current || mapInstance) return;
@@ -135,70 +140,79 @@ var Map = ({
135
140
  ...options
136
141
  });
137
142
  map.on("load", () => {
138
- setMapInstance(map);
139
- onMapLoad?.(map);
143
+ if (!isRemoving.current) {
144
+ setMapInstance(map);
145
+ onMapLoad?.(map);
146
+ }
140
147
  });
141
148
  return () => {
142
- if (map) map.remove();
149
+ if (map) {
150
+ isRemoving.current = true;
151
+ setTimeout(() => {
152
+ try {
153
+ map.remove();
154
+ } catch (e) {
155
+ console.warn("Mapbox remove error ignored:", e);
156
+ }
157
+ }, 0);
158
+ }
143
159
  };
144
- }, [mapLib, options, onMapLoad]);
160
+ }, [mapLib]);
145
161
  (0, import_react.useEffect)(() => {
146
- if (!mapInstance || !selectedMarkerId) return;
162
+ if (!mapInstance || !selectedMarkerId || isRemoving.current) return;
147
163
  const selectedStore = markers.find((m) => m.id === selectedMarkerId);
148
164
  if (selectedStore) {
149
165
  mapInstance.flyTo({
150
166
  center: [selectedStore.lng, selectedStore.lat],
151
167
  zoom: 16,
152
168
  essential: true,
153
- duration: 1500
169
+ duration: 1200
154
170
  });
155
171
  }
156
172
  }, [selectedMarkerId, mapInstance, markers]);
157
173
  const updateMarkers = (0, import_react.useCallback)(() => {
158
- if (!mapInstance || !mapLib) return;
159
- const bounds = mapInstance.getBounds();
160
- const zoom = Math.floor(mapInstance.getZoom());
161
- const clusters = clusterIndex.current.getClusters(
162
- [bounds.getWest(), bounds.getSouth(), bounds.getEast(), bounds.getNorth()],
163
- zoom
164
- );
165
- markersRef.current.forEach((m) => m.remove());
166
- markersRef.current = [];
167
- clusters.forEach((cluster) => {
168
- const [lng, lat] = cluster.geometry.coordinates;
169
- const { cluster: isCluster, point_count: count } = cluster.properties;
170
- let el;
171
- if (isCluster) {
172
- el = createClusterElement(count);
173
- el.onclick = (e) => {
174
- e.stopPropagation();
175
- const expansionZoom = Math.min(
176
- clusterIndex.current.getClusterExpansionZoom(cluster.id),
177
- 18
178
- );
179
- mapInstance.easeTo({
180
- center: [lng, lat],
181
- zoom: expansionZoom,
182
- duration: 800
174
+ if (!mapInstance || !mapLib || isRemoving.current) return;
175
+ try {
176
+ const bounds = mapInstance.getBounds();
177
+ const zoom = Math.floor(mapInstance.getZoom());
178
+ const clusters = clusterIndex.current.getClusters(
179
+ [bounds.getWest(), bounds.getSouth(), bounds.getEast(), bounds.getNorth()],
180
+ zoom
181
+ );
182
+ markersRef.current.forEach((m) => m.remove());
183
+ markersRef.current = [];
184
+ clusters.forEach((cluster) => {
185
+ const [lng, lat] = cluster.geometry.coordinates;
186
+ const { cluster: isCluster, point_count: count } = cluster.properties;
187
+ let el;
188
+ if (isCluster) {
189
+ el = createClusterElement(count);
190
+ el.onclick = (e) => {
191
+ e.stopPropagation();
192
+ const expansionZoom = Math.min(
193
+ clusterIndex.current.getClusterExpansionZoom(cluster.id),
194
+ 18
195
+ );
196
+ mapInstance.easeTo({ center: [lng, lat], zoom: expansionZoom });
197
+ };
198
+ } else {
199
+ const storeData = cluster.properties;
200
+ el = createCustomMarkerElement({
201
+ marker: storeData,
202
+ isSelected: storeData.id === selectedMarkerId,
203
+ logoSrc: markerLogoUrl
183
204
  });
184
- };
185
- } else {
186
- const storeData = cluster.properties;
187
- el = createCustomMarkerElement({
188
- marker: storeData,
189
- isSelected: storeData.id === selectedMarkerId,
190
- logoSrc: markerLogoUrl
191
- });
192
- el.onclick = () => onMarkerClick?.(storeData, 0, mapInstance);
193
- }
194
- const newMarker = new mapLib.Marker({
195
- element: el,
196
- anchor: isCluster ? "center" : "bottom"
197
- }).setLngLat([lng, lat]).addTo(mapInstance);
198
- markersRef.current.push(newMarker);
199
- });
205
+ el.onclick = () => onMarkerClick?.(storeData, 0, mapInstance);
206
+ }
207
+ const newMarker = new mapLib.Marker({ element: el, anchor: isCluster ? "center" : "bottom" }).setLngLat([lng, lat]).addTo(mapInstance);
208
+ markersRef.current.push(newMarker);
209
+ });
210
+ } catch (err) {
211
+ console.error("Update markers failed:", err);
212
+ }
200
213
  }, [mapInstance, mapLib, selectedMarkerId, markerLogoUrl, onMarkerClick]);
201
214
  (0, import_react.useEffect)(() => {
215
+ if (isRemoving.current) return;
202
216
  const points = markers.map((s) => ({
203
217
  type: "Feature",
204
218
  properties: { ...s },
@@ -208,13 +222,13 @@ var Map = ({
208
222
  updateMarkers();
209
223
  }, [markers, updateMarkers]);
210
224
  (0, import_react.useEffect)(() => {
211
- if (!mapInstance) return;
225
+ if (!mapInstance || isRemoving.current) return;
212
226
  mapInstance.on("moveend", updateMarkers);
213
227
  return () => {
214
228
  mapInstance.off("moveend", updateMarkers);
215
229
  };
216
230
  }, [mapInstance, updateMarkers]);
217
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "relative", ...style }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
231
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "relative", overflow: "hidden", ...style }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
218
232
  "div",
219
233
  {
220
234
  ref: mapContainerRef,
@@ -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, useRef, useCallback } 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 const [mapInstance, setMapInstance] = useState<any>(null);\r\n const sliderRef = useRef<HTMLDivElement>(null);\r\n\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 \r\n useEffect(() => {\r\n if (isMobile && props.selectedMarkerId && sliderRef.current) {\r\n const selectedCard = sliderRef.current.querySelector(`[data-id=\"${props.selectedMarkerId}\"]`);\r\n if (selectedCard) {\r\n selectedCard.scrollIntoView({\r\n behavior: 'smooth',\r\n inline: 'center',\r\n block: 'nearest'\r\n });\r\n }\r\n }\r\n }, [props.selectedMarkerId, isMobile]);\r\n\r\n \r\n const handleStoreSelect = useCallback((store: Store, index: number) => {\r\n \r\n if (!mapInstance) {\r\n console.warn(\"نقشه هنوز آماده نشده است.\");\r\n return;\r\n }\r\n \r\n \r\n if (props.onMarkerClick) {\r\n props.onMarkerClick(store, index, mapInstance);\r\n }\r\n }, [mapInstance, props]);\r\n\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 {/* سایدبار دسکتاپ */}\r\n {!isMobile && (\r\n <aside className=\"sidebar\">\r\n <header className=\"sidebar-header\">لیست فروشگاه‌ها</header>\r\n <div className=\"sidebar-list\">\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 {/* بخش نقشه */}\r\n <main className=\"map-container\">\r\n <Map \r\n {...props} \r\n onMapLoad={(map) => {\r\n \r\n if (map) {\r\n setMapInstance(map);\r\n if (props.onMapLoad) {\r\n props.onMapLoad(map);\r\n }\r\n }\r\n }} \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 {/* اسلایدر موبایل */}\r\n {isMobile && (\r\n <div className=\"mobile-slider\" ref={sliderRef}>\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\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 const clusterIndex = useRef(new Supercluster({\r\n radius: 60,\r\n maxZoom: 16\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 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, options, onMapLoad]);\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 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 const clusters = clusterIndex.current.getClusters(\r\n [bounds.getWest(), bounds.getSouth(), bounds.getEast(), bounds.getNorth()],\r\n zoom\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 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 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({ \r\n element: el, \r\n anchor: isCluster ? 'center' : 'bottom' \r\n })\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 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 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={{ width: '100%', height: '100%', backfaceVisibility: 'hidden' }} \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: '36px',\r\n height: '36px',\r\n borderRadius: '50%',\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 data-id={store.id} // برای پیدا کردن کارت در اسکرول موبایل\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\" onClick={(e) => e.stopPropagation()}>\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\" onClick={(e) => e.stopPropagation()}>\r\n <span>واتساپ</span>\r\n </a>\r\n )}\r\n <a href={googleMapUrl} target=\"_blank\" className=\"btn btn-route\" onClick={(e) => e.stopPropagation()}>\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/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, useRef, useCallback } 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 const [mapInstance, setMapInstance] = useState<any>(null);\r\n const sliderRef = useRef<HTMLDivElement>(null);\r\n\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 \r\n useEffect(() => {\r\n if (isMobile && props.selectedMarkerId && sliderRef.current) {\r\n const selectedCard = sliderRef.current.querySelector(`[data-id=\"${props.selectedMarkerId}\"]`);\r\n if (selectedCard) {\r\n selectedCard.scrollIntoView({\r\n behavior: 'smooth',\r\n inline: 'center',\r\n block: 'nearest'\r\n });\r\n }\r\n }\r\n }, [props.selectedMarkerId, isMobile]);\r\n\r\n \r\n const handleStoreSelect = useCallback((store: Store, index: number) => {\r\n \r\n if (!mapInstance) {\r\n console.warn(\"نقشه هنوز آماده نشده است.\");\r\n return;\r\n }\r\n \r\n \r\n if (props.onMarkerClick) {\r\n props.onMarkerClick(store, index, mapInstance);\r\n }\r\n }, [mapInstance, props]);\r\n\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 {/* سایدبار دسکتاپ */}\r\n {!isMobile && (\r\n <aside className=\"sidebar\">\r\n <header className=\"sidebar-header\">لیست فروشگاه‌ها</header>\r\n <div className=\"sidebar-list\">\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 {/* بخش نقشه */}\r\n <main className=\"map-container\">\r\n <Map \r\n {...props} \r\n onMapLoad={(map) => {\r\n \r\n if (map) {\r\n setMapInstance(map);\r\n if (props.onMapLoad) {\r\n props.onMapLoad(map);\r\n }\r\n }\r\n }} \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 {/* اسلایدر موبایل */}\r\n {isMobile && (\r\n <div className=\"mobile-slider\" ref={sliderRef}>\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\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 const isRemoving = useRef(false); \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 let mounted = true;\r\n import('@neshan-maps-platform/mapbox-gl').then((mod) => {\r\n if (mounted) setMapLib(mod.default || mod);\r\n });\r\n return () => { mounted = false; };\r\n }, []);\r\n\r\n \r\n useEffect(() => {\r\n if (!mapLib || !mapContainerRef.current || mapInstance) return;\r\n\r\n const map = new mapLib.Map({\r\n container: mapContainerRef.current,\r\n ...options,\r\n });\r\n\r\n map.on('load', () => {\r\n if (!isRemoving.current) {\r\n setMapInstance(map);\r\n onMapLoad?.(map);\r\n }\r\n });\r\n\r\n return () => {\r\n if (map) {\r\n isRemoving.current = true;\r\n \r\n setTimeout(() => {\r\n try {\r\n map.remove();\r\n } catch (e) {\r\n console.warn(\"Mapbox remove error ignored:\", e);\r\n }\r\n }, 0);\r\n }\r\n };\r\n \r\n }, [mapLib]); \r\n\r\n \r\n useEffect(() => {\r\n if (!mapInstance || !selectedMarkerId || isRemoving.current) 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: 1200\r\n });\r\n }\r\n }, [selectedMarkerId, mapInstance, markers]);\r\n\r\n \r\n const updateMarkers = useCallback(() => {\r\n if (!mapInstance || !mapLib || isRemoving.current) return;\r\n\r\n try {\r\n const bounds = mapInstance.getBounds();\r\n const zoom = Math.floor(mapInstance.getZoom());\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 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 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({ center: [lng, lat], zoom: expansionZoom });\r\n };\r\n } else {\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 } catch (err) {\r\n console.error(\"Update markers failed:\", err);\r\n }\r\n }, [mapInstance, mapLib, selectedMarkerId, markerLogoUrl, onMarkerClick]);\r\n\r\n \r\n useEffect(() => {\r\n if (isRemoving.current) return;\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 || isRemoving.current) 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', overflow: 'hidden', ...style }}>\r\n <div \r\n ref={mapContainerRef} \r\n style={{ width: '100%', height: '100%', backfaceVisibility: 'hidden' }} \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: '36px',\r\n height: '36px',\r\n borderRadius: '50%',\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 data-id={store.id} // برای پیدا کردن کارت در اسکرول موبایل\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\" onClick={(e) => e.stopPropagation()}>\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\" onClick={(e) => e.stopPropagation()}>\r\n <span>واتساپ</span>\r\n </a>\r\n )}\r\n <a href={googleMapUrl} target=\"_blank\" className=\"btn btn-route\" onClick={(e) => e.stopPropagation()}>\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"]}
package/dist/index.js CHANGED
@@ -82,14 +82,19 @@ var Map = ({
82
82
  const [mapInstance, setMapInstance] = useState(null);
83
83
  const [mapLib, setMapLib] = useState(null);
84
84
  const markersRef = useRef([]);
85
+ const isRemoving = useRef(false);
85
86
  const clusterIndex = useRef(new Supercluster({
86
87
  radius: 60,
87
88
  maxZoom: 16
88
89
  }));
89
90
  useEffect(() => {
91
+ let mounted = true;
90
92
  import("@neshan-maps-platform/mapbox-gl").then((mod) => {
91
- setMapLib(mod.default || mod);
93
+ if (mounted) setMapLib(mod.default || mod);
92
94
  });
95
+ return () => {
96
+ mounted = false;
97
+ };
93
98
  }, []);
94
99
  useEffect(() => {
95
100
  if (!mapLib || !mapContainerRef.current || mapInstance) return;
@@ -98,70 +103,79 @@ var Map = ({
98
103
  ...options
99
104
  });
100
105
  map.on("load", () => {
101
- setMapInstance(map);
102
- onMapLoad?.(map);
106
+ if (!isRemoving.current) {
107
+ setMapInstance(map);
108
+ onMapLoad?.(map);
109
+ }
103
110
  });
104
111
  return () => {
105
- if (map) map.remove();
112
+ if (map) {
113
+ isRemoving.current = true;
114
+ setTimeout(() => {
115
+ try {
116
+ map.remove();
117
+ } catch (e) {
118
+ console.warn("Mapbox remove error ignored:", e);
119
+ }
120
+ }, 0);
121
+ }
106
122
  };
107
- }, [mapLib, options, onMapLoad]);
123
+ }, [mapLib]);
108
124
  useEffect(() => {
109
- if (!mapInstance || !selectedMarkerId) return;
125
+ if (!mapInstance || !selectedMarkerId || isRemoving.current) return;
110
126
  const selectedStore = markers.find((m) => m.id === selectedMarkerId);
111
127
  if (selectedStore) {
112
128
  mapInstance.flyTo({
113
129
  center: [selectedStore.lng, selectedStore.lat],
114
130
  zoom: 16,
115
131
  essential: true,
116
- duration: 1500
132
+ duration: 1200
117
133
  });
118
134
  }
119
135
  }, [selectedMarkerId, mapInstance, markers]);
120
136
  const updateMarkers = useCallback(() => {
121
- if (!mapInstance || !mapLib) return;
122
- const bounds = mapInstance.getBounds();
123
- const zoom = Math.floor(mapInstance.getZoom());
124
- const clusters = clusterIndex.current.getClusters(
125
- [bounds.getWest(), bounds.getSouth(), bounds.getEast(), bounds.getNorth()],
126
- zoom
127
- );
128
- markersRef.current.forEach((m) => m.remove());
129
- markersRef.current = [];
130
- clusters.forEach((cluster) => {
131
- const [lng, lat] = cluster.geometry.coordinates;
132
- const { cluster: isCluster, point_count: count } = cluster.properties;
133
- let el;
134
- if (isCluster) {
135
- el = createClusterElement(count);
136
- el.onclick = (e) => {
137
- e.stopPropagation();
138
- const expansionZoom = Math.min(
139
- clusterIndex.current.getClusterExpansionZoom(cluster.id),
140
- 18
141
- );
142
- mapInstance.easeTo({
143
- center: [lng, lat],
144
- zoom: expansionZoom,
145
- duration: 800
137
+ if (!mapInstance || !mapLib || isRemoving.current) return;
138
+ try {
139
+ const bounds = mapInstance.getBounds();
140
+ const zoom = Math.floor(mapInstance.getZoom());
141
+ const clusters = clusterIndex.current.getClusters(
142
+ [bounds.getWest(), bounds.getSouth(), bounds.getEast(), bounds.getNorth()],
143
+ zoom
144
+ );
145
+ markersRef.current.forEach((m) => m.remove());
146
+ markersRef.current = [];
147
+ clusters.forEach((cluster) => {
148
+ const [lng, lat] = cluster.geometry.coordinates;
149
+ const { cluster: isCluster, point_count: count } = cluster.properties;
150
+ let el;
151
+ if (isCluster) {
152
+ el = createClusterElement(count);
153
+ el.onclick = (e) => {
154
+ e.stopPropagation();
155
+ const expansionZoom = Math.min(
156
+ clusterIndex.current.getClusterExpansionZoom(cluster.id),
157
+ 18
158
+ );
159
+ mapInstance.easeTo({ center: [lng, lat], zoom: expansionZoom });
160
+ };
161
+ } else {
162
+ const storeData = cluster.properties;
163
+ el = createCustomMarkerElement({
164
+ marker: storeData,
165
+ isSelected: storeData.id === selectedMarkerId,
166
+ logoSrc: markerLogoUrl
146
167
  });
147
- };
148
- } else {
149
- const storeData = cluster.properties;
150
- el = createCustomMarkerElement({
151
- marker: storeData,
152
- isSelected: storeData.id === selectedMarkerId,
153
- logoSrc: markerLogoUrl
154
- });
155
- el.onclick = () => onMarkerClick?.(storeData, 0, mapInstance);
156
- }
157
- const newMarker = new mapLib.Marker({
158
- element: el,
159
- anchor: isCluster ? "center" : "bottom"
160
- }).setLngLat([lng, lat]).addTo(mapInstance);
161
- markersRef.current.push(newMarker);
162
- });
168
+ el.onclick = () => onMarkerClick?.(storeData, 0, mapInstance);
169
+ }
170
+ const newMarker = new mapLib.Marker({ element: el, anchor: isCluster ? "center" : "bottom" }).setLngLat([lng, lat]).addTo(mapInstance);
171
+ markersRef.current.push(newMarker);
172
+ });
173
+ } catch (err) {
174
+ console.error("Update markers failed:", err);
175
+ }
163
176
  }, [mapInstance, mapLib, selectedMarkerId, markerLogoUrl, onMarkerClick]);
164
177
  useEffect(() => {
178
+ if (isRemoving.current) return;
165
179
  const points = markers.map((s) => ({
166
180
  type: "Feature",
167
181
  properties: { ...s },
@@ -171,13 +185,13 @@ var Map = ({
171
185
  updateMarkers();
172
186
  }, [markers, updateMarkers]);
173
187
  useEffect(() => {
174
- if (!mapInstance) return;
188
+ if (!mapInstance || isRemoving.current) return;
175
189
  mapInstance.on("moveend", updateMarkers);
176
190
  return () => {
177
191
  mapInstance.off("moveend", updateMarkers);
178
192
  };
179
193
  }, [mapInstance, updateMarkers]);
180
- return /* @__PURE__ */ jsx("div", { style: { position: "relative", ...style }, children: /* @__PURE__ */ jsx(
194
+ return /* @__PURE__ */ jsx("div", { style: { position: "relative", overflow: "hidden", ...style }, children: /* @__PURE__ */ jsx(
181
195
  "div",
182
196
  {
183
197
  ref: mapContainerRef,
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, useRef, useCallback } 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 const [mapInstance, setMapInstance] = useState<any>(null);\r\n const sliderRef = useRef<HTMLDivElement>(null);\r\n\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 \r\n useEffect(() => {\r\n if (isMobile && props.selectedMarkerId && sliderRef.current) {\r\n const selectedCard = sliderRef.current.querySelector(`[data-id=\"${props.selectedMarkerId}\"]`);\r\n if (selectedCard) {\r\n selectedCard.scrollIntoView({\r\n behavior: 'smooth',\r\n inline: 'center',\r\n block: 'nearest'\r\n });\r\n }\r\n }\r\n }, [props.selectedMarkerId, isMobile]);\r\n\r\n \r\n const handleStoreSelect = useCallback((store: Store, index: number) => {\r\n \r\n if (!mapInstance) {\r\n console.warn(\"نقشه هنوز آماده نشده است.\");\r\n return;\r\n }\r\n \r\n \r\n if (props.onMarkerClick) {\r\n props.onMarkerClick(store, index, mapInstance);\r\n }\r\n }, [mapInstance, props]);\r\n\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 {/* سایدبار دسکتاپ */}\r\n {!isMobile && (\r\n <aside className=\"sidebar\">\r\n <header className=\"sidebar-header\">لیست فروشگاه‌ها</header>\r\n <div className=\"sidebar-list\">\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 {/* بخش نقشه */}\r\n <main className=\"map-container\">\r\n <Map \r\n {...props} \r\n onMapLoad={(map) => {\r\n \r\n if (map) {\r\n setMapInstance(map);\r\n if (props.onMapLoad) {\r\n props.onMapLoad(map);\r\n }\r\n }\r\n }} \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 {/* اسلایدر موبایل */}\r\n {isMobile && (\r\n <div className=\"mobile-slider\" ref={sliderRef}>\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\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 const clusterIndex = useRef(new Supercluster({\r\n radius: 60,\r\n maxZoom: 16\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 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, options, onMapLoad]);\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 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 const clusters = clusterIndex.current.getClusters(\r\n [bounds.getWest(), bounds.getSouth(), bounds.getEast(), bounds.getNorth()],\r\n zoom\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 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 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({ \r\n element: el, \r\n anchor: isCluster ? 'center' : 'bottom' \r\n })\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 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 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={{ width: '100%', height: '100%', backfaceVisibility: 'hidden' }} \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: '36px',\r\n height: '36px',\r\n borderRadius: '50%',\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 data-id={store.id} // برای پیدا کردن کارت در اسکرول موبایل\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\" onClick={(e) => e.stopPropagation()}>\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\" onClick={(e) => e.stopPropagation()}>\r\n <span>واتساپ</span>\r\n </a>\r\n )}\r\n <a href={googleMapUrl} target=\"_blank\" className=\"btn btn-route\" onClick={(e) => e.stopPropagation()}>\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","useRef","useCallback","jsx","jsx","jsxs","useState","useRef","useEffect","useCallback"]}
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, useRef, useCallback } 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 const [mapInstance, setMapInstance] = useState<any>(null);\r\n const sliderRef = useRef<HTMLDivElement>(null);\r\n\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 \r\n useEffect(() => {\r\n if (isMobile && props.selectedMarkerId && sliderRef.current) {\r\n const selectedCard = sliderRef.current.querySelector(`[data-id=\"${props.selectedMarkerId}\"]`);\r\n if (selectedCard) {\r\n selectedCard.scrollIntoView({\r\n behavior: 'smooth',\r\n inline: 'center',\r\n block: 'nearest'\r\n });\r\n }\r\n }\r\n }, [props.selectedMarkerId, isMobile]);\r\n\r\n \r\n const handleStoreSelect = useCallback((store: Store, index: number) => {\r\n \r\n if (!mapInstance) {\r\n console.warn(\"نقشه هنوز آماده نشده است.\");\r\n return;\r\n }\r\n \r\n \r\n if (props.onMarkerClick) {\r\n props.onMarkerClick(store, index, mapInstance);\r\n }\r\n }, [mapInstance, props]);\r\n\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 {/* سایدبار دسکتاپ */}\r\n {!isMobile && (\r\n <aside className=\"sidebar\">\r\n <header className=\"sidebar-header\">لیست فروشگاه‌ها</header>\r\n <div className=\"sidebar-list\">\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 {/* بخش نقشه */}\r\n <main className=\"map-container\">\r\n <Map \r\n {...props} \r\n onMapLoad={(map) => {\r\n \r\n if (map) {\r\n setMapInstance(map);\r\n if (props.onMapLoad) {\r\n props.onMapLoad(map);\r\n }\r\n }\r\n }} \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 {/* اسلایدر موبایل */}\r\n {isMobile && (\r\n <div className=\"mobile-slider\" ref={sliderRef}>\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\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 const isRemoving = useRef(false); \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 let mounted = true;\r\n import('@neshan-maps-platform/mapbox-gl').then((mod) => {\r\n if (mounted) setMapLib(mod.default || mod);\r\n });\r\n return () => { mounted = false; };\r\n }, []);\r\n\r\n \r\n useEffect(() => {\r\n if (!mapLib || !mapContainerRef.current || mapInstance) return;\r\n\r\n const map = new mapLib.Map({\r\n container: mapContainerRef.current,\r\n ...options,\r\n });\r\n\r\n map.on('load', () => {\r\n if (!isRemoving.current) {\r\n setMapInstance(map);\r\n onMapLoad?.(map);\r\n }\r\n });\r\n\r\n return () => {\r\n if (map) {\r\n isRemoving.current = true;\r\n \r\n setTimeout(() => {\r\n try {\r\n map.remove();\r\n } catch (e) {\r\n console.warn(\"Mapbox remove error ignored:\", e);\r\n }\r\n }, 0);\r\n }\r\n };\r\n \r\n }, [mapLib]); \r\n\r\n \r\n useEffect(() => {\r\n if (!mapInstance || !selectedMarkerId || isRemoving.current) 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: 1200\r\n });\r\n }\r\n }, [selectedMarkerId, mapInstance, markers]);\r\n\r\n \r\n const updateMarkers = useCallback(() => {\r\n if (!mapInstance || !mapLib || isRemoving.current) return;\r\n\r\n try {\r\n const bounds = mapInstance.getBounds();\r\n const zoom = Math.floor(mapInstance.getZoom());\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 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 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({ center: [lng, lat], zoom: expansionZoom });\r\n };\r\n } else {\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 } catch (err) {\r\n console.error(\"Update markers failed:\", err);\r\n }\r\n }, [mapInstance, mapLib, selectedMarkerId, markerLogoUrl, onMarkerClick]);\r\n\r\n \r\n useEffect(() => {\r\n if (isRemoving.current) return;\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 || isRemoving.current) 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', overflow: 'hidden', ...style }}>\r\n <div \r\n ref={mapContainerRef} \r\n style={{ width: '100%', height: '100%', backfaceVisibility: 'hidden' }} \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: '36px',\r\n height: '36px',\r\n borderRadius: '50%',\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 data-id={store.id} // برای پیدا کردن کارت در اسکرول موبایل\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\" onClick={(e) => e.stopPropagation()}>\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\" onClick={(e) => e.stopPropagation()}>\r\n <span>واتساپ</span>\r\n </a>\r\n )}\r\n <a href={googleMapUrl} target=\"_blank\" className=\"btn btn-route\" onClick={(e) => e.stopPropagation()}>\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","useRef","useCallback","jsx","jsx","jsxs","useState","useRef","useEffect","useCallback"]}
package/dist/react.cjs CHANGED
@@ -119,14 +119,19 @@ var Map = ({
119
119
  const [mapInstance, setMapInstance] = (0, import_react.useState)(null);
120
120
  const [mapLib, setMapLib] = (0, import_react.useState)(null);
121
121
  const markersRef = (0, import_react.useRef)([]);
122
+ const isRemoving = (0, import_react.useRef)(false);
122
123
  const clusterIndex = (0, import_react.useRef)(new import_supercluster.default({
123
124
  radius: 60,
124
125
  maxZoom: 16
125
126
  }));
126
127
  (0, import_react.useEffect)(() => {
128
+ let mounted = true;
127
129
  import("@neshan-maps-platform/mapbox-gl").then((mod) => {
128
- setMapLib(mod.default || mod);
130
+ if (mounted) setMapLib(mod.default || mod);
129
131
  });
132
+ return () => {
133
+ mounted = false;
134
+ };
130
135
  }, []);
131
136
  (0, import_react.useEffect)(() => {
132
137
  if (!mapLib || !mapContainerRef.current || mapInstance) return;
@@ -135,70 +140,79 @@ var Map = ({
135
140
  ...options
136
141
  });
137
142
  map.on("load", () => {
138
- setMapInstance(map);
139
- onMapLoad?.(map);
143
+ if (!isRemoving.current) {
144
+ setMapInstance(map);
145
+ onMapLoad?.(map);
146
+ }
140
147
  });
141
148
  return () => {
142
- if (map) map.remove();
149
+ if (map) {
150
+ isRemoving.current = true;
151
+ setTimeout(() => {
152
+ try {
153
+ map.remove();
154
+ } catch (e) {
155
+ console.warn("Mapbox remove error ignored:", e);
156
+ }
157
+ }, 0);
158
+ }
143
159
  };
144
- }, [mapLib, options, onMapLoad]);
160
+ }, [mapLib]);
145
161
  (0, import_react.useEffect)(() => {
146
- if (!mapInstance || !selectedMarkerId) return;
162
+ if (!mapInstance || !selectedMarkerId || isRemoving.current) return;
147
163
  const selectedStore = markers.find((m) => m.id === selectedMarkerId);
148
164
  if (selectedStore) {
149
165
  mapInstance.flyTo({
150
166
  center: [selectedStore.lng, selectedStore.lat],
151
167
  zoom: 16,
152
168
  essential: true,
153
- duration: 1500
169
+ duration: 1200
154
170
  });
155
171
  }
156
172
  }, [selectedMarkerId, mapInstance, markers]);
157
173
  const updateMarkers = (0, import_react.useCallback)(() => {
158
- if (!mapInstance || !mapLib) return;
159
- const bounds = mapInstance.getBounds();
160
- const zoom = Math.floor(mapInstance.getZoom());
161
- const clusters = clusterIndex.current.getClusters(
162
- [bounds.getWest(), bounds.getSouth(), bounds.getEast(), bounds.getNorth()],
163
- zoom
164
- );
165
- markersRef.current.forEach((m) => m.remove());
166
- markersRef.current = [];
167
- clusters.forEach((cluster) => {
168
- const [lng, lat] = cluster.geometry.coordinates;
169
- const { cluster: isCluster, point_count: count } = cluster.properties;
170
- let el;
171
- if (isCluster) {
172
- el = createClusterElement(count);
173
- el.onclick = (e) => {
174
- e.stopPropagation();
175
- const expansionZoom = Math.min(
176
- clusterIndex.current.getClusterExpansionZoom(cluster.id),
177
- 18
178
- );
179
- mapInstance.easeTo({
180
- center: [lng, lat],
181
- zoom: expansionZoom,
182
- duration: 800
174
+ if (!mapInstance || !mapLib || isRemoving.current) return;
175
+ try {
176
+ const bounds = mapInstance.getBounds();
177
+ const zoom = Math.floor(mapInstance.getZoom());
178
+ const clusters = clusterIndex.current.getClusters(
179
+ [bounds.getWest(), bounds.getSouth(), bounds.getEast(), bounds.getNorth()],
180
+ zoom
181
+ );
182
+ markersRef.current.forEach((m) => m.remove());
183
+ markersRef.current = [];
184
+ clusters.forEach((cluster) => {
185
+ const [lng, lat] = cluster.geometry.coordinates;
186
+ const { cluster: isCluster, point_count: count } = cluster.properties;
187
+ let el;
188
+ if (isCluster) {
189
+ el = createClusterElement(count);
190
+ el.onclick = (e) => {
191
+ e.stopPropagation();
192
+ const expansionZoom = Math.min(
193
+ clusterIndex.current.getClusterExpansionZoom(cluster.id),
194
+ 18
195
+ );
196
+ mapInstance.easeTo({ center: [lng, lat], zoom: expansionZoom });
197
+ };
198
+ } else {
199
+ const storeData = cluster.properties;
200
+ el = createCustomMarkerElement({
201
+ marker: storeData,
202
+ isSelected: storeData.id === selectedMarkerId,
203
+ logoSrc: markerLogoUrl
183
204
  });
184
- };
185
- } else {
186
- const storeData = cluster.properties;
187
- el = createCustomMarkerElement({
188
- marker: storeData,
189
- isSelected: storeData.id === selectedMarkerId,
190
- logoSrc: markerLogoUrl
191
- });
192
- el.onclick = () => onMarkerClick?.(storeData, 0, mapInstance);
193
- }
194
- const newMarker = new mapLib.Marker({
195
- element: el,
196
- anchor: isCluster ? "center" : "bottom"
197
- }).setLngLat([lng, lat]).addTo(mapInstance);
198
- markersRef.current.push(newMarker);
199
- });
205
+ el.onclick = () => onMarkerClick?.(storeData, 0, mapInstance);
206
+ }
207
+ const newMarker = new mapLib.Marker({ element: el, anchor: isCluster ? "center" : "bottom" }).setLngLat([lng, lat]).addTo(mapInstance);
208
+ markersRef.current.push(newMarker);
209
+ });
210
+ } catch (err) {
211
+ console.error("Update markers failed:", err);
212
+ }
200
213
  }, [mapInstance, mapLib, selectedMarkerId, markerLogoUrl, onMarkerClick]);
201
214
  (0, import_react.useEffect)(() => {
215
+ if (isRemoving.current) return;
202
216
  const points = markers.map((s) => ({
203
217
  type: "Feature",
204
218
  properties: { ...s },
@@ -208,13 +222,13 @@ var Map = ({
208
222
  updateMarkers();
209
223
  }, [markers, updateMarkers]);
210
224
  (0, import_react.useEffect)(() => {
211
- if (!mapInstance) return;
225
+ if (!mapInstance || isRemoving.current) return;
212
226
  mapInstance.on("moveend", updateMarkers);
213
227
  return () => {
214
228
  mapInstance.off("moveend", updateMarkers);
215
229
  };
216
230
  }, [mapInstance, updateMarkers]);
217
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "relative", ...style }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
231
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "relative", overflow: "hidden", ...style }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
218
232
  "div",
219
233
  {
220
234
  ref: mapContainerRef,
@@ -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, useRef, useCallback } 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 const [mapInstance, setMapInstance] = useState<any>(null);\r\n const sliderRef = useRef<HTMLDivElement>(null);\r\n\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 \r\n useEffect(() => {\r\n if (isMobile && props.selectedMarkerId && sliderRef.current) {\r\n const selectedCard = sliderRef.current.querySelector(`[data-id=\"${props.selectedMarkerId}\"]`);\r\n if (selectedCard) {\r\n selectedCard.scrollIntoView({\r\n behavior: 'smooth',\r\n inline: 'center',\r\n block: 'nearest'\r\n });\r\n }\r\n }\r\n }, [props.selectedMarkerId, isMobile]);\r\n\r\n \r\n const handleStoreSelect = useCallback((store: Store, index: number) => {\r\n \r\n if (!mapInstance) {\r\n console.warn(\"نقشه هنوز آماده نشده است.\");\r\n return;\r\n }\r\n \r\n \r\n if (props.onMarkerClick) {\r\n props.onMarkerClick(store, index, mapInstance);\r\n }\r\n }, [mapInstance, props]);\r\n\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 {/* سایدبار دسکتاپ */}\r\n {!isMobile && (\r\n <aside className=\"sidebar\">\r\n <header className=\"sidebar-header\">لیست فروشگاه‌ها</header>\r\n <div className=\"sidebar-list\">\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 {/* بخش نقشه */}\r\n <main className=\"map-container\">\r\n <Map \r\n {...props} \r\n onMapLoad={(map) => {\r\n \r\n if (map) {\r\n setMapInstance(map);\r\n if (props.onMapLoad) {\r\n props.onMapLoad(map);\r\n }\r\n }\r\n }} \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 {/* اسلایدر موبایل */}\r\n {isMobile && (\r\n <div className=\"mobile-slider\" ref={sliderRef}>\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\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 const clusterIndex = useRef(new Supercluster({\r\n radius: 60,\r\n maxZoom: 16\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 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, options, onMapLoad]);\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 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 const clusters = clusterIndex.current.getClusters(\r\n [bounds.getWest(), bounds.getSouth(), bounds.getEast(), bounds.getNorth()],\r\n zoom\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 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 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({ \r\n element: el, \r\n anchor: isCluster ? 'center' : 'bottom' \r\n })\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 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 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={{ width: '100%', height: '100%', backfaceVisibility: 'hidden' }} \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: '36px',\r\n height: '36px',\r\n borderRadius: '50%',\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 data-id={store.id} // برای پیدا کردن کارت در اسکرول موبایل\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\" onClick={(e) => e.stopPropagation()}>\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\" onClick={(e) => e.stopPropagation()}>\r\n <span>واتساپ</span>\r\n </a>\r\n )}\r\n <a href={googleMapUrl} target=\"_blank\" className=\"btn btn-route\" onClick={(e) => e.stopPropagation()}>\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;;;ACEA,IAAAA,gBAAgE;;;ACAhE,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,IAEd,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,0BAAyB;AA8HnB;AA5HN,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;AAEnC,QAAM,mBAAe,qBAAO,IAAI,oBAAAC,QAAa;AAAA,IAC3C,QAAQ;AAAA,IACR,SAAS;AAAA,EACX,CAAC,CAAC;AAEF,8BAAU,MAAM;AACd,WAAO,iCAAiC,EAAE,KAAK,CAAC,QAAQ;AACtD,gBAAU,IAAI,WAAW,GAAG;AAAA,IAC9B,CAAC;AAAA,EACH,GAAG,CAAC,CAAC;AAEL,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,QAAQ,SAAS,SAAS,CAAC;AAE/B,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,QACN,WAAW;AAAA,QACX,UAAU;AAAA,MACZ,CAAC;AAAA,IACH;AAAA,EACF,GAAG,CAAC,kBAAkB,aAAa,OAAO,CAAC;AAE3C,QAAM,oBAAgB,0BAAY,MAAM;AACtC,QAAI,CAAC,eAAe,CAAC,OAAQ;AAE7B,UAAM,SAAS,YAAY,UAAU;AACrC,UAAM,OAAO,KAAK,MAAM,YAAY,QAAQ,CAAC;AAE7C,UAAM,WAAW,aAAa,QAAQ;AAAA,MACpC,CAAC,OAAO,QAAQ,GAAG,OAAO,SAAS,GAAG,OAAO,QAAQ,GAAG,OAAO,SAAS,CAAC;AAAA,MACzE;AAAA,IACF;AAEA,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;AACb,aAAK,qBAAqB,KAAK;AAC/B,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,MACF,OAAO;AACL,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;AAAA,QAClC,SAAS;AAAA,QACT,QAAQ,YAAY,WAAW;AAAA,MACjC,CAAC,EACE,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;AAExE,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;AAE3B,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;AAAA,IAAC;AAAA;AAAA,MACC,KAAK;AAAA,MACL,OAAO,EAAE,OAAO,QAAQ,QAAQ,QAAQ,oBAAoB,SAAS;AAAA;AAAA,EACvE,GACF;AAEJ;AAEA,IAAO,cAAQ;;;AGzHP,IAAAC,sBAAA;AAVD,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,MACT,WAAS,MAAM;AAAA,MAEf;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,gBAAe,SAAS,CAAC,MAAM,EAAE,gBAAgB,GACxF,uDAAC,UAAK,sCAAI,GACZ;AAAA,UAED,MAAM,YACL,6CAAC,OAAE,MAAM,iBAAiB,MAAM,QAAQ,IAAI,QAAO,UAAS,WAAU,cAAa,SAAS,CAAC,MAAM,EAAE,gBAAgB,GACnH,uDAAC,UAAK,kDAAM,GACd;AAAA,UAEF,6CAAC,OAAE,MAAM,cAAc,QAAO,UAAS,WAAU,iBAAgB,SAAS,CAAC,MAAM,EAAE,gBAAgB,GACjG,uDAAC,UAAK,8DAAQ,GAChB;AAAA,WACF,GACF,GACF;AAAA;AAAA;AAAA,EACF;AAEJ;;;AJKQ,IAAAC,sBAAA;AAhDD,IAAM,iBAAqC,CAAC,UAAU;AAC3D,QAAM,CAAC,UAAU,WAAW,QAAI,wBAAS,KAAK;AAC9C,QAAM,CAAC,aAAa,cAAc,QAAI,wBAAc,IAAI;AACxD,QAAM,gBAAY,sBAAuB,IAAI;AAG7C,+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;AAGL,+BAAU,MAAM;AACd,QAAI,YAAY,MAAM,oBAAoB,UAAU,SAAS;AAC3D,YAAM,eAAe,UAAU,QAAQ,cAAc,aAAa,MAAM,gBAAgB,IAAI;AAC5F,UAAI,cAAc;AAChB,qBAAa,eAAe;AAAA,UAC1B,UAAU;AAAA,UACV,QAAQ;AAAA,UACR,OAAO;AAAA,QACT,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF,GAAG,CAAC,MAAM,kBAAkB,QAAQ,CAAC;AAGrC,QAAM,wBAAoB,2BAAY,CAAC,OAAc,UAAkB;AAErE,QAAI,CAAC,aAAa;AACd,cAAQ,KAAK,+HAA2B;AACxC;AAAA,IACJ;AAGA,QAAI,MAAM,eAAe;AACrB,YAAM,cAAc,OAAO,OAAO,WAAW;AAAA,IACjD;AAAA,EACF,GAAG,CAAC,aAAa,KAAK,CAAC;AAEvB,QAAM,cAAc,MAAM,WAAW,CAAC;AAEtC,SACE,8CAAC,SAAI,WAAW,mBAAmB,WAAW,cAAc,YAAY,IAGrE;AAAA,KAAC,YACA,8CAAC,WAAM,WAAU,WACf;AAAA,mDAAC,YAAO,WAAU,kBAAiB,mGAAe;AAAA,MAClD,6CAAC,SAAI,WAAU,gBACZ,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,IAIF,8CAAC,UAAK,WAAU,iBACd;AAAA;AAAA,QAAC;AAAA;AAAA,UACE,GAAG;AAAA,UACJ,WAAW,CAAC,QAAQ;AAElB,gBAAI,KAAK;AACL,6BAAe,GAAG;AAClB,kBAAI,MAAM,WAAW;AACjB,sBAAM,UAAU,GAAG;AAAA,cACvB;AAAA,YACJ;AAAA,UACF;AAAA;AAAA,MACF;AAAA,MAGC,MAAM,eACL,6CAAC,SAAI,WAAU,iBACZ,uDAAC,UAAM,gBAAM,aAAY,GAC5B;AAAA,MAID,YACC,6CAAC,SAAI,WAAU,iBAAgB,KAAK,WACjC,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/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, useRef, useCallback } 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 const [mapInstance, setMapInstance] = useState<any>(null);\r\n const sliderRef = useRef<HTMLDivElement>(null);\r\n\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 \r\n useEffect(() => {\r\n if (isMobile && props.selectedMarkerId && sliderRef.current) {\r\n const selectedCard = sliderRef.current.querySelector(`[data-id=\"${props.selectedMarkerId}\"]`);\r\n if (selectedCard) {\r\n selectedCard.scrollIntoView({\r\n behavior: 'smooth',\r\n inline: 'center',\r\n block: 'nearest'\r\n });\r\n }\r\n }\r\n }, [props.selectedMarkerId, isMobile]);\r\n\r\n \r\n const handleStoreSelect = useCallback((store: Store, index: number) => {\r\n \r\n if (!mapInstance) {\r\n console.warn(\"نقشه هنوز آماده نشده است.\");\r\n return;\r\n }\r\n \r\n \r\n if (props.onMarkerClick) {\r\n props.onMarkerClick(store, index, mapInstance);\r\n }\r\n }, [mapInstance, props]);\r\n\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 {/* سایدبار دسکتاپ */}\r\n {!isMobile && (\r\n <aside className=\"sidebar\">\r\n <header className=\"sidebar-header\">لیست فروشگاه‌ها</header>\r\n <div className=\"sidebar-list\">\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 {/* بخش نقشه */}\r\n <main className=\"map-container\">\r\n <Map \r\n {...props} \r\n onMapLoad={(map) => {\r\n \r\n if (map) {\r\n setMapInstance(map);\r\n if (props.onMapLoad) {\r\n props.onMapLoad(map);\r\n }\r\n }\r\n }} \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 {/* اسلایدر موبایل */}\r\n {isMobile && (\r\n <div className=\"mobile-slider\" ref={sliderRef}>\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\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 const isRemoving = useRef(false); \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 let mounted = true;\r\n import('@neshan-maps-platform/mapbox-gl').then((mod) => {\r\n if (mounted) setMapLib(mod.default || mod);\r\n });\r\n return () => { mounted = false; };\r\n }, []);\r\n\r\n \r\n useEffect(() => {\r\n if (!mapLib || !mapContainerRef.current || mapInstance) return;\r\n\r\n const map = new mapLib.Map({\r\n container: mapContainerRef.current,\r\n ...options,\r\n });\r\n\r\n map.on('load', () => {\r\n if (!isRemoving.current) {\r\n setMapInstance(map);\r\n onMapLoad?.(map);\r\n }\r\n });\r\n\r\n return () => {\r\n if (map) {\r\n isRemoving.current = true;\r\n \r\n setTimeout(() => {\r\n try {\r\n map.remove();\r\n } catch (e) {\r\n console.warn(\"Mapbox remove error ignored:\", e);\r\n }\r\n }, 0);\r\n }\r\n };\r\n \r\n }, [mapLib]); \r\n\r\n \r\n useEffect(() => {\r\n if (!mapInstance || !selectedMarkerId || isRemoving.current) 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: 1200\r\n });\r\n }\r\n }, [selectedMarkerId, mapInstance, markers]);\r\n\r\n \r\n const updateMarkers = useCallback(() => {\r\n if (!mapInstance || !mapLib || isRemoving.current) return;\r\n\r\n try {\r\n const bounds = mapInstance.getBounds();\r\n const zoom = Math.floor(mapInstance.getZoom());\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 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 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({ center: [lng, lat], zoom: expansionZoom });\r\n };\r\n } else {\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 } catch (err) {\r\n console.error(\"Update markers failed:\", err);\r\n }\r\n }, [mapInstance, mapLib, selectedMarkerId, markerLogoUrl, onMarkerClick]);\r\n\r\n \r\n useEffect(() => {\r\n if (isRemoving.current) return;\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 || isRemoving.current) 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', overflow: 'hidden', ...style }}>\r\n <div \r\n ref={mapContainerRef} \r\n style={{ width: '100%', height: '100%', backfaceVisibility: 'hidden' }} \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: '36px',\r\n height: '36px',\r\n borderRadius: '50%',\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 data-id={store.id} // برای پیدا کردن کارت در اسکرول موبایل\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\" onClick={(e) => e.stopPropagation()}>\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\" onClick={(e) => e.stopPropagation()}>\r\n <span>واتساپ</span>\r\n </a>\r\n )}\r\n <a href={googleMapUrl} target=\"_blank\" className=\"btn btn-route\" onClick={(e) => e.stopPropagation()}>\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"]}
package/dist/react.js CHANGED
@@ -84,14 +84,19 @@ var Map = ({
84
84
  const [mapInstance, setMapInstance] = useState(null);
85
85
  const [mapLib, setMapLib] = useState(null);
86
86
  const markersRef = useRef([]);
87
+ const isRemoving = useRef(false);
87
88
  const clusterIndex = useRef(new Supercluster({
88
89
  radius: 60,
89
90
  maxZoom: 16
90
91
  }));
91
92
  useEffect(() => {
93
+ let mounted = true;
92
94
  import("@neshan-maps-platform/mapbox-gl").then((mod) => {
93
- setMapLib(mod.default || mod);
95
+ if (mounted) setMapLib(mod.default || mod);
94
96
  });
97
+ return () => {
98
+ mounted = false;
99
+ };
95
100
  }, []);
96
101
  useEffect(() => {
97
102
  if (!mapLib || !mapContainerRef.current || mapInstance) return;
@@ -100,70 +105,79 @@ var Map = ({
100
105
  ...options
101
106
  });
102
107
  map.on("load", () => {
103
- setMapInstance(map);
104
- onMapLoad?.(map);
108
+ if (!isRemoving.current) {
109
+ setMapInstance(map);
110
+ onMapLoad?.(map);
111
+ }
105
112
  });
106
113
  return () => {
107
- if (map) map.remove();
114
+ if (map) {
115
+ isRemoving.current = true;
116
+ setTimeout(() => {
117
+ try {
118
+ map.remove();
119
+ } catch (e) {
120
+ console.warn("Mapbox remove error ignored:", e);
121
+ }
122
+ }, 0);
123
+ }
108
124
  };
109
- }, [mapLib, options, onMapLoad]);
125
+ }, [mapLib]);
110
126
  useEffect(() => {
111
- if (!mapInstance || !selectedMarkerId) return;
127
+ if (!mapInstance || !selectedMarkerId || isRemoving.current) return;
112
128
  const selectedStore = markers.find((m) => m.id === selectedMarkerId);
113
129
  if (selectedStore) {
114
130
  mapInstance.flyTo({
115
131
  center: [selectedStore.lng, selectedStore.lat],
116
132
  zoom: 16,
117
133
  essential: true,
118
- duration: 1500
134
+ duration: 1200
119
135
  });
120
136
  }
121
137
  }, [selectedMarkerId, mapInstance, markers]);
122
138
  const updateMarkers = useCallback(() => {
123
- if (!mapInstance || !mapLib) return;
124
- const bounds = mapInstance.getBounds();
125
- const zoom = Math.floor(mapInstance.getZoom());
126
- const clusters = clusterIndex.current.getClusters(
127
- [bounds.getWest(), bounds.getSouth(), bounds.getEast(), bounds.getNorth()],
128
- zoom
129
- );
130
- markersRef.current.forEach((m) => m.remove());
131
- markersRef.current = [];
132
- clusters.forEach((cluster) => {
133
- const [lng, lat] = cluster.geometry.coordinates;
134
- const { cluster: isCluster, point_count: count } = cluster.properties;
135
- let el;
136
- if (isCluster) {
137
- el = createClusterElement(count);
138
- el.onclick = (e) => {
139
- e.stopPropagation();
140
- const expansionZoom = Math.min(
141
- clusterIndex.current.getClusterExpansionZoom(cluster.id),
142
- 18
143
- );
144
- mapInstance.easeTo({
145
- center: [lng, lat],
146
- zoom: expansionZoom,
147
- duration: 800
139
+ if (!mapInstance || !mapLib || isRemoving.current) return;
140
+ try {
141
+ const bounds = mapInstance.getBounds();
142
+ const zoom = Math.floor(mapInstance.getZoom());
143
+ const clusters = clusterIndex.current.getClusters(
144
+ [bounds.getWest(), bounds.getSouth(), bounds.getEast(), bounds.getNorth()],
145
+ zoom
146
+ );
147
+ markersRef.current.forEach((m) => m.remove());
148
+ markersRef.current = [];
149
+ clusters.forEach((cluster) => {
150
+ const [lng, lat] = cluster.geometry.coordinates;
151
+ const { cluster: isCluster, point_count: count } = cluster.properties;
152
+ let el;
153
+ if (isCluster) {
154
+ el = createClusterElement(count);
155
+ el.onclick = (e) => {
156
+ e.stopPropagation();
157
+ const expansionZoom = Math.min(
158
+ clusterIndex.current.getClusterExpansionZoom(cluster.id),
159
+ 18
160
+ );
161
+ mapInstance.easeTo({ center: [lng, lat], zoom: expansionZoom });
162
+ };
163
+ } else {
164
+ const storeData = cluster.properties;
165
+ el = createCustomMarkerElement({
166
+ marker: storeData,
167
+ isSelected: storeData.id === selectedMarkerId,
168
+ logoSrc: markerLogoUrl
148
169
  });
149
- };
150
- } else {
151
- const storeData = cluster.properties;
152
- el = createCustomMarkerElement({
153
- marker: storeData,
154
- isSelected: storeData.id === selectedMarkerId,
155
- logoSrc: markerLogoUrl
156
- });
157
- el.onclick = () => onMarkerClick?.(storeData, 0, mapInstance);
158
- }
159
- const newMarker = new mapLib.Marker({
160
- element: el,
161
- anchor: isCluster ? "center" : "bottom"
162
- }).setLngLat([lng, lat]).addTo(mapInstance);
163
- markersRef.current.push(newMarker);
164
- });
170
+ el.onclick = () => onMarkerClick?.(storeData, 0, mapInstance);
171
+ }
172
+ const newMarker = new mapLib.Marker({ element: el, anchor: isCluster ? "center" : "bottom" }).setLngLat([lng, lat]).addTo(mapInstance);
173
+ markersRef.current.push(newMarker);
174
+ });
175
+ } catch (err) {
176
+ console.error("Update markers failed:", err);
177
+ }
165
178
  }, [mapInstance, mapLib, selectedMarkerId, markerLogoUrl, onMarkerClick]);
166
179
  useEffect(() => {
180
+ if (isRemoving.current) return;
167
181
  const points = markers.map((s) => ({
168
182
  type: "Feature",
169
183
  properties: { ...s },
@@ -173,13 +187,13 @@ var Map = ({
173
187
  updateMarkers();
174
188
  }, [markers, updateMarkers]);
175
189
  useEffect(() => {
176
- if (!mapInstance) return;
190
+ if (!mapInstance || isRemoving.current) return;
177
191
  mapInstance.on("moveend", updateMarkers);
178
192
  return () => {
179
193
  mapInstance.off("moveend", updateMarkers);
180
194
  };
181
195
  }, [mapInstance, updateMarkers]);
182
- return /* @__PURE__ */ jsx("div", { style: { position: "relative", ...style }, children: /* @__PURE__ */ jsx(
196
+ return /* @__PURE__ */ jsx("div", { style: { position: "relative", overflow: "hidden", ...style }, children: /* @__PURE__ */ jsx(
183
197
  "div",
184
198
  {
185
199
  ref: mapContainerRef,
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, useRef, useCallback } 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 const [mapInstance, setMapInstance] = useState<any>(null);\r\n const sliderRef = useRef<HTMLDivElement>(null);\r\n\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 \r\n useEffect(() => {\r\n if (isMobile && props.selectedMarkerId && sliderRef.current) {\r\n const selectedCard = sliderRef.current.querySelector(`[data-id=\"${props.selectedMarkerId}\"]`);\r\n if (selectedCard) {\r\n selectedCard.scrollIntoView({\r\n behavior: 'smooth',\r\n inline: 'center',\r\n block: 'nearest'\r\n });\r\n }\r\n }\r\n }, [props.selectedMarkerId, isMobile]);\r\n\r\n \r\n const handleStoreSelect = useCallback((store: Store, index: number) => {\r\n \r\n if (!mapInstance) {\r\n console.warn(\"نقشه هنوز آماده نشده است.\");\r\n return;\r\n }\r\n \r\n \r\n if (props.onMarkerClick) {\r\n props.onMarkerClick(store, index, mapInstance);\r\n }\r\n }, [mapInstance, props]);\r\n\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 {/* سایدبار دسکتاپ */}\r\n {!isMobile && (\r\n <aside className=\"sidebar\">\r\n <header className=\"sidebar-header\">لیست فروشگاه‌ها</header>\r\n <div className=\"sidebar-list\">\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 {/* بخش نقشه */}\r\n <main className=\"map-container\">\r\n <Map \r\n {...props} \r\n onMapLoad={(map) => {\r\n \r\n if (map) {\r\n setMapInstance(map);\r\n if (props.onMapLoad) {\r\n props.onMapLoad(map);\r\n }\r\n }\r\n }} \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 {/* اسلایدر موبایل */}\r\n {isMobile && (\r\n <div className=\"mobile-slider\" ref={sliderRef}>\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\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 const clusterIndex = useRef(new Supercluster({\r\n radius: 60,\r\n maxZoom: 16\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 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, options, onMapLoad]);\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 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 const clusters = clusterIndex.current.getClusters(\r\n [bounds.getWest(), bounds.getSouth(), bounds.getEast(), bounds.getNorth()],\r\n zoom\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 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 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({ \r\n element: el, \r\n anchor: isCluster ? 'center' : 'bottom' \r\n })\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 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 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={{ width: '100%', height: '100%', backfaceVisibility: 'hidden' }} \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: '36px',\r\n height: '36px',\r\n borderRadius: '50%',\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 data-id={store.id} // برای پیدا کردن کارت در اسکرول موبایل\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\" onClick={(e) => e.stopPropagation()}>\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\" onClick={(e) => e.stopPropagation()}>\r\n <span>واتساپ</span>\r\n </a>\r\n )}\r\n <a href={googleMapUrl} target=\"_blank\" className=\"btn btn-route\" onClick={(e) => e.stopPropagation()}>\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","useRef","useCallback","jsx","jsx","jsxs","useState","useRef","useEffect","useCallback"]}
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, useRef, useCallback } 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 const [mapInstance, setMapInstance] = useState<any>(null);\r\n const sliderRef = useRef<HTMLDivElement>(null);\r\n\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 \r\n useEffect(() => {\r\n if (isMobile && props.selectedMarkerId && sliderRef.current) {\r\n const selectedCard = sliderRef.current.querySelector(`[data-id=\"${props.selectedMarkerId}\"]`);\r\n if (selectedCard) {\r\n selectedCard.scrollIntoView({\r\n behavior: 'smooth',\r\n inline: 'center',\r\n block: 'nearest'\r\n });\r\n }\r\n }\r\n }, [props.selectedMarkerId, isMobile]);\r\n\r\n \r\n const handleStoreSelect = useCallback((store: Store, index: number) => {\r\n \r\n if (!mapInstance) {\r\n console.warn(\"نقشه هنوز آماده نشده است.\");\r\n return;\r\n }\r\n \r\n \r\n if (props.onMarkerClick) {\r\n props.onMarkerClick(store, index, mapInstance);\r\n }\r\n }, [mapInstance, props]);\r\n\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 {/* سایدبار دسکتاپ */}\r\n {!isMobile && (\r\n <aside className=\"sidebar\">\r\n <header className=\"sidebar-header\">لیست فروشگاه‌ها</header>\r\n <div className=\"sidebar-list\">\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 {/* بخش نقشه */}\r\n <main className=\"map-container\">\r\n <Map \r\n {...props} \r\n onMapLoad={(map) => {\r\n \r\n if (map) {\r\n setMapInstance(map);\r\n if (props.onMapLoad) {\r\n props.onMapLoad(map);\r\n }\r\n }\r\n }} \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 {/* اسلایدر موبایل */}\r\n {isMobile && (\r\n <div className=\"mobile-slider\" ref={sliderRef}>\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\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 const isRemoving = useRef(false); \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 let mounted = true;\r\n import('@neshan-maps-platform/mapbox-gl').then((mod) => {\r\n if (mounted) setMapLib(mod.default || mod);\r\n });\r\n return () => { mounted = false; };\r\n }, []);\r\n\r\n \r\n useEffect(() => {\r\n if (!mapLib || !mapContainerRef.current || mapInstance) return;\r\n\r\n const map = new mapLib.Map({\r\n container: mapContainerRef.current,\r\n ...options,\r\n });\r\n\r\n map.on('load', () => {\r\n if (!isRemoving.current) {\r\n setMapInstance(map);\r\n onMapLoad?.(map);\r\n }\r\n });\r\n\r\n return () => {\r\n if (map) {\r\n isRemoving.current = true;\r\n \r\n setTimeout(() => {\r\n try {\r\n map.remove();\r\n } catch (e) {\r\n console.warn(\"Mapbox remove error ignored:\", e);\r\n }\r\n }, 0);\r\n }\r\n };\r\n \r\n }, [mapLib]); \r\n\r\n \r\n useEffect(() => {\r\n if (!mapInstance || !selectedMarkerId || isRemoving.current) 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: 1200\r\n });\r\n }\r\n }, [selectedMarkerId, mapInstance, markers]);\r\n\r\n \r\n const updateMarkers = useCallback(() => {\r\n if (!mapInstance || !mapLib || isRemoving.current) return;\r\n\r\n try {\r\n const bounds = mapInstance.getBounds();\r\n const zoom = Math.floor(mapInstance.getZoom());\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 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 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({ center: [lng, lat], zoom: expansionZoom });\r\n };\r\n } else {\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 } catch (err) {\r\n console.error(\"Update markers failed:\", err);\r\n }\r\n }, [mapInstance, mapLib, selectedMarkerId, markerLogoUrl, onMarkerClick]);\r\n\r\n \r\n useEffect(() => {\r\n if (isRemoving.current) return;\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 || isRemoving.current) 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', overflow: 'hidden', ...style }}>\r\n <div \r\n ref={mapContainerRef} \r\n style={{ width: '100%', height: '100%', backfaceVisibility: 'hidden' }} \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: '36px',\r\n height: '36px',\r\n borderRadius: '50%',\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 data-id={store.id} // برای پیدا کردن کارت در اسکرول موبایل\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\" onClick={(e) => e.stopPropagation()}>\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\" onClick={(e) => e.stopPropagation()}>\r\n <span>واتساپ</span>\r\n </a>\r\n )}\r\n <a href={googleMapUrl} target=\"_blank\" className=\"btn btn-route\" onClick={(e) => e.stopPropagation()}>\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","useRef","useCallback","jsx","jsx","jsxs","useState","useRef","useEffect","useCallback"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mehdi-akbari-map",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "description": "A professional Map",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",