{"version":3,"file":"MapClusterer.mjs","sources":[""],"sourcesContent":["import { Clusterer as ClustererElement, useYMaps } from \"@pbe/react-yandex-maps\";\r\nimport type {\r\n  ReactElement,\r\n  FC,\r\n  ReactNode,\r\n} from \"react\";\r\nimport {\r\n  useCallback,\r\n  useMemo,\r\n  useState,\r\n  useEffect,\r\n  useContext,\r\n} from \"react\";\r\nimport type {\r\n  Clusterer, IClassConstructor, IEvent, ILayout, IGeoObject, IPointGeometry,\r\n} from \"yandex-maps\";\r\nimport { constants } from \"../../../constants\";\r\nimport { mapDefaults, optionsDefaults } from \"../config\";\r\nimport type {\r\n  TCluster, TClusterOptions, TFactoryCallback, TMapActionArg, TMapContext, TMapInstance, ILayoutBalloonCluster,\r\n} from \"../config/types\";\r\nimport { MapContext } from \"../context\";\r\nimport {\r\n  bindEvents, getNodeFromElementInstance, manageGeoObjectClass, managePaneVariables,\r\n} from \"../lib\";\r\nimport { ClustererInstanceLayout } from \"../lib/clusterer\";\r\n\r\ntype TMapClustererProps = {\r\n  cluster: TCluster;\r\n  onReady?: (instance: Clusterer) => void;\r\n  children?: ReactNode;\r\n};\r\n\r\n/**\r\n * Внутренний компонент содержимого карты\r\n * @returns {ReactElement}\r\n */\r\nconst MapClusterer: FC<TMapClustererProps> = ({\r\n  onReady, cluster, children,\r\n}): ReactElement => {\r\n  type TClusterActionType = \"clusterClick\" | \"clusterBalloonOpen\" | \"clusterBalloonClose\";\r\n\r\n  const {\r\n    containerRef, getCN, onAction, api,\r\n  } = useContext<TMapContext>(MapContext);\r\n\r\n  const ymaps = useYMaps(api?.load ?? mapDefaults.api!.load);\r\n\r\n  const [ instance, setInstance ] = useState<Clusterer | null>(null);\r\n\r\n  const [ layout, setLayout ] = useState<{\r\n    icon: IClassConstructor<ILayout> | \"\";\r\n    balloon: IClassConstructor<ILayout> | \"\";\r\n  }>({\r\n    icon: \"\",\r\n    balloon: \"\",\r\n  });\r\n\r\n  // balloon activity\r\n  const handleBalloonActivity = useCallback<TFactoryCallback<ILayoutBalloonCluster>>((type, Balloon) => {\r\n\r\n    const onBalloonClick = (e: IEvent<MouseEvent>) => {\r\n      const originalEvent = e\r\n        .get(\"domEvent\").originalEvent;\r\n      const target: EventTarget | null = originalEvent.target;\r\n      if (target && (target as HTMLElement).hasAttribute(\"data-js-map-cluster-balloon-close\")) {\r\n        originalEvent.preventDefault();\r\n        const balloon = Balloon.getData()?.balloon;\r\n\r\n        if (balloon) {\r\n          balloon.close();\r\n        }\r\n      }\r\n      if (target && (target as HTMLElement).hasAttribute(\"data-js-map-cluster-balloon-tab\")) {\r\n        originalEvent.preventDefault();\r\n\r\n        const pointId = (target as HTMLElement).getAttribute(\"data-js-map-cluster-balloon-tab\");\r\n        const data = Balloon.getData();\r\n        const activePoint = data?.properties.points.find((p: { id: string; }) => p.id === pointId);\r\n\r\n        if (activePoint) {\r\n          Balloon.getData().properties.activePoint = activePoint;\r\n          Balloon.rebuild();\r\n          manageGeoObjectClass(Balloon.getElement() as HTMLElement, constants.stateClasses.visible, true);\r\n        }\r\n      }\r\n    };\r\n\r\n    const getActionArgs = (type: TClusterActionType): TMapActionArg => {\r\n      return {\r\n        el: Balloon.getElement(),\r\n        ref: instance,\r\n        stateActions: {},\r\n        getData: () => {\r\n          return {\r\n            type,\r\n            cluster,\r\n          };\r\n        },\r\n      };\r\n    };\r\n\r\n    const manageVars = (isAdd = true) => {\r\n      const paneNode = Balloon.getParentElement();\r\n      queueMicrotask(() => {\r\n        managePaneVariables(containerRef, isAdd, paneNode);\r\n      });\r\n    };\r\n\r\n    switch (type) {\r\n      case \"build\": {\r\n        Balloon.events.add(\"click\", onBalloonClick);\r\n        const parentLayout: HTMLElement = Balloon.getParentElement();\r\n        parentLayout.classList.add(getCN(\"balloons\"));\r\n        (parentLayout.parentNode as HTMLElement)!.style.zIndex = cluster.options?.panelZIndex ?? \"6003\";\r\n        manageVars(true);\r\n        break;\r\n      }\r\n      case \"rebuild\": {\r\n        manageVars(true);\r\n        break;\r\n      }\r\n      case \"destroy\": {\r\n        Balloon.events.remove(\"click\", onBalloonClick as (event: object | IEvent<unknown, unknown>) => void);\r\n        if (typeof onAction === \"function\") {\r\n          onAction(getActionArgs(\"clusterBalloonClose\"));\r\n        }\r\n        manageVars(false);\r\n        break;\r\n      }\r\n    }\r\n  }, [ instance, onAction, getCN, containerRef, cluster.options ]);\r\n\r\n  useEffect(() => {\r\n    if (!instance) {\r\n      return;\r\n    }\r\n    const commonArgs = {\r\n      ymaps, getCN, props: cluster, instance,\r\n    };\r\n    Promise.all([\r\n      ClustererInstanceLayout({ ...commonArgs, type: \"icon\" }),\r\n      ClustererInstanceLayout({ ...commonArgs, type: \"balloon\", callback: handleBalloonActivity }),\r\n    ])\r\n      .then(([ icon, balloon ]) => {\r\n        return setLayout({\r\n          icon, balloon,\r\n        });\r\n      })\r\n      .catch((err: Error) => {\r\n        console.error(err);\r\n      });\r\n  }, [ ymaps, instance, getCN, cluster, handleBalloonActivity ]);\r\n\r\n  const currentOptions = useMemo<TClusterOptions>(() => {\r\n    const size = 48;\r\n\r\n    const { icon } = layout;\r\n\r\n    return {\r\n      // Основные настройки кластеризации\r\n      gridSize: 64,\r\n      groupByCoordinates: false,\r\n      minimumClusterSize: 2,\r\n\r\n      // Настройки поведения\r\n      clusterDisableClickZoom: true,\r\n      clusterOpenBalloonOnClick: false,\r\n\r\n      // Настройки внешнего вида кластеров\r\n      clusterIconLayout: icon,\r\n      clusterIconImageSize: [ size, size ],\r\n      clusterIconImageOffset: [ size / -2, size / -2 ],\r\n      clusterIconShape: {\r\n        type: \"Rectangle\",\r\n        coordinates: [\r\n          [ 0, 0 ],\r\n          [ size, size ],\r\n        ],\r\n      },\r\n\r\n      // Настройки балуна\r\n      hasBalloon: false,\r\n      hasHint: false,\r\n\r\n      // Отключение стандартного поведения\r\n      clusterHideIconOnBalloonOpen: false,\r\n      clusterBalloonAutoPan: false,\r\n      geoObjectHideIconOnBalloonOpen: false,\r\n\r\n      ...cluster.options,\r\n    };\r\n  }, [ cluster.options, layout ]);\r\n\r\n  const handleClick = async (event: IEvent) => {\r\n    const obj: IGeoObject<IPointGeometry> & {\r\n      options: TClusterOptions & {\r\n        _name: string;\r\n      };\r\n    } & Clusterer = event.get(\"target\");\r\n\r\n    if (obj.options._name === \"cluster\") {\r\n      const el = await getNodeFromElementInstance(obj);\r\n      const getOnActionFn = (type: TClusterActionType) => {\r\n        if (typeof onAction === \"function\") {\r\n          onAction({\r\n            event,\r\n            ref: instance,\r\n            el,\r\n            stateActions: {},\r\n            getData: () => {\r\n              return {\r\n                type,\r\n                cluster,\r\n              };\r\n            },\r\n          });\r\n        }\r\n      };\r\n\r\n      const defaultFn = async () => {\r\n        const coordinates = (obj?.geometry)?.getCoordinates();\r\n        const map: TMapInstance = obj.getMap();\r\n        if (map && coordinates) {\r\n          if (instance?.balloon) { // balloon activity\r\n            if (instance!.balloon.isOpen()) {\r\n              await instance!.balloon.close(true)\r\n                .then(() => {\r\n                  return getOnActionFn(\"clusterBalloonClose\");\r\n                });\r\n            } else {\r\n              const points = obj?.getGeoObjects().map((obj, index) => {\r\n                const balloonHeader = obj.properties.get(\"balloonHeader\", \"\" as unknown as object) as unknown as string;\r\n                const balloonContent = obj.properties.get(\"balloonContent\", \"\" as unknown as object) as unknown as string;\r\n                const id = obj.properties.get(\"idPoint\", \"\" as unknown as object) as unknown as string;\r\n                const coords = obj.properties.get(\"coords\", \"\" as unknown as object) as unknown as string[];\r\n                return {\r\n                  index,\r\n                  id,\r\n                  coords,\r\n                  balloonHeader: balloonHeader || `# ${(index + 1).toString()}`,\r\n                  balloonContent: balloonContent || coords.join(\", \"),\r\n                };\r\n              });\r\n              const activePoint = points.find((item) => item.index === 0);\r\n              const map = instance.getMap();\r\n              await map.panTo(coordinates, {\r\n                checkZoomRange: true,\r\n                duration: 200,\r\n                useMapMargin: true,\r\n              })\r\n                .then(() => {\r\n                  return instance!.balloon.open(coordinates, { // set actual data\r\n                    properties: {\r\n                      points,\r\n                      activePoint,\r\n                    },\r\n                    balloon: instance!.balloon,\r\n                  })\r\n                    // @ts-expect-error (api error)\r\n                    .then(() => getNodeFromElementInstance(instance.balloon))\r\n                    .then((node: HTMLElement | null) => manageGeoObjectClass(node, constants.stateClasses.visible, true))\r\n                    .then(() => {\r\n                      return getOnActionFn(\"clusterBalloonOpen\");\r\n                    })\r\n                    .catch((err: Error) => {\r\n                      console.error(err);\r\n                    });\r\n                });\r\n            }\r\n          } else { // zoom\r\n            await map.panTo(coordinates, {\r\n              checkZoomRange: true,\r\n              duration: 200,\r\n              useMapMargin: true,\r\n            })\r\n              .then(() => {\r\n                const maxZoom = map.options.get(\"maxZoom\", optionsDefaults.maxZoom as unknown as object) as unknown as number;\r\n                const newZoom: number = map.getZoom() + 3;\r\n                return map.setZoom(newZoom > maxZoom ? maxZoom : newZoom);\r\n              })\r\n              .catch((err: Error) => {\r\n                console.error(err);\r\n              });\r\n          }\r\n        }\r\n      };\r\n      if (typeof onAction === \"function\") {\r\n        getOnActionFn(\"clusterClick\");\r\n        if (!event.getSourceEvent()?.isDefaultPrevented()) {\r\n          await defaultFn();\r\n        }\r\n      } else {\r\n        await defaultFn();\r\n      }\r\n    }\r\n  };\r\n\r\n  useEffect(() => {\r\n    if (instance && ymaps) {\r\n      const events = [\r\n        {\r\n          event: \"click\",\r\n          handler: handleClick,\r\n        },\r\n      ];\r\n      // unbind current events\r\n      bindEvents(instance, events, false);\r\n\r\n      // bind current events\r\n      bindEvents(instance, events, true);\r\n\r\n      // set balloons\r\n      if (!instance.balloon\r\n        && instance.options.get(\"hasBalloon\", false as unknown as object) as unknown as boolean && layout.balloon) {\r\n        const map = instance.getMap();\r\n        // @ts-expect-error (api error)\r\n        instance.balloon = new ymaps.Balloon(map, {\r\n          autoPan: false,\r\n          panelMaxMapArea: 0,\r\n          layout: layout.balloon,\r\n        });\r\n        // @ts-expect-error (api error)\r\n        instance.balloon.options.setParent(map.options);\r\n      }\r\n\r\n      return () => {\r\n        // unbind on unmount\r\n        bindEvents(instance, events, false);\r\n      };\r\n\r\n    }\r\n  }, [ instance, layout, ymaps ]);\r\n\r\n  useEffect(() => {\r\n    if (instance && layout.icon && layout.balloon && typeof onReady === \"function\") {\r\n      onReady(instance);\r\n    }\r\n  }, [ instance, layout ]);\r\n\r\n  return (\r\n    <ClustererElement\r\n      ymaps={ymaps}\r\n      instanceRef={(instance: Clusterer) => {\r\n        setInstance(instance);\r\n      }}\r\n      options={currentOptions}\r\n    >\r\n      {children}\r\n    </ClustererElement>\r\n  );\r\n\r\n};\r\n\r\nexport {\r\n  MapClusterer,\r\n};\r\n"],"names":["MapClusterer","onReady","cluster","children","containerRef","getCN","onAction","api","useContext","MapContext","ymaps","useYMaps","load","mapDefaults","instance","setInstance","useState","layout","setLayout","icon","balloon","handleBalloonActivity","useCallback","type","Balloon","onBalloonClick","e","originalEvent","get","target","hasAttribute","preventDefault","getData","close","pointId","getAttribute","data","activePoint","properties","points","find","p","id","rebuild","manageGeoObjectClass","getElement","constants","stateClasses","visible","getActionArgs","el","ref","stateActions","manageVars","isAdd","paneNode","getParentElement","queueMicrotask","managePaneVariables","events","add","parentLayout","classList","parentNode","style","zIndex","options","panelZIndex","remove","useEffect","commonArgs","props","Promise","all","ClustererInstanceLayout","callback","then","catch","err","console","error","currentOptions","useMemo","size","gridSize","groupByCoordinates","minimumClusterSize","clusterDisableClickZoom","clusterOpenBalloonOnClick","clusterIconLayout","clusterIconImageSize","clusterIconImageOffset","clusterIconShape","coordinates","hasBalloon","hasHint","clusterHideIconOnBalloonOpen","clusterBalloonAutoPan","geoObjectHideIconOnBalloonOpen","handleClick","async","obj","event","_name","getNodeFromElementInstance","getOnActionFn","defaultFn","geometry","getCoordinates","map","getMap","isOpen","getGeoObjects","index","balloonHeader","balloonContent","coords","toString","join","item","panTo","checkZoomRange","duration","useMapMargin","open","node","maxZoom","optionsDefaults","newZoom","getZoom","setZoom","getSourceEvent","isDefaultPrevented","handler","bindEvents","autoPan","panelMaxMapArea","setParent","_jsx","ClustererElement","instanceRef"],"mappings":"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"}