'Why a click on svg element is not captured by node contains method?

I have a basic modal with a svg icon within its html structure. I want to detect clicks outside the modal so I can close it. The code to do so is something like the following:

document.addEventListener('mousedown', (e) => {
  if(modal.contains(e.target)) return;
  closeModal();
});

However, when I click on the svg (which inside the modal), the click event is considered to be outside the modal and I can't figure out why. To have it work as I want, I have to do pointer-events: none to the svg element.

The simplified html structure looks like this:

  <div class="modal">
    <div class="css-t7awl3">
      <form>
        <div class="css-1incodr">
          <label class="css-g2pzhe" for="previousPassword">
            Mot de passe actuel<span class="css-5fl39m">*</span>
          </label>
        <div class="css-1xm32e0">
          <input
            class="css-17lan49"
            type="text"
            placeholder="**********"
            name="previousPassword"
          />
            <div id="test-input-toggle-password-visibility" class="css-1vw18kh">
              <svg
                height="16"
                viewBox="0 0 32 32"
                fill="none"
                xmlns="http://www.w3.org/2000/svg"
              />
            </div>
          </div>
        </div>
        <button class="css-1pnrrva" disabled="">
          Valider
        </button>
      </form>
    </div>
  </div>

Edit: Maybe it can help if I paste the actual react code here, as I am not sure if it's and issue with svg elements rather than my code.

    export const PasswordInput = React.forwardRef<HTMLInputElement, PasswordInputProps>(({
  placeholder,
  name,
  id,
  disabled,
  className,
}: PasswordInputProps, ref) => {
  const [showPassword, setShowPassword] = useState(false);

  const DisplayedPicto = showPassword ? ClosedEyePicto : EyePicto;

  return (
    <Container className={className}>
      <CustomInput
        className={className}
        type={showPassword ? 'text' : 'password'}
        placeholder={placeholder}
        name={name}
        disabled={disabled}
        ref={ref}
        id={id}
      />
      <StyledSuffix
        onMouseLeave={() => setShowPassword(false)}
        onMouseDown={() => setShowPassword(true)}
        onMouseUp={() => setShowPassword(false)}
        id='test-input-toggle-password-visibility'
      >
        <DisplayedPicto height={16} />  //svg element
      </StyledSuffix>
    </Container>
  );
});


Solution 1:[1]

I just understood what was going on, and it has nothing to do with svgs. What's happening is that when I click on the svg, first a 'mousedown' event is triggered. Remember that a 'click' event consists of 2 events following each other: first a 'mousedown' and then a 'mouseup'.

So with the 'mousedown' event on the svg, the onMouseDown event handler, on the component StyledSuffix surrounding my svg, is triggered. That leads to the svg on the dom actually changing from EyePicto to ClosedEyPicto (const DisplayedPicto = showPassword ? ClosedEyePicto : EyePicto;). Therefore, the modal no longer 'contains' the original svg since it has changed!

A solution is to listen to the click with the event 'click' rather than with 'mousedown'. Because the orginal svg (EyePicto) will only register the 'mousedown' event and not then 'mouseup' because it disappeared from the dom between the 2.

Solution 2:[2]

I had this same problem, despite using a click event as OP suggested. The solution that finally worked for me was to apply pointer-events: none to the svg element inside the button, so that the SVG was never returned as the event target.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Faris Marouane
Solution 2 Arajay