solarite 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/benchmarks/naive/Solarite.min.js +4 -0
  2. package/benchmarks/naive/index.html +14 -0
  3. package/benchmarks/naive/main.js +339 -0
  4. package/benchmarks/naive/package-lock.json +13 -0
  5. package/benchmarks/naive/package.json +23 -0
  6. package/benchmarks/readme.md +48 -0
  7. package/build/build.bat +3 -2
  8. package/build/build.js +1 -1
  9. package/dist/Solarite-debug.js +1941 -2791
  10. package/dist/Solarite.js +1697 -2511
  11. package/dist/Solarite.min.js +3 -3
  12. package/dist/udomdiff-license.txt +18 -0
  13. package/docs/index.md +695 -235
  14. package/docs/js/Playground.js +1 -1
  15. package/docs/js/codemirror/codemirror6.js +3683 -3206
  16. package/docs/js/codemirror/themeSolarIce.js +2 -2
  17. package/docs/js/documentation.js +2 -2
  18. package/docs/js/ui/CodeEditor.js +183 -45
  19. package/docs/js/ui/FlexResizer.js +15 -5
  20. package/docs/js/util/Errors.js +11 -0
  21. package/docs/media/documentation.css +6 -3
  22. package/index.html +462 -26
  23. package/package.json +1 -1
  24. package/readme.md +11 -1
  25. package/src/solarite/ExprPath.js +422 -135
  26. package/src/solarite/Globals.js +53 -0
  27. package/src/solarite/NodeGroup.js +300 -388
  28. package/src/solarite/Shell.js +31 -33
  29. package/src/solarite/Solarite.js +17 -2
  30. package/src/solarite/Template.js +75 -25
  31. package/src/solarite/Util.js +131 -7
  32. package/src/solarite/createSolarite.js +32 -25
  33. package/src/solarite/getArg.js +12 -12
  34. package/src/solarite/hash.js +18 -35
  35. package/src/solarite/r.js +128 -118
  36. package/src/solarite/watch3.js +98 -0
  37. package/src/{solarite → unused}/NodeGroupManager.js +86 -224
  38. package/src/unused/onConnect.js +79 -0
  39. package/src/{solarite → unused}/watch.js +2 -2
  40. package/src/{solarite → unused}/watch2.js +4 -4
  41. package/src/{solarite → util}/MultiValueMap.js +23 -12
  42. package/src/util/WeakArray.js +33 -0
  43. package/tests/Solarite.test.js +1108 -166
  44. package/tests/Testimony.js +274 -67
  45. package/tests/index.html +6 -35
  46. package/tests/run.bat +2 -0
  47. package/tests/NodeGroup.test.js +0 -115
  48. package/tests/Shell.test.js +0 -75
package/docs/index.md CHANGED
@@ -1,27 +1,34 @@
1
1
  ---
2
- title: Solarite Documentation
3
- append-head: <script src="docs/js/ui/DarkToggle.js"></script><script type="module" src="docs/js/documentation.js"></script><link rel="stylesheet" href="docs/media/documentation.css"><link rel="stylesheet" href="/docs/media/eternium.css">
2
+ title: Solarite JS Library
3
+ append-head: <script src="docs/js/ui/DarkToggle.js"></script><script type="module" src="docs/js/documentation.js"></script><link rel="stylesheet" href="docs/media/documentation.css"><link rel="stylesheet" href="docs/media/eternium.css"><link rel="icon" href="docs/media/solarite-machine.webp" type="image/webp"><script async defer src="https://buttons.github.io/buttons.js"></script>
4
4
 
5
5
  ---
6
6
 
7
- <!-- To create documentation: (1) Open in Typora. (2) Select the GitHub theme. (3) Export as html with styles to index.html. -->
7
+ <!-- To convert documentation to html: (1) Open in Typora. (2) Select the GitHub theme, or go to Settings -> Export -> Html -> Theme -> Github. (3) Export as html with styles to index.html. -->
8
8
 
9
- # Solarite Docs
9
+ <!-- Playgrounds that don't have a lowercase language name will not have a preview. -->
10
10
 
11
- Solarite is a small (10KB min+gzip), fast, compilation-free JavaScript web component library that closely follows modern web standards.
11
+ # Solarite
12
12
 
13
- This project is currently in ALPHA stage and not yet recommended for production code. This documentations is also incomplete.
13
+ Solarite is a small (8KB min+gzip), fast, compilation-free JavaScript library for creating elements and web components. Features:
14
14
 
15
- If using Visual Studio Code, the Leet-Html extension is recommended to syntax highlight html inside template strings.
15
+ - Minimal DOM updates when rendering.
16
+ - No magic: Renderer only when you want via the manually invoked `render()` method.
17
+ - No setting up state variables. Render any regular variable or data structure.
18
+ - Local scoped styles: Inherit external styles but define new styles that apply only to the web component and its children.
19
+ - Elements with `id` or `data-id` attributes become class properties.
20
+ - Attributes are passed as constructor arguments to nested components.
21
+ - Single file. No build steps and no dependencies. Not even Node.js. Just `import` Solarite.js or Solarite.min.js into your vanilla JavaScript and start coding.
22
+ - MIT license. Free for commercial use. No attribution needed.
16
23
 
17
24
  ```javascript
18
- // Type here to edit this code!
19
- import {Solarite, r} from '/src/solarite/Solarite.js';
25
+ import {r} from './dist/Solarite.js';
20
26
 
21
- class ShoppingList extends Solarite {
27
+ class ShoppingList extends HTMLElement {
22
28
  constructor(items=[]) {
23
29
  super();
24
30
  this.items = items;
31
+ this.render();
25
32
  }
26
33
 
27
34
  addItem() {
@@ -34,318 +41,406 @@ class ShoppingList extends Solarite {
34
41
  this.render();
35
42
  }
36
43
 
37
- render() {
38
- this.html = r`
39
- <shopping-list>
40
- <style>
41
- :host input { width: 50px }
42
- </style>
43
- <button onclick=${this.addItem}>Add Item</button>
44
- ${this.items.map(item => r`
45
- <div style="display: flex; flex-direction: row">
46
- <input value=${item.name} oninput=${[item, 'name']} placeholder="Name">
47
- <input value=${item.qty} oninput=${[item, 'qty']}>
48
- <button onclick=${[this.removeItem, item]}>x</button>
49
- </div>
50
- `)}
51
- <pre>items = ${() => JSON.stringify(this.items, null, 4)}</pre>
52
- </shopping-list>`
44
+ render() {
45
+ // Think of r(this) as like:
46
+ // this.outerHTML = `<shopping-list>...`
47
+ // but rendering only minimal DOM updates when the html changes.
48
+ r(this)`
49
+ <shopping-list>
50
+ <style> /* scoped styles */
51
+ :host input { width: 80px }
52
+ </style>
53
+
54
+ <button onclick=${this.addItem}>Add Item</button>
55
+
56
+ ${this.items.map(item => r`
57
+ <div>
58
+ <input placeholder="Item" oninput=${this.render}
59
+ value=${[item, 'name']}> <!-- 2-way binding -->
60
+ <input type="number" oninput=${this.render}
61
+ value=${[item, 'qty']}>
62
+ <button onclick=${()=>this.removeItem(item)}>x</button>
63
+ </div>
64
+ `)}
65
+
66
+ <pre>items = ${JSON.stringify(this.items, null, 4)}</pre>
67
+ </shopping-list>`
53
68
  }
54
69
  }
55
- document.body.append(new ShoppingList()); // adds a child named <shopping-list>
70
+
71
+ customElements.define('shopping-list', ShoppingList);
72
+ document.body.append(new ShoppingList()); // add <shopping-list> element
56
73
  ```
57
74
 
58
- ==TODO Does it re-render all Items on update?==
75
+ To use, import one of these pre-bundled es6 modules into your project:
59
76
 
60
- ## Features
77
+ - [Solarite.js](https://cdn.jsdelivr.net/gh/Vorticode/Solarite/dist/Solarite.js) - 85KB
78
+ - [Solarite.min.js](https://cdn.jsdelivr.net/gh/Vorticode/Solarite/dist/Solarite.min.js) - 24KB / 8KB gzipped
61
79
 
62
- ==TODO: Show benchmark==
80
+ Or get Solarite from GitHub or NPM:
63
81
 
64
- - No custom build steps and no dependencies. Not even Node.js. Just `import` Solarite.js or Solarite.min.js.
65
- - Creates native HTML Elements and Web Components which can be used anywhere in your document and alongside other libraries.
66
- - No need to set up state. Instead, use any regular variables or data structures in html templates.
67
- - Minimal updates on render
68
- - Local (scoped) styles
69
- - Two-way form element binding.
70
- - Optional shadow DOM (coming soon)
71
- - Optional JSX support (coming soon)
72
- - MIT license. Free for commercial use. No attribution needed.
82
+ - [Solarite GitHub Repository](https://github.com/Vorticode/solarite) <a class="github-button" href="https://github.com/vorticode/solarite" data-color-scheme="no-preference: light; light: light; dark: dark;" data-icon="octicon-star" data-size="small" data-show-count="true" aria-label="Star vorticode/solarite on GitHub">Star</a>
83
+ - `git clone https://github.com/Vorticode/solarite.git`
84
+ - `npm install solarite`
73
85
 
74
- ## Using
86
+ This project is currently in BETA stage. Tip: A JetBrains IDE like [WebStorm](https://www.jetbrains.com/webstorm/), [PhpStorm](https://www.jetbrains.com/phpstorm/), or [IDEA](https://www.jetbrains.com/idea/) will syntax highlight the html template strings.
75
87
 
76
- Import one of these pre-bundled es6 modules into your project:
88
+ ## Concepts
77
89
 
78
- - [RedComponent.js](https://cdn.jsdelivr.net/gh/Vorticode/Solarite/dist/Solarite.js) - 76KB
79
- - [RedComponent.min.js](https://cdn.jsdelivr.net/gh/Vorticode/Solarite/dist/Solarite.js) - 21KB / 7KB gzipped
90
+ ### Regular Elements
80
91
 
81
- ==TODO: NPM==
92
+ The `r()` function can create elements. Pass any object with a `render()` function as the first argument. This object can optionally have additional properties and methods, which become bound to the resulting element. When `render()` is called, only the changed nodes will be updated.
82
93
 
83
- ## Examples
94
+ ```javascript
95
+ import {r} from './dist/Solarite.js';
84
96
 
85
- ## Concepts
97
+ let button = r({
98
+ count: 0,
86
99
 
87
- ### Creating Web Components
100
+ inc() {
101
+ this.count++;
102
+ this.render();
103
+ },
88
104
 
89
- In this minimal example, we make a new class called `MyComponent` and provide a `render()` function to set its html.
105
+ render() {
106
+ r(this)`<button onclick=${this.inc}>I've been clicked ${this.count} times.</button>`
107
+ }
108
+ });
109
+ document.body.append(button);
110
+ ```
90
111
 
91
- All browsers require custom web component names to have a dash in the middle. Red Component looks at the case of the class name and converts it to a name with dashes. If it can't find at least one place to put a dash, it will append `-element` to the end.
112
+ If you want multiple instances of such an element, the code above can be wrapped in a function:
92
113
 
93
114
  ```javascript
94
- import {Solarite, r} from '../dist/Solarite.js';
115
+ import {r} from './dist/Solarite.js';
95
116
 
96
- class MyComponent extends Solarite {
97
- name = 'Red Component';
98
- render() {
99
- this.html = r`<my-component>Hello <b>${this.name}!<b></my-component>`
100
- }
101
- }
117
+ function createButton(text) {
118
+ return r({
119
+ count: 0,
102
120
 
103
- document.body.append(new MyComponent());
104
- ```
121
+ inc() {
122
+ this.count++;
123
+ this.render();
124
+ },
105
125
 
106
- A JetBrains IDE like [WebStorm](https://www.jetbrains.com/webstorm/), [PhpStorm](https://www.jetbrains.com/phpstorm/), or [IDEA](https://www.jetbrains.com/idea/) will syntax highlight the html template strings.
126
+ render() {
127
+ r(this)`<button onclick=${this.inc}>${this.count} ${text}</button>`
128
+ }
129
+ })
130
+ }
131
+ document.body.append(createButton('clicks'));
132
+ document.body.append(createButton('tickles'));
133
+ ```
107
134
 
108
- Note that the template strings use the `r` prefix. This `r` parses the html into a data structure that Red Component can use.
135
+ ### Web Components
109
136
 
110
- Alternatively, instead of instantiating the element in JavaScript, we could can instantiate the element directly from html. This only works if we first call `MyComponent.define()` so so that our element's tag name is mapped to our class:
137
+ Solarite can also create [web components](https://developer.mozilla.org/en-US/docs/Web/API/Web_components). In this minimal example, we make a new class called `MyComponent` which extends from `HTMLElement` like any other web component. We provide a `render()` function to set its html, and a constructor to call it when a new instance is created.
111
138
 
112
- ```html2
113
- <script>
114
- // ...
115
- // document.body.append(new MyComponent());
116
- MyComponent.define()
117
- </script>
139
+ All browsers require web component tag names to have at least one dash in the middle.
118
140
 
119
- <my-component></my-component>
120
- ```
141
+ ```javascript
142
+ import {r} from './dist/Solarite.js';
121
143
 
122
- Internally, the `define()` function calculates the tag name from the class name and then calls the built-in [customElements.define()](https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/define).
144
+ class MyComponent extends HTMLElement {
145
+ name = 'Solarite';
146
+
147
+ constructor() {
148
+ super(); // JavaScript requires a super() call for sub-class construtors.
149
+ this.render();
150
+ }
151
+
152
+ render() {
153
+ // This is how we'd create a web component using vanilla JavaScript
154
+ // without Solarite. But this recreates all children on every render!
155
+ //this.innerHTML = `Hello <b>${this.name}!<b>`;
156
+
157
+ // Using Solarite's r() function performs minimal updates on render.
158
+ r(this)`<my-component>Hello <b>${this.name}!</b></my-component>`
159
+ }
160
+ }
123
161
 
124
- If you want the component to have a tag name that's different than the name derived from the class name, you can pass a different name to `define()`:
162
+ // Register the <my-component> tag name with the browser.
163
+ // Browsers require this for all web components.
164
+ customElements.define('my-component', MyComponent);
125
165
 
126
- ```javascript2
127
- MyComponent.define('my-awesome-component')
166
+ document.body.append(new MyComponent());
128
167
  ```
129
168
 
130
- ### The render() function, r, and this.html
169
+ Alternatively, instead of instantiating the element in JavaScript, we could can instantiate the element directly from html:
131
170
 
132
- The `r` function, when used as part of a template literal, converts the html and embedded expressions into a data structure. When that data structure is assigned to `this.html`, it updates the content of the web component. You can think of this like assigning to the browser's built-in `this.outerHTML` property, except in this case instead of replacing all of the content, only the changed elements are replaced, which is much faster.
171
+ ```Html
172
+ <my-component></my-component>
173
+ ```
174
+
175
+ JavaScript veterans will realize that other than the `r()` function, this is highly similar to one might create vanilla JavaScript web components. This is by design!
133
176
 
134
- The render() function is called automatically when an element is added to the DOM via [connectedCallback()](https://developer.mozilla.org/en-US/docs/Web/API/Web_components#connectedcallback).
177
+ Since these are just regular web components, they can define the [connectedCallback()](https://developer.salesforce.com/docs/platform/lwc/guide/create-lifecycle-hooks-dom.html#connectedcallback) and [disconnectedCallback()](https://developer.salesforce.com/docs/platform/lwc/guide/create-lifecycle-hooks-dom.html#disconnectedcallback) methods that will be called when they're added and removed from the DOM, respectively. These functions are only supported for web components and not regular elements.
135
178
 
136
- Unlike other frameworks Red Component does not re-render automatically when data changes, so you should call the render() function manually as needed. This is a deliberate design choice to reduce "magic," since in some cases you may want to update internal data without rendering.
179
+ ### Rendering
137
180
 
181
+ The `r` function, when used as a [tagged template literal](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates) , converts the html and embedded expressions into a Solarite `Template`. This is a data structure used by Solarite to store processed html and expressions. The call to `r(this)` then renders that `Template` as an element's attributes and children. You can think of this like assigning to the browser's built-in `this.outerHTML` property, except updates are much faster because only the changed elements are replaced, instead of all nodes.
138
182
 
183
+ Unlike other frameworks, Solarite does not re-render automatically when data changes, so you should call the `render()` function manually. This is a deliberate design choice to reduce unexpected side effects, since in some cases you may want to update internal data without rendering.
139
184
 
140
- Wrapping the web component's html in its tag name is optional. You could instead just assign the html for the child elements to `this.html`. But then you will have to set any attributes on your web component some other way.
185
+ Wrapping the web component's html in its tag name is optional. But without it you then must set any attributes on your web component manually, as seen in this example:
141
186
 
142
187
  ```javascript
143
- import {Solarite, r} from '../dist/Solarite.js';
188
+ import {r} from './dist/Solarite.js';
144
189
 
145
- class MyComponent extends Solarite {
190
+ class MyComponent extends HTMLElement {
146
191
  name = 'Solarite';
147
192
  render() {
148
193
  // With optional element tags:
149
- // this.html = r`<my-component>Hello <b>${this.name}!<b></my-component>`
194
+ // r(this)`<my-component class="big">Hello <b>${this.name}!<b></my-component>`
150
195
 
151
196
  // Without optional element tags:
152
- this.html = r`Hello <b>${this.name}!<b>`
197
+ r(this)`Hello <b>${this.name}!<b>`;
198
+ this.setAttribute('class', 'big');
153
199
  }
154
200
  }
155
-
201
+ customElements.define('my-component', MyComponent);
156
202
  document.body.append(new MyComponent());
157
203
  ```
158
204
 
159
- If you do provide the outer tag, its name must exactly match the "dashes" version of the class name, or a custom name if you pass one to `define()`.
160
-
161
- ### Inheriting from existing DOM elements.
205
+ If you do wrap the web component's html in its tag, that tag name must exactly match the tag name passed to `customElements.define()`.
162
206
 
163
- Suppose you want to use a custom component for each `<tr>` in a `<table>`. Html won't allow you to put just any element as a child of table or tbody. In this case you can make your web component inherit from the browser's built in `<tr>` element:
207
+ Note that by default, `r()` will render expressions as text, with escaped html entities. To render as html, wrap a variable in the `r()` function:
164
208
 
165
209
  ```javascript
166
- import {Solarite, r} from '../dist/Solarite.js';
167
-
168
- class LineItem extends Solarite('tr') {
169
- constructor(user) {
170
- super();
171
- this.user = user;
172
- }
173
-
174
- render() {
175
- this.html = r`
176
- <tr>
177
- <td>${this.user.name}</td>
178
- <td>${this.user.email}</td>
179
- </tr>`
180
- }
181
- }
182
- LineItem.define();
210
+ import {r} from './dist/Solarite.js';
183
211
 
184
- let table = document.createElement('table')
185
- for (let i=0; i<10; i++) {
186
- let user = {name: 'User ' + i, email: 'user'+i+'@example.com'};
187
- table.append(new LineItem(user));
188
- }
189
- document.body.append(table)
190
- ```
191
-
192
- ### Loops
193
-
194
- Just as in some of the examples above, loops can be written with the build-in [Array.map()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) function:
212
+ let folderIcon = `
213
+ <svg width="10em" height="10em" viewBox="0 0 24 24">
214
+ <path fill="currentColor" d="M2 4h8l2 2h10v14H2V4Zm2 2v12h16V8h-8.825l-2-2H4Zm0 12V6v12Z"/>
215
+ </svg>`;
195
216
 
196
- ```javascript
197
- import {Solarite, r} from '../dist/Solarite.js';
198
217
 
199
- class TodoList extends Solarite {
200
- render() {
201
- this.html = r`
202
- <todo-list>
203
- ${this.items.map(item =>
204
- r`${item}<br>`
205
- )}
206
- </todo-list>`
218
+ let icon1 = r({
219
+ render() {
220
+ r(this)`<div>${folderIcon}</div>`
207
221
  }
208
- }
222
+ });
223
+ document.body.append(icon1);
209
224
 
210
- let list = new TodoList();
211
- list.items = ['one', 'two', 'three'];
212
- document.body.append(list); // calls render() if it hasn't been called already.
213
225
 
214
- list.items[1] = '2';
215
- list.render();
226
+ let icon2 = r({
227
+ render() { // string wrapped in r()
228
+ r(this)`<div>${r(folderIcon)}</div>`
229
+ }
230
+ });
231
+ document.body.append(icon2);
216
232
 
217
- list.items.splice(1, 0, 'two and a half');
218
- list.render();
219
233
  ```
220
234
 
221
- Note that nested template literals must also have the `r` prefix. Otherwise they'll be rendered as escaped text instead of HTML elements.
235
+ Folder icon comes from [Google](https://icon-sets.iconify.design/material-symbols/folder-outline/).
222
236
 
223
- When we change an element or add another element to the `items` list, calling `render()` only redraws the changed or new element. The other list items are not modified.
237
+ These types of objects can be returned by in expressions with `r` tagged template literals:
238
+
239
+ 1. strings and numbers.
240
+ 2. boolean true, which will be rendered as 'true'
241
+ 3. false, null, and undefined, which will be rendered as empty string.
242
+ 4. Solarite Templates created by `r`-tagged template literals.
243
+ 5. DOM Nodes, including other web components.
244
+ 6. Arrays of any of the above.
245
+ 7. Functions that return any of the above.
224
246
 
225
247
  ### Attributes
226
248
 
227
- Attributes can be specified by inserting expressions inside a tag. An expression can be part or all of an attribute value, or a string specifying multiple whole attributes. For example:
249
+ Dynamic attributes can be specified by inserting expressions inside a tag. An expression can be part or all of an attribute value, or a string specifying multiple whole attributes. For example:
228
250
 
229
251
  ```javascript
230
- import {Solarite, r} from '../dist/Solarite.js';
252
+ import {r} from './dist/Solarite.js';
231
253
 
232
254
  let style = 'width: 100px; height: 40px; background: orange';
233
255
  let isEditable = true;
234
256
  let height = 40;
235
257
 
236
- class AttributeDemo extends Solarite {
258
+ let attributeDemo = r({
237
259
  render() {
238
- this.html = r`
239
- <attribute-demo class="big">
240
-
241
- <div style=${style}>Look at me</div>
260
+ r(this)`
261
+ <div class="big">
262
+ <div style=${style}>Look at me</div>
263
+ <div style="${'width: 100px'}; height: ${height}px; background: gray">Look at me</div>
264
+ <div style="width: 100px; height: 40px; background: brown" ${'title="I have a title"'}>Hover me</div>
265
+ <div style="width: 100px; height: 40px; background: red" contenteditable=${isEditable} >Edit me</div>
266
+ </div>`
267
+ }
268
+ });
242
269
 
243
- <div style="${'width: 100px'}; height: ${height}px; background: gray">Look at me</div>
244
-
245
- <div style="width: 100px; height: 40px; background: red" ${'title="I have a title"'}>Hover me</div>
270
+ document.body.append(attributeDemo);
246
271
 
247
- <div style="width: 100px; height: 40px; background: brown" contenteditable=${isEditable} >Edit me</div>
248
- </attribute-demo>`
249
- }
250
- }
251
- document.body.append(new AttributeDemo());
272
+ style = 'width: 100px; height: 40px; background: green';
273
+ setTimeout(attributeDemo.render, 2000);
252
274
  ```
253
275
 
254
276
  Expressions can also toggle the presence of an attribute. In the last div above, if `isEditable` is false, null, or undefined, the contenteditable attribute will be removed.
255
277
 
256
278
  Note that attributes can also be assigned to the root element, such as `class="big"` on the `<attribute-demo>` tag above.
257
279
 
280
+ ### Id's
281
+
282
+ Any element in the html with an `id` or `data-id` attribute is automatically bound to a property with the same name on the class instance. But this only happens after `render()` is first called:
283
+
284
+ ```javascript
285
+ import {r} from './dist/Solarite.js';
286
+
287
+ let raceTeam = r({
288
+ render() {
289
+ r(this)`
290
+ <div>
291
+ <input id="driver" value="Mario">
292
+ <div data-id="car">Cutlas Supreme</div>
293
+ <div data-id="instructor.name">Lightning McQueen</div>
294
+ </div>`
295
+ }
296
+ });
297
+ document.body.append(raceTeam);
298
+
299
+ raceTeam.driver.value = 'Luigi';
300
+ raceTeam.car.style.border = '1px solid green';
301
+ // We don't need to call render() because we're editing the DOM Directly.
302
+ ```
303
+
304
+ Id's that have values matching built-in HTMLElement attribute names such as `title` or `disabled` are not allowed.
305
+
258
306
  ### Events
259
307
 
260
- Listen for events by assigning a function expression to any event attribute. Or by passing an array where the first item is a function and subsequent items are arguments to that function.
308
+ To intercept events, set the value of an event attribute like `onclick` to a function. Alternatively, set the value to an array where the first item is a function and subsequent items are arguments to that function.
261
309
 
262
310
  ```javascript
263
- import {Solarite, r} from '../dist/Solarite.js';
311
+ import {r} from './dist/Solarite.js';
264
312
 
265
- class EventDemo extends Solarite {
266
- showMessage(message) {
267
- alert(message);
268
- }
269
-
313
+ class EventDemo extends HTMLElement {
314
+ constructor() {
315
+ super();
316
+ this.render();
317
+ }
318
+
319
+ showMessage(message) {
320
+ alert(message);
321
+ }
322
+
270
323
  render() {
271
- this.html = r`
272
- <event-demo>
273
- <div onclick=${()=>alert('I was clicked!')}>Click me</div>
274
- <div onclick=${[this.showMessage, 'I too was clicked!']}>Click me</div>
275
- </event-demo>`
324
+ r(this)`
325
+ <event-demo>
326
+ <button onclick=${(ev, el)=>alert('Element ' + el.tagName + ' clicked!')}>Click me</button>
327
+ <button onclick=${[this.showMessage, 'I too was clicked!']}>Click me too!</button>
328
+ </event-demo>`
276
329
  }
277
- }
330
+ }
331
+ customElements.define('event-demo', EventDemo);
278
332
  document.body.append(new EventDemo());
279
333
  ```
280
334
 
281
- Event binding with an array containing a function and its arguments is slightly faster, since when render() is called, Red Component can see that the function hasn't changed, and it doesn't need to be unbound and rebound. But the performance difference is negligible in most cases.
335
+ Event binding with an array containing a function and its arguments is slightly faster, since the function isn't recreated when `render()` is called, and it doesn't need to be unbound and rebound. But the performance difference is usually negligible.
282
336
 
283
337
  Make sure to put your events inside `${...}` expressions, because classic events can't reference variables in the current scope.
284
338
 
285
339
  ### Two-Way Binding
286
340
 
287
- ==TODO: This demo should use auto-rendering==
288
-
289
- Form elements can update the properties that provide their values if an event attribute such as `oninput` is assigned the path to a property to update:
341
+ Form elements can update the properties that provide their values if an event attribute such as `oninput` is assigned a function to perform the update:
290
342
 
291
343
  ```javascript
292
- import {Solarite, r} from '../dist/Solarite.js';
344
+ import {r} from './dist/Solarite.js';
293
345
 
294
- class BindingDemo extends Solarite {
346
+ class BindingDemo extends HTMLElement {
295
347
 
296
348
  constructor() {
297
- super();
349
+ super();
298
350
  this.count = 0;
299
- // autoRender(this, 'count');
351
+ this.render();
300
352
  }
301
353
 
302
354
  render() {
303
- this.html = r`
304
- <binding-demo>
305
- <input type="number" value=${this.count} oninput=${[this, 'count']}>
306
- <pre>count is ${this.count}</pre>
307
- <button onclick=${()=>this.count=0}>Reset</button>
308
- </binding-demo>`
355
+ r(this)`
356
+ <binding-demo>
357
+ <input type="number" value=${this.count}
358
+ oninput=${ev => {
359
+ this.count = ev.target.value;
360
+ this.render();
361
+ }}>
362
+ <pre>count is ${this.count}</pre>
363
+ <button onclick=${()=> {
364
+ this.count = 0;
365
+ this.render();
366
+ }}>Reset</button>
367
+ </binding-demo>`
309
368
  }
310
369
  }
370
+ customElements.define('binding-demo', BindingDemo);
371
+
311
372
  document.body.append(new BindingDemo());
312
373
  ```
313
374
 
314
- In addition to `<input>`, `<select>` and `<textarea>` can also use the `value` attribute to set their value on render. Likewise so can any custom web components that define a `value` property.
375
+ In addition to `<input>`, `<select>` and `<textarea>` can also use the `value` attribute to set their value on render. Likewise so can any custom web component that defines a `value` property.
315
376
 
316
- ### Ids
317
-
318
- Any element in the html with an `id` or `data-id` attribute is automatically bound to a property with the same name on the root element. But this only happens after `render()` is first called:
377
+ A shorthand way to do two-way binding is to pass a property path as the value expression. Here with `value=${[this, 'count']}`. When a user types in the input, Solarite listens to the `oninput` listener and updates `this.count`. Note that we still need a second `oninput` attribute if we want to trigger rendering.
319
378
 
320
379
  ```javascript
321
- import {Solarite, r} from '../dist/Solarite.js';
380
+ import {r} from './dist/Solarite.js';
322
381
 
323
- class RaceTeam extends Solarite {
324
- constructor() {
382
+ class BindingDemo extends HTMLElement {
383
+
384
+ constructor() {
325
385
  super();
326
-
327
- // Id's are not set until render() is first called.
386
+ this.count = 0;
328
387
  this.render();
329
-
330
- // No need to render() again since we're changing the DOM manually.
331
- this.driver.value = 'Mario';
332
388
  }
333
389
 
334
- render() { this.html = r`
335
- <race-team>
336
- <input id="driver" value="Vermin Supreme">
337
- <div data-id="car">Cutlas Supreme</div>
338
- <div data-id="instructor.name">Lightning McQueen</div>
339
- </race-team>`
390
+ render() {
391
+ r(this)`
392
+ <binding-demo>
393
+ <input type="number" value=${[this, 'count']}
394
+ oninput=${() => this.render()}>
395
+ <pre>count is ${this.count}</pre>
396
+ <button onclick=${()=> {
397
+ this.count = 0;
398
+ this.render();
399
+ }}>Reset</button>
400
+ </binding-demo>`
340
401
  }
341
402
  }
342
- let rt = new RaceTeam();
343
- document.body.append(rt);
344
- rt.car.style.border = '1px solid green';
403
+ customElements.define('binding-demo', BindingDemo);
345
404
 
405
+ document.body.append(new BindingDemo());
346
406
  ```
347
407
 
348
- Ids that match built-in HTMLElement attribute names such as `title` or `disabled` are not allowed.
408
+
409
+
410
+ ### Loops
411
+
412
+ As previously seen, loops can be written with JavaScript's [Array.map()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) function:
413
+
414
+ ```javascript
415
+ import {r} from './dist/Solarite.js';
416
+
417
+ class TodoList extends HTMLElement {
418
+ render() {
419
+ r(this)`
420
+ <todo-list>
421
+ ${this.items.map(item =>
422
+ r`${item}<br>`
423
+ )}
424
+ </todo-list>`
425
+ }
426
+ }
427
+ customElements.define('todo-list', TodoList);
428
+
429
+ let list = new TodoList();
430
+ list.items = ['one', 'two', 'three'];
431
+ list.render();
432
+ document.body.append(list);
433
+
434
+ list.items[1] = '2';
435
+ list.render();
436
+
437
+ list.items.splice(1, 0, 'two and a half');
438
+ list.render();
439
+ ```
440
+
441
+ When we change an element or add another element to the `items` list, calling `render()` only redraws the changed or new element. The other list items are not modified.
442
+
443
+ Note that nested template literals must also have the `r` prefix. Otherwise they'll be rendered as escaped text instead of HTML elements.
349
444
 
350
445
  ### Scoped Styles
351
446
 
@@ -353,71 +448,436 @@ Html with `style` elements will be rewritten so that the `:host` selector applie
353
448
 
354
449
  These local, "scoped" styles are implemented by:
355
450
 
356
- 1. Adding a `data-style` attribute to the root element with a unique, incrementing id value.
451
+ 1. Adding a `data-style` attribute to the root element with a unique, incrementing id value for each instance.
357
452
  2. Replacing any `:host` selectors inside the style with `element-name[data-style="1"]`. For example the `:host` selector below becomes `fancy-text[data-style="1"]`.
358
453
 
359
454
  ```javascript
360
- import {Solarite, r} from '../dist/Solarite.js';
455
+ import {r} from './dist/Solarite.js';
361
456
 
362
- class FancyText extends Solarite {
457
+ class FancyText extends HTMLElement {
363
458
  render() {
364
- return r`
459
+ r(this)`
365
460
  <fancy-text>
366
461
  <style>
367
- :host { border: 10px dashed red } /* style for <fancy-text> */
368
- :host p { text-shadow: 0 0 5px orange }
462
+ :host { display: block; border: 10px dashed red }
463
+ :host p { text-shadow: 0 0 3px #f40 }
464
+ </style>
465
+ <p>I have a red border and shadow!</p>
466
+ </fancy-text>`
467
+
468
+ /* The code above is rewritten as:
469
+ <fancy-text data-style="1">
470
+ <style>
471
+ fancy-text[data-style="1"] { display: block; border: 10px dashed red }
472
+ fancy-text[data-style="1"] p { text-shadow: 0 0 3px #f40 }
369
473
  </style>
370
474
  <p>I have a red border and shadow!</p>
371
475
  </fancy-text>`
476
+ */
477
+ }
478
+ }
479
+ customElements.define('fancy-text', FancyText);
480
+ let el = new FancyText();
481
+ el.render();
482
+ document.body.append(el);
483
+ ```
484
+
485
+ Note that if shadow-dom is used, the element will not replace the `:host` selector in styles, as browsers natively support the `:host` selector when inside shadow DOM.
486
+
487
+ ### Child Components
488
+
489
+ When one web component is embedded within another, its attributes and children are passed as arguments to the constructor:
490
+
491
+ ```javascript
492
+ import {r} from './dist/Solarite.js';
493
+
494
+ class NotesItem extends HTMLElement {
495
+ // Constructor receives item object from attributes.
496
+ constructor({item}, children) {
497
+ super();
498
+ this.item = item;
499
+ this.render();
500
+ }
501
+
502
+ render({item}={}) {
503
+ // If item passed to the constructor has changed.
504
+ if (item)
505
+ this.item = item;
506
+ r(this)`
507
+ <notes-item>
508
+ <b>${this.item.name}</b> - ${this.item.description}<br>
509
+ </notes-item>`
510
+ }
511
+ }
512
+ customElements.define('notes-item', NotesItem);
513
+
514
+ class NotesList extends HTMLElement {
515
+ render() {
516
+ r(this)`
517
+ <notes-list>
518
+ ${this.items.map(item => // Pass item object to NotesItem constructor:
519
+ r`<notes-item item=${item}"></notes-item>`
520
+ )}
521
+ </notes-list>`
372
522
  }
373
523
  }
374
- document.body.append(new FancyText());
524
+ customElements.define('notes-list', NotesList);
525
+
526
+ let list = new NotesList();
527
+ list.items = [
528
+ {
529
+ name: 'English',
530
+ description: 'See spot run.'
531
+ },
532
+
533
+ {
534
+ name: 'Science',
535
+ description: 'Snails are mollusks.'
536
+ }
537
+ ]
538
+ list.render();
539
+ document.body.append(list);
540
+
541
+ list.items[0].name = 'PhysEd';
542
+
543
+ // list.items[0] has changed,
544
+ // so this will call render() on the first NotesItem,
545
+ // passing the new item object to its render() function.
546
+ list.render();
547
+
375
548
  ```
376
549
 
377
- Note that if shadown-dom is used, the element will not rewrite the `:host` selector in styles, as browsers natively support the `:host` selector when inside shadow DOM.
550
+ Calling `render()` on a parent component will call `render()` on child components if the attributes passed to the child component have changed. The new attributes will be passed as an object as the first argument to the child component's `render()` function. The `render()` function can then decide what to do with that data, and if it should re-render itself by calling `r(this)`, which will in turn call `render()` on its own child web components.
378
551
 
379
- ### Sub Components
552
+ In the above code, we alternatively could've created the `<notes-item>` element via the `new` keyword, but doing so would cause all `NotesItem` components to be recreated on every render.
380
553
 
381
- And constructors
554
+ ```JavaScript
555
+ class NotesList extends HTMLElement {
556
+ render() {
557
+ r(this)`
558
+ <notes-list>
559
+ ${this.items.map(item => // Pass item object to NotesItem constructor:
560
+ new NotesItem({item: item})
561
+ )}
562
+ </notes-list>`
563
+ }
564
+ }
565
+ ```
382
566
 
383
- Calling render() on a parent component will call it on sub-components too.
384
567
 
385
- ### Slots
386
568
 
387
569
  ### The r() function
388
570
 
389
- ### Classless Elements
571
+ The `r()` function renders templates and elements. There are multiple ways to use the `r()` function:
572
+
573
+ ```JavaScript
574
+ import {r} from './dist/Solarite.js';
575
+
576
+ // r`string`
577
+ // Convert the html to a Template that can later be used to create nodes.
578
+ let template = r`Hello ${"World"}!`;
579
+
580
+ // r(HTMLElement, Template)
581
+ // Render the template created by #1 to the <body> tag.
582
+ r(document.body, template);
583
+
584
+ // r(Template):Node|HTMLElement
585
+ // Render Template created by #1 creating a standaline HTML Element
586
+ let el = r(template);
587
+
588
+ // r(HTMLElement)`string`
589
+ // Create template and render its nodes to el.
590
+ r(el)`<b>${'Hi'}</b>`;
591
+
592
+ // r(html:string):TextNode
593
+ // Create single text node.
594
+ let textNode = r('Hello');
390
595
 
391
- ### Watches (Experimental)
596
+ // r(html:string):HTMLElement
597
+ // Create single HTMLElement.
598
+ let el2 = r('<b>Hello</b>');
392
599
 
393
- ## Reference
600
+ // r(html:string):DocumentFragment
601
+ // Create document fragment because there's more than one node.
602
+ let fragment = r('Hello <u>Goodbye</u>');
603
+
604
+ // r(function():Template, Object<string, function|*>):HTMLElement
605
+ // Crete a button element, with the fist function being the render function.
606
+ let button = r({
607
+ render() {
608
+ r(this)`<button>Submit</button>`
609
+ }
610
+ });
611
+ document.body.append(button);
612
+
613
+ ```
614
+
615
+ ### Extending Other DOM Elements.
616
+
617
+ Suppose you want to use a custom component for each `<tr>` in a `<table>`. Html won't allow you to put just any element as a child of table or tbody. In this case you can make your web component inherit from the browser's built in `<tr>` element, by passing it as the third argument to `customElements.define`:
618
+
619
+ ```javascript
620
+ import {r} from './dist/Solarite.js';
621
+
622
+ class LineItem extends HTMLElement {
623
+ constructor(user) {
624
+ super();
625
+ this.user = user;
626
+ this.render();
627
+ }
628
+
629
+ render() {
630
+ r(this)`
631
+ <th>${this.user.name}</td>
632
+ <td>${this.user.email}</td>`
633
+ }
634
+ }
635
+
636
+ customElements.define('line-item', LineItem, HTMLTableRowElement);
637
+
638
+ let table = document.createElement('table')
639
+ for (let i=0; i<10; i++) {
640
+ let user = {name: 'User ' + i, email: 'user'+i+'@example.com'};
641
+ table.append(new LineItem(user));
642
+ }
643
+ document.body.append(table);
644
+ ```
645
+
646
+ ### Manual DOM Ops
647
+
648
+ You can perform manual DOM operations on your elements in these cases:
649
+
650
+ 1. Modify any attributes that are not created by expressions, on any nodes not created by expressions.
651
+ 2. Add/remove nodes that:
652
+ 1. Are not created by an expression
653
+ 2. Are not directly before or after an expression that creates nodes.
654
+ 3. Do not have any attributes created by expressions.
655
+ 3. Modify any node, as long as you restore its previous position and attributes before `render()` is called again.
656
+
657
+ This example creates a list inside a `div` element and demonstrates which manual DOM operations are allowed.
658
+
659
+ ```javascript
660
+ import {r} from './dist/Solarite.js';
661
+
662
+ let list = r({
663
+ items: [],
664
+
665
+ add() {
666
+ this.items.push('Item ' + this.items.length);
667
+ this.render();
668
+ },
669
+
670
+ render() {
671
+ r(this)`<div>
672
+ <button onclick=${this.add}>Add Item</button>
673
+ <hr>
674
+ ${this.items.map(item => r`
675
+ <p>${item}</p>
676
+ `)}
677
+ </div>`
678
+ }
679
+ });
680
+
681
+ document.body.append(list);
682
+
683
+ // Set attributes not created by expressions. This is allowed.
684
+ list.setAttribute('title', 'DOM manipuulation demo');
685
+ list.querySelector('button').setAttribute('title', 'Click me');
686
+
687
+ // Remove the <hr> element.
688
+ // This is fine, because the hr element isn't part of an expression.
689
+ // And isn't adjacent to an expression, because there's a whitespace
690
+ // node between the <hr> and the expression.
691
+ // You could also put a comment node between them.
692
+ list.querySelector('hr').remove();
693
+ list.render();
694
+
695
+ // Remove the first <p> element and add it back again.
696
+ // This is fine, because we put it back the way it was before render()
697
+ list.add();
698
+ let p = list.querySelector('p');
699
+ list.append(p); // put it back.
700
+ list.render();
701
+
702
+ // Remove the first <p> element.
703
+ // This will cause an error because we're modifying nodes created by an expression.
704
+ // list.querySelector('p').remove();
705
+ // list.render();
706
+ ```
707
+
708
+
709
+
710
+ ### The Solarite Class (Experimental)
711
+
712
+ Instead of inheriting from HTMLElement, you can inherit from the `Solarite` class, which will add a little bit of magic to your web component:
713
+
714
+ 1. `render()` is automatically called when the element is added to the DOM, via a `connectedCallback()` function in the Solarite parent class.
715
+ 2. `customElements.define()` is automatically called when an element is instantiated via `new`. It defines the element name based on the class name, by converting the class name to a tag name with dashes, because browsers require all custom elements to have at least one dash within the name. If it can't find at least one place to put a dash, it will append `-element` to the end.
716
+
717
+ If you want the component to have a tag name that's different than the name derived from the class name, you can pass a different name to `define()`:
718
+
719
+ ```javascript
720
+ import {r, Solarite} from './dist/Solarite.js';
721
+
722
+ class TodoList extends Solarite {
723
+ render() {
724
+ r(this)`
725
+ <todo-list>
726
+ ${this.items.map(item =>
727
+ r`${item}<br>`
728
+ )}
729
+ </todo-list>`
730
+ }
731
+ }
732
+ // This is called automatically when we extend from Solarite:
733
+ //customElements.define('todo-list', TodoList);
734
+
735
+ let list = new TodoList();
736
+ list.items = ['one', 'two', 'three'];
737
+ //list.render(); render() is called automatically when appended to body.
738
+ document.body.append(list);
739
+
740
+ list.items[1] = '2';
741
+ list.render();
742
+ ```
394
743
 
395
744
  ## How it works
396
745
 
397
- Suppose you're looping over an array of 100 objects and printing them to a list or table. Something like this:
398
-
399
- ```html2
400
- <ul>
401
- ${tasks().map((task, index) => (
402
- <li key={index} class={task.completed ? "completed" : ""}>
403
- <input
404
- type="checkbox"
405
- checked={task.completed}
406
- onChange={() => toggleTask(index)}
407
- />
408
- {task.text}
409
- <button onClick={() => deleteTask(index)}>Delete</button>
410
- </li>
411
- ))}
412
- </ul>
746
+ Suppose you're looping over an array of 10 objects and printing them to a list or table:
747
+
748
+ ```javascript
749
+ import {r} from './dist/Solarite.js';
750
+
751
+ class MyTasks extends HTMLElement {
752
+ tasks = [];
753
+
754
+ deleteTask(index) {
755
+ this.tasks.splice(index, 1);
756
+ this.render();
757
+ }
758
+
759
+ render() {
760
+ r(this)`
761
+ <div>
762
+ ${this.tasks.map((task, index) => r`
763
+ <div>
764
+ ${task}
765
+ <button onclick=${() => this.deleteTask(index)}>Delete</button>
766
+ </div>`
767
+ )}
768
+ </div>`;
769
+ }
770
+ }
771
+ customElements.define('my-tasks', MyTasks);
772
+
773
+ let myTasks = new MyTasks();
774
+ for (let i=0; i<10; i++)
775
+ myTasks.tasks.push('Item ' + i);
776
+ myTasks.render();
777
+ document.body.append(myTasks);
778
+ ```
779
+
780
+ When `render()` is called:
781
+
782
+ 1. Solarite's `r()` function gets the array of raw strings created by `tasks.map()` using a template literal function.
783
+ 2. It then creates a hash of the value of every `${...}` expression given to the `r()` function.
784
+ 3. Then it compares those hashes with the hashes from the last time rendering happened, and only update elements and attributes given values that have changed.
785
+
786
+ Solarite uses [WebReflection/udomdiff](https://github.com/WebReflection/udomdiff) to compare DOM nodes to update.
787
+
788
+ ## Examples
789
+
790
+ This is the time example from Lit.js implemented with Solarite:
791
+
792
+ ```html
793
+ <script type="module">
794
+ import {r, getArg} from './dist/Solarite.js';
795
+
796
+ const replay = r`<svg enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><title>Replay</title><g><rect fill="none" height="24" width="24"/><rect fill="none" height="24" width="24"/><rect fill="none" height="24" width="24"/></g><g><g/><path d="M12,5V1L7,6l5,5V7c3.31,0,6,2.69,6,6s-2.69,6-6,6s-6-2.69-6-6H4c0,4.42,3.58,8,8,8s8-3.58,8-8S16.42,5,12,5z"/></g></svg>`;
797
+ const pause = r`<svg height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><title>Pause</title><path d="M0 0h24v24H0V0z" fill="none"/><path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/></svg>`;
798
+ const play = r`<svg height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><title>Play</title><path d="M0 0h24v24H0V0z" fill="none"/><path d="M10 8.64L15.27 12 10 15.36V8.64M8 5v14l11-7L8 5z"/></svg>`;
799
+
800
+ class MyTimer extends HTMLElement {
801
+
802
+ constructor({duration}={}) {
803
+ super();
804
+ this.duration = getArg(this, 'duration', duration)*1;
805
+ this.end = null;
806
+ this.remaining = this.duration * 1000;
807
+ this.render();
808
+ }
809
+
810
+ render() {
811
+ const min = Math.floor(this.remaining / 60000);
812
+ const sec = pad(min, Math.floor((this.remaining / 1000) % 60));
813
+ const hun = pad(true, Math.floor((this.remaining % 1000) / 10));
814
+ r(this)`
815
+ <my-timer>
816
+ ${min ? `${min}:${sec}` : `${sec}.${hun}`}
817
+ <footer>
818
+ <style>
819
+ :host { display: inline-block; min-width: 90px; font-size: 30px; text-align: center; padding: 0.2em; margin: 0.2em 0.1em;
820
+ footer { user-select: none }
821
+ }
822
+ </style>
823
+ ${
824
+ this.remaining === 0
825
+ ? ''
826
+ : this.running
827
+ ? r`<span onclick=${this.pause}>${pause}</span>`
828
+ : r`<span onclick=${this.start}>${play}</span>`
829
+ }
830
+ <span onclick=${this.reset}>${replay}</span>
831
+ </footer>
832
+ </my-timer>`;
833
+ }
834
+
835
+ start() {
836
+ this.end = Date.now() + this.remaining;
837
+ this.tick();
838
+ }
839
+
840
+ pause() {
841
+ this.end = null;
842
+ this.render();
843
+ }
844
+
845
+ reset() {
846
+ this.remaining = this.duration * 1000;
847
+ this.end = this.running ? Date.now() + this.remaining : null;
848
+ this.render();
849
+ }
850
+
851
+ tick() {
852
+ if (this.running) {
853
+ this.remaining = Math.max(0, this.end - Date.now());
854
+ this.render();
855
+ requestAnimationFrame(() => this.tick());
856
+ }
857
+ }
858
+
859
+ get running() {
860
+ return this.end && this.remaining;
861
+ }
862
+ }
863
+ customElements.define('my-timer', MyTimer);
864
+
865
+ function pad(pad, val) {
866
+ return pad ? String(val).padStart(2, '0') : val;
867
+ }
868
+ </script>
869
+ <my-timer duration="7"></my-timer>
870
+ <my-timer duration="60"></my-timer>
871
+ <my-timer duration="300"></my-timer>
413
872
  ```
414
873
 
415
- The code gets the array of raw strings created by tasks.map() using a template literal function. Then it creates a hash of each of those. Then it compares those hashes with the hashes from the last time rendering happened, and only update elements associated with the changed hashes.
416
874
 
417
- ## Differences from other Libraries
418
875
 
419
- ### React
876
+ ## Upcoming Features
420
877
 
421
- ### Lit.js
878
+ These are possible features to come:
422
879
 
423
- ### Solid.js
880
+ 1. Optional shadow DOM support
881
+ 2. Optional JSX support
882
+ 3. Option to render automatically when properties change, without calling render()
883
+ 4. Faster rendering performance.