svelte icon indicating copy to clipboard operation
svelte copied to clipboard

SVG elements not displaying when passed as slot content

Open mhkeller opened this issue 3 years ago • 3 comments

Describe the bug

If you have an SVG component that allows you to pass in child content as a slot like so...

<svg width="150" height="50">
  <slot></slot>
</svg>

...no content will display if you pass in content wrapped in an anchor tag like so...

<script>
  import Svg from './Svg.svelte';
</script>

<Svg>
  <a href="https://example.com">
    <path d="M0,0L50,50Z" stroke="#000" stroke-width="2"></path>
  </a>
</Svg>

However, if you don't use an SVG component and simply write plain SVG, everything displays fine.

<svg width="150" height="50">
  <a href="https://example.com">
    <path d="M0,0L50,50Z" stroke="#000" stroke-width="2"></path>
  </a>
</svg>

It seems that something is breaking when the child content gets sent through the slot.

Reproduction

https://svelte.dev/repl/08270639a2154ea18192cbdf30861da8?version=3.49.0

Logs

No response

System Info

REPL

Severity

blocking all usage of svelte

mhkeller avatar Aug 25 '22 06:08 mhkeller

Very similar to https://github.com/sveltejs/svelte/issues/7450 and https://github.com/sveltejs/svelte/issues/7563 (basically a duplicate I would say)

Array.from(document.querySelectorAll('a')).map(e => e.namespaceURI)

0: "http://www.w3.org/2000/svg" <=============
1: "http://www.w3.org/1999/xhtml" 

Prinzhorn avatar Aug 25 '22 07:08 Prinzhorn

If you can't add <svelte:options namespace="svg"> into all nested components you can use this action :

<script lang="ts">

  /** Ensure all "a" elements inside an SVG node belong to the correct namespace */
  function ensureSVGA(node: SVGSVGElement) {
    const namespaceSVG = 'http://www.w3.org/2000/svg'
    const links = node.querySelectorAll<HTMLLinkElement>('a')
    for (const link of links) {
      if (link.namespaceURI === namespaceSVG) continue
      const a = document.createElementNS(namespaceSVG, 'a')
      for (const { name, value } of link.attributes) {
        a.setAttribute(name, value)
      }
      a.append(...link.children)
      link.insertAdjacentElement('beforebegin', a)
      link.remove()
    }
  }
</script>

<svg use:ensureSVGA>
  <slot />
</svg>

peufo avatar Dec 22 '22 06:12 peufo

I've found a similar problem when loading SVG defs via a svelte:fragment when the svg element is also a slot and the parent element is an HTML div.

Here's the repro: https://svelte.dev/repl/2647caa253d648428dfd96b8d1d6b974?version=3.55.0

If you comment out the main wrapper div, svelte is able to detect the SVG namspace and the gradient shows up. If you wrap the whole thing in a div, it will interpret <linearGradient> an HTML tag. I tried adding <svelte:options namespace="svg"/> to the Svg.svelte component but that has no effect.

mhkeller avatar Dec 24 '22 05:12 mhkeller

@mhkeller can you <svelte:options namespace="svg"/> in nested component? https://svelte.dev/repl/822371299ddf401885091c66715c66f7?version=3.55.0

peufo avatar Dec 25 '22 13:12 peufo

Yes that it as a viable workaround for now – similar to adding the xmlns attribute inline to the linearGradient tag. It would still be nice if Svelte detected that the children of the defs object were svg elements, though.

mhkeller avatar Dec 25 '22 16:12 mhkeller