4

I am trying to create a leaflet map control with a material-ui slider using react. For some reason when I drag the slider it also drags the map. When I try to disable click/mousemove propagation on the control I am no longer able to slide the slider.

https://codesandbox.io/s/ecstatic-taussig-iv8rj?file=/src/App.tsx

import * as React from "react";
import ReactDOM from "react-dom";
import L from "leaflet";
import { Slider } from "@material-ui/core";

function useMap(): [React.RefObject<HTMLDivElement>, L.Map | null] {
  const mapRef = React.useRef<HTMLDivElement>(null);

  const [map, setMap] = React.useState<L.Map>(null);

  React.useEffect(() => {
    if (!map && mapRef.current) {
      const newMap = new L.Map(mapRef.current, {
        center: [38.810820900566156, -95.54946899414064],
        zoom: 5
      });
      L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png").addTo(
        newMap
      );
      setMap(newMap);
    }
  }, [map]);

  return [mapRef, map];
}

function useLeafletWidget(
  map: L.Map | null,
  component: React.ReactElement | null
) {
  const containerRef = React.useRef(document.createElement("div"));
  React.useEffect(() => {
    if (map) {
      const Control = L.Control.extend({
        onAdd() {
          // L.DomEvent.disableClickPropagation(containerRef.current);

          // L.DomEvent.on(containerRef.current, "mousemove", (event) => {
          //   event.stopPropagation();
          // });
          return containerRef.current;
        }
      });
      const control = new Control({
        position: "topleft"
      });
      map.addControl(control);
      return () => {
        map.removeControl(control);
      };
    }
  }, [map]);

  React.useEffect(() => {
    if (component) {
      const container = containerRef.current;
      ReactDOM.render(component, container);
    }
  }, [component]);

  React.useEffect(() => {
    const container = containerRef.current;
    return () => {
      ReactDOM.unmountComponentAtNode(container);
    };
  }, [containerRef]);
}

function useSliderWidget(map: L.Map | null) {
  const widget = React.useMemo(
    () => (
      <div style={{ backgroundColor: "white", padding: 10, width: 150 }}>
        Slider: <Slider />
      </div>
    ),
    []
  );

  useLeafletWidget(map, widget);
}

export const App: React.FC = () => {
  const [mapRef, map] = useMap();

  useSliderWidget(map);

  return (
    <>
      <link
        rel="stylesheet"
        href="https://unpkg.com/[email protected]/dist/leaflet.css"
        integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ=="
        crossOrigin=""
      />
      <div ref={mapRef} style={{ flexGrow: 1 }}></div>
    </>
  );
};
2
  • Have you found a solution? I am experiencing same issue... I am not sure what to do. Commented Jan 5, 2021 at 8:34
  • How did you solve it? I have the same issue Commented Sep 7, 2021 at 0:06

3 Answers 3

1

You can add while moveing or the mouse is over the slider map.dragging.disable() and later map.dragging.enable().

1

Based on Falke's answer I've implemented a hook for disabling map drag and scroll propagation on interacting with elements which are on top of the map.

import L from 'leaflet'
import { useEffect } from 'react'
import { useMap } from 'react-leaflet'

export const useDisablePropagationInMap = ({
  ref,
}: {
  ref: React.MutableRefObject<HTMLElement | null>
}) => {
  const map = useMap()

  useEffect(() => {
    if (!ref.current) return
    L.DomEvent.disableScrollPropagation(ref.current)
    ref.current.onmouseenter = e => map.dragging.disable()
    ref.current.onmouseleave = e => map.dragging.enable()

    return () => {
      map.dragging.enable()
    }
  }, [ref])
}

0

You can create a component that encapsulates the rule to disable map draggin

import React, { useCallback } from "react";
import { useMap } from "react-leaflet";
import { StyledControl } from "./styled_map_components";

export default function CustomPanelControl({
  children,
  position = "bottomleft",
  preventDragging = false,
}: {
  children: React.ReactNode;
  position?: "topleft" | "topright" | "bottomleft" | "bottomright";
  preventDragging?: boolean;
}) {
  const map = useMap();

  const onMouseEnter = useCallback(() => {
    if (!map || !preventDragging) return;
    if (preventDragging) {
      map.dragging.disable();
    }
  }, [map, preventDragging]);

  const onMouseLeave = useCallback(() => {
    if (!map || !preventDragging) return;
    if (preventDragging) {
      map.dragging.enable();
    }
  }, [map, preventDragging]);

  return (
    <StyledControl position={position}>
      <div onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
        {children}
      </div>
    </StyledControl>
  );
}

Not the answer you're looking for? Browse other questions tagged or ask your own question.