solarite 0.1.0 → 0.1.1

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.
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">
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. (3) Export as html with styles to index.html. -->
8
8
 
9
- # Solarite Docs
9
+ # Solarite
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 is a small (8KB min+gzip), fast, compilation-free JavaScript library to enhance your vanilla web components. Features:
12
12
 
13
- This project is currently in ALPHA stage and not yet recommended for production code. This documentations is also incomplete.
13
+ - Very similar to writing native Web Components.
14
+ - Minimal DOM updates when rendering.
15
+ - No magic: Rendering only when you want it, via the manually invoked render() method.
16
+ - Local scoped styles: Inherit external styles but define new styles that apply only to the web component and its children.
17
+ - Elements with `id` or `data-id` attributes become class properties.
18
+ - Attributes are passed as constructor arguments to nested Solarite components.
19
+ - 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.
20
+ - MIT license. Free for commercial use. No attribution needed.
14
21
 
15
- If using Visual Studio Code, the Leet-Html extension is recommended to syntax highlight html inside template strings.
22
+ With Solarite there's no need to set up state like with other frameworks. Instead, use any regular variables or data structures in html templates. Call `render()` manually and it will update changed elements synchronously.
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,182 +41,145 @@ 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="Name" value=${item.name}
59
+ oninput=${e => {
60
+ item.name = e.target.value;
61
+ this.render()
62
+ }}>
63
+ <input type="number" value=${item.qty}
64
+ oninput=${e => {
65
+ item.qty = e.target.value;
66
+ this.render()
67
+ }}>
68
+ <button onclick=${()=>this.removeItem(item)}>x</button>
69
+ </div>
70
+ `)}
71
+
72
+ <pre>items = ${JSON.stringify(this.items, null, 4)}</pre>
73
+ </shopping-list>`
53
74
  }
54
75
  }
55
- document.body.append(new ShoppingList()); // adds a child named <shopping-list>
56
- ```
57
-
58
- ==TODO Does it re-render all Items on update?==
59
-
60
- ## Features
61
76
 
62
- ==TODO: Show benchmark==
63
-
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.
77
+ customElements.define('shopping-list', ShoppingList);
78
+ document.body.append(new ShoppingList()); // add <shopping-list> element
79
+ ```
73
80
 
74
- ## Using
81
+ This project is currently in BETA stage and not yet recommended for production code.
75
82
 
76
- Import one of these pre-bundled es6 modules into your project:
83
+ To use, import one of these pre-bundled es6 modules into your project:
77
84
 
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
85
+ - [Solarite.js](https://cdn.jsdelivr.net/gh/Vorticode/Solarite/dist/Solarite.js) - 87KB
86
+ - [Solarite.min.js](https://cdn.jsdelivr.net/gh/Vorticode/Solarite/dist/Solarite.js) - 24KB / 8KB gzipped
80
87
 
81
- ==TODO: NPM==
88
+ Or get Solarite from GitHub or NPM:
82
89
 
83
- ## Examples
90
+ - [Solarite GitHub Repository](https://github.com/Vorticode/solarite)
91
+ - `git clone https://github.com/Vorticode/solarite.git`
92
+ - `npm install solarite`
84
93
 
85
94
  ## Concepts
86
95
 
87
- ### Creating Web Components
96
+ ### Web Components
88
97
 
89
- In this minimal example, we make a new class called `MyComponent` and provide a `render()` function to set its html.
98
+ In this minimal example, we make a new class called `MyComponent` which extends from `HTMLElement` like other web components. We provide a `render()` function to set its html, and a constructor to call it when a new instance is created.
90
99
 
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.
100
+ All browsers require custom web component tag names to have at least one dash in the middle.
92
101
 
93
102
  ```javascript
94
- import {Solarite, r} from '../dist/Solarite.js';
103
+ import {r} from './dist/Solarite.js';
95
104
 
96
- class MyComponent extends Solarite {
97
- name = 'Red Component';
105
+ class MyComponent extends HTMLElement {
106
+ name = 'Solarite';
107
+
108
+ constructor() {
109
+ super();
110
+ this.render();
111
+ }
112
+
98
113
  render() {
99
- this.html = r`<my-component>Hello <b>${this.name}!<b></my-component>`
114
+ r(this)`<my-component>Hello <b>${this.name}!<b></my-component>`
100
115
  }
101
116
  }
102
117
 
118
+ customElements.define('my-component', MyComponent);
103
119
  document.body.append(new MyComponent());
104
120
  ```
105
121
 
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.
107
-
108
- Note that the template strings use the `r` prefix. This `r` parses the html into a data structure that Red Component can use.
109
-
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:
111
-
112
- ```html2
113
- <script>
114
- // ...
115
- // document.body.append(new MyComponent());
116
- MyComponent.define()
117
- </script>
122
+ Alternatively, instead of instantiating the element in JavaScript, we could can instantiate the element directly from html:
118
123
 
124
+ ```html
119
125
  <my-component></my-component>
120
126
  ```
121
127
 
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).
123
-
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()`:
125
-
126
- ```javascript2
127
- MyComponent.define('my-awesome-component')
128
- ```
129
-
130
- ### The render() function, r, and this.html
128
+ 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!
131
129
 
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.
130
+ 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.
133
131
 
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).
132
+ ### render() and r()
135
133
 
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.
134
+ The `r` function, when used as part of 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 web component'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.
137
135
 
136
+ 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 magic, since in some cases you may want to update internal data without rendering.
138
137
 
139
-
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.
138
+ 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
139
 
142
140
  ```javascript
143
- import {Solarite, r} from '../dist/Solarite.js';
141
+ import {r} from './dist/Solarite.js';
144
142
 
145
- class MyComponent extends Solarite {
143
+ class MyComponent extends HTMLElement {
146
144
  name = 'Solarite';
147
145
  render() {
148
146
  // With optional element tags:
149
- // this.html = r`<my-component>Hello <b>${this.name}!<b></my-component>`
147
+ // r(this)`<my-component class="big">Hello <b>${this.name}!<b></my-component>`
150
148
 
151
149
  // Without optional element tags:
152
- this.html = r`Hello <b>${this.name}!<b>`
150
+ r(this)`Hello <b>${this.name}!<b>`;
151
+ this.setAttribute('class', 'big');
153
152
  }
154
153
  }
155
-
154
+ customElements.define('my-component', MyComponent);
156
155
  document.body.append(new MyComponent());
157
156
  ```
158
157
 
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.
162
-
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:
164
-
165
- ```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();
183
-
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
- ```
158
+ If you do wrap the components html in its tag, that tag name must exactly match the tag name passed to customElements.define().
191
159
 
192
160
  ### Loops
193
161
 
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:
162
+ 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:
195
163
 
196
164
  ```javascript
197
- import {Solarite, r} from '../dist/Solarite.js';
165
+ import {r} from './dist/Solarite.js';
198
166
 
199
- class TodoList extends Solarite {
167
+ class TodoList extends HTMLElement {
200
168
  render() {
201
- this.html = r`
202
- <todo-list>
203
- ${this.items.map(item =>
204
- r`${item}<br>`
205
- )}
206
- </todo-list>`
169
+ r(this)`
170
+ <todo-list>
171
+ ${this.items.map(item =>
172
+ r`${item}<br>`
173
+ )}
174
+ </todo-list>`
207
175
  }
208
176
  }
177
+ customElements.define('todo-list', TodoList);
209
178
 
210
179
  let list = new TodoList();
211
180
  list.items = ['one', 'two', 'three'];
212
- document.body.append(list); // calls render() if it hasn't been called already.
181
+ list.render();
182
+ document.body.append(list);
213
183
 
214
184
  list.items[1] = '2';
215
185
  list.render();
@@ -218,37 +188,40 @@ list.items.splice(1, 0, 'two and a half');
218
188
  list.render();
219
189
  ```
220
190
 
221
- Note that nested template literals must also have the `r` prefix. Otherwise they'll be rendered as escaped text instead of HTML elements.
222
-
223
191
  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.
224
192
 
193
+ Note that nested template literals must also have the `r` prefix. Otherwise they'll be rendered as escaped text instead of HTML elements.
194
+
225
195
  ### Attributes
226
196
 
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:
197
+ 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
198
 
229
199
  ```javascript
230
- import {Solarite, r} from '../dist/Solarite.js';
200
+ import {r} from './dist/Solarite.js';
231
201
 
232
202
  let style = 'width: 100px; height: 40px; background: orange';
233
203
  let isEditable = true;
234
204
  let height = 40;
235
205
 
236
- class AttributeDemo extends Solarite {
206
+ class AttributeDemo extends HTMLElement {
237
207
  render() {
238
- this.html = r`
239
- <attribute-demo class="big">
240
-
241
- <div style=${style}>Look at me</div>
208
+ r(this)`
209
+ <attribute-demo class="big">
210
+
211
+ <div style=${style}>Look at me</div>
212
+
213
+ <div style="${'width: 100px'}; height: ${height}px; background: gray">Look at me</div>
242
214
 
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>
215
+ <div style="width: 100px; height: 40px; background: red" ${'title="I have a title"'}>Hover me</div>
246
216
 
247
- <div style="width: 100px; height: 40px; background: brown" contenteditable=${isEditable} >Edit me</div>
248
- </attribute-demo>`
217
+ <div style="width: 100px; height: 40px; background: brown" contenteditable=${isEditable} >Edit me</div>
218
+ </attribute-demo>`
249
219
  }
250
220
  }
251
- document.body.append(new AttributeDemo());
221
+ customElements.define('attribute-demo', AttributeDemo);
222
+ let ad = new AttributeDemo();
223
+ ad.render();
224
+ document.body.append(ad);
252
225
  ```
253
226
 
254
227
  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.
@@ -260,92 +233,110 @@ Note that attributes can also be assigned to the root element, such as `class="b
260
233
  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.
261
234
 
262
235
  ```javascript
263
- import {Solarite, r} from '../dist/Solarite.js';
236
+ import {r} from './dist/Solarite.js';
264
237
 
265
- class EventDemo extends Solarite {
266
- showMessage(message) {
267
- alert(message);
268
- }
269
-
238
+ class EventDemo extends HTMLElement {
239
+ constructor() {
240
+ super();
241
+ this.render();
242
+ }
243
+
244
+ showMessage(message) {
245
+ alert(message);
246
+ }
247
+
270
248
  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>`
249
+ r(this)`
250
+ <event-demo>
251
+ <button onclick=${(ev, el)=>alert('Element ' + el.tagName + ' clicked!')}>Click me</button>
252
+ <button onclick=${[this.showMessage, 'I too was clicked!']}>Click me too!</button>
253
+ </event-demo>`
276
254
  }
277
- }
255
+ }
256
+ customElements.define('event-demo', EventDemo);
278
257
  document.body.append(new EventDemo());
279
258
  ```
280
259
 
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.
260
+ Event binding with an array containing a function and its arguments is slightly faster, since when `render()` is called, Solarite can see that the function hasn't changed, and it doesn't need to be unbound and rebound. But the performance difference is usually negligible.
282
261
 
283
262
  Make sure to put your events inside `${...}` expressions, because classic events can't reference variables in the current scope.
284
263
 
285
264
  ### Two-Way Binding
286
265
 
287
- ==TODO: This demo should use auto-rendering==
288
-
289
266
  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:
290
267
 
291
268
  ```javascript
292
- import {Solarite, r} from '../dist/Solarite.js';
269
+ import {r} from './dist/Solarite.js';
293
270
 
294
- class BindingDemo extends Solarite {
271
+ class BindingDemo extends HTMLElement {
295
272
 
296
273
  constructor() {
297
- super();
274
+ super();
298
275
  this.count = 0;
299
- // autoRender(this, 'count');
276
+ this.render();
300
277
  }
301
278
 
302
279
  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>`
280
+ r(this)`
281
+ <binding-demo>
282
+ <input type="number" value=${this.count}
283
+ oninput=${ev => {
284
+ this.count = ev.target.value;
285
+ this.render();
286
+ }}>
287
+ <pre>count is ${this.count}</pre>
288
+ <button onclick=${()=> {
289
+ this.count = 0;
290
+ this.render();
291
+ }}>Reset</button>
292
+ </binding-demo>`
309
293
  }
310
294
  }
295
+ customElements.define('binding-demo', BindingDemo);
296
+
311
297
  document.body.append(new BindingDemo());
312
298
  ```
313
299
 
314
300
  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.
315
301
 
316
- ### Ids
302
+ ### Id's
317
303
 
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:
304
+ 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:
319
305
 
320
306
  ```javascript
321
- import {Solarite, r} from '../dist/Solarite.js';
307
+ import {r} from './dist/Solarite.js';
322
308
 
323
- class RaceTeam extends Solarite {
309
+ class RaceTeam extends HTMLElement {
324
310
  constructor() {
325
311
  super();
326
312
 
327
313
  // Id's are not set until render() is first called.
328
314
  this.render();
329
315
 
316
+ // Change the value of the input.
330
317
  // No need to render() again since we're changing the DOM manually.
331
- this.driver.value = 'Mario';
318
+ this.driver.value = 'Luigi';
332
319
  }
333
320
 
334
- render() { this.html = r`
321
+ render() {
322
+ r(this)`
335
323
  <race-team>
336
- <input id="driver" value="Vermin Supreme">
324
+ <input id="driver" value="Mario">
337
325
  <div data-id="car">Cutlas Supreme</div>
338
326
  <div data-id="instructor.name">Lightning McQueen</div>
339
327
  </race-team>`
340
328
  }
341
329
  }
342
- let rt = new RaceTeam();
343
- document.body.append(rt);
344
- rt.car.style.border = '1px solid green';
330
+ customElements.define('race-team', RaceTeam);
331
+ let raceTeam = new RaceTeam();
332
+ document.body.append(raceTeam);
333
+
334
+
335
+ raceTeam.car.style.border = '1px solid green';
345
336
 
346
337
  ```
347
338
 
348
- Ids that match built-in HTMLElement attribute names such as `title` or `disabled` are not allowed.
339
+ Id's that have values matching built-in HTMLElement attribute names such as `title` or `disabled` are not allowed.
349
340
 
350
341
  ### Scoped Styles
351
342
 
@@ -353,71 +344,230 @@ Html with `style` elements will be rewritten so that the `:host` selector applie
353
344
 
354
345
  These local, "scoped" styles are implemented by:
355
346
 
356
- 1. Adding a `data-style` attribute to the root element with a unique, incrementing id value.
347
+ 1. Adding a `data-style` attribute to the root element with a unique, incrementing id value for each instance.
357
348
  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
349
 
359
350
  ```javascript
360
- import {Solarite, r} from '../dist/Solarite.js';
351
+ import {r} from './dist/Solarite.js';
361
352
 
362
- class FancyText extends Solarite {
353
+ class FancyText extends HTMLElement {
363
354
  render() {
364
- return r`
355
+ r(this)`
365
356
  <fancy-text>
366
357
  <style>
367
- :host { border: 10px dashed red } /* style for <fancy-text> */
368
- :host p { text-shadow: 0 0 5px orange }
358
+ :host { display: block; border: 10px dashed red }
359
+ :host p { text-shadow: 0 0 3px #f40 }
369
360
  </style>
370
361
  <p>I have a red border and shadow!</p>
371
362
  </fancy-text>`
363
+
364
+ /* This is rewritten as:
365
+ <fancy-text data-style="1">
366
+ <style>
367
+ fancy-text[data-style="1"] { display: block; border: 10px dashed red }
368
+ fancy-text[data-style="1"] p { text-shadow: 0 0 3px #f40 }
369
+ </style>
370
+ <p>I have a red border and shadow!</p>
371
+ </fancy-text>`
372
+ */
372
373
  }
373
374
  }
374
- document.body.append(new FancyText());
375
+ customElements.define('fancy-text', FancyText);
376
+ let el = new FancyText();
377
+ el.render();
378
+ document.body.append(el);
375
379
  ```
376
380
 
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.
381
+ 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.
378
382
 
379
383
  ### Sub Components
380
384
 
381
- And constructors
385
+ When one Solarite component is embedded within another, its attributes and children are passed as arguments to the constructor:
386
+
387
+ ```javascript
388
+ import {r} from './dist/Solarite.js';
382
389
 
383
- Calling render() on a parent component will call it on sub-components too.
390
+ class NotesItem extends HTMLElement {
391
+ // Constructor receives item object from attributes.
392
+ constructor({item}, children) {
393
+ super();
394
+ this.item = item;
395
+ this.render();
396
+ }
397
+
398
+ render() {
399
+ r(this)`
400
+ <notes-item>
401
+ <b>${this.item.name}</b> - ${this.item.description}<br>
402
+ </notes-item>`
403
+ }
404
+ }
405
+ customElements.define('notes-item', NotesItem);
384
406
 
385
- ### Slots
407
+ class NotesList extends HTMLElement {
408
+ render() {
409
+ r(this)`
410
+ <notes-list>
411
+ ${this.items.map(item => // Pass item object to NotesItem constructor:
412
+ r`<notes-item item=${item}"></notes-item>`
413
+ )}
414
+ </notes-list>`
415
+ }
416
+ }
417
+ customElements.define('notes-list', NotesList);
418
+
419
+ let list = new NotesList();
420
+ list.items = [
421
+ {
422
+ name: 'English',
423
+ description: 'See spot run.'
424
+ },
425
+
426
+ {
427
+ name: 'Science',
428
+ description: 'Snails are mollusks.'
429
+ }
430
+ ]
431
+ list.render();
432
+ document.body.append(list);
433
+ ```
386
434
 
387
- ### The r() function
435
+ Note that calling `render()` on a parent component will call it on sub-components too.
388
436
 
389
437
  ### Classless Elements
390
438
 
391
- ### Watches (Experimental)
439
+ The `r()` function can also create elements outside of a class. Pass a function that returns a `Template` as the first argument. Optionally set the second argument to an object of additional properties and methods.
392
440
 
393
- ## Reference
441
+ ```javascript
442
+ import {r} from './dist/Solarite.js';
443
+
444
+ let count = 0;
445
+ let button = r(
446
+ // Render function
447
+ () => r`
448
+ <button onclick=${(ev, self) => {
449
+ count++;
450
+ self.render();
451
+ }}>I've been clicked ${count} times</button>`,
452
+
453
+ // User-defined function
454
+ {
455
+ inc() {
456
+ count++;
457
+ this.render();
458
+ }
459
+ }
460
+ );
461
+ document.body.append(button);
462
+ ```
394
463
 
395
- ## How it works
464
+ ### Extending Other DOM Elements.
465
+
466
+ 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`:
467
+
468
+ ```javascript
469
+ import {r} from './dist/Solarite.js';
470
+
471
+ class LineItem extends HTMLElement {
472
+ constructor(user) {
473
+ super();
474
+ this.user = user;
475
+ this.render();
476
+ }
477
+
478
+ render() {
479
+ r(this)`
480
+ <th>${this.user.name}</td>
481
+ <td>${this.user.email}</td>`
482
+ }
483
+ }
484
+
485
+ customElements.define('line-item', LineItem, HTMLTableRowElement);
486
+
487
+ let table = document.createElement('table')
488
+ for (let i=0; i<10; i++) {
489
+ let user = {name: 'User ' + i, email: 'user'+i+'@example.com'};
490
+ table.append(new LineItem(user));
491
+ }
492
+ document.body.append(table);
493
+ ```
494
+
495
+ ### The Solarite Class (Experimental)
496
+
497
+ Instead of inheriting from HTMLElement, you can inherit from the `Solarite` class, which will add a little bit of magic to your web component:
498
+
499
+ 1. `render()` is automatically called when the element is added to the DOM, via a `connectedCallback()` function in the Solarite parent class.
500
+ 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.
501
+
502
+ 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()`:
503
+
504
+ ```javascript
505
+ import {r, Solarite} from './dist/Solarite.js';
506
+
507
+ class TodoList extends Solarite {
508
+ render() {
509
+ r(this)`
510
+ <todo-list>
511
+ ${this.items.map(item =>
512
+ r`${item}<br>`
513
+ )}
514
+ </todo-list>`
515
+ }
516
+ }
517
+ // This is called automatically when we extend from Solarite:
518
+ //customElements.define('todo-list', TodoList);
396
519
 
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>
520
+ let list = new TodoList();
521
+ list.items = ['one', 'two', 'three'];
522
+ //list.render(); render() is called automatically when appended to body.
523
+ document.body.append(list);
524
+
525
+ list.items[1] = '2';
526
+ list.render();
413
527
  ```
414
528
 
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.
529
+ ## How it works
530
+
531
+ Suppose you're looping over an array of 10 objects and printing them to a list or table:
532
+
533
+ ```javascript
534
+ import {r} from './dist/Solarite.js';
416
535
 
417
- ## Differences from other Libraries
536
+ class MyTasks extends HTMLElement {
537
+ tasks = [];
538
+
539
+ deleteTask(index) {
540
+ this.tasks.splice(index, 1);
541
+ this.render();
542
+ }
543
+
544
+ render() {
545
+ r(this)`
546
+ <div>
547
+ ${this.tasks.map((task, index) => r`
548
+ <div>
549
+ ${task.text}
550
+ <button onClick=${() => this.deleteTask(index)}>Delete</button>
551
+ </div>`
552
+ )}
553
+ </div>`;
554
+ }
555
+ }
556
+ customElements.define('my-tasks', MyTasks);
557
+
558
+ let myTasks = new MyTasks();
559
+ for (let i=0; i<10; i++)
560
+ myTasks.tasks.push({
561
+ text: 'Item ' + i,
562
+ completed: false
563
+ });
564
+ myTasks.render();
565
+ document.body.append(myTasks);
566
+ ```
418
567
 
419
- ### React
568
+ When `render()` is called:
420
569
 
421
- ### Lit.js
570
+ 1. Solarite's `r()` function gets the array of raw strings created by tasks.map() using a template literal function.
571
+ 2. It then creates a hash of the values of each item in the array.
572
+ 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.
422
573
 
423
- ### Solid.js