Skip to content

Support <template> as custom element root for class directives, event handlers etc #9149

Open
@willnationsdev

Description

@willnationsdev

Describe the bug

With a Svelte custom element, if you export variables and then bind them to attributes on a root <template> tag, then those attributes are automatically reflected to the host element (i.e. this works):

<svelte:options customElement={{ tag: "my-tab" }} />

<script>
  export let role = "tab"
</script>

<template {role}>
  <svelte:element this="slot" />
</template>

This results in...

<my-tab role="tab">The "[role='tab']" bit is automatically reflected.</my-tab>

If you try to leverage this binding behavior further in ways that follow conventional Svelte patterns, the consistency ends there.

Reproduction

Judging from the initial binding behavior, I would then expect all of the following to also work with the root <template> element.

<svelte:options customElement={{ tag: "my-tab", props: {
  expanded: { reflect: true, type: 'Boolean' }
}}} />

<script>
  export let expanded = false;

  function onClick() {
    expanded = !expanded;
    console.log("my-tab was clicked!");
  }
</script>

<template
  {expanded}
  class:expanded
  aria-expanded={expanded ? "true" : undefined}
  on:click={() => expanded = !expanded}
  on:click={onClick}
  >
  <svelte:element this="slot" />
</template>

The only one that did work for me was the first feature (the {expanded} bit):

  • No class named "expanded" was added or removed from the my-tab element's class attribute in response to state changes.
  • Attempting to do so with attributes that include dashes, like aria-expanded, merely has no effect whatsoever.
  • Attempting to bind an inline event handler actually resulted in some sort of parse error in my experience. All text after the => just ends becoming part of a text node prepended to the element's shadow DOM (in this case, it would contain expanded = !expanded}>, including the curly brace and angled bracket). I could only fix it by switching to a direct function reference; however...
  • Attempting to bind an event handler with a direct function reference never actually triggers the event handler.

As it stands, I am having to fetch a reference to the host element via the new extends feature and do a bunch of reactive statements to handle the desired sync operations, but it would be ideal if I could make use of the declarative and succinct Svelte syntax I'm used to.

Logs

No response

System Info

System:
    OS: Windows 10 10.0.22000
    CPU: (20) x64 13th Gen Intel(R) Core(TM) i9-13900H
    Memory: 16.82 GB / 63.66 GB
  Binaries:
    Node: 20.2.0 - C:\Program Files\nodejs\node.EXE
    Yarn: 1.22.19 - C:\Program Files\nodejs\yarn.CMD
    npm: 9.6.6 - C:\Program Files\nodejs\npm.CMD
    pnpm: 8.6.9 - C:\Program Files\nodejs\pnpm.CMD
  Browsers:
    Edge: Spartan (44.22000.120.0), Chromium (115.0.1901.203)
    Internet Explorer: 11.0.22000.120
  npmPackages:
    svelte: ^4.0.0 => 4.1.1

Severity

annoyance

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions