diff --git a/.builderrc b/.builderrc
new file mode 100644
index 0000000..7218a72
--- /dev/null
+++ b/.builderrc
@@ -0,0 +1,3 @@
+---
+archetypes:
+ - builder-js-package
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..8290e61
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,12 @@
+# EditorConfig is awesome: http://EditorConfig.org
+
+# top-most EditorConfig file
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 4
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..82ffc5a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+node_modules/
+package-lock.json
+dist/
+.vscode/
+
+*.log
+**/.rush/
diff --git a/.npmignore b/.npmignore
new file mode 100644
index 0000000..910b461
--- /dev/null
+++ b/.npmignore
@@ -0,0 +1,6 @@
+# this file is copied into the dist/ after build, and the lib is published
+# relative to the dist/ folder.
+/**/*.test.js
+/**/*.test.js.map
+/**/*.test.d.ts
+/tests/
diff --git a/.npmrc b/.npmrc
new file mode 100644
index 0000000..43c97e7
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1 @@
+package-lock=false
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000..b947077
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,2 @@
+node_modules/
+dist/
diff --git a/.prettierrc.js b/.prettierrc.js
new file mode 100644
index 0000000..dffccfe
--- /dev/null
+++ b/.prettierrc.js
@@ -0,0 +1,10 @@
+module.exports = {
+ tabWidth: 4,
+ useTabs: true,
+ semi: false,
+ singleQuote: true,
+ trailingComma: 'all',
+ bracketSpacing: false,
+ printWidth: 120,
+ arrowParens: 'avoid',
+}
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..1c53bf0
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 Joseph Orbegoso Pea (joe@trusktr.io)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..f3aa6e7
--- /dev/null
+++ b/README.md
@@ -0,0 +1,302 @@
+# @lume/element
+
+Create Custom Elements with reactivity and automatic re-rendering.
+
+#### `npm install @lume/element --save`
+
+## Intro
+
+[Custom](https://developers.google.com/web/fundamentals/web-components/customelements)
+[Elements](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements)
+(also known as [Web
+Components](https://developer.mozilla.org/en-US/docs/Web/Web_Components) are
+a feature of browsers that allow us to define new HTML elements that the
+browser understands in the same way as built-in elements like `
` or
+`
`.
+
+If that flew over your head then you might first want to try a [beginner HTML
+tutorial](https://htmldog.com/guides/html/beginner/). You will also need to
+some basic knowledge of
+[JavaScript](https://www.google.com/search?q=JavaScript%20for%20absolute%20beginners).
+
+`@lume/element` provides a set of features that make it easier to manipulate
+elements and to define new custom elements and easily compose them together
+into an application.
+
+With `@lume/element` we can create custom elements that have the following
+features:
+
+- Properties are reactive variables that make it easy to react to changes in these properties.
+- Each custom element has an HTML template (in the form of [HTML markup inside JavaScript, or
+ JSX](https://facebook.github.io/jsx)) that automatically "re-renders" when any
+ reactive variable used in the template changes.
+- When a template "re-renders", the whole template doesn't render, only the
+ part of the template where a variable changed is re-rendered. The term
+ "re-render" is quoted because tempate don't really re-render, but instead
+ reactive variables are mapped to discrete parts of the live
+ [DOM](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model)
+ generated from a template.
+- Changes to HTML attributes on a custom element can be easily mapped to the
+ custom element's properties. Because properties are reactive, this will cause
+ the custom element's template to update.
+
+Additionally `@lume/element` can be used to create and manipulate trees of
+DOM elements without necessarily creating new custom elements.
+
+## Install and Setup
+
+This section is TODO, and will explain how to set up an environment that
+supports JSX syntax, which is needed for things to work.
+
+## Basic Usage
+
+### Manipulating and composing trees of elements
+
+The following is an example that shows how to create a tree of HTML elements
+whose attributes or text content automatically update when the value of a
+reactive variable changes.
+
+```jsx
+import {variable} from '@lume/element'
+
+// Make a reactive variable with an initial value of 0.
+const count = variable(0)
+
+// Increment the value of the variable every second.
+setInterval(() => count(count() + 1), 1000)
+
+// Create a element with a child
element. The data-count attribute
+// and the text content of the element will automatically update whenever
+// the count variable changes. You will see the text content update live in your
+// browser.
+const el = (
+
+
The count is: {count()}
+
+)
+
+// The result stored in the `el` variable is a regular DOM element! For example,
+// you can call regular DOM APIs like setAttribute on it.
+el.setAttribute('foo', 'bar')
+
+// Append the element to the body of the page, and now you'll see a
+// continually-updating message on your screen.
+document.body.appendChild(el)
+```
+
+Continuing with the same `count` variable from the previous example, here's a
+simple way to compose DOM trees using "functional components".
+
+```jsx
+// A functional component is a function that returns the
+// The Label functional component uses the empty <>> tag to contain more than
+// one root-level child; it will not render any actual element output for the
+// empty tags.
+const Label = props => (
+ <>
+ {props.greeting}
+ {props.children}
+ >
+)
+
+// This Greeting functional component nests the content of the Label component
+// in its template, and the inside the
gets distributed to the
+// part of the Label component where we see `{props.children}`.
+const Greeting = () => (
+
+)
+
+// You only need to call this once, and you get an element reference. You do NOT
+// need to call it over and over to re-render like you do in some other
+// libraries. That's what makes all of this simple and clean. The reactivity
+// inside the component templates takes care of updating content of the created
+// DOM tree.
+const elem = Greeting()
+
+// It's just DOM! Use regular DOM APIs to append the element to the body.
+document.body.prepend(elem)
+```
+
+### Create custom elements
+
+Composing DOM trees with functions is one way that you can build an application.
+
+Another way to create re-usable components is to create Custom Elements. The
+advantage of custom elements is that they follow web standards, and therefore
+they can be used in any web application and manipulated by any DOM
+manipulation libraries like [jQuery](https://jquery.com/),
+[React](https://reactjs.org), [Vue](https://vuejs.org), or
+[Angular](https://angular.io), and many more.
+
+In contrast to custom elements, functional components made with
+`@lume/element` only work within the context of other functional components
+or custom elements made with `@lume/element`. For portability across
+applicatins and frameworks, it is preferable to create custom elements.
+
+The following is a class-based web component (custom element) with a reactive
+property `firstName` that also accepts values from an attribute named
+`first-name` (the property name is converted to dash-case for the attribute
+name).
+
+```jsx
+import {
+ Element, // A base class for LUME custom elements
+ attribute, // A property decorator to map attributes to properties
+ reactive, // A property decorator to make a property reactive
+} from '@lume/element'
+
+@customElement('greeting-card') // defines the element tag name
+class GreetingCard extends Element {
+ // Make the firstName property a reactive variable, and also map any value
+ // from an attribute named 'first-name' back to this property (the attribute
+ // name is the dash-case version of the property name).
+ @reactive @attribute firstName = 'Roger'
+
+ // connectedCallback is a method that fires any time this custom element is
+ // connected into a web site's live DOM tree.
+ connectedCallback() {
+ // Once the element is connected, let's update the `.firstName` prop after a
+ // couple of seconds, and we'll see the change on screen change.
+ setTimeout(() => (this.firstName = 'Zaya'), 2000)
+
+ // And show that it works with by setting HTML attributes too, two seconds later.
+ setTimeout(() => this.setAttribute('first-name', 'Raquel'), 4000)
+ }
+
+ // Define the structure of the DOM tree that we want rendered on screen by
+ // providing a template() method. This template() method should simply
+ // return an DOM element, and that DOM element will be, by default, appended
+ // into the ShadowRoot of our custom element.
+ //
+ // To take advantage of reactivity in our template, simply use the same
+ // technique here as we did in the section above titled "Manipulating and
+ // composing trees of elements", by using reactive variables or properties
+ // in the places where they should be "rendered".
+ template() {
+ // any time the `.firstName` reactive property's value changes, the DOM
+ // will be automatically updated, thanks how the JSX works (it comiles
+ // to reactive computations).
+ const result = (
+
+
+ Hello {this.firstName}
+
+
+ )
+
+ return result
+ }
+}
+```
+
+Now we can use it in the HTML of a web site, or in the template of another
+component:
+
+```html
+
+```
+
+Just like in the section [Manipulating and composing trees of
+elements](#manipulating-and-composing-trees-of-elements), inside a custom
+element's `template()` method we can assign bits and pieces of DOM to
+variables, and we can also use other custom elements and functional
+components. For example the following is an alternative way to write the
+previous `template()` method.
+
+```jsx
+@customElement('greeting-card')
+class GreetingCard extends Element {
+ // ... same as before ...
+
+ template() {
+ const greeting = (
+
+ Hello {this.firstName}
+
+ )
+
+ const result = {greeting}
+
+ return result
+ }
+}
+```
+
+### TypeScript
+
+In TypeScript, all JSX expressions return the type `JSX.Element`. But with
+`@lume/element`, JSX expressions return actual DOM nodes, and we want the
+variable types to reflect that fact. For this we have a set of convenience
+helpers to cast JSX types to DOM element types.
+
+Modifying the very first example from above for TypeScript, it would look
+like the following.
+
+```tsx
+import {variable, div} from '@lume/element'
+
+const count = variable(0)
+
+setInterval(() => count(count() + 1), 1000)
+
+const el = div(
+
+
The count is: {count()}
+ ,
+)
+
+el.setAttribute('foo', 'bar')
+
+document.body.appendChild(el)
+```
+
+The main difference is that the `div()` helper function explicitly returns
+the type `HTMLDivElement` so that the `el` will be typed as `HTMLDivElement`
+instead of `JSX.Element`. Under the hood, the `div()` function doesn't do
+anything, it simply returns whatever you pass into it (an identity function
+at runtime), and serves only as a convenient type cast helper.
+
+We should remember to use the correct type helper depending on what the root
+element of the JSX expression is. For for example, if the root was a ``
+element then we'd use need to use the `menu()` helper like follows.
+
+```tsx
+import {variable, menu} from '@lume/element'
+
+// ...
+
+// The type of `el` will be `HTMLMenuElement`.
+const el = menu(
+
+ The count is: {count()}
+ ,
+)
+```
+
+If we use the wrong helper, then it will effectively cast the expression to
+the wrong type. For example, in the next snippte the `el` variable will be of
+type `HTMLDivElement` despite the fact that at runtime we will be have an
+`HTMLMenuElement`.
+
+```tsx
+import {variable, div} from '@lume/element'
+
+// ...
+
+// This is wrong, don't do this!
+const el = div(... )
+```
+
+Without the type helpers, we would need to write more verbose code like the following to have the proper types.
+
+```tsx
+import {variable} from '@lume/element'
+
+// ...
+
+const el = ((... ) as any) as HTMLMenuElement
+```
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..e584586
--- /dev/null
+++ b/package.json
@@ -0,0 +1,81 @@
+{
+ "name": "@lume/element",
+ "version": "0.0.0",
+ "description": "Create Custom Elements with reactivity and automatic re-rendering.",
+ "type": "module",
+ "main": "dist/index.js",
+ "types": "src/index.ts",
+ "exports COMMENT:": "This removes 'dist' from import statements, as well as replaces the 'main' field. See https://github.com/nodejs/node/issues/14970#issuecomment-571887546",
+ "exports": {
+ ".": "./dist/index.js",
+ "./": "./dist/"
+ },
+ "scripts": {
+ "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX BUILDER SCRIPTS": "",
+ "test": "LOCAL_DEV=true builder run npm:test",
+ "test:debug": "LOCAL_DEV=true builder run npm:test-debug",
+ "tsc": "LOCAL_DEV=true builder run tsc",
+ "tsc:watch": "LOCAL_DEV=true builder run tsc:watch",
+ "prettier": "LOCAL_DEV=true builder run prettier",
+ "release:patch": "LOCAL_DEV=true builder run release:patch",
+ "release:minor": "LOCAL_DEV=true builder run release:minor",
+ "release:major": "LOCAL_DEV=true builder run release:major",
+ "version": "LOCAL_DEV=true builder run npm:version",
+ "postversion": "LOCAL_DEV=true builder run npm:postversion",
+ "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX LUME SCRIPTS": "",
+ "clean": "lume clean",
+ "build": "lume build",
+ "dev": "lume dev",
+ "typecheck": "lume typecheck",
+ "typecheck:watch": "lume typecheckWatch",
+ "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX OTHER SCRIPTS": ""
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+ssh://git@github.com/infamous/element.git"
+ },
+ "author": "Joe Pea ",
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/infamous/element/issues"
+ },
+ "homepage": "http://github.com/infamous/element#readme",
+ "dependencies": {
+ "@babel/runtime": "7.0.0-beta.46",
+ "@lume/variable": "^0.0.0",
+ "@types/react": "^16.9.20",
+ "create-emotion": "^10.0.27",
+ "lowclass": "^4.9.2",
+ "react": "^16.12.0",
+ "solid-js": "^0.16.8"
+ },
+ "devDependencies": {
+ "builder": "^5.0.0",
+ "builder-js-package": "^1.0.0",
+ "prettier": "^1.19.1"
+ },
+ "keywords": [
+ "custom elements",
+ "custom-elements",
+ "web components",
+ "web-components",
+ "html",
+ "shadowdom",
+ "shadow-dom",
+ "ui",
+ "ui components",
+ "ui-components",
+ "frp",
+ "functional reactive programming",
+ "functional-reactive-programming",
+ "reactive programming",
+ "reactive-programming",
+ "reactive coding",
+ "reactive-coding",
+ "reactive variables",
+ "reactive-variables",
+ "reactivity",
+ "reactive computation",
+ "reactive-computation"
+ ]
+}
diff --git a/src/element-type-helpers.ts b/src/element-type-helpers.ts
new file mode 100644
index 0000000..8d1f1dc
--- /dev/null
+++ b/src/element-type-helpers.ts
@@ -0,0 +1,41 @@
+/* eslint-disable typescript/explicit-function-return-type */
+
+// These are for use in TypeScript to cast JSX to certain DOM types. They are
+// not needed in plain JavaScript.
+export const div = (e: JSX.Element) => (e as any) as HTMLDivElement
+export const h1 = (e: JSX.Element) => (e as any) as HTMLHeadingElement
+export const h2 = (e: JSX.Element) => (e as any) as HTMLHeadingElement
+export const h3 = (e: JSX.Element) => (e as any) as HTMLHeadingElement
+export const h4 = (e: JSX.Element) => (e as any) as HTMLHeadingElement
+export const h5 = (e: JSX.Element) => (e as any) as HTMLHeadingElement
+export const h6 = (e: JSX.Element) => (e as any) as HTMLHeadingElement
+export const p = (e: JSX.Element) => (e as any) as HTMLParagraphElement
+export const span = (e: JSX.Element) => (e as any) as HTMLSpanElement
+export const br = (e: JSX.Element) => (e as any) as HTMLBRElement
+export const pre = (e: JSX.Element) => (e as any) as HTMLPreElement
+export const code = (e: JSX.Element) => (e as any) as HTMLElement
+export const canvas = (e: JSX.Element) => (e as any) as HTMLCanvasElement
+export const img = (e: JSX.Element) => (e as any) as HTMLImageElement
+export const video = (e: JSX.Element) => (e as any) as HTMLVideoElement
+export const object = (e: JSX.Element) => (e as any) as HTMLVideoElement
+export const select = (e: JSX.Element) => (e as any) as HTMLSelectElement
+export const option = (e: JSX.Element) => (e as any) as HTMLOptionElement
+export const ul = (e: JSX.Element) => (e as any) as HTMLUListElement
+export const ol = (e: JSX.Element) => (e as any) as HTMLOListElement
+export const li = (e: JSX.Element) => (e as any) as HTMLLIElement
+export const iframe = (e: JSX.Element) => (e as any) as HTMLIFrameElement
+export const button = (e: JSX.Element) => (e as any) as HTMLButtonElement
+export const form = (e: JSX.Element) => (e as any) as HTMLFormElement
+export const input = (e: JSX.Element) => (e as any) as HTMLInputElement
+export const a = (e: JSX.Element) => (e as any) as HTMLAnchorElement
+export const link = (e: JSX.Element) => (e as any) as HTMLLinkElement
+export const script = (e: JSX.Element) => (e as any) as HTMLScriptElement
+export const section = (e: JSX.Element) => (e as any) as HTMLElement
+export const menu = (e: JSX.Element) => (e as any) as HTMLMenuElement
+export const svg = (e: JSX.Element) => (e as any) as SVGSVGElement
+export const q = (e: JSX.Element) => (e as any) as HTMLQuoteElement
+export const blockquote = (e: JSX.Element) => (e as any) as HTMLQuoteElement
+
+export function elm(e: JSX.Element) {
+ return (e as any) as T
+}
diff --git a/src/index.ts b/src/index.ts
new file mode 100644
index 0000000..63211dc
--- /dev/null
+++ b/src/index.ts
@@ -0,0 +1,361 @@
+import * as React from 'react'
+import {render} from 'solid-js/dom'
+import {Constructor, Mixin, MixinResult} from 'lowclass'
+import createEmotion, {Emotion} from 'create-emotion'
+
+export * from '@lume/variable'
+export * from './element-type-helpers'
+
+export class Element extends HTMLElement {
+ constructor() {
+ super()
+ this.__handleInitialPropertyValuesIfAny()
+ }
+
+ static observedAttributes?: string[]
+
+ private __attributesToProps?: Record
+
+ private __handleInitialPropertyValuesIfAny() {
+ // We need to delete value-descriptor properties and store the initial
+ // values in the storage for our reactive variable accessors.
+ //
+ // If we don't do this, then DOM APIs like cloneNode will create our node
+ // without first upgrading it and set property values, which means those
+ // values will be set as value descriptor properties on the instance instead
+ // of interacting with our accessors (i.e. overriding our accessors that the
+ // instance will gain once the upgrade process places our prototype in the
+ // instance's prototype chain).
+ //
+ // This can also happen if you set properties on an element that isn't
+ // upgraded into a custom element yet, and thus will not yet have our
+ // accessors.
+
+ if (this.__attributesToProps) {
+ for (const attr in this.__attributesToProps) {
+ const prop = this.__attributesToProps[attr]
+ const propName = prop.name as keyof this
+
+ if (this.hasOwnProperty(propName)) {
+ // override only value descriptors (we assume a getter/setter descriptor is intentional and meant to override or extend our getter/setter)
+ const descriptor = Object.getOwnPropertyDescriptor(this, propName)!
+
+ if (descriptor.value) {
+ // delete the value descriptor...
+ delete this[propName]
+
+ // ...and re-assign the value so that it goes through an inherited accessor
+ //
+ // NOTE, deferring allows preexisting preupgrade values to be
+ // handled *after* default constructor values are have been set
+ // during Custom Element upgrade.
+ defer(() => (this[propName] = descriptor.value))
+ }
+ }
+ }
+ }
+ }
+
+ protected makeStyle?(): string
+ protected elementStyle?(): string
+ protected template?(): Element
+
+ /**
+ * Subclasses can override this to provide an alternate Node to render into
+ * (f.e. a subclass can `return this` to render into itself instead of making a root)
+ */
+ protected get root(): Node {
+ if (this.shadowRoot) return this.shadowRoot
+ return this.attachShadow({mode: 'open'})
+ }
+
+ private __dispose?: () => void
+ private __hasShadow = true
+
+ protected connectedCallback() {
+ this.__hasShadow = this.root instanceof ShadowRoot
+
+ this.__setStyle()
+
+ // TODO the cast to Element should not be needed, will be fixed by
+ // https://github.com/ryansolid/solid/issues/87
+ if (this.template) this.__dispose = render(this.template.bind(this), this.root as Element)
+ }
+
+ protected disconnectedCallback() {
+ this.__dispose && this.__dispose()
+
+ this.__cleanupStyle()
+ }
+
+ private static __rootNodeRefCountPerTagName = new WeakMap>()
+
+ private __setStyle() {
+ const hostSelector = this.__hasShadow ? ':host' : this.tagName.toLowerCase()
+
+ const style = document.createElement('style')
+
+ style.innerHTML = `
+ ${hostSelector} {
+ display: block;
+ ${this.elementStyle ? this.elementStyle() : ''}
+ }
+
+ ${this.makeStyle ? this.makeStyle() : ''}
+ `
+
+ if (this.__hasShadow) {
+ // If this element has a shadow root, put the style there. This is the
+ // standard way to scope styles to a component.
+
+ this.root.appendChild(style)
+ } else {
+ // If this element doesn't have a shadow root, then we want to append the
+ // style only once to the rootNode where it lives (a ShadoowRoot or
+ // Document). If there are multiple of this same element in the rootNode,
+ // then the style will be added only once and will style all the elements
+ // in the same rootNode.
+
+ const _rootNode = this.getRootNode()
+ const rootNode = _rootNode === document ? document.head : _rootNode
+
+ let refCountPerTagName = Element.__rootNodeRefCountPerTagName.get(rootNode)
+ if (!refCountPerTagName) Element.__rootNodeRefCountPerTagName.set(rootNode, (refCountPerTagName = {}))
+ const refCount = refCountPerTagName[this.tagName] || 0
+ refCountPerTagName[this.tagName] = refCount + 1
+
+ if (refCount === 0) {
+ style.id = this.tagName
+ rootNode.appendChild(style)
+ }
+ }
+ }
+
+ private __cleanupStyle() {
+ if (this.__hasShadow) return
+
+ const rootNode = this.getRootNode()
+ const refCountPerTagName = Element.__rootNodeRefCountPerTagName.get(rootNode)
+
+ if (!refCountPerTagName) return
+
+ const refCount = refCountPerTagName[this.tagName]
+
+ if (refCount === undefined) return
+
+ refCountPerTagName[this.tagName] = refCount - 1
+
+ if (refCount === 0) {
+ const style = (rootNode as Element).querySelector('#' + this.tagName)
+ if (style) rootNode.removeChild(style)
+ }
+ }
+
+ // not used currently, but we'll leave this hear so that child classes can call super,
+ // and that way we can always add an implementation later when needed.
+ protected adoptedCallback() {}
+
+ protected attributeChangedCallback(attr: string, _oldVal: string, newVal: string) {
+ // map attribute to property
+ const prop = this.__attributesToProps && this.__attributesToProps[attr]
+ if (prop) {
+ const handler = prop.attributeHandler
+ ;(this as any)[prop.name] = handler && handler.from ? handler.from(newVal) : newVal
+ }
+ }
+}
+
+// prettier-ignore
+const base26Chars = [
+ 'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'
+]
+
+/**
+ * Given an array of characters `baseChars` with length X, convert an int
+ * `value` to base X, using the chars in the array for the digit representation.
+ */
+// We need this because Emotion.js accepts only letters in the createEmotion key
+// option.
+// Based on https://stackoverflow.com/a/923814/454780
+function integerToString(value: number, baseChars: string[]): string {
+ value = Math.floor(value)
+
+ let result = ''
+ const targetBase = baseChars.length
+
+ do {
+ result = baseChars[value % targetBase] + result
+ value = value / targetBase
+ } while (value > 1)
+
+ return result
+}
+
+let emotionCount = 0
+
+const emotions = new WeakMap()
+
+// eslint-disable-next-line typescript/explicit-function-return-type
+function WithEmotionMixin>(Base?: T) {
+ if (!Base) Base = Constructor(Element)
+
+ class WithEmotion extends Constructor(Base) {
+ get emotion(): ReturnType {
+ let emotion = emotions.get(this)
+
+ if (!emotion) {
+ emotions.set(
+ this,
+ (emotion = createEmotion({
+ // The key option is required when there will be multiple instances
+ // of Emotion in a single app, and we need one instance of Emotion
+ // per element instance, for now.
+ key: this.tagName.toLowerCase() + '-' + integerToString(++emotionCount, base26Chars),
+
+ // The `as HTMLElement` cast is needed because the type def for
+ // `createEmotion` is too specific, and does not accept just Node
+ // (f.e. shadow roots are Node, but not HTMLElement, and we may want
+ // to attach the generated