{"id":13532662,"url":"https://github.com/lume/element","last_synced_at":"2025-04-12T22:26:10.349Z","repository":{"id":59184886,"uuid":"241555187","full_name":"lume/element","owner":"lume","description":"Fast and simple custom elements.","archived":false,"fork":false,"pushed_at":"2025-03-06T21:21:46.000Z","size":1137,"stargazers_count":151,"open_issues_count":9,"forks_count":5,"subscribers_count":7,"default_branch":"main","last_synced_at":"2025-03-28T01:09:22.996Z","etag":null,"topics":["3d","3d-graphics","css","custom-elements","html","html-elements","javascript","jsx","lume","shadow-dom","solid","threejs","tsx","typescript","web-components","webgl"],"latest_commit_sha":null,"homepage":"https://lume.io","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lume.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-02-19T07:03:59.000Z","updated_at":"2025-03-24T09:31:10.000Z","dependencies_parsed_at":"2023-11-23T13:02:47.126Z","dependency_job_id":"077c2a60-885d-432a-97aa-bbc91a32e1b7","html_url":"https://github.com/lume/element","commit_stats":{"total_commits":263,"total_committers":3,"mean_commits":87.66666666666667,"dds":"0.026615969581749055","last_synced_commit":"6270241d79466f144b672bb69e8a356e79775ec3"},"previous_names":[],"tags_count":81,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lume%2Felement","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lume%2Felement/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lume%2Felement/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lume%2Felement/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lume","download_url":"https://codeload.github.com/lume/element/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247107828,"owners_count":20884797,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["3d","3d-graphics","css","custom-elements","html","html-elements","javascript","jsx","lume","shadow-dom","solid","threejs","tsx","typescript","web-components","webgl"],"created_at":"2024-08-01T07:01:12.699Z","updated_at":"2025-04-04T02:03:26.625Z","avatar_url":"https://github.com/lume.png","language":"TypeScript","readme":"# @lume/element\n\nEasily and concisely write Custom Elements with simple templates and reactivity.\n\nUse the custom elements on their own in plain HTML or vanilla JavaScript, or in\nVue, Svelte, Solid.js, Stencil.js, React, and Preact, with full type checking,\nautocompletion, and intellisense in all the template systems of those\nframeworks, in any IDE that supports TypeScript such as VS Code.\n\nWrite your elements once, then use them in any app, with a complete developer\nexperience no matter which base component system your app uses.\n\n\u003ch4\u003e\u003ccode\u003e\u003cstrong\u003enpm install @lume/element\u003c/strong\u003e\u003c/code\u003e\u003c/h4\u003e\n\n\u003e :bulb:**Tip:**\n\u003e\n\u003e If you are new to Custom Elements, first [learn about the basics of Custom\n\u003e Element\n\u003e APIs](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements)\n\u003e available natively in browsers. Lume Element simplifies the creation of Custom\n\u003e Elements compared to writing them with vanilla APIs, but sometimes vanilla\n\u003e APIs are all that is needed.\n\n# Live demos\n\n- [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)\n- [CodePen, html template tag, no decorators](https://codepen.io/trusktr/pen/zYeRqaR)\n- [Stackblitz with Babel, JSX, decorators](https://stackblitz.com/edit/webpack-webpack-js-org-wdzlbb?file=src%2Findex.js)\n- [Stackblitz with Vite, JSX, TypeScript, decorators](https://stackblitz.com/edit/solidjs-templates-wyjc1i?file=src%2Findex.tsx)\n- [Solid Playground, TypeScript, no decorators](https://playground.solidjs.com/anonymous/0cc05f53-b665-44d2-a73c-1db9eb992a4f)\n\n# Cliché Usage Example\n\nDefine a `\u003cclick-counter\u003e` element:\n\n```js\nimport {Element, element, numberAttribute} from '@lume/element'\nimport html from 'solid-js/html'\nimport {createEffect} from 'solid-js'\n\n@element('click-counter')\nclass ClickCounter extends Element {\n  @numberAttribute count = 0\n\n  template = () =\u003e html`\u003cbutton onclick=${() =\u003e this.count++}\u003eClick! (count is: ${() =\u003e this.count})\u003c/button\u003e`\n\n  css = `\n\t\tbutton {\n\t\t\tborder: 2px solid deeppink;\n\t\t\tmargin: 5px;\n\t\t}\n\t`\n\n  connectedCallback() {\n    super.connectedCallback()\n\n    // Log the `count` any time it changes:\n    createEffect(() =\u003e {\n      console.log('count is:', this.count)\n    })\n  }\n}\n```\n\nUse the `\u003cclick-counter\u003e` in a plain HTML file:\n\n```html\n\u003cbody\u003e\n  \u003cclick-counter\u003e\u003c/click-counter\u003e\n\n  \u003c!-- Manually set the `count` value in HTML: --\u003e\n  \u003cclick-counter count=\"100\"\u003e\u003c/click-counter\u003e\n\n  \u003cscript type=\"module\"\u003e\n    import './click-counter.js'\n\n    // Manually set the `count` value in JS:\n    document.querySelector('click-counter').count = 200\n  \u003c/script\u003e\n\u003c/body\u003e\n```\n\n[Example on CodePen](https://codepen.io/trusktr/pen/zYeRqaR) (without decorators)\n\n\u003e [!Note]\n\u003e Once decorators land in browsers, the above example will work out of the box\n\u003e as-is without compiling, but for now a compile step is needed for using decorators.\n\u003e\n\u003e JSX can be used for the `template` of an element, but that will always require\n\u003e compiling:\n\u003e\n\u003e ```jsx\n\u003e template = () =\u003e \u003cbutton\u003e Click! (count is: {this.count}) \u003c/button\u003e\n\u003e ```\n\u003e\n\u003e Further examples below show how to define elements without decorators or JSX, which\n\u003e works today without a compiler.\n\nUse the `\u003cclick-counter\u003e` in another element's `template`,\n\n```js\nimport {Element, element} from '@lume/element'\nimport html from 'solid-js/html'\nimport {signal} from 'classy-solid'\n\n@element('counter-example')\nclass CounterExample extends Element {\n  @signal count = 50 // Not an attribute, only a signal.\n\n  template = () =\u003e html`\u003cclick-counter count=${() =\u003e this.count}\u003e\u003c/click-counter\u003e`\n}\n\ndocument.body.append(new CounterExample())\n```\n\nUse `\u003cclick-counter\u003e` in a plain function component (i.e. a Solid.js component):\n\n```js\n// At this point this, this boils down to plain Solid.js code (`@lume/element` comes\n// with `solid-js`)\n\nimport {createSignal} from 'solid-js'\nimport html from 'solid-js/html'\n\nfunction CounterExample() {\n  const [count, setCount] = createSignal(50)\n\n  return html`\u003cclick-counter count=${count()}\u003e\u003c/click-counter\u003e`\n}\n\ndocument.body.append(CounterExample())\n```\n\n# Intro\n\n[Custom](https://developers.google.com/web/fundamentals/web-components/customelements)\n[Elements](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements)\n(also known as [Web\nComponents](https://developer.mozilla.org/en-US/docs/Web/Web_Components) are a\nfeature of browsers that allow us to define new HTML elements that the browser\nunderstands in the same way as built-in elements like `\u003cdiv\u003e` or `\u003cbutton\u003e`.\nThey are very useful for organizaing web apps into separately and sometimes\nre-usable pieces (elements).\n\nIf that flew over your head then you might first want to try a [beginner HTML\ntutorial](https://htmldog.com/guides/html/beginner/). You will also need to\nsome basic knowledge of\n[JavaScript](https://www.google.com/search?q=JavaScript%20for%20absolute%20beginners).\n\n`@lume/element` provides a set of features that make it easier to manipulate\nelements and to define new custom elements and easily compose them together\ninto an application.\n\nWith `@lume/element` we can create custom elements that have the following\nfeatures:\n\n- Reactive instance properties that receive values from element attributes of the same name (but dash-cased).\n- Declarative templates, written with JSX or `html` template tag, that automatically update when reactive instance properties are used in the templates.\n- Scoped styling with or without a ShadowRoot.\n- Decorators for concise element definitions.\n\n\u003cdetails\u003e\u003csummary\u003eA more detailed feature description:\u003c/summary\u003e\n\n-\n- Element attributes are defined with `@attribute` decorators on class fields.\n  - Class fields decorated with `@attribute` receive values from HTML attributes\n    (with the same name but dash-cased) when the HTML attribute values change.\n  - Decorators are powered by\n    [`classy-solid`](https://github.com/lume/classy-solid): utilities for using\n    [Solid.js](https://solidjs.com) patterns on `class`es, such as the `@signal`\n    decorator for making class fields reactive (backed by Solid signals).\n    Decorators from `@lume/element` compose the `@signal` decorator to make\n    properties be reactive.\n  - As decoraators are not out in browsers yet, an alternative non-decorator API\n    can be used, which does not require a build.\n- Each custom element can have an HTML template that automatically updates the\n  DOM when any reactive variables used in the template changes.\n  - Templates can be written in the form of HTML-like markup inside JavaScript\n    called [JSX](https://facebook.github.io/jsx), specifically the JSX flavor from\n    Solid.js. This requires a build step.\n  - Templates can also be written using Solid's `html` template string tag,\n    which does not require a build step.\n  - When a template updates, the whole template does not re-run, only the part\n    of the template where a variable changed is updated, and only that particular\n    piece of\n    [DOM](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model)\n    gets modified. There is no (expensive) virtual DOM diffing.\n  - Because changes to HTML attributes on an element map to properties backed by\n    signals on the element instance, this will cause the custom element's template\n    to update if its template uses those properties.\n- Custom element styles are automatically scoped, similar to Vue, Svelte, and\n  other systems with style scoping.\n  - If you're familiar with custom elements, you know that the browser gives\n    this to us for free when using ShadowDOM.\n  - If you opt an element out of having a ShadowRoot, `@lume/element` applies\n    its own style scoping for the element at the nearest root node.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003e\u003ch2\u003eInstall and Setup\u003c/h2\u003e\u003c/summary\u003e\n\n\u003e **STUB:** This section needs expansion, but should be enough for anyone\n\u003e familiar with common build tooling in the webdev/JS ecosystem. Contributions\n\u003e very welcome!\n\n\u003cdetails\u003e\u003csummary\u003e\u003ch3\u003eCDN method (easiest, no compiler or command line needed)\u003c/h3\u003e\u003c/summary\u003e\n\nFollow the guide on [installing `lume` from\nCDN](https://docs.lume.io/guide/install/?id=cdn-easiest), but simply replace\n`lume` with `@lume/element`. The process is otherwise the same.\n\nThe examples here in the README follow the CDN approach to keep things simple,\n[for example](https://codepen.io/trusktr/pen/zYeRqaR).\n\n\u003e [!Note]\n\u003e Decorator syntax and JSX syntax are both not supported with this install\n\u003e method as it does not use a build step. In the near future, decorators will be\n\u003e out natively in browsers and JS engines (but not JSX).\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003e\u003ch3\u003eLocal install with build\u003c/h3\u003e\u003c/summary\u003e\n\nThis assumes some familiarity with command lines and JavScript build tools.\n\nFirst make sure Node.js is installed so that we have the `npm` package manager avaiable.\n\nInstall the `@lume/element` package using the following in a terminal:\n\n```sh\nnpm install @lume/element\n```\n\nIn order to use decorators today (recommended), we need to compile\nthem with TypeScript 5 or higher (recommended, even if writing JS and not TS, as\ndecorator syntax works out of the box with TypeScript), or use the [Babel\ncompiler](http://babeljs.io) with\n[`@babel/plugin-proposal-decorators`](https://babeljs.io/docs/babel-plugin-proposal-decorators).\n\n```sh\nnpm install --save-dev typescript\n# or\nnpm install --save-dev @babel/cli @babel/core @babel/plugin-proposal-decorators\n```\n\nIf using TypeScript, set `allowJs` in `tsconfig.json` to allow compiling JS files, f.e.:\n\n```js\n{\n\t\"compilerOptions\": {\n\t\t\"allowJs\": true,\n\t\t\"outDir\": \"dist\"\n\t},\n\t\"include\": [\"./src/**/*\"]\n}\n```\n\nand running `npx tsc`. See the [TypeScript](#typescript) section below for configuring JSX\ntypes for various frameworks (Solid, React, Preact, etc).\n\nIf using Babel, add the decorators plugin to `.babelrc`, f.e.\n\n```json\n{\n  \"plugins\": [\"@babel/plugin-proposal-decorators\"]\n}\n```\n\nand running `npx babel src --out-dir dist`.\n\nIf you'd like to use the HTML-like markup inside JavaScript known as \"JSX\",\ninstead of [Solid's `html` template\ntag](https://github.com/solidjs/solid/tree/main/packages/solid/html) which\nrequires no build, the\n[`babel-preset-solid`](https://npmjs.com/babel-preset-solid) package will also\nbe needed:\n\n```sh\nnpm install --save-dev babel-preset-solid\n```\n\nConfigure Babel to use the preset inside your project's `.babelrc` file or in\nyour Webpack `babel-loader` config:\n\n```json\n{\n  \"plugins\": [\"@babel/plugin-proposal-decorators\"],\n  \"presets\": [\"solid\"]\n}\n```\n\n\u003e [!Note]\n\u003e If compiling decorators with TypeScript, Babel is still needed for Solid JSX\n\u003e because TypeScript does not compile JSX into Solid.js format, only into React\n\u003e format. Either compile decorators with TypeScript and have Babel compile\n\u003e JSX in a second step, or compile both decorators and JSX with Babel in a\n\u003e single step.\n\n\u003c/details\u003e\n\n\u003c/details\u003e\n\n# Basic Usage\n\n## Create custom elements\n\nA great way to create re-usable components is to create Custom Elements. The\nadvantage of custom elements is that they follow web standards, and therefore\nthey can be used in any web application and manipulated by any DOM\nmanipulation libraries like [jQuery](https://jquery.com/),\n[React](https://reactjs.org), [Vue](https://vuejs.org), [Svelte](https://svelte.dev/), or\n[Angular](https://angular.io), [Solid.js](https://solidjs.com), and all the rest.\n\nThe following is a custom element definition with a reactive property\n`firstName` that also accepts values from an attribute named `first-name` (the\nproperty name is converted to dash-case for the attribute name).\n\n\u003e [!Note]\n\u003e Deorators and JSX are not required. The non-decorator and non-JSX forms are\n\u003e shown further below. The [Decorators](#decorators) section has details on each\n\u003e decorator available.\n\n```jsx\nimport {\n  Element, // A base class for LUME custom elements\n  element, // A decorator for defining elements, required for reactive JS properties.\n  attribute, // A property decorator to map attributes to properties, and that makes properties reactive\n  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.\n  css, // A no-op identity template tag function (useful to enable CSS syntax highlighting in various text editors)\n\n  // Decorators for defining specific attributes types (string values are coerced to the respective JS type):\n  stringAttribute,\n  numberAttribute,\n  booleanAttribute,\n} from '@lume/element'\n\n@element('greeting-card') // defines the element tag name\nclass GreetingCard extends Element {\n  // The firstName property will be a reactive variable, and any value from an\n  // attribute named 'first-name' will be mapped back to this property (the\n  // attribute name is the dash-case version of the property name).\n  @attribute firstName = 'Roger'\n\n  // Specific attribute types (the JS property will always be of the specified\n  // type):\n  @stringAttribute someString = ''\n  @numberAttribute someNumber = 123\n  @booleanAttribute someBoolean = false\n\n  // Define event properties to specify which events the element dispatches.\n  // Besides being useful for type definitions in JSX, these properties work\n  // like the builtin event properties such as \"onclick\" (JS property or DOM\n  // attribute code string).\n  //\n  // For example, a user can write `el.onhello = event =\u003e {...}` just like\n  // they can do with builtin event properties like `el.onclick = event =\u003e\n  // {...}`.\n  @eventAttribute onhello = null\n\n  // Define a DOM tree that we want rendered on screen by providing a\n  // `template`. The `template` should be a function that returns a DOM\n  // element or array of DOM elements (which we can create with JSX, or with\n  // an `html` template tag, or with plain JS). The DOM content will be, by\n  // default, appended into the ShadowRoot of our custom element.\n  //\n  // To take advantage of reactivity in our template, simply interpolate\n  // properties that were decoratored with an attribute decorator or defined\n  // with `static observedAttributeHandlers` into the template.\n  //\n  // Here, any time the `.firstName` property's value changes, the DOM will be\n  // automatically updated.\n  template = () =\u003e (\n    \u003cdiv\u003e\n      \u003cspan\u003e\n        Hello \u003ci\u003e{this.firstName}\u003c/i\u003e\n      \u003c/span\u003e\n      {/* Children of a \u003cgreeting-card\u003e element get rendered here. */}\n      \u003cslot\u003e\u003c/slot\u003e\n    \u003c/div\u003e\n  )\n\n  // Apply styling to this element and its content with the static `css` property.\n  // Because the property is static, this style is re-used across all instances of the element.\n  // Styles are by default scoped to the element's content due to ShadowRoot style encapsulation.\n  static css = css`\n    :host {\n      background: skyblue;\n    }\n    div {\n      color: pink;\n    }\n  `\n\n  // For instance-specific styling, use the non-static `css` property.  This\n  // style has higher precedence over styles in the `static css` property.  In\n  // this example, the divs in each instance of this element will have borders\n  // of random sizes.  Note, `css` is currently not reactive, it runs once\n  // initially, so using a reactive property in the css will currently not\n  // update the style.\n  css = css`\n    div {\n      border: ${Math.random() * 5}px solid teal;\n    }\n  `\n\n  // connectedCallback is a method that fires any time this custom element is\n  // connected into a web site's live DOM tree.\n  connectedCallback() {\n    super.connectedCallback() // Don't forget to call the super method!\n\n    // Once the element is connected, let's update the `.firstName` prop after a\n    // couple of seconds, and we'll see the change on screen.\n    setTimeout(() =\u003e (this.firstName = 'Zaya'), 2000)\n\n    // And show that it works by setting HTML attributes too, two seconds later.\n    setTimeout(() =\u003e this.setAttribute('first-name', 'Raquel'), 4000)\n\n    // The element may dispatch events.\n    setTimeout(() =\u003e this.dispatchEvent(new Event('hello')), 3000)\n  }\n\n  // Use the disconnectedCallback to clean anything up when the element is removed from the DOM.\n  disconnectedCallback() {\n    super.disconnectedCallback()\n    // ... clean up ...\n  }\n}\n```\n\nNow we can use it in the HTML of a web site, or in the template of another\ncomponent:\n\n```jsx\n\u003cgreeting-card first-name=\"Raynor\" onhello={() =\u003e console.log(event.target.firstName, 'says hello')}\u003e\u003c/greeting-card\u003e\n```\n\n[Example on CodePen](https://codepen.io/trusktr/pen/WNqVWaL?editors=1011) (without decorators, with Solid's `html` template tag instead of JSX)\n\nInside an element's `template()` method we can assign bits and pieces of DOM to\nvariables, and we can also use other custom elements and functional components.\nSimilary, the `css` property can also be a method:\n\n```jsx\n@element('greeting-card')\nclass GreetingCard extends Element {\n  // ... same as before ...\n\n  // This time 'template' is a method that has some logic, and refers to pieces of DOM using variables.\n  template() {\n    const greeting = (\n      \u003cspan\u003e\n        Hello \u003ci\u003e{this.firstName}\u003c/i\u003e\n      \u003c/span\u003e\n    )\n\n    console.log(greeting instanceof HTMLSpanElement) // true\n\n    // One piece of DOM can be composed into another:\n    const result = \u003cdiv\u003e{greeting}\u003c/div\u003e\n\n    console.log(result instanceof HTMLDivElement) // true\n\n    return result\n  }\n\n  // ... same as before ...\n\n  css() {\n    const thickness = Math.random() * 5\n\n    return css`\n      div {\n        border: ${thickness}px solid teal;\n      }\n    `\n  }\n\n  // ... same as before ...\n}\n```\n\n## Easily create and manipulate DOM\n\nLume Element is built on Solid.js, so we can also use Solid.js at the top level\nof a module for example. This sort of code can be useful in the `template` of a\ncustom element, or the body of a functional component.\n\n```jsx\nimport {createSignal} from 'solid-js'\n\n// Make a signal with an initial value of 0.\nconst [count, setCount] = createSignal(0)\n\n// Increment the value of count every second.\nsetInterval(() =\u003e setCount(count() + 1), 1000)\n\n// Create a \u003cdiv\u003e element with a child \u003ch1\u003e element. The data-count attribute\n// and the text content of the \u003ch1\u003e element will automatically be updated whenever\n// the count variable changes.\nconst el = (\n  \u003cdiv\u003e\n    \u003ch1 data-count={count()}\u003eThe count is: {count()}\u003c/h1\u003e\n  \u003c/div\u003e\n)\n\n// The result stored in the `el` variable is a `\u003cdiv\u003e` element! For example,\n// we can call regular DOM APIs like `setAttribute` on it.\nel.setAttribute('foo', 'bar')\n\n// Append the element to the body of the page, and now we'll see a\n// continually-updating message on the screen.\ndocument.body.append(el)\n```\n\n[Example on CodePen](https://codepen.io/trusktr/pen/bGPXmEJ) (with Solid's `html` template tag instead of JSX)\n\n## Create functional components\n\nContinuing with the same `count` variable from the previous example, here's how\nto compose DOM trees using \"functional components\". This is plain Solid.js, and\nfunctional components (Solid.js components) can be used in a custom element's\n`template`.\n\nA functional component is a function that simply returns one or more DOM\nelements. JSX expressions and the `html` template string tag both return the top\nlevel elements defined in the markup.\n\n```jsx\n// This is just plain Solid.js code. See https://solidjs.com for more on writing\n// functional components.\n\n// This Label functional component uses the empty \u003c\u003e\u003c/\u003e tag to contain more than\n// one root-level child, and the return value will be an array of DOM nodes.\nconst Label = props =\u003e (\n  \u003c\u003e\n    \u003cdiv\u003e{props.greeting}\u003c/div\u003e\n    {props.children}\n  \u003c/\u003e\n)\n\n// This Greeting functional component nests the content of the Label component\n// in its template, and the \u003cdiv\u003e inside the \u003cLabel\u003e gets distributed to the\n// part of the Label component where we see `{props.children}`.\nconst Greeting = () =\u003e (\n  \u003csection\u003e\n    \u003cLabel greeting={'hello (' + count() + ')'}\u003e\n      \u003cdiv\u003eJohn\u003c/div\u003e\n    \u003c/Label\u003e\n  \u003c/section\u003e\n)\n\n// The `Greeting` function only needs to be called once, and it will return a\n// reference to an element or multiple elements. The `Greeting` function does\n// NOT need to be called over and over to re-render like in some other libraries\n// (for example React). That's what makes all of this simple and clean. The\n// reactivity inside the component templates takes care of updating content of\n// the created DOM tree.\n// Here `elem` will be a reference to an actual `\u003csection\u003e` element that the\n// `Greeting` function returned.\nconst elem = Greeting()\n\n// It's just DOM! Use regular DOM APIs to append the element to the body.\ndocument.body.append(elem)\n```\n\n[Example on CodePen](https://codepen.io/trusktr/pen/eYwqPzz) (with Solid's `html` template tag instead of JSX)\n\n## Using functional components inside custom elements\n\nContinuing from above, here's a custom element that re-uses the `Greeting`\ncomponent. This shows that any regular Solid.js component can be\nused in the `template` of a custom element made with `@lume/element`:\n\n```jsx\n@element('cool-element')\nclass CoolElement extends Element {\n  template = () =\u003e (\n    \u003c\u003e\n      \u003ch2\u003eHere's a greeting:\u003c/h2\u003e\n      \u003cGreeting /\u003e\n    \u003c/\u003e\n  )\n}\n\ndocument.body.insertAdjacentHTML('beforeend', `\u003ccool-element\u003e\u003c/cool-element\u003e`)\n```\n\n[Example on CodePen](https://codepen.io/trusktr/pen/bGPXmRX) (without decorators, with Solid's `html` template tag instead of JSX)\n\n## Functional components vs custom elements\n\nWriting function components can sometimes be simpler, but functional components\ndo not have features that custom elements have such as native style scoping\n(style scoping with function components requires an additional Solid.js library\nor compiler plugin), etc.\n\nIn contrast to custom elements, functional components only work within the\ncontext of other functional components made with Solid.js or custom elements\nmade with `@lume/element`. Functional components are not compatible with HTML,\nReact, Vue, Angular, Svelte, or all the other web libraries and frameworks. For\nportability across applications and frameworks, this is where custom elements\nshine.\n\nCustom elements are also debuggable in a browser's element inspector _out of the\nbox_, while functional components are not (functional components require\ndevtools plugins for each browser, if they even exist). See Lume's [Debugging\nguide](https://docs.lume.io/guide/debugging) for an example.\n\n# API\n\n## `Element`\n\nA base class for custom elements made with `@lume/element`.\n\n\u003e [!Note]\n\u003e The `Element` class from `@lume/element` extends from `HTMLElement`.\n\u003e\n\u003e Safari does not support customized built-ins, and neither does\n\u003e `@lume/element`, so at the moment we do not support extending from other classes\n\u003e such as `HTMLButtonElement`, etc.\n\nThe `Element` class provides:\n\n### `template`\n\nA subclass can define a `.template` that returns a DOM node, and this DOM node\nwill be appened into the element's `ShadowRoot` by default, or to the element\nitself if `.hasShadow` is `false`.\n\nOne way to write a `template` is using [Solid\nJSX](https://www.solidjs.com/tutorial/introduction_jsx) syntax (this will always\nrequire a build step).\n\n```js\nimport {Element} from '@lume/element'\nimport {createSignalFunction} from 'classy-solid' // a small wrapper around Solid's createSignal that allows reading and writing from the same function.\n\nclass CoolElement extends Element {\n  count = createSignalFunction(100)\n\n  template = () =\u003e (\n    \u003cdiv\u003e\n      \u003cspan\u003eThe count is: {this.count()}!\u003c/span\u003e\n    \u003c/div\u003e\n  )\n  // ...\n}\n\ncustomElements.define('cool-element', CoolElement)\n```\n\nAnother way to write a `template` is using Solid's `html` template string tag\n(which does not require a build step). Using the following `template`, the\nexample can run in a browser without a compile step (note, we're not using\ndecorators yet):\n\n```js\n// ...\ntemplate = () =\u003e html`\n  \u003cdiv\u003e\n    \u003cspan\u003eThe count is: ${this.count}!\u003c/span\u003e\n  \u003c/div\u003e\n`\n// ...\n```\n\n[Example on CodePen](https://codepen.io/trusktr/pen/xxovyQW) (with `html` template tag instead of JSX)\n\n\u003e [!Note]\n\u003e When `count` changes, the template updates automatically.\n\nWe can also manually create DOM any other way, for example here we make and\nreturn a DOM tree using DOM APIs, and using a Solid effect to update the element\nwhen `count` changes (but we could have used React or jQuery, or anything\nelse!):\n\n```js\n// ...same...\n\nimport {createEffect} from 'solid-js'\n\n// ...same...\n\n// Replace the previous `template` with this one:\ntemplate = () =\u003e {\n  const div = document.createElement('div')\n  const span = document.createElement('span')\n  div.append(span)\n\n  createEffect(() =\u003e {\n    // Automatically set the textContent whenever `count` changes (this is a\n    // conceptually-simplified example of what Solid JSX compiles to).\n    span.textContent = `The count is: ${this.count()}!`\n  })\n\n  return div\n}\n\n// ...same...\n```\n\n[Example on CodePen](https://codepen.io/trusktr/pen/ExBqdMQ)\n\n### `static css`\n\nUse the _static_ `css` field to define a CSS string for styling all instances of\nthe given class. A static property allows `@lume/element` to optimize by sharing\na single `CSSStyleSheet` across all instances of the element, which could be\nbeneficial for performance if there are _many thousands_ of instances.\n\n```js\nimport {Element} from '@lume/element'\n\nclass CoolElement extends Element {\n  template = () =\u003e \u003cspan\u003eThis is some DOM!\u003c/span\u003e\n\n  // Style is scoped to our element, this will only style the \u003cspan\u003e inside our element.\n  static css = `\n    span { color: violet; }\n  `\n}\n\ncustomElements.define('cool-element', CoolElement)\n```\n\n[Example on CodePen](https://codepen.io/trusktr/pen/OJeKBKP) (with `html` template tag instead of JSX)\n\nThe `static css` property can also be a function:\n\n```js\n// ...\n\nclass CoolElement extends Element {\n  // ...\n  static css = () =\u003e {\n    const color = 'limegreen'\n\n    return `\n      span { color: ${color}; }\n    `\n  }\n  // ...\n}\n```\n\n[Example on CodePen](https://codepen.io/trusktr/pen/GRbVwzj) (with `html` template tag instead of JSX)\n\n\u003e :bulb:**Tip:**\n\u003e\n\u003e Use the `css` identity template tag to enable syntax highlighting and code formatting in some IDEs:\n\n```js\nimport {css} from '@lume/element'\n// ...\n\nclass CoolElement extends Element {\n  // ...\n  static css = css`\n    span {\n      color: cornflowerblue;\n    }\n  `\n  // ...\n}\n```\n\n### `css`\n\nUse the _non-static_ `css` property to define styles that are applied _per\ninstance_ of the given element. This is useful for style that should differ\nacross instances. This will not be as optimized as `static css` will be because\nit will create one stylesheet per element instance, but the performance\ndifference will not matter for most use cases.\n\n```js\nimport {Element, css} from '@lume/element'\n\nclass CoolElement extends Element {\n  template = () =\u003e \u003cspan\u003eThis is some DOM!\u003c/span\u003e\n\n  // A random color per instance.\n  #color = `hsl(calc(${Math.random()} * 360) 50% 50%)`\n\n  // Style is scoped to our element, this will only style the \u003cspan\u003e inside our element.\n  css = css`\n    span {\n      color: ${this.#color};\n    }\n  `\n}\n```\n\n[Example on CodePen](https://codepen.io/trusktr/pen/NWZQEJa) (with `html` template tag instead of JSX)\n\n### `connectedCallback`\n\nNothing new here, this is simply a part of the browser's [native Custom Elements\n`connectedCallback` API](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks).\nIt is triggered when the element is connected into the document. Use it to\ncreate things.\n\n```js\nimport {Element} from '@lume/element'\n\nclass CoolElement extends Element {\n  connectedCallback() {\n    // Don't forget to call the super method from the Element class!\n    super.connectedCallback()\n\n    // ...Create things...\n  }\n  // ...\n}\n```\n\n### `disconnectedCallback`\n\nNothing new here, this is simply a part of the browser's [native Custom Elements\n`disconnectedCallback` API](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks).\nIt is triggered when the element is disconnected from the document. Use it to\nclean things up.\n\n```js\nimport {Element} from '@lume/element'\n\nclass CoolElement extends Element {\n  disconnectedCallback() {\n    // Don't forget to call the super method from the Element class!\n    super.disconnectedCallback()\n\n    // ...Clean things up...\n  }\n  // ...\n}\n```\n\n### `adoptedCallback`\n\nNothing new here, this is simply a part of the browser's [native Custom Elements\n`adoptedCallback` API](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks).\nIt is triggered when the element is adopted into a new document (f.e. in an iframe).\n\n```js\nimport {Element} from '@lume/element'\n\nclass CoolElement extends Element {\n  adoptedCallback() {\n    // Don't forget to call the super method from the Element class!\n    super.adoptedCallback()\n\n    // ...Do something when the element was transferred into another window's or iframe's document...\n  }\n  // ...\n}\n```\n\n### `attributeChangedCallback`\n\nNothing new here, this is simply a part of the browser's [native Custom Elements\n`attributeChangedCallback` API](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks).\nIt is triggered when an _observed attribute_ of the element is added, modified,\nor removed.\n\n```js\nimport {Element} from '@lume/element'\n\nclass CoolElement extends Element {\n  static observedAttributes = ['foo', 'bar']\n\n  attributeChangedCallback(attributeName, oldValue, newValue) {\n    // Don't forget to call the super method from the Element class!\n    super.attributeChangedCallback(attributeName, oldValue, newValue)\n\n    // Attribute name is the name of the attribute change changed.\n    // If `oldValue` is `null` and `newValue` is a string, it means the attribute was added.\n    // If `oldValue` and `newValue` are both strings, it means the value changed.\n    // If `oldValue` is a string and `newValue` is `null`, it means the attribute was removed.\n  }\n  // ...\n}\n```\n\n\u003e [!Warning]\n\u003e The `static observedAttributes` property is required for observing attributes, and specifies which\n\u003e attributes will trigger `attributeChangedCallback`. `attributeChangedCallback`\n\u003e will not be triggered for any attributes that are not listed in `static observedAttributes`!\n\n### `static observedAttributes`\n\nNothing new here, this is simply a part of the browser's [native Custom Elements\n`static observedAttributes` API](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#responding_to_attribute_changes).\nIt defines which attributes will be observed. From the previous example:\n\n```js\nclass CoolElement extends Element {\n  static observedAttributes = ['foo', 'bar']\n  // ...\n}\n```\n\nNote! Although `static observedAttributes` works, it is recommended to use the\n`static observedAttributeHandlers` property instead:\n\n### `static observedAttributeHandlers`\n\nThis is an alternative to attribute decorators (recommended, see the\n[Decorators](#decorators) docs below), and will be removed after decorators\nare supported natively in JS engines.\n\nAs an alternative to `static observedAttributes`, and mainly for non-decorator\nusers (because not all JS engines support them yet at time of writing this),\nobserved attributes can be defined with `static observedAttributeHandlers`, a\nmap of attribute names to attribute handlers. This requires using the `@element`\ndecorator (calling it as a plain function for non-decorator usage). This will\nmap attributes to JS properties and make the JS properties reactive.\n\n`static observedAttributeHandlers` is an object where each key is a property\nname to be associated with an attribute, and each value is an object with the\nfollowing shape:\n\n\u003ca id=\"attributehandler\"\u003e\u003c/a\u003e\n\n```ts\n/**\n * Defines how values are mapped from an attribute to a JS property on a custom\n * element class.\n */\nexport type AttributeHandler\u003cT = any\u003e = {\n  // TODO The `to` handler currently does nothing. In the future, if there is demand\n  // for it, this will be for property-to-attribute reflection.\n  to?: (propValue: T) =\u003e string | null\n\n  /**\n   * Define how to deserialize an attribute string value on its way to the\n   * respective JS property.\n   *\n   * If not defined, the attribute string value is passed to the JS property\n   * untouched.\n   *\n   * **Default when omitted:** `value =\u003e value`\n   */\n  from?: (AttributeValue: string) =\u003e T\n\n  /**\n   * A side effect to run when the value is set on the JS property. It also\n   * runs on with the initial value. Avoid this if you can, and instead use\n   * effects. One use case of this is to call addEventListener with event\n   * listener values, just like with native `.on*` properties.\n   *\n   * **Default when omitted:** `() =\u003e {}` (no sideeffect)\n   */\n  sideEffect?: (instance: Element, prop: string, propValue: T) =\u003e void\n\n  /**\n   * @deprecated - Define a field with the initial value instead of providing\n   * the initial value here. When decorators land in browsers, this will be\n   * removed.\n   *\n   * The default value that the respective JS property should have when the\n   * attribute is removed.\n   *\n   * If this is not specified, and the respective class field is defined, it\n   * will default to the initial value of the class field.  If this is\n   * specified, it will take precedence over the respective field's initial\n   * value. This should generally be avoided, and the class field initial\n   * value should be relied on as the source of the default value.\n   *\n   * When defined, an attribute's respective JS property will be set to this\n   * value when the attribute is removed. If not defined, then the JS property\n   * will always receive the initial value of the respective JS class field or\n   * `undefined` if the field was not defined (that's the \"initial value\" of\n   * the field), when the attribute is removed.\n   *\n   * **Default when omitted:** the value of the respective class field, or\n   * `undefined` if the field was not defined.\n   */\n  default?: T\n\n  /**\n   * Whether to convert the property name to dash-case for the attribute name.\n   * This option is ignore if the `name` option is set.\n   *\n   * The default is `true`, where the attribute name will be the same as the\n   * property name but dash-cased (and all lower case). For example, `fooBar`\n   * becomes `foo-bar` and `foo-bar` stays `foo-bar`.\n   *\n   * If this is set to `false`, the attribute name will be the same as the\n   * property name, but all lowercased (attributes are case insensitive). For\n   * example `fooBar` becomes `foobar` and `foo-bar` stays `foo-bar`.\n   *\n   * Note! Using this option to make a non-standard prop-attribute mapping\n   * will result in template type definitions (f.e. in JSX) missing the\n   * customized attribute names and will require custom type definition\n   * management.\n   *\n   * **Default when omitted:** `true`\n   */\n  dashcase?: boolean\n\n  /**\n   * The name of the attribute to use. Use of this options bad practice to be\n   * avoided, but it may be useful in rare cases.\n   *\n   * If this is not specified, see `dashcase` for how the attribute name is\n   * derived from the property name.\n   *\n   * Note! Using this option to make a non-standard prop-attribute mapping\n   * will result in template type definitions (f.e. in JSX) missing the\n   * customized attribute names and will require custom type definition\n   * management.\n   *\n   * **Default when omitted:** the attribute name derived from the property\n   * name, converted to dash-case based on the `dashcase` option.\n   */\n  name?: string\n\n  /**\n   * Whether to suppress warnings about the attribute attribute name clashes\n   * when not using default `dashcase` and `name` settings. This is\n   * discouraged, and should only be used when you know what you're doing,\n   * such as overriding a property that has `dashcase` set to `false` or\n   * `name` set to the same name as the attribue of another property.\n   *\n   * **Default when omitted:** `false`\n   */\n  noWarn?: boolean\n}\n```\n\nHere's an example of an element definition with no decorators, with\nHTML attributes mapped to same-name JS properties:\n\n```js\nimport {Element, element} from '@lume/element'\n\nelement('cool-element')(\n  class CoolElement extends Element {\n    static observedAttributeHandlers = {\n      foo: {from: Number},\n      bar: {from: Boolean},\n    }\n\n    // Due to the `observedAttributeHandlers` definition, any time the `\"foo\"` attribute\n    // on the element changes, the attribute string value will be converted into a\n    // `Number` and assigned to the JS `.foo` property.\n    // Not only does `.foo` have an initial value of `123`, but when the element's\n    // `\"foo\"` attribute is removed, `.foo` will be set back to the initial value\n    // of `123`.\n    foo = 123\n\n    // Due to the `observedAttributeHandlers` definition, any time the `\"bar\"` attribute\n    // on the element changes, the attribute string value will be converted into a\n    // `Boolean` and assigned to the JS `.bar` property.\n    // Not only does `.bar` have an initial value of `123`, but when the element's\n    // `\"bar\"` attribute is removed, `.bar` will be set back to the initial value\n    // of `false`.\n    bar = false\n\n    // ...\n  },\n)\n```\n\n[Example on CodePen](https://codepen.io/trusktr/pen/rNEXoOb?editors=1111)\n\n`@lume/element` comes with a set of basic handlers available out of the box, each of\nwhich are alternatives to a respective set of included [decorators](#decorators):\n\n```js\nimport {Element, element, attribute} from '@lume/element'\n\nelement('cool-element')(\n  class CoolElement extends Element {\n    static observedAttributeHandlers = {\n      lorem: {}, // Effectively the same as attribute.string()\n      foo: attribute.string(), // Effectively the same as the @stringAttribute decorator. Values get passed to the JS property as strings.\n      bar: attribute.number(), // Effectively the same as the @numberAttribute decorator. Values get passed to the JS property as numbers.\n      baz: attribute.boolean(), // Effectively the same as the @booleanAttribute decorator. Values get passed to the JS property as booleans.\n\n      // 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.\n      bespoke: {from: value =\u003e JSON.parse(value)}, // f.e. besoke='{\"b\": true}' results in the JS property having the value `{b: true}`\n    }\n\n    // The initial values of the JS properties define the values that the JS properties get reset back to when the corresponding attributes are removed.\n    lorem = 'hello'\n    foo = 'world'\n    bar = 123\n    baz = false\n    bespoke = {n: 123}\n\n    // ...\n  },\n)\n```\n\n[Example on CodePen](https://codepen.io/trusktr/pen/rNEXbOR?editors=1011)\n\nIf decorator support is present (either with a build, or natively in near-future\nJS engines), defining attributes with [decorators](#decorators) is simpler and more concise:\n\n```js\nimport {Element, element, numberAttribute, booleanAttribute} from '@lume/element'\n\n@element('cool-element')\nclass CoolElement extends Element {\n  // Due to the `@numberAttribute` decorator, any time the `\"foo\"` attribute\n  // on the element changes, the attribute string value will be converted into a\n  // `Number` and assigned to the JS `.foo` property.\n  // Not only does `.foo` have an initial value of `123`, but when the element's\n  // `\"foo\"` attribute is removed, `.foo` will be set back to the initial value\n  // of `123`.\n  @numberAttribute foo = 123\n\n  // Due to the `@booleanAttribute` decorator, any time the `\"bar\"` attribute\n  // on the element changes, the attribute string value will be converted into a\n  // `Boolean` and assigned to the JS `.bar` property.\n  // Not only does `.bar` have an initial value of `true`, but when the element's\n  // `\"bar\"` attribute is removed, `.bar` will be set back to the initial value\n  // of `true`.\n  @booleanAttribute bar = true\n\n  // ...\n}\n```\n\n\u003e [!Note]\n\u003e Not only do decorators make the definition more concise, but they avoid surface\n\u003e area for human error: the non-decorator form requires defining the same-name\n\u003e property in both the `static observedAttributeHandlers` object and in the class fields, and if\n\u003e we miss one or the other then things might not work as expected.\n\nEach of the available decorators are detailed further [below](#decorators).\n\nDecorators, and the `static observedAttributeHandlers` object format, both work with\ngetter/setter properties as well:\n\n```js\nimport {Element, element, numberAttribute, booleanAttribute} from '@lume/element'\n\n@element('cool-element')\nclass CoolElement extends Element {\n  #foo = 123\n\n  // Like with class fields, the initial value is 123, so when the \"foo\"\n  // attribute is removed the setter will receive 123.\n  @numberAttribute\n  get foo() {\n    return this.#foo\n  }\n  set foo(v) {\n    this.#foo = v\n  }\n  // ...\n}\n```\n\nThey also work with \"auto accessors\", which creates a _prototype_ getter/setter:\n\n```js\n@element('cool-element')\nclass CoolElement extends Element {\n  // The same rules with initial values and attribute removal apply.\n  @numberAttribute accessor foo = 123\n  @booleanAttribute accessor bar = false\n\n  // ...\n}\n```\n\nIt may be redundant to write `accessor` repeatedly for each property when the\nalternative non-accessor format works too. The `accessor` format can be a\nfallback in very rare cases where a performance boost is needed (for example\nthousands of objects with many non-accessor properties being instantiated all at\nonce). Most likely there will be _other_ performance issues at the point in\nwhich we have thousands of elements being instantiated at once causing an any\nissues.\n\n#### events with `static observedAttributeHandlers`\n\nThis is an alternative for the `@eventAttribute` decorator (recommended, see the\n[`@eventAttribute`](#eventattribute) docs below), and will be removed after\nnative support for decorators lands in JS engines.\n\n```js\nimport {Element, element, attribute} from '@lume/element'\n\nconst SomeEl = element('some-el')(\n  class extends Element {\n    static observedAttributeHandlers = {\n      onjump: attribute.event(),\n    }\n\n    // Also define the property explicitly (here with an optional type definition).\n    /** @type {EventListener | null} */\n    onjump = null\n\n    connectedCallback() {\n      super.connectedCallback()\n\n      // This element dispatches a \"jump\" event every second:\n      setInterval(() =\u003e this.dispatchEvent(new Event('jump')), 1000)\n    }\n  },\n)\n\nconst el = new SomeEl()\n\nel.onjump = () =\u003e console.log('jump!')\n// or, as with \"onclick\" and other built-in attributes:\nel.setAttribute('onjump', \"console.log('jump!')\")\n\ndocument.body.append(el)\n\n// \"jump!\" will be logged every second.\n```\n\nNote that for TypeScript JSX types (TSX), we want to also define event\nproperties on the class, for example `onjump` in the last example. Any\nproperties that start with `on` will be mapped to `on`-prefixed JSX props for\ntype checking. See the [TypeScript](#typescript) section for more info.\n\n### `static elementName`\n\nThe default tag name of the elements this class instantiates. When using\nthe `@element` decorator, this property will be set to the value defined\nby the decorator.\n\n```js\n@element\nclass SomeEl extends LumeElement {\n  static elementName = 'some-el'\n}\n\nSomeEl.defineElement() // defines \u003csome-el\u003e with the SomeEl class\n```\n\n[Example on CodePen](https://codepen.io/trusktr/pen/ZEdgMZY)\n\n### `static defineElement`\n\nDefine this class for the given element `name`, or using its default name\n(`TheClass.elementName`) if no was `name` given and the element was not already\ndefined using the `@element` decorator. Defaults to using the global\n`customElements` registry unless another registry is provided (for example a\nShadowRoot-scoped registry) as a second argument.\n\n```js\n@element('some-el') // defines \u003csome-el\u003e with the decorated class\nclass SomeEl extends LumeElement {}\n\nconst OtherEl = SomeEl.defineElement('other-el') // defines \u003cother-el\u003e with an empty subclass of SomeEl\nconsole.log(OtherEl === SomeEl) // false\n\n@element // without a name, the decorator does not perform the element definition\nclass AnotherEl extends LumeElement {}\n\nconst El = AnotherEl.defineElement('another-el') // defines \u003canother-el\u003e\nconsole.log(El === AnotherEl) // true\nconst El2 = AnotherEl.defineElement('yet-another-el') // defines \u003cyet-another-el\u003e\nconsole.log(El2 === AnotherEl) // false\n```\n\nIf the class is already registered with another name, then the class will be\nextended with an empty subclass so that a new class is used for the new name,\nbecause a CustomElementRegistry does not allow the same class reference to be\nused more than once regardless of the name.\n\nReturns the defined element class, which may be a different subclass of the\nclass this is called on if the class this is called on is already associated\nwith another name, otherwise returns the same class this is called on.\n\n[Example on CodePen](https://codepen.io/trusktr/pen/JjQgaxb)\n\n### `hasShadow`\n\nWhen `true`, the custom element will have a `ShadowRoot`. Set to `false`\nto not use a `ShadowRoot`. When `false`, styles will not be scoped via\nthe built-in `ShadowRoot` scoping mechanism, but by a much more simple\nshared style sheet placed at the nearest root node, with `:host`\nselectors converted to tag names.\n\n```js\n@element('some-el')\nclass SomeEl extends Element {\n  hasShadow = false\n\n  template = () =\u003e html`\u003cdiv\u003ehello\u003c/div\u003e`\n}\n```\n\nThe `template` content will be appended to the SomeEl instance directly, with no `ShadowRoot`:\n\n```html\n\u003csome-el id=\"el\"\u003e\u003c/some-el\u003e\n\u003cscript\u003e\n  const el = document.getElementById('el')\n  console.log(el.shadowRoot) // null\n  console.log(el.children[0]) // \u003cdiv\u003ehello\u003c/div\u003e\n\u003c/script\u003e\n```\n\n[Example on CodePen](https://codepen.io/trusktr/pen/eYwqLPY)\n\n\u003e [!Note]\n\u003e Note that without a ShadowRoot, `\u003cslot\u003e` no longer works because it must be\n\u003e inside a ShadowRoot, therefore going without a ShadowRoot is useful moreso for\n\u003e elements that are leafs at the end of DOM tree branches and elements that\n\u003e will not accept any slotted children and will only have `template` content as their\n\u003e children.\n\n### `templateRoot`\n\nSubclasses can override the `templateRoot` property to provide an alternate Node for\n`template` content to be placed into (f.e. a subclass can set it to `this` to have\n`template` content appended to itself regardless of the value of `hasShadow`).\n\nA primary use case for this is customizing the ShadowRoot:\n\n```js\n@element('some-el')\nclass SomeEl extends Element {\n  // Create the element's ShadowRoot with custom options for example:\n  templateRoot = this.attachShadow({\n    mode: 'closed',\n  })\n\n  template = () =\u003e html`\u003cdiv\u003ehello\u003c/div\u003e`\n}\n```\n\n[Example on CodePen](https://codepen.io/trusktr/pen/MWMNpbR)\n\n### `shadowOptions`\n\nDefine a `shadowOptions` property to specify any options for the element's\nShadowRoot. These options are passed to `attachShadow()`. This is a simpler\nalternative to overriding `templateRoot` in the previous example.\n\n```js\n@element('some-el')\nclass SomeEl extends Element {\n  shadowOptions = {mode: 'closed'}\n\n  template = () =\u003e html`\u003cdiv\u003ehello\u003c/div\u003e`\n}\n```\n\n### `styleRoot`\n\nSimilar to the previous `templateRoot`, this defines which `Node` to append style\nsheets to when `hasShadow` is `true`. This is ignored if `hasShadow` is\n`false`. It defaults to `this.templateRoot`, which in turn defaults to the element's\n`ShadowRoot`.\n\nWhen `hasShadow` is `true`, an alternate `styleRoot` is sometimes desired so\nthat styles will be appended elsewhere than the `templateRoot`. To customize\nthis, override it:\n\n```js\n@element('some-el')\nclass SomeEl extends Element {\n  styleRoot = document.createElement('div')\n\n  template = () =\u003e html`\n    \u003cdiv\u003e\n      \u003cdiv\u003e${this.styleRoot}\u003c/div\u003e\n\n      \u003cspan\u003ehello\u003c/span\u003e\n    \u003c/div\u003e\n  `\n}\n```\n\n[Example on CodePen](https://codepen.io/trusktr/pen/yLdmxEW)\n\nThis can be useful for fixing issues where the default append location of an\nelement's style sheet into the `ShadowRoot` conflicts with how DOM is created in\n`template` (f.e. if the user's DOM creation in `template` clears the\n`ShadowRoot` content, or etc, then the user may want to place the stylesheet\nsomewhere else).\n\n### `createEffect`\n\nThe `createEffect` method is a wrapper around Solid's `createEffect` with some differences for convenience:\n\n- `createRoot` is not required in order to dispose of effects created with `this.createEffect()`\n- Effects created with `this.createEffect()` will automatically be cleaned up when the element is disconnected.\n- Besides being useful for re-running logic on signals changes,\n  `this.createEffect()` is useful as an alternative to `disconnectedCallback` when\n  paired with Solid's `onCleanup`.\n\n```js\nimport {Element} from '@lume/element'\nimport {createSignal, onCleanup} from 'solid-js'\n\nconst [count, setCount] = createSignal(0)\n\nsetInterval(() =\u003e setCount(n =\u003e ++n), 1000)\n\nclass CoolElement extends Element {\n  connectedCallback() {\n    super.connectedCallback()\n\n    // Log `count()` any time it changes.\n    this.createEffect(() =\u003e console.log(count()))\n\n    this.createEffect(() =\u003e {\n      const interval1 = setInterval(() =\u003e console.log('interval 1'), 1000)\n      onCleanup(() =\u003e clearInterval(interval1))\n\n      const interval2 = setInterval(() =\u003e console.log('interval 2'), 1000)\n      onCleanup(() =\u003e clearInterval(interval2))\n    })\n  }\n}\n\ncustomElements.define('cool-element', CoolElement)\n\n// After removing the element, onCleanup fires and cleans up the intervals created in connectedCallback (not the count interval outside the element)\nsetTimeout(() =\u003e {\n  const el = document.querySelector('cool-element')\n  el.remove()\n}, 2000)\n```\n\n[Example on CodePen](https://codepen.io/trusktr/pen/MWNgaGQ?editors=1011)\n\nCompare that to using `disconnectedCallback`:\n\n```js\nimport {Element} from '@lume/element'\nimport {createSignal, onCleanup} from 'solid-js'\n\nconst [count, setCount] = createSignal(0)\n\nsetInterval(() =\u003e setCount(n =\u003e ++n), 1000)\n\nclass CoolElement extends Element {\n  #interval1 = 0\n  #interval2 = 0\n\n  connectedCallback() {\n    super.connectedCallback()\n\n    // Log `count()` any time it changes.\n    this.createEffect(() =\u003e console.log(count()))\n\n    this.#interval1 = setInterval(() =\u003e console.log('interval 1'), 1000)\n    this.#interval2 = setInterval(() =\u003e console.log('interval 2'), 1000)\n  }\n\n  disconnectedCallback() {\n    super.disconnectedCallback()\n\n    clearInterval(this.#interval1)\n    clearInterval(this.#interval2)\n  }\n}\n\ncustomElements.define('cool-element', CoolElement)\n```\n\n\u003e :bulb:**Tip:**\n\u003e\n\u003e Prefer `onCleanup` instead of `disconnectedCallback` because composition of\n\u003e logic will be easier while also keeping it co-located and easier to read. That\n\u003e example is simple, but when logic grows, having to clean things up in\n\u003e `disconnectedCallback` can get more complicated, especially when each piece of\n\u003e creation logic and cleanup logic is multiple lines long and interleaving\n\u003e them would be harder to read. Plus, putting them in effects makes them\n\u003e creatable+cleanable if signals in the effects change, not just if the element is\n\u003e connected or disconnected. For example, the following element cleans up the\n\u003e interval any time the signal changes, not only on disconnect:\n\n```js\nimport {Element} from '@lume/element'\nimport {createSignal, onCleanup} from 'solid-js'\n\nconst [count, setCount] = createSignal(0)\n\nsetInterval(() =\u003e setCount(n =\u003e ++n), 1000)\n\nclass CoolElement extends Element {\n  connectedCallback() {\n    super.connectedCallback()\n\n    // Log `count()` any time it changes.\n    this.createEffect(() =\u003e console.log(count()))\n\n    this.createEffect(() =\u003e {\n      // Run the interval only during moments that count() is an even number.\n      // Whenever count() is odd, the running interval will be cleaned up and a new interval will not be created.\n      // Also, when the element is disconnected (while count() is even), the interval will be cleaned up.\n      if (count() % 2 !== 0) return\n      const interval = setInterval(() =\u003e console.log('interval'), 100)\n      onCleanup(() =\u003e clearInterval(interval))\n    })\n  }\n}\n\ncustomElements.define('cool-element', CoolElement)\n\n// After removing the element, onCleanup fires and cleans up any interval currently created in connectedCallback (not the count interval outside the element)\nsetTimeout(() =\u003e {\n  const el = document.querySelector('cool-element')\n  el.remove()\n}, 2500)\n```\n\n[Example on CodePen](https://codepen.io/trusktr/pen/qBeWOLz?editors=1011)\n\nThe beauty of this is we can write logic based on signals, without worrying\nabout `disconnectedCallback`, and we'll rest assured things clean up properly.\nCleanup logic is co-located with the pieces they are relevant to, which opens\nthe door to powerful compositional patterns...\n\n## Decorators\n\nUsing decorators (if available in your build, or natively in your JS engine)\ninstead of `static observedAttributeHandlers` or `static events` is more concise\nand less error prone.\n\nHere's the list of included attribute decorators and the attribute handler\nequivalents:\n\n- Use `@stringAttribute foo` in place of `foo: {}`\n- Use `@stringAttribute foo` in place of `foo: attribute.string()`\n- Use `@numberAttribute foo` in place of `foo: attribute.number()`\n- Use `@booleanAttribute foo` in place of `foo: attribute.boolean()`\n\n\u003e [!Warning]\n\u003e When using attribute decorators, the `@element` decorator is also required on\n\u003e the class, or the attribute decorators won't work.\n\nBelow are more details on each decorator:\n\n### `@element`\n\nThe star of the show, a decorator for defining a custom element.\n\nWhen passed a string, it will be the element's tag name:\n\n```js\nimport {Element, element} from '@lume/element'\n\n@element('cool-element')\nclass CoolElement extends Element {\n  // ...\n}\n```\n\n\u003e [!Note]\n\u003e Make sure you extend from the `Element` base class from `@lume/element` when\n\u003e using the `@element` decorator.\n\nWhen not passed a string, the element will not be defined (while reactivity\nfeatures will still be applied), and `customElements.define` should be used\nmanually, which can be useful for upcoming scoped registries:\n\n```js\nimport {Element, element} from '@lume/element'\n\n@element\nclass CoolElement extends Element {\n  // ...\n}\n\ncustomElements.define('cool-element', CoolElement)\n// or\nconst myRegistry = new CustomElementRegistry()\nmyRegistry.define('cool-element', CoolElement)\n```\n\nFinally, even if passed a string for the element name, a second boolean option\ncan disable automatic definition as well. The constructor's `.defineElement()`\nmethod can be used to trigger the definition using the given name:\n\n```js\nimport {Element, element} from '@lume/element'\n\nconst autoDefine = false\n\n@element('cool-element', autoDefine)\nclass CoolElement extends Element {\n  // ...\n}\n\nCoolElement.defineElement() // defines \u003ccool-element\u003e\n```\n\nA custom name can be passed to `.defineElement()` too:\n\n```js\nCoolElement.defineElement('other-element') // defines \u003cother-element\u003e (even if `\u003ccool-element\u003e` is already defined)\n```\n\n### `@attribute`\n\nA decorator for defining a generic element attribute. The name of the property\nis mapped from camelCase to dash-case.\n\nThe `@attribute` decorator is effectively the same as the `@stringAttribute` decorator.\n\n```ts\nimport {Element, element, attribute} from '@lume/element'\n\n@element('cool-element')\nclass CoolElement extends Element {\n  @attribute firstName = null // the attribute name is first-name\n  // ...\n}\n```\n\nWhen an attribute is removed, the JS property will receive the default value\ndetermined by the initial value of the JS property.\n\nSample usage of the attribute from the outside:\n\n```js\nconst el = document.querySelector('cool-element')\n\nel.setAttribute('first-name', 'Superman')\nconsole.log(el.firstName) // logs \"Superman\"\n\nel.removeAttribute('first-name')\nconsole.log(el.firstName) // logs null\n```\n\nHad we defined a different initial value,\n\n```js\n\t@attribute firstName = 'Batman'\n```\n\nthen removing the attribute would have set the JS property back to that non-null value:\n\n```js\nconst el = document.querySelector('cool-element')\n\nel.setAttribute('first-name', 'Superman')\nconsole.log(el.firstName) // logs \"Superman\"\n\nel.removeAttribute('first-name')\nconsole.log(el.firstName) // logs \"Batman\"\n```\n\nThis is great because the intial values that we see in the class definition are\nalways the expected values when the element has no attributes, or when all\nattributes are removed, making the outcome more _predictable and consistent_.\n\nFor TypeScript, if the initial value is a string and we're using `@attribute` (or\n`@stringAttribute`), then no type annotation is needed because it will always\nreceive a string (f.e. even when the attribute is removed) and the type will be\ninferred from the initial value:\n\n```ts\n@element('cool-element')\nclass CoolElement extends Element {\n  @attribute firstName = 'Batman' // always a `string`\n  // ...\n}\n```\n\nYou could of course make the string type more specific,\n\n```ts\n@element('cool-element')\nclass CoolElement extends Element {\n  @attribute firstName: 'Batman' | 'Robin' = 'Batman'\n  // ...\n}\n```\n\nbut note that this does not prevent any string value being set via the\nattribute.\n\nYou can of course make a broader type that accepts a string from the element\nattribute, but also other types via the JS property directly, but you'd\ngenerally want to avoid this, unless you're using a getter/setter to coerce\nsetter values into a single consistent type that the getter always returns (like\nhow the builtin `el.style=` can accept a string but the return value of\n`el.style` is always an object), or the user's input is always unchanged and\nmapped separately to internal structures:\n\n```ts\nimport {Element, element, attribute} from '@lume/element'\n\n@element('cool-element')\nclass CoolElement extends Element {\n  @attribute firstName: string | number = 'Batman'\n  // ...\n}\n```\n\n```js\nconst el = document.querySelector('cool-element')\n\nel.firstName = 123 // ok\n```\n\nThe `@attribute` decorator is also useful for defining custom handling of\nattributes. For example, the following shows how we can define an attribute that\ncan accept JSON string values by providing an\n[`AttributeHandler`](#attributehandler) definition, using the `from` option to\ndefine how string values _from_ the attribute are coerced when they are assigned\nto the JS property:\n\n```js\n// Here, `attribute` is not called as a decorator. When `attribute` is given an\n// argument that defines how to handle an attribute, it will return a new\n// decorator function.\nconst jsonAttribute = attribute({from: str =\u003e JSON.parse(str)})\n```\n\nNow we can use the new `jsonAttribute` decorator in an element class:\n\n```js\n@element('cool-element')\nclass CoolElement extends Element {\n  @jsonAttribute someValue = {foo: 123}\n  // ...\n}\n```\n\nNow in HTML it can accept a JSON string:\n\n```html\n\u003ccool-element id=\"el\" some-value='{\"foo\": 456}'\u003e\u003c/cool-element\u003e\n\u003cscript\u003e\n  console.log(el.someValue) // logs the object {foo: 456}\n  el.setAttribute('foo', '{\"foo\": 789}')\n  console.log(el.someValue) // logs the object {foo: 789}\n\u003c/script\u003e\n```\n\nNote that we could have used `attribute` result as a decorator directly,\n\n```js\n@element('cool-element')\nclass CoolElement extends Element {\n  @attribute({from: str =\u003e JSON.parse(str)}) someValue = {foo: 123}\n  // ...\n}\n```\n\nbut then the result would not have been saved into a re-usable `jsonAttribute`\nvariable, and the class field definition would have been a little messier to\nread.\n\nWhat new attribute decorators will you make? A `@stringEnumAttribute` that\naccepts only certain string values otherwise throws an error? A\n`@cssColorAttribute` that accepts only CSS-format color strings otherwise throws\nan error? A `@threeColorAttribute` that coerces CSS color values into Three.js\n`Color` objects? The sky is not the limit!\n\n### `@stringAttribute`\n\nThe `@stringAttribute` decorator is effectively the same as the `@attribute`\ndecorator, but without the ability to accept arguments to define new attribute\ndecorators. See the previous section.\n\nThis is preferable over plain `@attribute` for keeping the class definition\nsemantic and clear. Prefer using `@attribute` for custom attribute types that\nare not supported out of the box.\n\n### `@numberAttribute`\n\nA decorator that defines an attribute that accepts a number. Any value the\nattribute receives will be passed to the JS property, which is then coerced into\na number with `parseFloat`. The JS property will convert a `null` value\n(attribute removed) to the default value defined by the initial property value,\nand will convert any string into a number (if the string is invalid the property\nvalue will result in `NaN`).\n\n```ts\nimport {Element, element, numberAttribute} from '@lume/element'\n\n@element('cool-element')\nclass CoolElement extends Element {\n  @numberAttribute age = 10\n  // ...\n}\n```\n\n```js\nconst el = document.querySelector('cool-element')\n\nel.setAttribute('age', '20')\nconsole.log(el.age) // logs 20\nconsole.log(typeof el.age) // logs \"number\"\n\nel.removeAttribute('age')\nconsole.log(el.age) // logs 10\nconsole.log(typeof el.age) // logs \"number\"\n\nel.age = '30' // assign a string (type error in TypeScript)\nconsole.log(el.age) // logs 30\nconsole.log(typeof el.age) // logs \"number\"\n```\n\nFor TypeScript, you don't need a type annotation if the initial value is a\nnumber. Add a type annotation only if you use a non-number initial value, f.e.\n`number | SomeOtherType`, but that is not recommended:\n\n```ts\nimport {Element, element, numberAttribute} from '@lume/element'\n\n@element('cool-element')\nclass CoolElement extends Element {\n  @numberAttribute age: 'ten' | number = 'ten'\n  // ...\n}\n```\n\n```js\nconst el = document.querySelector('cool-element')\n\nel.setAttribute('age', '20')\nconsole.log(el.age) // logs 20\nconsole.log(typeof el.age) // logs \"number\"\n\nel.removeAttribute('age')\nconsole.log(el.age) // logs \"ten\"\nconsole.log(typeof el.age) // logs \"string\"\n\nel.age = 'ten'\nconsole.log(el.age) // logs \"NaN\", which is confusing (hence, avoid doing this).\nconsole.log(typeof el.age) // logs \"number\"\n```\n\n### `@booleanAttribute`\n\nA decorator that defines a boolean attribute. Any value the attribute receives\nwill be passed to the JS property, which is then coerced into a `boolean`. The\nJS property will convert a `null` value (attribute removed) to the default value\ndefined by the initial property value, and will convert any string into boolean.\nAll string values except `\"false\"` result in the boolean `true`, and the string\n`\"false\"` results in the boolean `false`.\n\nTo mimick the same behavior as boolean attributes on built-in elements where the\npresence of the attribute is `true`, and absence of the attribute is `false`, start\nwith an initial value of `false`:\n\n```ts\nimport {Element, element, booleanAttribute} from '@lume/element'\n\n@element('cool-element')\nclass CoolElement extends Element {\n  @booleanAttribute hasPizza = false\n  // ...\n}\n```\n\nIf the attribute value exists, the JS property will receive `true`, except if\nthe value of the attribute is explicitly `\"false\"`:\n\n```js\nconst el = document.querySelector('cool-element')\n\nel.setAttribute('has-pizza', '')\nconsole.log(el.age) // logs true\nconsole.log(typeof el.age) // logs \"boolean\"\n\nel.setAttribute('has-pizza', 'blah blah')\nconsole.log(el.age) // logs true\nconsole.log(typeof el.age) // logs \"boolean\"\n\nel.removeAttribute('has-pizza')\nconsole.log(el.age) // logs false\nconsole.log(typeof el.age) // logs \"boolean\"\n\n// A literal \"false\" value is special and is treated as false.\nel.setAttribute('has-pizza', 'false')\nconsole.log(el.age) // logs false\nconsole.log(typeof el.age) // logs \"boolean\"\n```\n\nHere is the equivalent example in HTML describing the values of `has-pizza`:\n\n```html\n\u003c!-- hasPizza == true --\u003e\n\u003ccool-element has-pizza\u003e\u003c/cool-element\u003e\n\n\u003c!-- hasPizza == true --\u003e\n\u003ccool-element has-pizza=\"true\"\u003e\u003c/cool-element\u003e\n\n\u003c!-- hasPizza == true --\u003e\n\u003ccool-element has-pizza=\"blah blah\"\u003e\u003c/cool-element\u003e\n\n\u003c!-- hasPizza == false --\u003e\n\u003ccool-element\u003e\u003c/cool-element\u003e\n\n\u003c!-- hasPizza == false --\u003e\n\u003ccool-element has-pizza=\"false\"\u003e\u003c/cool-element\u003e\n```\n\nThe purpose of treating `\"false\"` as explicitly `false` is that this makes it\npossible to have the attribute be present while still being able to express both\nvalues,\n\n```html\n\u003ccool-element has-pizza=\"true\"\u003e\u003c/cool-element\u003e \u003ccool-element has-pizza=\"false\"\u003e\u003c/cool-element\u003e\n```\n\nwhile also having the option to express the same thing using only attribute\npresence:\n\n```html\n\u003ccool-element has-pizza\u003e\u003c/cool-element\u003e \u003ccool-element\u003e\u003c/cool-element\u003e\n```\n\nIf you start with an initial value of `true`, then when the attribute is removed\nor never existed, the JS property will be `true`, which again is useful for\npredictability of default state.\n\n```ts\nimport {Element, element, booleanAttribute} from '@lume/element'\n\n@element('cool-element')\nclass CoolElement extends Element {\n  @booleanAttribute hasPizza = true\n  // ...\n}\n```\n\nIn this case, only an attribute value of `\"false\"` can set the JS property to `false`:\n\n```js\nconst el = document.querySelector('cool-element')\n\nel.setAttribute('has-pizza', '')\nconsole.log(el.age) // logs true\nconsole.log(typeof el.age) // logs \"boolean\"\n\nel.setAttribute('has-pizza', 'blah blah')\nconsole.log(el.age) // logs true\nconsole.log(typeof el.age) // logs \"boolean\"\n\nel.removeAttribute('has-pizza')\nconsole.log(el.age) // logs true\nconsole.log(typeof el.age) // logs \"boolean\"\n\n// Only a literal value of \"false\" will cause the JS property to be false in this case.\nel.setAttribute('has-pizza', 'false')\nconsole.log(el.age) // logs false\nconsole.log(typeof el.age) // logs \"boolean\"\n```\n\nEquivalent HTML:\n\n```html\n\u003c!-- hasPizza == true --\u003e\n\u003ccool-element has-pizza\u003e\u003c/cool-element\u003e\n\n\u003c!-- hasPizza == true --\u003e\n\u003ccool-element has-pizza=\"foo\"\u003e\u003c/cool-element\u003e\n\n\u003c!-- hasPizza == true --\u003e\n\u003ccool-element has-pizza=\"blah blah\"\u003e\u003c/cool-element\u003e\n\n\u003c!-- hasPizza == true --\u003e\n\u003ccool-element\u003e\u003c/cool-element\u003e\n\n\u003c!-- hasPizza == false --\u003e\n\u003ccool-element has-pizza=\"false\"\u003e\u003c/cool-element\u003e\n```\n\nIn this form, with the property initial value as `true`, then the following two\nare identical (the JS property is `true` in either case),\n\n```html\n\u003ccool-element has-pizza\u003e\u003c/cool-element\u003e \u003ccool-element\u003e\u003c/cool-element\u003e\n```\n\nand expressing both true and false side by side would require\nexplicit values:\n\n```html\n\u003ccool-element has-pizza=\"true\"\u003e\u003c/cool-element\u003e \u003ccool-element has-pizza=\"false\"\u003e\u003c/cool-element\u003e\n```\n\n\u003e :bulb:**Tip:**\n\u003e\n\u003e Avoid attribute values like `has-pizza=\"blah blah\"`, because they are not semantic.\n\u003e When the default JS property value is `false`, always use the form\n\u003e `has-pizza=\"false\"` or no attribute for `false`, and `has-pizza` or\n\u003e `has-pizza=\"true\"` for `true`.\n\u003e When the default JS property value is `true`, always use the form\n\u003e `has-pizza=\"false\"` for `false`, and `has-pizza`, `has-pizza=\"true\"`, or no\n\u003e attribute, for `true`.\n\n### `@eventAttribute`\n\nUse this decorator to create event listener attributes/properties, the same as\nwith built-in event attributes/properties such as \"onclick\".\n\n```js\nimport {Element, element, eventAttribute} from '@lume/element'\n\n@element('some-el')\nclass MyEl extends Element {\n  /** @type {EventListener | null} */\n  @eventAttribute onjump = null\n\n  connectedCallback() {\n    super.connectedCallback()\n\n    // This element dispatches a \"jump\" event every second:\n    setInterval(() =\u003e this.dispatchEvent(new Event('jump')), 1000)\n  }\n}\n\nconst el = new SomeEl()\n\nel.onjump = () =\u003e console.log('jump!')\n// or, as with \"onclick\" and other built-in attributes:\nel.setAttribute('onjump', \"console.log('jump!')\")\n\ndocument.body.append(el)\n\n// \"jump!\" will be logged every second.\n```\n\nNote that besides the event properties working in JS, the attributes also work\nin plain HTML as with native event attributes such as `onclick`:\n\n```html\n\u003cbody\u003e\n  \u003cmy-el onjump=\"console.log('jump!')\"\u003e\u003c/my-el\u003e\n\u003c/body\u003e\n```\n\n### `@signal`\n\nThis is from [`classy-solid`](https://github.com/lume/classy-solid) for creating\nsignal properties, but because `@element` is composed with classy-solid's\n`@reactive` class decorator, a non-attribute signal property can be defined\nwithout also having to use classy-solid's `@reactive` decorator on the class:\n\n```ts\nimport {Element, element, booleanAttribute} from '@lume/element'\nimport {reactive, signal} from 'classy-solid'\n\n// Non element class, requires `@reactive` for fields decorated with `@signal`:\n@reactive\nclass Something {\n  @signal foo = 123 // This will be reactive\n}\n\n// An element class decoratorated with `@element` (or passed to `element()` when\n// not using decorators) does not also need to be decorated with `@reactive`:\n@element('cool-element')\nclass CoolElement extends Element {\n  // hasPizza will be reactive but an attribute will not be observed for this\n  // property, and the property can only be set via JS.\n  @signal hasPizza = false\n\n  // This property *does* get updated when a `has-drink` attribute is updated, and is also reactive.\n  @booleanAttribute hasDrink = false\n\n  // ...\n}\n```\n\n### `@noSignal`\n\nOnce in a blue moon you might need to define an attribute property that is not\nreactive, for some reason. Avoid it if you can, but you can do it with\n`@noSignal`:\n\n```ts\nimport {Element, element, booleanAttribute, noSignal} from '@lume/element'\n\n@element('cool-element')\nclass CoolElement extends Element {\n  // This property gets updated when a `has-drink` attribute is updated, but it is not reactive.\n  @booleanAttribute @noSignal hasDrink = false\n}\n```\n\nThis is more useful on a getter/setter where you may implement your own\nreactivity for the property:\n\n```ts\nimport {Element, element, booleanAttribute, noSignal} from '@lume/element'\n\n@element('cool-element')\nclass CoolElement extends Element {\n  #hasDrink = false\n\n  // This property gets updated when a `has-drink` attribute is updated, and\n  // it is not reactive due to the `@booleanAttribute` decorator but due to a\n  // custom implementation in the getter/setter (for example, maybe the\n  // getter/setter reads from and writes to a Solid signal.\n  @booleanAttribute @noSignal get hasDrink() {\n    // ...\n    return this.#hasDrink\n  }\n  @booleanAttribute @noSignal set hasDrink(value) {\n    // ...\n    this.#hasDrink = value\n  }\n}\n```\n\n\u003e [!Note]\n\u003e Make sure the `@noSignal` decorator is listed _after_ the attribute decorator. This will not work:\n\n```js\nclass CoolElement extends Element {\n  // This won't work because the attribute decorator will run before the\n  // noSignal decorator so the attribute decorator will miss the signal that\n  // it should skip the signal (pun intended!).\n  @noSignal @booleanAttribute hasDrink = false\n}\n```\n\n# Runtime Type Checking\n\nThe `from` handler of a newly-defined attribute decorator (defining new\nattribute decorators is described in the `@attribute` doc above) can throw an\nerror when an invalid string is encountered. Expanding the previous `jsonAttribute` example:\n\n```js\nconst jsonAttribute = attribute({\n  from(str){\n    const result = JSON.parse(str)\n    if (/* some condition not met with result */) throw new Error('...describe the error...')\n    return result\n  }\n})\n```\n\nThis error handling will work regardless if setting an attribute, or setting a\nstring via the JS property.\n\nAn alternative approach is to throw an error in the `set`ter of an\n`@attribute`-decorated property, which can be useful for existing code that\nmight already exist where the `@attribute` decorator is being added:\n\n```js\n@element('cool-element')\nclass CoolElement extends Element {\n  #foo = 123\n\n  @attribute get someValue() {\n    return this.#foo\n  }\n  @attribute set someValue(value) {\n    if (/* some condition not met with value */) throw new Error('...error description...')\n    this.#foo = value\n  }\n}\n```\n\n# TypeScript\n\n## Attribute property types\n\nHere are the recommended types for properties depending on the type of attribute\nbeing defined, with non-null initial values:\n\n```ts\nimport {Element, element, attribute, stringAttribute, numberAttribute, booleanAttribute} from '@lume/element'\n\n@element('cool-element')\nclass CoolElement extends Element {\n  @attribute firstName: string = 'John'\n  @stringAttribute lastName: string = 'Doe'\n  @numberAttribute age: number = 75\n  @booleanAttribute likesPizza: boolean = true\n  // ...\n}\n```\n\nIf properties are initialized with `null` values, add `| null` to each type:\n\n```ts\n@element('cool-element')\nclass CoolElement extends Element {\n  @attribute firstName: string | null = null\n  @stringAttribute lastName: string | null = null\n  @numberAttribute age: number | null = null\n  @booleanAttribute likesPizza: boolean | null = null\n  // ...\n}\n```\n\nAll attribute properties can technically accept strings too, as this is how\nattribute string values get coerced in case of non-string attributes.\nAlthough it is not recommended, this aspect of the properties can be exposed if\nneeded:\n\n```ts\n@element('cool-element')\nclass CoolElement extends Element {\n  @attribute firstName: string = 'John'\n  @stringAttribute lastName: string = 'Doe'\n  @numberAttribute age: `${number}` | number = 75\n  @booleanAttribute likesPizza: `${boolean}` | boolean = true\n  // ...\n}\n\nconst el = new CoolElement()\n\nel.age = '80' // no type error here\n\nconsole.log(el.age, typeof el.age) // logs \"80 number\"\n\nel.age = 'blah' // type error, 'blah' is not assignable to a string with number format defined by `${number}`.\n```\n\nIt is nice to _not_ include the string types, especially because the string\nvalues are always coerced by the attribute `from` handler, and JS/TS users can\nset the actual values directly (f.e. numbers or booleans). You will always\nreceive a `number` when _reading_ a JS property decorated with\n`@numberAttribute`, for example, so including the string type could make things\nconfusing and less ideal. For example, when _reading_ the value of a\n`@numberAttribute` property, the following may be redundant and annoying when\nreading the value, especially in TypeScript:\n\n```js\nif (typeof el.age === 'number') { // this check is unnecessary/annoying, required for types to check out\n  const n: number = el.age\n}\n// or\nconst n: number = el.age as number // unnecessary/annoying cast in this case\n\nif (typeof el.age === 'string') console.log('this will never be logged')\n```\n\n## Solid.js JSX expressions\n\n(Note this section is only for Solid.js, as other frameworks like React or Preact do not have DOM-returning JSX expressions.)\n\nLoad the required JSX types in one of two ways:\n\n1.  Import the types locally within particular files where JSX is used (this is\n    useful for preventing type conflicts if you have other files that use React\n    JSX types or other JSX types):\n\n    ```ts\n    /* jsxImportSource solid-js */\n    ```\n\n2.  Place the `jsxImportSource` in your tsconfig.json to have it apply to all\n    files (this works great if you use only one form of JSX types in your\n    project, but if you have files with different types of JSX, you'll want to\n    use option 1 instead).\n\n    ```js\n    {\n    \t\"compilerOptions\": {\n    \t\t/* Solid.js Config */\n    \t\t// Note, you need to use an additional tool such as Babel, Vite, etc, to\n    \t\t// compile Solid JSX. `npm create solid` will scaffold things for you.\n    \t\t\"jsx\": \"preserve\",\n    \t\t\"jsxImportSource\": \"solid-js\"\n    \t}\n    }\n    ```\n\nIn TypeScript, all JSX expressions have the type `JSX.Element`. But Solid's JSX\nexpressions return actual DOM nodes, and we want the JSX expression types to\nreflect that fact. For this we have a set of convenience helpers to cast JSX\nexpressions to DOM element types in the `@lume/element/dist/type-helpers.js`\nmodule.\n\nModifying the example from [Easily create and manipulate DOM](#easily-create-and-manipulate-dom)\nfor TypeScript, it would look like the following.\n\n```tsx\nimport {createSignal} from 'solid-js'\nimport {div} from '@lume/element/dist/type-helpers.js'\n\nconst [count, setCount] = createSignal(0)\n\nsetInterval(() =\u003e setCount(count() + 1), 1000)\n\nconst el = div(\n  \u003cdiv\u003e\n    \u003ch1 data-count={count()}\u003eThe count is: {count()}\u003c/h1\u003e\n  \u003c/div\u003e,\n)\n\nel.setAttribute('foo', 'bar')\n\ndocument.body.appendChild(el)\n```\n\nThe main differences from plain JS are\n\n- Use of the `@jsxImportSource` comment to place JSX types into scope. This is\n  required, or TypeScript will not know what the types of elements in JSX\n  markup are. Alternative to comments, configure it in tsconfig.json's\n  `compilerOptions`.\n- The `div()` helper function explicitly returns the type `HTMLDivElement` so\n  that the `el` variable will be typed as `HTMLDivElement` instead of\n  `JSX.Element`. Under the hood, the `div()` function is an identity function\n  at runtime, it simply returns whatever you pass into it, and serves only as a\n  convenient type cast helper.\n\n\u003e [!Warning]\n\u003e Keep in mind to use the correct type helper depending on what the root element\n\u003e of the JSX expression is. For for example, if the root of a JSX expression is a\n\u003e `\u003cmenu\u003e` element then we need to use the `menu()` helper like follows.\n\n```tsx\nimport {createSignal} from 'solid-js'\nimport {menu} from '@lume/element/dist/type-helpers.js'\n\n// ...\n\n// The type of `el` will be `HTMLMenuElement`.\nconst el = menu(\n  \u003cmenu\u003e\n    \u003ch1 data-count={count()}\u003eThe count is: {count()}\u003c/h1\u003e\n  \u003c/menu\u003e,\n)\n```\n\nIf the wrong helper is used, then it will effectively cast the expression to\nthe wrong type. For example, in the next snippet the `el` variable will be of\ntype `HTMLDivElement` despite the fact that at runtime we will be have an\n`HTMLMenuElement` instance.\n\n```tsx\nimport {div, button} from '@lume/element/dist/type-helpers.js'\n\n// GOOD.\nconst el = button(\u003cbutton\u003e...\u003c/button\u003e)\n\n// BAD! Don't do this! Remember to double check, because the helpers are not\n// type safe, you will not get an error here, and el2 will incorrectly be type\n// HTMLDivElement.\nconst el2 = div(\u003cmenu\u003e...\u003c/menu\u003e)\n```\n\nWithout the type helpers, we would need to write more verbose code like the\nfollowing to have the proper types, but note that the following is also not type\nsafe:\n\n```tsx\n// GOOD.\nconst el = (\u003cmenu\u003e...\u003c/menu\u003e) as any as HTMLMenuElement\n\n// BAD! Don't do this! Remember to double check, because the helpers are not\n// type safe, you will not get an error here.\nconst el2 = (\u003cmenu\u003e...\u003c/menu\u003e) as any as HTMLDivElement\n```\n\n## Type definitions for custom elements in frameworks\n\n(For type definitions for function components, see [Solid.js](https://solidjs.com) docs).\n\n### In Solid JSX (in Lume Elements)\n\nExample: ['kitchen-sink-tsx'](./examples/kitchen-sink-tsx/)\n\nFirst set up `jsxImportSource` as mentioned above.\n\nTo give our Custom Elements type checking for use with DOM APIs, and type\nchecking in Solid JSX, we can add the element type definition to\n`HTMLElementTagNameMap` and `JSX.IntrinsicElements`. Use the `ElementAttributes`\nhelper to specify which attributes/properties should be exposed in the JSX type\n(we do not want to expose methods for example, or we may want to skip exposing\nsome properties that are implementation details such as those prefixed with\nunderscores to represent that they are internal, etc):\n\n```tsx\nimport type {ElementAttributes} from '@lume/element'\nimport {Element, element, stringAttribute, numberAttribute, eventAttribute} from '@lume/element'\n\n// List the properties that should be picked from the class type for JSX props.\n// Note! Make sure that the properties listed are either decorated with\n// attribute decorators, or that they are on* event properties.\nexport type CoolElementAttributes = 'coolType' | 'coolFactor' | 'oncoolness'\n\n@element('cool-element')\nexport class CoolElement extends Element {\n\t@stringAttribute coolType: 'beans' | 'hair' = 'beans'\n\t@numberAttribute coolFactor = 100\n\t// ^ NOTE: These are the camelCase equivalents of the attributes defined above.\n\n  // For any given event our element will dispatch, define an event prop with\n  // the event name prefixed with 'on', and that accepts an event listener\n  // function or null.\n  @eventAttribute oncoolness: ((event: CoolnessEvent) =\u003e void) | null = null\n\n  // This property will not appear in the JSX types because it is not listed in\n  // the `CoolElementAttributes` that are passed to `ElementAttributes` below.\n  notJsxProp = 123\n\n\t// ... Define the class as described above. ...\n}\n\n/** This an event that our element dispatches, for example. */\nclass CoolnessEvent extends Event {\n  constructor() {\n    super('coolness', {...})\n  }\n}\n\n// Add our element to the list of known HTML elements. This makes it possible\n// for browser APIs to have the expected return types. For example, the return\n// type of `document.createElement('cool-element')` will be `CoolElement`.\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'cool-element': CoolElement\n\t}\n}\n\n// Hook up the type for use in Solid JSX templates\ndeclare module 'solid-js' {\n\tnamespace JSX {\n\t\tinterface IntrinsicElements {\n\t\t\t'cool-element': ElementAttributes\u003cCoolElement, CoolElementAttributes\u003e\n\t\t}\n\t}\n}\n```\n\nNow when we use `\u003ccool-element\u003e` in Solid JSX, it will be type checked:\n\n```jsx\nreturn (\n\t\u003ccool-element\n\t\t// cool-type={123} // Type error: number is not assignable to 'beans' | 'hair'\n\t\t// cool-factor={'foo'} // Type error: string is not assignable to number\n    cool-type=\"hair\" // ok\n    cool-factor=\"200\" // ok\n    oncoolness={() = console.log('coolness happened')} // ok\n\t\u003e\u003c/cool-element\u003e\n)\n```\n\n### In React JSX\n\nExample: ['kitchen-sink-react19'](./examples/kitchen-sink-react19/)\n\nDefining the types of custom elements for React JSX is similar as for Solid JSX\nabove, but with some small differences for React JSX:\n\n```js\n// tsconfig.json\n{\n  \"compilerOptions\": {\n\t\t/* React Config */\n    \"jsx\": \"react-jsx\",\n    \"jsxImportSource\": \"react\" // React \u003e=19 (Omit for React \u003c=18)\n  }\n}\n```\n\n```ts\nimport type {ReactElementAttributes} from '@lume/element/dist/framework-types/react.js'\n\n// ... the same CoolElement class and HTMLElementTagNameMap definitions as above ...\n\n// Hook up the type for use in React JSX templates\ndeclare module 'react' {\n  namespace JSX {\n    interface IntrinsicElements {\n      'cool-element': ReactElementAttributes\u003cCoolElement, CoolElementAttributes\u003e\n    }\n  }\n}\n```\n\nNow when we use `\u003ccool-element\u003e` in React JSX, it will be type checked:\n\n```jsx\nreturn (\n\t\u003ccool-element\n\t\t// coolType={123} // Type error: number is not assignable to 'beans' | 'hair'\n\t\t// coolFactor={'foo'} // Type error: string is not assignable to number\n    coolType=\"hair\" // ok\n    coolFactor=\"200\" // ok\n    oncoolness={() = console.log('coolness happened')} // ok\n\t\u003e\u003c/cool-element\u003e\n)\n```\n\n\u003e [!Note]\n\u003e You may want to define React JSX types for your elements in separate files than the Solid JSX types, and\n\u003e have React users import those separate files if they need the types, and similar if you make\n\u003e JSX types for Vue, Svelte, etc (we don't have helpers for those other fameworks\n\u003e yet, but you can manually augment JSX in that case, contributions welcome!).\n\n### In Preact JSX\n\nExample: ['kitchen-sink-preact'](./examples/kitchen-sink-preact/)\n\nThe definition is exactly the same as the previous section for React JSX. Define\nthe element types with the same `ReactElementAttributes` helper as described\nabove.\n\nIn our TypeScript `compilerOptions` we should make sure to link to the React\ncompatibility layer:\n\n```json\n{\n  \"compilerOptions\": {\n    /* Preact Config */\n    \"jsx\": \"react-jsx\",\n    \"jsxImportSource\": \"preact\",\n    \"paths\": {\n      \"react\": [\"./node_modules/preact/compat/\"],\n      \"react-dom\": [\"./node_modules/preact/compat/\"]\n    }\n  }\n}\n```\n\n\u003e [!Note]\n\u003e A default Preact app created with `npm init preact` will already have this set up.\n\nThe rest is the same as with defining types in a React app.\n\n### In Angular\n\nExample: ['kitchen-sink-angular'](./examples/kitchen-sink-angular/)\n\nRegister the element type for Angular like so:\n\n```ts\n// ... the same CoolElement class definition as above ...\n\n// Type checking in angular is currently limited to only knowing the custom\n// element tag names (from HTMLElementTagNameMap), but there is no way to\n// provide types of template props for type checking. For now, this is the best\n// we can do. See:\n// https://github.com/angular/angular/issues/58483\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'cool-element': CoolElement\n  }\n}\n```\n\n### In Vue\n\nExample: ['kitchen-sink-vue'](./examples/kitchen-sink-vue/)\n\nRegister the element type for Vue like so:\n\n```ts\nimport type {VueElementAttributes} from '@lume/element/dist/framework-types/vue.js'\n\n// ... the same CoolElement class and HTMLElementTagNameMap definitions as above ...\n\n// Hook up the type for use in Vue templates\ndeclare module 'vue' {\n  interface GlobalComponents {\n    'cool-element': VueElementAttributes\u003cCoolElement, CoolElementAttributes\u003e\n  }\n}\n```\n\n### In Svelte\n\nExample: ['kitchen-sink-svelte'](./examples/kitchen-sink-svelte/)\n\nRegister the element type for Svelte like so:\n\n```ts\nimport type {SvelteElementAttributes} from '@lume/element/dist/framework-types/vue.js'\n\n// ... the same CoolElement class and HTMLElementTagNameMap definitions as above ...\n\n// Hook up the type for use in Svelte templates\ndeclare module 'svelte/elements' {\n  interface SvelteHTMLElements {\n    'cool-element': SvelteElementAttributes\u003cCoolElement, CoolElementAttributes\u003e\n  }\n}\n```\n\n### In Stencil.js JSX\n\nExample: ['kitchen-sink-stencil'](./examples/kitchen-sink-stencil/)\n\nRegister the element type for Stencil.js like so:\n\n```ts\nimport type {StencilElementAttributes} from '@lume/element/dist/framework-types/stencil.js'\n\n// ... the same CoolElement class and HTMLElementTagNameMap definitions as above ...\n\n// Hook up the type for use in Svelte templates\ndeclare module '@stencil/core' {\n  export namespace JSX {\n    interface IntrinsicElements {\n      'cool-element': StencilElementAttributes\u003cCoolElement, CoolElementAttributes\u003e\n    }\n  }\n}\n```\n\n## Setter types in framework templates\n\nGiven a custom element definition like so,\n\n```ts\n@element('my-el')\nclass MyEl extends Element {\n  #position = new Vec3()\n\n  get position(): Vec3 {\n    return this.#position\n  }\n\n  set position(value: Vec3 | `${number} ${number} ${number}` | [number, number, number]) {\n    const {x, y, z} = parseValue(value)\n    this.#position.set(x, y, z)\n  }\n}\n```\n\nusing it in JSX or other framework templates will show type errors for a use\ncases like this one,\n\n```jsx\nfunction MyComponent() {\n  return (\n    \u003c\u003e\n      \u003cmy-el position={[1, 2, 3]} /\u003e\n      \u003cmy-el position={'1 2 3'} /\u003e\n    \u003c/\u003e\n  )\n}\n```\n\ndespite the fact that `[1, 2, 3]` and `\"1 2 3\"` are both valid values that the\nelement's `position` setter can accept.\n\nWe can fix this by using a `__set__`-prefixed property that matches the\ngetter/setter name to define the setter type that will appear in JSX/framework\ntemplates:\n\n```ts\n@element('my-el')\nclass MyEl extends Element {\n  #position = new Vec3()\n\n  get position(): Vec3 {\n    return this.#position\n  }\n\n  set position(value: this['__set__position']) {\n    const {x, y, z} = parseValue(value)\n    this.#position.set(x, y, z)\n  }\n\n  // Add a note telling people not to use this property, it is for template type definition only.\n  /** @deprecated Do not use this directly. */\n  declare __set__position: Vec3 | `${number} ${number} ${number}` | [number, number, number]\n}\n```\n\nNote that we re-used the `__set__position` type for the setter to avoid having\nto write tye type definition twice. With this additional type-only property defined, the previous\n`MyComponent` JSX example will not have type errors and will allow the values.\n\n# Resources\n\nSee https://solid.js.com, https://primitives.solidjs.community, and\nhttps://github.com/lume/classy-solid for APIs that are useful with\n`@lume/element`.\n\nAlso see Custom Element (i.e. Web Component) systems that are alternative to\n`@lume/element`:\n\n- [Lit](https://lit.dev/)\n- [Atomico](https://atomicojs.dev)\n- [solid-element](https://github.com/solidjs/solid/tree/main/packages/solid-element)\n- [ReadyMade](https://readymade-ui.github.io/readymade)\n- [Enhance](https://enhance.dev/)\n- [Stencil](https://stenciljs.com)\n- [Fast Elements](https://www.fast.design/docs/fast-element/getting-started)\n- [Lightning](https://lwc.dev)\n- [GitHub Elements](https://github.com/github/github-elements)\n- [and more](https://webcomponents.dev/new)\n\n# Status\n\n![](https://github.com/lume/element/workflows/Build%20and%20Test/badge.svg)\n","funding_links":[],"categories":["TypeScript","📦 Components \u0026 Libraries","Libraries"],"sub_categories":["Web Components","Frameworks \u0026 Component Libraries","Class Based"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flume%2Felement","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flume%2Felement","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flume%2Felement/lists"}