npm package: @lume/element
Description: Create Custom Elements with reactivity and automatic re-rendering.
Author: Joe Pea
Latest version: 0.17.1

# @lume/element <!-- omit in toc -->

Easily and concisely write Custom Elements with simple templates and reactivity.

Use the custom elements on their own in plain HTML or vanilla JavaScript, or in
Vue, Svelte, Solid.js, Stencil.js, React, and Preact, with full type checking,
autocompletion, and intellisense in all the template systems of those
frameworks, in any IDE that supports TypeScript such as VS Code.

Write your elements once, then use them in any app, with a complete developer
experience no matter which base component system your app uses.

<h4><code><strong>npm install @lume/element</strong></code></h4>

> :bulb:**Tip:**
>
> If you are new to Custom Elements, first [learn about the basics of Custom
> Element
> APIs](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements)
> available natively in browsers. Lume Element simplifies the creation of Custom
> Elements compared to writing them with vanilla APIs, but sometimes vanilla
> APIs are all that is needed.

# Live demos <!-- omit in toc -->

- [Lume 3D HTML](https://lume.io) (The landing page, all of Lume's 3D elements, and the live code editors themselves in the doc pages)
- [CodePen, html template tag, no decorators](https://codepen.io/trusktr/pen/zYeRqaR)
- [Stackblitz with Babel, JSX, decorators](https://stackblitz.com/edit/webpack-webpack-js-org-wdzlbb?file=src%2Findex.js)
- [Stackblitz with Vite, JSX, TypeScript, decorators](https://stackblitz.com/edit/solidjs-templates-wyjc1i?file=src%2Findex.tsx)
- [Solid Playground, TypeScript, no decorators](https://playground.solidjs.com/anonymous/0cc05f53-b665-44d2-a73c-1db9eb992a4f)

# Table of contents <!-- omit in toc -->

- [Cliché Click Counter Example](#cliché-click-counter-example)
- [Intro](#intro)
- [Basic Usage](#basic-usage)
  - [Create custom elements](#create-custom-elements)
  - [Easily create and manipulate DOM](#easily-create-and-manipulate-dom)
  - [Create functional components](#create-functional-components)
  - [Using functional components inside custom elements](#using-functional-components-inside-custom-elements)
  - [Functional components vs custom elements](#functional-components-vs-custom-elements)
- [API](#api)
  - [`Element`](#element)
    - [`static elementName`](#static-elementname)
    - [`template`](#template)
    - [`static css`](#static-css)
    - [`css`](#css)
    - [`static observedAttributes`](#static-observedattributes)
    - [`static observedAttributeHandlers`](#static-observedattributehandlers)
      - [events with `static observedAttributeHandlers`](#events-with-static-observedattributehandlers)
    - [`attributeChangedCallback`](#attributechangedcallback)
    - [`connectedCallback`](#connectedcallback)
    - [`disconnectedCallback`](#disconnectedcallback)
    - [`adoptedCallback`](#adoptedcallback)
    - [`createEffect` (prefer `@effect` decorator instead)](#createeffect-prefer-effect-decorator-instead)
    - [`static autoDefine`](#static-autodefine)
    - [`static defineElement`](#static-defineelement)
    - [`hasShadow`](#hasshadow)
    - [`templateRoot`](#templateroot)
    - [`shadowOptions`](#shadowoptions)
    - [`styleRoot`](#styleroot)
  - [Decorators](#decorators)
    - [`@element`](#element-1)
    - [`@attribute`](#attribute)
      - [Custom attribute handlers](#custom-attribute-handlers)
    - [`@stringAttribute`](#stringattribute)
    - [`@numberAttribute`](#numberattribute)
    - [`@booleanAttribute`](#booleanattribute)
    - [`@eventAttribute`](#eventattribute)
    - [`@jsonAttribute`](#jsonattribute)
    - [`@signal`](#signal)
    - [`@memo`](#memo)
    - [`@effect`](#effect)
    - [`@noSignal`](#nosignal)
- [Runtime Type Checking](#runtime-type-checking)
- [TypeScript](#typescript)
  - [Attribute property types](#attribute-property-types)
  - [Solid.js JSX expressions](#solidjs-jsx-expressions)
  - [Type definitions for custom elements in frameworks](#type-definitions-for-custom-elements-in-frameworks)
    - [In Solid JSX (in Lume Elements)](#in-solid-jsx-in-lume-elements)
    - [In React JSX](#in-react-jsx)
    - [In Preact JSX](#in-preact-jsx)
    - [In Angular](#in-angular)
    - [In Vue](#in-vue)
    - [In Svelte](#in-svelte)
    - [In Stencil.js JSX](#in-stenciljs-jsx)
  - [Setter types in framework templates](#setter-types-in-framework-templates)
- [Resources](#resources)
- [Status](#status)

# Cliché Click Counter Example

Define a `<click-counter>` element:

```js
import {Element, element, numberAttribute} from '@lume/element'
import html from 'solid-js/html'
import {createEffect} from 'solid-js'

@element
class ClickCounter extends Element {
  @numberAttribute count = 0

  template = () => html`<button onclick=${() => this.count++}>Click! (count is: ${() => this.count})</button>`

  css = `
		button {
			border: 2px solid deeppink;
			margin: 5px;
		}
	`

  // Log the `count` any time it changes:
  @effect logCount() {
    console.log('count is:', this.count)
  }
}
```

Use the `<click-counter>` in a plain HTML file:

```html
<body>
  <click-counter></click-counter>

  <!-- Manually set the `count` value in HTML: -->
  <click-counter count="100"></click-counter>

  <script type="module">
    import './click-counter.js'

    // Manually set the `count` value in JS:
    document.querySelector('click-counter').count = 200
  </script>
</body>
```

[Example on CodePen](https://codepen.io/trusktr/pen/zYeRqaR) (without decorators)

> [!Note]
> Once decorators land in browsers, the above example will work out of the box
> as-is without compiling, but for now a compile step is needed for using decorators.
>
> JSX can be used for the `template` of an element, but that will always require
> compiling:
>
> ```jsx
> template = () => <button> Click! (count is: {this.count}) </button>
> ```
>
> Further examples below show how to define elements without decorators or JSX, which
> works today without a compiler.

Use the `<click-counter>` in another element's `template`,

```js
import {Element, element} from '@lume/element'
import html from 'solid-js/html'
import {signal} from 'classy-solid'

@element('counter-example')
class CounterExample extends Element {
  @signal count = 50 // Not an attribute, only a signal.

  template = () => html`<click-counter count=${() => this.count}></click-counter>`
}

document.body.append(new CounterExample())
```

Use `<click-counter>` in a plain function component (i.e. a Solid.js component):

```js
// At this point this, this boils down to plain Solid.js code (`@lume/element` comes
// with `solid-js`)

import {createSignal} from 'solid-js'
import html from 'solid-js/html'

function CounterExample() {
  const [count, setCount] = createSignal(50)

  return html`<click-counter count=${count()}></click-counter>`
}

document.body.append(CounterExample())
```

# Intro

[Custom](https://developers.google.com/web/fundamentals/web-components/customelements)
[Elements](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements)
(also known as [Web
Components](https://developer.mozilla.org/en-US/docs/Web/Web_Components) are a
feature of browsers that allow us to define new HTML elements that the browser
understands in the same way as built-in elements like `<div>` or `<button>`.
They are very useful for organizaing web apps into separately and sometimes
re-usable pieces (elements).

If that flew over your head then you might first want to try a [beginner HTML
tutorial](https://htmldog.com/guides/html/beginner/). You will also need to
some basic knowledge of
[JavaScript](https://www.google.com/search?q=JavaScript%20for%20absolute%20beginners).

`@lume/element` provides a set of features that make it easier to manipulate
elements and to define new custom elements and easily compose them together
into an application.

With `@lume/element` we can create custom elements that have the following
features:

- Reactive instance properties that receive values from element attributes of the same name (but dash-cased).
- Declarative templates, written with JSX or `html` template tag, that automatically update when reactive instance properties are used in the templates.
- Scoped styling with or without a ShadowRoot.
- Decorators for concise element definitions.

<details><summary>A more detailed feature description:</summary>

-
- Element attributes are defined with `@attribute` decorators on class fields.
  - Class fields decorated with `@attribute` receive values from HTML attributes
    (with the same name but dash-cased) when the HTML attribute values change.
  - Decorators are powered by
    [`classy-solid`](https://github.com/lume/classy-solid): utilities for using
    [Solid.js](https://solidjs.com) patterns on `class`es, such as the `@signal`
    decorator for making class fields reactive (backed by Solid signals).
    Decorators from `@lume/element` compose the `@signal` decorator to make
    properties be reactive.
  - As decoraators are not out in browsers yet, an alternative non-decorator API
    can be used, which does not require a build.
- Each custom element can have an HTML template that automatically updates the
  DOM when any reactive variables used in the template changes.
  - Templates can be written in the form of HTML-like markup inside JavaScript
    called [JSX](https://facebook.github.io/jsx), specifically the JSX flavor from
    Solid.js. This requires a build step.
  - Templates can also be written using Solid's `html` template string tag,
    which does not require a build step.
  - When a template updates, the whole template does not re-run, only the part
    of the template where a variable changed is updated, and only that particular
    piece of
    [DOM](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model)
    gets modified. There is no (expensive) virtual DOM diffing.
  - Because changes to HTML attributes on an element map to properties backed by
    signals on the element instance, this will cause the custom element's template
    to update if its template uses those properties.
- Custom element styles are automatically scoped, similar to Vue, Svelte, and
  other systems with style scoping.
  - If you're familiar with custom elements, you know that the browser gives
    this to us for free when using ShadowDOM.
  - If you opt an element out of having a ShadowRoot, `@lume/element` applies
    its own style scoping for the element at the nearest root node.

</details>

<details><summary><h2>Install and Setup</h2></summary>

> **STUB:** This section needs expansion, but should be enough for anyone
> familiar with common build tooling in the webdev/JS ecosystem. Contributions
> very welcome!

<details><summary><h3>CDN method (easiest, no compiler or command line needed)</h3></summary>

Follow the guide on [installing `lume` from
CDN](https://docs.lume.io/guide/install/?id=cdn-easiest), but simply replace
`lume` with `@lume/element`. The process is otherwise the same.

The examples here in the README follow the CDN approach to keep things simple,
[for example](https://codepen.io/trusktr/pen/zYeRqaR).

> [!Note]
> Decorator syntax and JSX syntax are both not supported with this install
> method as it does not use a build step. In the near future, decorators will be
> out natively in browsers and JS engines (but not JSX).

</details>

<details><summary><h3>Local install with build</h3></summary>

This assumes some familiarity with command lines and JavScript build tools.

First make sure Node.js is installed so that we have the `npm` package manager avaiable.

Install the `@lume/element` package using the following in a terminal:

```sh
npm install @lume/element
```

In order to use decorators today (recommended), we need to compile
them with TypeScript 5 or higher (recommended, even if writing JS and not TS, as
decorator syntax works out of the box with TypeScript), or use the [Babel
compiler](http://babeljs.io) with
[`@babel/plugin-proposal-decorators`](https://babeljs.io/docs/babel-plugin-proposal-decorators).

```sh
npm install --save-dev typescript
# or
npm install --save-dev @babel/cli @babel/core @babel/plugin-proposal-decorators
```

If using TypeScript, set `allowJs` in `tsconfig.json` to allow compiling JS files, f.e.:

```js
{
	"compilerOptions": {
		"allowJs": true,
		"outDir": "dist"
	},
	"include": ["./src/**/*"]
}
```

and running `npx tsc`. See the [TypeScript](#typescript) section below for configuring JSX
types for various frameworks (Solid, React, Preact, etc).

If using Babel, add the decorators plugin to `.babelrc`, f.e.

```json
{
  "plugins": ["@babel/plugin-proposal-decorators"]
}
```

and running `npx babel src --out-dir dist`.

If you'd like to use the HTML-like markup inside JavaScript known as "JSX",
instead of [Solid's `html` template
tag](https://github.com/solidjs/solid/tree/main/packages/solid/html) which
requires no build, the
[`babel-preset-solid`](https://npmjs.com/babel-preset-solid) package will also
be needed:

```sh
npm install --save-dev babel-preset-solid
```

Configure Babel to use the preset inside your project's `.babelrc` file or in
your Webpack `babel-loader` config:

```json
{
  "plugins": ["@babel/plugin-proposal-decorators"],
  "presets": ["solid"]
}
```

> [!Note]
> If compiling decorators with TypeScript, Babel is still needed for Solid JSX
> because TypeScript does not compile JSX into Solid.js format, only into React
> format. Either compile decorators with TypeScript and have Babel compile
> JSX in a second step, or compile both decorators and JSX with Babel in a
> single step.

</details>

</details>

# Basic Usage

## Create custom elements

A great way to create re-usable components is to create Custom Elements. The
advantage of custom elements is that they follow web standards, and therefore
they can be used in any web application and manipulated by any DOM
manipulation libraries like [jQuery](https://jquery.com/),
[React](https://reactjs.org), [Vue](https://vuejs.org), [Svelte](https://svelte.dev/), or
[Angular](https://angular.io), [Solid.js](https://solidjs.com), and all the rest.

The following is a custom element definition with a reactive property
`firstName` that also accepts values from an attribute named `first-name` (the
property name is converted to dash-case for the attribute name).

> [!Note]
> Deorators and JSX are not required. The non-decorator and non-JSX forms are
> shown further below. The [Decorators](#decorators) section has details on each
> decorator available.

```jsx
import {
  Element, // A base class for LUME custom elements
  element, // A decorator for defining elements, required for reactive JS properties.
  attribute, // A property decorator to map attributes to properties, and that makes properties reactive
  eventAttribute, // A property decorator that causes values from the attributes/properties to be set as an event listener for the respective event, just like built-in "onclick" attributes/properties.
  css, // A no-op identity template tag function (useful to enable CSS syntax highlighting in various text editors)

  // Decorators for defining specific attributes types (string values are coerced to the respective JS type):
  stringAttribute,
  numberAttribute,
  booleanAttribute,
} from '@lume/element'

@element('greeting-card') // defines the element tag name
class GreetingCard extends Element {
  // The firstName property will be a reactive variable, and any value from an
  // attribute named 'first-name' will be mapped back to this property (the
  // attribute name is the dash-case version of the property name).
  @attribute firstName = 'Roger'

  // Specific attribute types (the JS property will always be of the specified
  // type):
  @stringAttribute someString = ''
  @numberAttribute someNumber = 123
  @booleanAttribute someBoolean = false

  // Define event properties to specify which events the element dispatches.
  // Besides being useful for type definitions in JSX, these properties work
  // like the builtin event properties such as "onclick" (JS property or DOM
  // attribute code string).
  //
  // For example, a user can write `el.onhello = event => {...}` just like
  // they can do with builtin event properties like `el.onclick = event =>
  // {...}`.
  @eventAttribute onhello = null

  // Define a DOM tree that we want rendered on screen by providing a
  // `template`. The `template` should be a function that returns a DOM
  // element or array of DOM elements (which we can create with JSX, or with
  // an `html` template tag, or with plain JS). The DOM content will be, by
  // default, appended into the ShadowRoot of our custom element.
  //
  // To take advantage of reactivity in our template, simply interpolate
  // properties that were decoratored with an attribute decorator or defined
  // with `static observedAttributeHandlers` into the template.
  //
  // Here, any time the `.firstName` property's value changes, the DOM will be
  // automatically updated.
  template = () => (
    <div>
      <span>
        Hello <i>{this.firstName}</i>
      </span>
      {/* Children of a <greeting-card> element get rendered here. */}
      <slot></slot>
    </div>
  )

  // Apply styling to this element and its content with the static `css` property.
  // Because the property is static, this style is re-used across all instances of the element.
  // Styles are by default scoped to the element's content due to ShadowRoot style encapsulation.
  static css = css`
    :host {
      background: skyblue;
    }
    div {
      color: pink;
    }
  `

  // For instance-specific styling, use the non-static `css` property.  This
  // style has higher precedence over styles in the `static css` property.  In
  // this example, the divs in each instance of this element will have borders
  // of random sizes.  Note, `css` is currently not reactive, it runs once
  // initially, so using a reactive property in the css will currently not
  // update the style.
  css = css`
    div {
      border: ${Math.random() * 5}px solid teal;
    }
  `

  // connectedCallback is a method that fires any time this custom element is
  // connected into a web site's live DOM tree.
  connectedCallback() {
    super.connectedCallback() // Don't forget to call the super method!

    // Once the element is connected, let's update the `.firstName` prop after a
    // couple of seconds, and we'll see the change on screen.
    setTimeout(() => (this.firstName = 'Zaya'), 2000)

    // And show that it works by setting HTML attributes too, two seconds later.
    setTimeout(() => this.setAttribute('first-name', 'Raquel'), 4000)

    // The element may dispatch events.
    setTimeout(() => this.dispatchEvent(new Event('hello')), 3000)
  }

  // Use the disconnectedCallback to clean anything up when the element is removed from the DOM.
  disconnectedCallback() {
    super.disconnectedCallback()
    // ... clean up ...
  }
}
```

Now we can use it in the HTML of a web site, or in the template of another
component:

```jsx
<greeting-card first-name="Raynor" onhello={() => console.log(event.target.firstName, 'says hello')}></greeting-card>
```

[Example on CodePen](https://codepen.io/trusktr/pen/WNqVWaL?editors=1011) (without decorators, with Solid's `html` template tag instead of JSX)

Inside an element's `template()` method we can assign bits and pieces of DOM to
variables, and we can also use other custom elements and functional components.
Similary, the `css` property can also be a method:

```jsx
@element('greeting-card')
class GreetingCard extends Element {
  // ... same as before ...

  // This time 'template' is a method that has some logic, and refers to pieces of DOM using variables.
  template() {
    const greeting = (
      <span>
        Hello <i>{this.firstName}</i>
      </span>
    )

    console.log(greeting instanceof HTMLSpanElement) // true

    // One piece of DOM can be composed into another:
    const result = <div>{greeting}</div>

    console.log(result instanceof HTMLDivElement) // true

    return result
  }

  // ... same as before ...

  css() {
    const thickness = Math.random() * 5

    return css`
      div {
        border: ${thickness}px solid teal;
      }
    `
  }

  // ... same as before ...
}
```

## Easily create and manipulate DOM

Lume Element is built on Solid.js, so we can also use Solid.js at the top level
of a module for example. This sort of code can be useful in the `template` of a
custom element, or the body of a functional component.

```jsx
import {createSignal} from 'solid-js'

// Make a signal with an initial value of 0.
const [count, setCount] = createSignal(0)

// Increment the value of count every second.
setInterval(() => setCount(count() + 1), 1000)

// Create a <div> element with a child <h1> element. The data-count attribute
// and the text content of the <h1> element will automatically be updated whenever
// the count variable changes.
const el = (
  <div>
    <h1 data-count={count()}>The count is: {count()}</h1>
  </div>
)

// The result stored in the `el` variable is a `<div>` element! For example,
// we can call regular DOM APIs like `setAttribute` on it.
el.setAttribute('foo', 'bar')

// Append the element to the body of the page, and now we'll see a
// continually-updating message on the screen.
document.body.append(el)
```

[Example on CodePen](https://codepen.io/trusktr/pen/bGPXmEJ) (with Solid's `html` template tag instead of JSX)

## Create functional components

Continuing with the same `count` variable from the previous example, here's how
to compose DOM trees using "functional components". This is plain Solid.js, and
functional components (Solid.js components) can be used in a custom element's
`template`.

A functional component is a function that simply returns one or more DOM
elements. JSX expressions and the `html` template string tag both return the top
level elements defined in the markup.

```jsx
// This is just plain Solid.js code. See https://solidjs.com for more on writing
// functional components.

// This Label functional component uses the empty <></> tag to contain more than
// one root-level child, and the return value will be an array of DOM nodes.
const Label = props => (
  <>
    <div>{props.greeting}</div>
    {props.children}
  </>
)

// This Greeting functional component nests the content of the Label component
// in its template, and the <div> inside the <Label> gets distributed to the
// part of the Label component where we see `{props.children}`.
const Greeting = () => (
  <section>
    <Label greeting={'hello (' + count() + ')'}>
      <div>John</div>
    </Label>
  </section>
)

// The `Greeting` function only needs to be called once, and it will return a
// reference to an element or multiple elements. The `Greeting` function does
// NOT need to be called over and over to re-render like in some other libraries
// (for example React). That's what makes all of this simple and clean. The
// reactivity inside the component templates takes care of updating content of
// the created DOM tree.
// Here `elem` will be a reference to an actual `<section>` element that the
// `Greeting` function returned.
const elem = Greeting()

// It's just DOM! Use regular DOM APIs to append the element to the body.
document.body.append(elem)
```

[Example on CodePen](https://codepen.io/trusktr/pen/eYwqPzz) (with Solid's `html` template tag instead of JSX)

## Using functional components inside custom elements

Continuing from above, here's a custom element that re-uses the `Greeting`
component. This shows that any regular Solid.js component can be
used in the `template` of a custom element made with `@lume/element`:

```jsx
@element // The 'cool-element' name is implied from the constructor name (dash-cased)
class CoolElement extends Element {
  template = () => (
    <>
      <h2>Here's a greeting:</h2>
      <Greeting />
    </>
  )
}

document.body.insertAdjacentHTML('beforeend', `<cool-element></cool-element>`)
```

[Example on CodePen](https://codepen.io/trusktr/pen/bGPXmRX) (without decorators, with Solid's `html` template tag instead of JSX)

## Functional components vs custom elements

Writing function components can sometimes be simpler, but functional components
do not have features that custom elements have such as native style scoping
(style scoping with function components requires an additional Solid.js library
or compiler plugin), etc.

In contrast to custom elements, functional components only work within the
context of other functional components made with Solid.js or custom elements
made with `@lume/element`. Functional components are not compatible with HTML,
React, Vue, Angular, Svelte, or all the other web libraries and frameworks. For
portability across applications and frameworks, this is where custom elements
shine.

Custom elements are also debuggable in a browser's element inspector _out of the
box_, while functional components are not (functional components require
devtools plugins for each browser, if they even exist). See Lume's [Debugging
guide](https://docs.lume.io/guide/debugging) for an example.

# API

## `Element`

A base class for custom elements made with `@lume/element`.

> [!Note]
> The `Element` class from `@lume/element` extends from `HTMLElement`.
>
> Safari does not support customized built-ins, and neither does
> `@lume/element`, so at the moment we do not support extending from other classes
> such as `HTMLButtonElement`, etc.

The `Element` class provides:

### `static elementName`

The default tag name of the elements that are instances of this class.

This field is optional: if not provided the element tag name will default to the
dash-cased version of the class name. For example instances of a class
`CoolElement` will be written with the `<cool-element>` tag in HTML.

When using the [`@element`](#element) decorator, this field's value
will be used if an element name is not passed to the decorator.

```js
@element
class SomeEl extends LumeElement {
  static elementName = 'some-el'
}

console.log(document.createElement('some-el') instanceof SomeEl) // true

@element
class CoolEl extends LumeElement {
  // static elementName omitted
}

console.log(document.createElement('cool-el') instanceof CoolEl) // true

@element
class SaucyElement extends LumeElement {
  static elementName = 'juicy-el'
}

console.log(document.createElement('juicy-el') instanceof SaucyElement) // true
```

[Example on CodePen](https://codepen.io/trusktr/pen/ZEdgMZY)

### `template`

A subclass can define a `.template` that returns a DOM node, and this DOM node
will be appened into the element's `ShadowRoot` by default, or to the element
itself if `.hasShadow` is `false`.

One way to write a `template` is using [Solid
JSX](https://www.solidjs.com/tutorial/introduction_jsx) syntax (this will always
require a build step).

```js
import {Element} from '@lume/element'
import {createSignalFunction} from 'classy-solid' // a small wrapper around Solid's createSignal that allows reading and writing from the same function.

class CoolElement extends Element {
  count = createSignalFunction(100)

  template = () => (
    <div>
      <span>The count is: {this.count()}!</span>
    </div>
  )
  // ...
}

customElements.define('cool-element', CoolElement)
```

Another way to write a `template` is using Solid's `html` template string tag
(which does not require a build step). Using the following `template`, the
example can run in a browser without a compile step (note, we're not using
decorators yet):

```js
// ...
template = () => html`
  <div>
    <span>The count is: ${this.count}!</span>
  </div>
`
// ...
```

[Example on CodePen](https://codepen.io/trusktr/pen/xxovyQW) (with `html` template tag instead of JSX)

> [!Note]
> When `count` changes, the template updates automatically.

We can also manually create DOM any other way, for example here we make and
return a DOM tree using DOM APIs, and using a Solid effect to update the element
when `count` changes (but we could have used React or jQuery, or anything
else!):

```js
// ...same...

import {createEffect} from 'solid-js'

// ...same...

// Replace the previous `template` with this one:
template = () => {
  const div = document.createElement('div')
  const span = document.createElement('span')
  div.append(span)

  createEffect(() => {
    // Automatically set the textContent whenever `count` changes (this is a
    // conceptually-simplified example of what Solid JSX compiles to).
    span.textContent = `The count is: ${this.count()}!`
  })

  return div
}

// ...same...
```

[Example on CodePen](https://codepen.io/trusktr/pen/ExBqdMQ)

### `static css`

Use the _static_ `css` field to define a CSS string for styling all instances of
the given class. A static property allows `@lume/element` to optimize by sharing
a single `CSSStyleSheet` across all instances of the element, which could be
beneficial for performance if there are _many thousands_ of instances.

```js
import {Element} from '@lume/element'

class CoolElement extends Element {
  template = () => <span>This is some DOM!</span>

  // Style is scoped to our element, this will only style the <span> inside our element.
  static css = `
    span { color: violet; }
  `
}

customElements.define('cool-element', CoolElement)
```

[Example on CodePen](https://codepen.io/trusktr/pen/OJeKBKP) (with `html` template tag instead of JSX)

The `static css` property can also be a function:

```js
// ...

class CoolElement extends Element {
  // ...
  static css = () => {
    const color = 'limegreen'

    return `
      span { color: ${color}; }
    `
  }
  // ...
}
```

[Example on CodePen](https://codepen.io/trusktr/pen/GRbVwzj) (with `html` template tag instead of JSX)

> :bulb:**Tip:**
>
> Use the `css` identity template tag to enable syntax highlighting and code formatting in some IDEs:

```js
import {css} from '@lume/element'
// ...

class CoolElement extends Element {
  // ...
  static css = css`
    span {
      color: cornflowerblue;
    }
  `
  // ...
}
```

### `css`

Use the _non-static_ `css` property to define styles that are applied _per
instance_ of the given element. This is useful for style that should differ
across instances. This will not be as optimized as `static css` will be because
it will create one stylesheet per element instance, but the performance
difference will not matter for most use cases.

```js
import {Element, css} from '@lume/element'

class CoolElement extends Element {
  template = () => <span>This is some DOM!</span>

  // A random color per instance.
  #color = `hsl(calc(${Math.random()} * 360) 50% 50%)`

  // Style is scoped to our element, this will only style the <span> inside our element.
  css = css`
    span {
      color: ${this.#color};
    }
  `
}
```

[Example on CodePen](https://codepen.io/trusktr/pen/NWZQEJa) (with `html` template tag instead of JSX)

### `static observedAttributes`

Nothing new here, this is simply a part of the browser's [native Custom Elements
`static observedAttributes` API](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#responding_to_attribute_changes).
It defines which attributes will be observed. From the previous example:

```js
class CoolElement extends Element {
  static observedAttributes = ['foo', 'bar']
  // ...
}
```

Note! Although `static observedAttributes` works, it is recommended to use the
`static observedAttributeHandlers` property instead:

### `static observedAttributeHandlers`

This is an alternative to attribute decorators (recommended, see the
[Decorators](#decorators) docs below), and will be removed after decorators
are supported natively in JS engines.

As an alternative to `static observedAttributes`, and mainly for non-decorator
users (because not all JS engines support them yet at time of writing this),
observed attributes can be defined with `static observedAttributeHandlers`, a
map of attribute names to attribute handlers. This requires using the `@element`
decorator (calling it as a plain function for non-decorator usage). This will
map attributes to JS properties and make the JS properties reactive.

`static observedAttributeHandlers` is an object where each key is a property
name to be associated with an attribute, and each value is an object with the
following shape:

<a id="attributehandler"></a>

```ts
/**
 * Defines how values are mapped from an attribute to a JS property on a custom
 * element class.
 */
export type AttributeHandler<T = any> = {
  // TODO The `to` handler currently does nothing. In the future, if there is demand
  // for it, this will be for property-to-attribute reflection.
  to?: (propValue: T) => string | null

  /**
   * Define how to deserialize an attribute string value on its way to the
   * respective JS property.
   *
   * If not defined, the attribute string value is passed to the JS property
   * untouched.
   *
   * **Default when omitted:** `value => value`
   */
  from?: (AttributeValue: string) => T

  /**
   * A side effect to run when the value is set on the JS property. It also
   * runs on with the initial value. Avoid this if you can, and instead use
   * effects. One use case of this is to call addEventListener with event
   * listener values, just like with native `.on*` properties.
   *
   * **Default when omitted:** `() => {}` (no sideeffect)
   */
  sideEffect?: (instance: Element, prop: string, propValue: T) => void

  /**
   * @deprecated - Define a field with the initial value instead of providing
   * the initial value here. When decorators land in browsers, this will be
   * removed.
   *
   * The default value that the respective JS property should have when the
   * attribute is removed.
   *
   * If this is not specified, and the respective class field is defined, it
   * will default to the initial value of the class field.  If this is
   * specified, it will take precedence over the respective field's initial
   * value. This should generally be avoided, and the class field initial
   * value should be relied on as the source of the default value.
   *
   * When defined, an attribute's respective JS property will be set to this
   * value when the attribute is removed. If not defined, then the JS property
   * will always receive the initial value of the respective JS class field or
   * `undefined` if the field was not defined (that's the "initial value" of
   * the field), when the attribute is removed.
   *
   * **Default when omitted:** the value of the respective class field, or
   * `undefined` if the field was not defined.
   */
  default?: T

  /**
   * Whether to convert the property name to dash-case for the attribute name.
   * This option is ignore if the `name` option is set.
   *
   * The default is `true`, where the attribute name will be the same as the
   * property name but dash-cased (and all lower case). For example, `fooBar`
   * becomes `foo-bar` and `foo-bar` stays `foo-bar`.
   *
   * If this is set to `false`, the attribute name will be the same as the
   * property name, but all lowercased (attributes are case insensitive). For
   * example `fooBar` becomes `foobar` and `foo-bar` stays `foo-bar`.
   *
   * Note! Using this option to make a non-standard prop-attribute mapping
   * will result in template type definitions (f.e. in JSX) missing the
   * customized attribute names and will require custom type definition
   * management.
   *
   * **Default when omitted:** `true`
   */
  dashcase?: boolean

  /**
   * The name of the attribute to use. Use of this options bad practice to be
   * avoided, but it may be useful in rare cases.
   *
   * If this is not specified, see `dashcase` for how the attribute name is
   * derived from the property name.
   *
   * Note! Using this option to make a non-standard prop-attribute mapping
   * will result in template type definitions (f.e. in JSX) missing the
   * customized attribute names and will require custom type definition
   * management.
   *
   * **Default when omitted:** the attribute name derived from the property
   * name, converted to dash-case based on the `dashcase` option.
   */
  name?: string

  /**
   * Whether to suppress warnings about the attribute attribute name clashes
   * when not using default `dashcase` and `name` settings. This is
   * discouraged, and should only be used when you know what you're doing,
   * such as overriding a property that has `dashcase` set to `false` or
   * `name` set to the same name as the attribue of another property.
   *
   * **Default when omitted:** `false`
   */
  noWarn?: boolean
}
```

Here's an example of an element definition with no decorators, with
HTML attributes mapped to same-name JS properties:

```js
import {Element, element} from '@lume/element'

element(
  class CoolElement extends Element {
    static elementName = 'cool-element'

    static observedAttributeHandlers = {
      foo: {from: Number},
      bar: {from: Boolean},
    }

    // Due to the `observedAttributeHandlers` definition, any time the `"foo"` attribute
    // on the element changes, the attribute string value will be converted into a
    // `Number` and assigned to the JS `.foo` property.
    // Not only does `.foo` have an initial value of `123`, but when the element's
    // `"foo"` attribute is removed, `.foo` will be set back to the initial value
    // of `123`.
    foo = 123

    // Due to the `observedAttributeHandlers` definition, any time the `"bar"` attribute
    // on the element changes, the attribute string value will be converted into a
    // `Boolean` and assigned to the JS `.bar` property.
    // Not only does `.bar` have an initial value of `123`, but when the element's
    // `"bar"` attribute is removed, `.bar` will be set back to the initial value
    // of `false`.
    bar = false

    // ...
  },
)
```

[Example on CodePen](https://codepen.io/trusktr/pen/rNEXoOb?editors=1111)

`@lume/element` comes with a set of basic handlers available out of the box, each of
which are alternatives to a respective set of included [decorators](#decorators):

```js
import {Element, element, attribute} from '@lume/element'

element(
  class CoolElement extends Element {
    static elementName = 'cool-element'

    static observedAttributeHandlers = {
      lorem: {}, // Effectively the same as attribute.string
      foo: attribute.string, // Effectively the same as the @stringAttribute decorator. Values get passed to the JS property as strings.
      bar: attribute.number, // Effectively the same as the @numberAttribute decorator. Values get passed to the JS property as numbers.
      baz: attribute.boolean, // Effectively the same as the @booleanAttribute decorator. Values get passed to the JS property as booleans.

      // Here we define an attribute with custom handling of the string value, in this case making it accept a JSON string that maps it to a parsed object on the JS property.
      bespoke: {from: value => JSON.parse(value)}, // f.e. besoke='{"b": true}' results in the JS property having the value `{b: true}`
    }

    // The initial values of the JS properties define the values that the JS properties get reset back to when the corresponding attributes are removed.
    lorem = 'hello'
    foo = 'world'
    bar = 123
    baz = false
    bespoke = {n: 123}

    // ...
  },
)
```

[Example on CodePen](https://codepen.io/trusktr/pen/rNEXbOR?editors=1011)

If decorator support is present (either with a build, or natively in near-future
JS engines), defining attributes with [decorators](#decorators) is simpler and more concise:

```js
import {Element, element, numberAttribute, booleanAttribute} from '@lume/element'

@element
class CoolElement extends Element {
  static elementName = 'cool-element'

  // Due to the `@numberAttribute` decorator, any time the `"foo"` attribute
  // on the element changes, the attribute string value will be converted into a
  // `Number` and assigned to the JS `.foo` property.
  // Not only does `.foo` have an initial value of `123`, but when the element's
  // `"foo"` attribute is removed, `.foo` will be set back to the initial value
  // of `123`.
  @numberAttribute foo = 123

  // Due to the `@booleanAttribute` decorator, any time the `"bar"` attribute
  // on the element changes, the attribute string value will be converted into a
  // `Boolean` and assigned to the JS `.bar` property.
  // Not only does `.bar` have an initial value of `true`, but when the element's
  // `"bar"` attribute is removed, `.bar` will be set back to the initial value
  // of `true`.
  @booleanAttribute bar = true

  // ...
}
```

> [!Note]
> Not only do decorators make the definition more concise, but they avoid surface
> area for human error: the non-decorator form requires defining the same-name
> property in both the `static observedAttributeHandlers` object and in the class fields, and if
> we miss one or the other then things might not work as expected.

Each of the available decorators are detailed further [below](#decorators).

Decorators, and the `static observedAttributeHandlers` object format, both work with
getter/setter properties as well:

```js
import {Element, element, numberAttribute, booleanAttribute} from '@lume/element'

@element // The 'cool-element' name is implied from the constructor name (dash-cased)
class CoolElement extends Element {
  #foo = 123

  // Like with class fields, the initial value is 123, so when the "foo"
  // attribute is removed the setter will receive 123.
  @numberAttribute
  get foo() {
    return this.#foo
  }
  set foo(v) {
    this.#foo = v
  }
  // ...
}
```

They also work with "auto accessors", which creates a _prototype_ getter/setter:

```js
@element
class CoolElement extends Element {
  // The same rules with initial values and attribute removal apply.
  @numberAttribute accessor foo = 123
  @booleanAttribute accessor bar = false

  // ...
}
```

It may be redundant to write `accessor` repeatedly for each property when the
alternative non-accessor format works too. The `accessor` format can be a
fallback in very rare cases where a performance boost is needed (for example
thousands of objects with many non-accessor properties being instantiated all at
once). Most likely there will be _other_ performance issues at the point in
which we have thousands of elements being instantiated at once causing an any
issues.

#### events with `static observedAttributeHandlers`

This is an alternative for the `@eventAttribute` decorator (recommended, see the
[`@eventAttribute`](#eventattribute) docs below), and will be removed after
native support for decorators lands in JS engines.

```js
import {Element, element, attribute} from '@lume/element'

const SomeEl = element('some-el')(
  class extends Element {
    static observedAttributeHandlers = {
      onjump: attribute.event,
    }

    // Also define the property explicitly (here with an optional type definition).
    /** @type {EventListener | null} */
    onjump = null

    connectedCallback() {
      super.connectedCallback()

      // This element dispatches a "jump" event every second:
      setInterval(() => this.dispatchEvent(new Event('jump')), 1000)
    }
  },
)

const el = new SomeEl()

el.onjump = () => console.log('jump!')
// or, as with "onclick" and other built-in attributes:
el.setAttribute('onjump', "console.log('jump!')")

document.body.append(el)

// "jump!" will be logged every second.
```

Note that for TypeScript JSX types (TSX), we want to also define event
properties on the class, for example `onjump` in the last example. Any
properties that start with `on` will be mapped to `on`-prefixed JSX props for
type checking. See the [TypeScript](#typescript) section for more info.

### `attributeChangedCallback`

Nothing new here, this is simply a part of the browser's [native Custom Elements
`attributeChangedCallback` API](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks).
It is triggered when an _observed attribute_ of the element is added, modified,
or removed.

> [!Note]
> If you're using Lume Elements's features only, you do not need to define this
> method. Lume Element's reactive properties will automatically receive updated
> values when attributes change, and the element's `template` will automatically
> update. Writing an `attributesChanged` callback is only useful if you need to
> handle something custom that is not covered by Lume Element, for example a
> 3rd-party lib such as a mixin that implements logic in
> `attributeChangedCallback` that needs to be overriden.

```js
import {Element} from '@lume/element'

class CoolElement extends Element {
  static observedAttributes = ['foo', 'bar']

  attributeChangedCallback(attributeName, oldValue, newValue) {
    // Don't forget to call the super method from the Element class!
    super.attributeChangedCallback(attributeName, oldValue, newValue)

    // Attribute name is the name of the attribute change changed.
    // If `oldValue` is `null` and `newValue` is a string, it means the attribute was added.
    // If `oldValue` and `newValue` are both strings, it means the value changed.
    // If `oldValue` is a string and `newValue` is `null`, it means the attribute was removed.
  }
  // ...
}
```

> [!Warning]
> The `static observedAttributes` property is required for observing attributes, and specifies which
> attributes will trigger `attributeChangedCallback`. `attributeChangedCallback`
> will not be triggered for any attributes that are not listed in `static observedAttributes`!

### `connectedCallback`

Nothing new here, this is simply a part of the browser's [native Custom Elements
`connectedCallback` API](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks).
It is triggered when the element is connected into the document. Use it to
create initialize any processes.

With Lume Element, the main use case of this is to create effects when not using decorator-style effects.

```js
import {Element} from '@lume/element'

class CoolElement extends Element {
  connectedCallback() {
    // Don't forget to call the super method from the Element class!
    super.connectedCallback()

    // ...Create processes, such as effects...

    this.createEffect(() => {
      // ... re-runs when any properties or signals change ...
    })
  }
  // ...
}
```

When using decorators, the following is preferred for less boilerplate:

```js
import {Element} from '@lume/element'

class CoolElement extends Element {
  // Use the method name to describe the effect for readability.
  @effect someEffect() {
    // ... re-runs when any signals change ...
  }

  connectedCallback() {
    super.connectedCallback() // Don't forget to call the base-class method!

    // ...Create processes (non-effects)...
  }
  // ...
}
```

### `disconnectedCallback`

Nothing new here, this is simply a part of the browser's [native Custom Elements
`disconnectedCallback` API](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks).
It is triggered when the element is disconnected from the document. Use it to
clean things up.

> ![Note]
> When using only effects, it is unnecessary to define `disconnectedCallback`.
> This example shows how to clean up custom processes.

```js
import {Element} from '@lume/element'
import {effect} from 'classy-solid'

class CoolElement extends Element {
  @effect someEffect() {
    // ...
  }

  connectedCallback() {
    super.connectedCallback() // Don't forget to call the base-class method!

    this.interval = setInterval(() => {...}, 1000)
  }

  disconnectedCallback() {
    super.disconnectedCallback() // Don't forget to call the base-class method!

    // ...Clean up anything that is not Lume-Element-specific...
    clearInterval(this.interval)

    // You do not need to manually clean up effects made with `this.createEffect()`.
  }

  // ...
}
```

### `adoptedCallback`

Nothing new here, this is simply a part of the browser's [native Custom Elements
`adoptedCallback` API](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks).
It is triggered when the element is adopted into a new document (f.e. in an iframe).
You almost never need this.

```js
import {Element} from '@lume/element'

class CoolElement extends Element {
  adoptedCallback() {
    super.adoptedCallback() // Don't forget to call the base-class method!

    // ...Do something when the element was transferred into another window's or iframe's document...
  }
  // ...
}
```

### `createEffect` (prefer [`@effect`](#effect) decorator instead)

The `createEffect` method is a wrapper around Solid's `createEffect` with some
differences for convenience and when not using decorators:

- `createRoot` is not required in order to dispose of effects created with `this.createEffect()`
- Effects created with `this.createEffect()` will automatically be cleaned up when the element is disconnected.
- Besides being useful for re-running logic on signals changes,
  `this.createEffect()` is useful as an alternative to `disconnectedCallback` when
  paired with Solid's `onCleanup`.

> [!Note]
> When using decorators, use the `@effect` decorator instead, for conciseness
> (see concise decorator example further below).

```js
import {Element} from '@lume/element'
import {createSignal, onCleanup} from 'solid-js'

const [count, setCount] = createSignal(0)

setInterval(() => setCount(n => ++n), 1000)

class CoolElement extends Element {
  connectedCallback() {
    super.connectedCallback()

    // Log `count()` any time it changes.
    this.createEffect(() => console.log(count()))

    this.createEffect(() => {
      const interval1 = setInterval(() => console.log('interval 1'), 1000)
      onCleanup(() => clearInterval(interval1))

      const interval2 = setInterval(() => console.log('interval 2'), 1000)
      onCleanup(() => clearInterval(interval2))
    })
  }

  // disconnectedCallback is not required here for effects to clean up on disconnect.
  // Prefer createEffect+onCleanup over disconnectedCallback for composable logic.
}

customElements.define('cool-element', CoolElement)

// After removing the element, onCleanup fires and cleans up the intervals created in connectedCallback (not the count interval outside the element)
setTimeout(() => {
  const el = document.querySelector('cool-element')
  el.remove()
}, 2000)
```

[Example on CodePen](https://codepen.io/trusktr/pen/MWNgaGQ?editors=1011)

Compare that to using `disconnectedCallback`:

```js
import {Element} from '@lume/element'
import {createSignal, onCleanup} from 'solid-js'

const [count, setCount] = createSignal(0)

setInterval(() => setCount(n => ++n), 1000)

class CoolElement extends Element {
  #interval1 = 0
  #interval2 = 0

  connectedCallback() {
    super.connectedCallback()

    // Log `count()` any time it changes.
    this.createEffect(() => console.log(count()))

    this.#interval1 = setInterval(() => console.log('interval 1'), 1000)
    this.#interval2 = setInterval(() => console.log('interval 2'), 1000)
  }

  disconnectedCallback() {
    super.disconnectedCallback()

    clearInterval(this.#interval1)
    clearInterval(this.#interval2)
  }
}

customElements.define('cool-element', CoolElement)
```

Here's what the more concise example looks like with decorators.

```js
import {Element} from '@lume/element'
import {createSignal, onCleanup} from 'solid-js'
import {effect} from 'classy-solid'

const [count, setCount] = createSignal(0)

setInterval(() => setCount(n => ++n), 1000)

class CoolElement extends Element {
  @effect logCount() {
    console.log(count())
  }

  // This effect does not re-run on signal changes, but is useful for setup+cleanup.
  @effect makeIntervals() {
    const interval1 = setInterval(() => console.log('interval 1'), 1000)
    onCleanup(() => clearInterval(interval1))

    const interval2 = setInterval(() => console.log('interval 2'), 1000)
    onCleanup(() => clearInterval(interval2))
  }
}

customElements.define('cool-element', CoolElement)

// After removing the element, onCleanup fires and cleans up the intervals created in connectedCallback (not the count interval outside the element)
setTimeout(() => {
  const el = document.querySelector('cool-element')
  el.remove()
}, 2000)
```

> :bulb:**Tip:**
>
> Prefer `onCleanup` instead of `disconnectedCallback` because composition of
> logic will be easier while also keeping it co-located and easier to read. That
> example is simple, but when logic grows, having to clean things up in
> `disconnectedCallback` can get more complicated, especially when each piece of
> creation logic and cleanup logic is multiple lines long and interleaving
> them would be harder to read. Plus, putting them in effects makes them
> creatable+cleanable if signals in the effects change, not just if the element is
> connected or disconnected. For example, the following element cleans up the
> interval any time the signal changes, not only on disconnect:

```js
import {Element} from '@lume/element'
import {createSignal, onCleanup} from 'solid-js'
import {effect} from 'classy-solid'

const [count, setCount] = createSignal(0)

setInterval(() => setCount(n => ++n), 1000)

class CoolElement extends Element {
  @effect logCount() {
    console.log(count())
  }

  @effect conditionalInterval() {
    // Run the interval only during moments that count() is an even number.
    // Whenever count() is odd, the running interval will be cleaned up and a new interval will not be created.
    // Also, when the element is disconnected (while count() is even), the interval will be cleaned up.
    if (count() % 2 !== 0) return
    const interval = setInterval(() => console.log('interval'), 100)
    onCleanup(() => clearInterval(interval))
  }
}

customElements.define('cool-element', CoolElement)

// After removing the element, onCleanup fires and cleans up any interval
// currently created within the element (not the count interval outside the
// element)
setTimeout(() => {
  const el = document.querySelector('cool-element')
  el.remove()
}, 2500)
```

[Example on CodePen (without decorators)](https://codepen.io/trusktr/pen/qBeWOLz?editors=1011)

The beauty of this is we can write logic based on signals, without worrying
about `disconnectedCallback`, and we'll rest assured things clean up properly.
Cleanup logic is co-located with the pieces they are relevant to, which opens
the door to powerful compositional patterns...

### `static autoDefine`

Set this to `false` to tell the `@element` decorator (or `element()` when called
as a function) to not automatically define the element in the global
`customElements` registry. When un-specified, it defaults to `true`.

```js
@element
class SomeEl extends LumeElement {
  static elementName = 'some-el'
  static autoDefine = false
}

const el = document.createElement('some-el')
console.log(el instanceof SomeEl) // false
customElements.define(SomeEl.elementName, SomeEl)
console.log(el instanceof SomeEl) // true
```

Preventing automatic definition can be useful for use with non-global
CustomElementRegistry instances for scoping element definitions to ShadowRoots,

```js
// Use a non-global element registry instead of the default global element registry:
const myRegistry = new CustomElementRegistry()
SomeEl.defineElement(myRegistry)

// Use the non-global registry for scoped element definitions inside a custom element's ShadowRoot:
class SomeElementWithScopedRegistry extends HTMLElement {
  constructor() {
    super()
    const root = this.attachShadow({mode: 'open', customElementRegistry: myRegistry})
    root.innerHTML = `<some-el></some-el>`
  }
}
```

or for re-naming elements in case of a name collision:

```js
SomeEl.defineElement('some-el-renamed')
```

### `static defineElement`

Define this class for the given element `name`, or using its default name
(`TheClass.elementName`) if no `name` was given and the element was not already
defined using the `@element` decorator. Defaults to using the global
`customElements` registry unless another registry is provided (for example a
ShadowRoot-scoped registry) as a second argument.

```js
// Defines <some-el> with the decorated class, using the passed-in name.
@element('some-el')
class SomeEl extends LumeElement {}

// Defines <other-el> with an empty subclass of SomeEl using the name passed
// into .defineElement().
const OtherEl = SomeEl.defineElement('other-el')
console.log(OtherEl === SomeEl) // false

@element
class AnotherEl extends LumeElement {
  static autoDefine = false
}

// The first call to .defineElement() will not make a subclass if the class has
// not been used in a definition yet.
const El = AnotherEl.defineElement('another-el') // defines <another-el>
console.log(El === AnotherEl) // true

// The second call to .defineElement() will make a new subclass.
const El2 = AnotherEl.defineElement('yet-another-el') // defines <yet-another-el>
console.log(El2 === AnotherEl) // false

// Use a non-global element registry instead of the default global element registry:
const myRegistry = new CustomElementRegistry()
AnotherEl.defineElement('one-more-el', myRegistry)

// Use the non-global registry for scoped element definitions inside a custom element's ShadowRoot:
class SomeElementWithScopedRegistry extends HTMLElement {
  constructor() {
    super()
    const root = this.attachShadow({mode: 'open', customElementRegistry: myRegistry})
    root.innerHTML = `<one-more-el></one-more-el>`
  }
}
```

If the class is already registered with another name, then the class will be
extended with an empty subclass so that a new class is used for the new name,
because a CustomElementRegistry does not allow the same class reference to be
used more than once regardless of the name.

Returns the defined element class, which may be a different subclass of the
class this is called on if the class this is called on is already associated
with another name, otherwise returns the same class this is called on.

[Example on CodePen](https://codepen.io/trusktr/pen/JjQgaxb)

### `hasShadow`

When `true`, the custom element will have a `ShadowRoot`. Set to `false`
to not use a `ShadowRoot`. When `false`, styles will not be scoped via
the built-in `ShadowRoot` scoping mechanism, but by a much more simple
shared style sheet placed at the nearest root node, with `:host`
selectors converted to tag names.

```js
@element('some-el')
class SomeEl extends Element {
  hasShadow = false

  template = () => html`<div>hello</div>`
}
```

The `template` content will be appended to the SomeEl instance directly, with no `ShadowRoot`:

```html
<some-el id="el"></some-el>
<script>
  const el = document.getElementById('el')
  console.log(el.shadowRoot) // null
  console.log(el.children[0]) // <div>hello</div>
</script>
```

[Example on CodePen](https://codepen.io/trusktr/pen/eYwqLPY)

> [!Note]
> Note that without a ShadowRoot, `<slot>` no longer works because it must be
> inside a ShadowRoot, therefore going without a ShadowRoot is useful moreso for
> elements that are leafs at the end of DOM tree branches and elements that
> will not accept any slotted children and will only have `template` content as their
> children.

### `templateRoot`

Subclasses can override the `templateRoot` property to provide an alternate Node for
`template` content to be placed into (f.e. a subclass can set it to `this` to have
`template` content appended to itself regardless of the value of `hasShadow`).

A primary use case for this is customizing the ShadowRoot:

```js
@element('some-el')
class SomeEl extends Element {
  // Create the element's ShadowRoot with custom options for example:
  templateRoot = this.attachShadow({
    mode: 'closed',
  })

  template = () => html`<div>hello</div>`
}
```

[Example on CodePen](https://codepen.io/trusktr/pen/MWMNpbR)

### `shadowOptions`

Define a `shadowOptions` property to specify any options for the element's
ShadowRoot. These options are passed to `attachShadow()`. This is a simpler
alternative to overriding `templateRoot` in the previous example.

```js
@element('some-el')
class SomeEl extends Element {
  shadowOptions = {mode: 'closed'}

  template = () => html`<div>hello</div>`
}
```

### `styleRoot`

Similar to the previous `templateRoot`, this defines which `Node` to append style
sheets to when `hasShadow` is `true`. This is ignored if `hasShadow` is
`false`. It defaults to `this.templateRoot`, which in turn defaults to the element's
`ShadowRoot`.

When `hasShadow` is `true`, an alternate `styleRoot` is sometimes desired so
that styles will be appended elsewhere than the `templateRoot`. To customize
this, override it:

```js
@element('some-el')
class SomeEl extends Element {
  styleRoot = document.createElement('div')

  template = () => html`
    <div>
      <div>${this.styleRoot}</div>

      <span>hello</span>
    </div>
  `
}
```

[Example on CodePen](https://codepen.io/trusktr/pen/yLdmxEW)

This can be useful for fixing issues where the default append location of an
element's style sheet into the `ShadowRoot` conflicts with how DOM is created in
`template` (f.e. if the user's DOM creation in `template` clears the
`ShadowRoot` content, or etc, then the user may want to place the stylesheet
somewhere else).

## Decorators

Using decorators (if available in your build, or natively in your JS engine)
instead of `static observedAttributeHandlers` or `static events` is more concise
and less error prone.

Here's the list of included attribute decorators and the attribute handler
equivalents:

- Use `@stringAttribute foo` in place of `foo: {}`
- Use `@stringAttribute foo` in place of `foo: attribute.string`
- Use `@numberAttribute foo` in place of `foo: attribute.number`
- Use `@booleanAttribute foo` in place of `foo: attribute.boolean`
- Use `@eventAttribute foo` in place of `foo: attribute.event`
- Use `@jsonAttribute foo` in place of `foo: attribute.json`

The `@noSignal` decorator can be paired with attribute decorators to disable
reactivity on specific attribute-decorated properties. This is not recommended
unless you have a very special reason to do so, and refactoring to avoid this
is preferred.

> [!Warning]
> When using attribute decorators, the `@element` decorator is currently also required on
> the class, or the attribute decorators won't work.

Additional decorators from `classy-solid` are also available:

- Use `@signal` in place of `createSignal()` or for non-attribute properties
  that need to be reactive.
- Use `@memo` in place of `createMemo()`
- Use `@effect` in place of `this.createEffect()`

Below are more details on each decorator:

### `@element`

The star of the decorator show, a decorator for defining a custom element.

When passed a name string, it will be the element's tag name:

```js
import {Element, element} from '@lume/element'

@element('my-element') // <my-element> will be defined
class CoolElement extends Element {
  // ...
}
```

> [!Note]
> Make sure you extend from the `Element` base class from `@lume/element` when
> using the `@element` decorator.

When not passed a name string, the name is derived from the dash-cased name of
the class:

```js
import {Element, element} from '@lume/element'

@element // The 'cool-element' name is implied
class CoolElement extends Element {
  // ...
}
```

A second boolean argument can disable automatic definition in the global
`customElements` registry. The constructor's `.defineElement()` method can then
be used to manually trigger the definition using the given name:

```js
import {Element, element} from '@lume/element'

const autoDefine = false

@element('cool-element', autoDefine)
class CoolElement extends Element {
  // ...
}

CoolElement.defineElement() // uses the global customElements to define <cool-element>
// or
const myRegistry = new CustomElementRegistry()
CoolElement.defineElement(myRegistry) // uses a non-global registry to define <cool-element>

// Use a non-global registry for scoped element definitions inside a custom element's ShadowRoot:
class SomeElementWithScopedRegistry extends HTMLElement {
  constructor() {
    super()
    const root = this.attachShadow({mode: 'open', customElementRegistry: myRegistry})
    root.innerHTML = `<cool-element></cool-element>`
  }
}
```

A custom name can be passed to `.defineElement()` too:

```js
CoolElement.defineElement('other-element') // defines <other-element> (even if `<cool-element>` is already defined)
```

`@element` also accepts options as an object:

```js
const autoDefine = false

@element({elementName: 'cool-element', autoDefine})
class CoolElement extends Element {
  // ...
}
```

Without passing arguments to `@element`, options can be specified using
static class fields:

```js
const autoDefine = false

@element
class CoolElement extends Element {
  static elementName = 'cool-element'
  static autoDefine = autoDefine
  // ...
}
```

If a name is not passed into `@element`, and if the `static elementName` class
field is not defined, the name will be inferred as the dash-cased version of the
class name:

```js
@element // The "cool-element" tag name is inferred.
class CoolElement extends Element {
  // ...
}
```

Using the class fields is nice and clean if you like all the aspects of your
class defined _within_ the class, or if your minifier is mangling your cla