mi-element 0.6.7 → 0.8.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.
- package/README.md +13 -9
- package/dist/context.js +6 -0
- package/dist/element.js +88 -112
- package/dist/html.js +74 -0
- package/dist/index.js +5 -5
- package/dist/refs.js +1 -9
- package/dist/styling.js +12 -5
- package/dist/utils.js +12 -0
- package/docs/context.md +43 -27
- package/docs/controller.md +4 -0
- package/docs/element.md +47 -60
- package/docs/reactivity.md +2 -2
- package/docs/render.md +357 -0
- package/docs/signal.md +13 -6
- package/docs/store.md +1 -0
- package/docs/styling.md +17 -5
- package/package.json +20 -24
- package/src/context.js +8 -0
- package/src/element.js +196 -202
- package/src/html.js +208 -0
- package/src/index.js +4 -14
- package/src/refs.js +0 -24
- package/src/styling.js +19 -9
- package/src/utils.js +22 -0
- package/types/context.d.ts +2 -0
- package/types/element.d.ts +53 -34
- package/types/html.d.ts +50 -0
- package/types/index.d.ts +4 -8
- package/types/refs.d.ts +0 -11
- package/types/styling.d.ts +1 -3
- package/types/utils.d.ts +2 -0
- package/dist/escape.js +0 -13
- package/dist/index.min.js +0 -2
- package/dist/index.min.js.map +0 -1
- package/src/escape.js +0 -56
- package/src/min.js +0 -17
- package/types/escape.d.ts +0 -3
- package/types/min.d.ts +0 -1
package/docs/element.md
CHANGED
|
@@ -28,44 +28,32 @@ from the `static attributes` object. From there setters and getters for property
|
|
|
28
28
|
changes using `.[name] = newValue` instead of `setAttribute(name, newValue)` are
|
|
29
29
|
applied.
|
|
30
30
|
|
|
31
|
-
Direct properties can also made observable with `static properties` as long as
|
|
32
|
-
not yet being defined within attributes.
|
|
33
31
|
|
|
34
32
|
```js
|
|
35
33
|
class extends MiElement {
|
|
36
34
|
/**
|
|
37
|
-
* Declare observable attributes
|
|
38
|
-
*
|
|
35
|
+
* Declare observable attributes with this getter.
|
|
36
|
+
* Use `true` to define boolean attributes!
|
|
37
|
+
* Do not use `static attribute = { text: false }` as components attributes
|
|
39
38
|
* will use a shallow copy only. With the getter we always get a real "deep"
|
|
40
39
|
* copy.
|
|
41
40
|
*
|
|
42
|
-
* For yet to defined numbers, boolean or strings use `Number`, `Boolean`,
|
|
43
|
-
* `String`. Attributes are accessible via `.[name]` or `.getAttribute(name)`.
|
|
44
41
|
* Avoid using attributes which are HTMLElement properties e.g. `className`.
|
|
42
|
+
* camelCased attributes will be made observable using its kebab-cased name.
|
|
45
43
|
*/
|
|
46
|
-
static get
|
|
44
|
+
static get properties () {
|
|
47
45
|
return {
|
|
48
|
-
text:
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
text: {},
|
|
47
|
+
focus: { type: Boolean },
|
|
48
|
+
// define property only
|
|
49
|
+
numberPropOnly: { attribute: false, type: Number }
|
|
51
50
|
}
|
|
52
51
|
}
|
|
52
|
+
|
|
53
53
|
/**
|
|
54
|
-
*
|
|
55
|
-
* Any changed value of a property, using `.[name] = nextValue` assignment,
|
|
56
|
-
* will cause a rerender.
|
|
57
|
-
* In case of objects or arrays consider changing the reference with the
|
|
58
|
-
* spread operator like `{...obj}` or `[...arr]` creating a new reference.
|
|
59
|
-
* If name is already declared in `attributes` it will be ignored.
|
|
54
|
+
* optionally declare "non observable" or internal properties
|
|
60
55
|
*/
|
|
61
|
-
|
|
62
|
-
return { prop: 0 }
|
|
63
|
-
}
|
|
64
|
-
constructor() {
|
|
65
|
-
super()
|
|
66
|
-
// optionally declare "non observable" and internal properties
|
|
67
|
-
this.foo = 'foo'
|
|
68
|
-
}
|
|
56
|
+
foo = 'foo'
|
|
69
57
|
}
|
|
70
58
|
```
|
|
71
59
|
|
|
@@ -74,10 +62,10 @@ class extends MiElement {
|
|
|
74
62
|
Invoked when a component is being added to the document's DOM.
|
|
75
63
|
|
|
76
64
|
Micro components create `this.renderRoot` (typically same as `this.shadowRoot`
|
|
77
|
-
for open components) using `this.attachShadow(
|
|
78
|
-
options are taken from the components `static
|
|
65
|
+
for open components) using `this.attachShadow(shadowRootInit)`. Shadow root
|
|
66
|
+
options are taken from the components `static shadowRootInit = { mode: 'open'
|
|
79
67
|
}`. In advanced cases where no shadow root is desired, set `static
|
|
80
|
-
|
|
68
|
+
shadowRootInit = null`
|
|
81
69
|
|
|
82
70
|
The most common use case is adding event listeners to external nodes in
|
|
83
71
|
`connectedCallback()`. Typically, anything done in `connectedCallback()` should
|
|
@@ -90,7 +78,7 @@ Then the first `render()` is issued with a `requestUpdate()`
|
|
|
90
78
|
class extends MiElement {
|
|
91
79
|
// { mode: 'open' } is the default shadow root option
|
|
92
80
|
// use `null` for no shadow root or { mode: 'closed' } for closed mode
|
|
93
|
-
static
|
|
81
|
+
static shadowRootInit = { mode: 'open' }
|
|
94
82
|
|
|
95
83
|
connectedCallback() {
|
|
96
84
|
super.connectedCallback() // don't forget to call the super method
|
|
@@ -160,24 +148,19 @@ flowchart TD
|
|
|
160
148
|
disconnectedCallback("disconnectedCallback()")
|
|
161
149
|
render("render()")
|
|
162
150
|
requestUpdate("requestUpdate()")
|
|
163
|
-
shouldUpdate("shouldUpdate(changedAttributes)")
|
|
164
151
|
update("update(changedAttributes)")
|
|
165
152
|
|
|
166
|
-
setAttribute("setAttribute(name, newVale)")
|
|
167
|
-
setProperty(".[name] = newValue")
|
|
153
|
+
setAttribute("el.setAttribute(name, newVale)")
|
|
154
|
+
setProperty("el.[name] = newValue")
|
|
168
155
|
|
|
169
156
|
START --> constructoR
|
|
170
157
|
constructoR -.->|"mount to DOM"| connectedCallback
|
|
171
158
|
connectedCallback --> render
|
|
172
159
|
render --> requestUpdate
|
|
173
|
-
requestUpdate -.->|async|
|
|
174
|
-
shouldUpdate -->|true| update
|
|
175
|
-
|
|
176
|
-
setAttribute -->|"attributeChangedCallback"| requestUpdate
|
|
177
|
-
setProperty --> requestUpdate
|
|
160
|
+
requestUpdate -.->|async| update
|
|
178
161
|
|
|
179
|
-
|
|
180
|
-
|
|
162
|
+
setAttribute -->|"attributeChangedCallback()"| requestUpdate
|
|
163
|
+
setProperty -->requestUpdate
|
|
181
164
|
|
|
182
165
|
connectedCallback -.->|"unmount from DOM"| disconnectedCallback
|
|
183
166
|
disconnectedCallback --> END
|
|
@@ -189,8 +172,8 @@ A micro component usually implements `render()` and `update()`:
|
|
|
189
172
|
import { define, MiElement, refsBySelector } from 'mi-element'
|
|
190
173
|
|
|
191
174
|
class Counter extends MiElement {
|
|
192
|
-
static get
|
|
193
|
-
return { value:
|
|
175
|
+
static get properties() {
|
|
176
|
+
return { value: { type: Number } }
|
|
194
177
|
}
|
|
195
178
|
|
|
196
179
|
// define the innerHTML template for the component
|
|
@@ -226,20 +209,19 @@ class Counter extends MiElement {
|
|
|
226
209
|
## render()
|
|
227
210
|
|
|
228
211
|
Initial rendering of the component. Try to render the component only once!
|
|
229
|
-
If you need re-rendering by recrating the DOM do this outside of `render()`
|
|
230
212
|
|
|
231
213
|
Within the `render()` method, bear in mind to:
|
|
232
214
|
|
|
233
215
|
- Avoid changing the component's state.
|
|
234
216
|
- Avoid producing any side effects.
|
|
235
|
-
- Use only the component's
|
|
217
|
+
- Use only the component's properties as input.
|
|
236
218
|
|
|
237
219
|
!!! WARNING XSS - Cross-Site Scripting
|
|
238
220
|
|
|
239
221
|
Using [`innerHTML`][innerHTML] to create the components DOM is susceptible to
|
|
240
222
|
[XSS][XSS] attacks in case that user-supplied data contains valid HTML markup.
|
|
241
223
|
|
|
242
|
-
In all other cases you may consider the <code>
|
|
224
|
+
In all other cases you may consider the <code>html``</code> template literal or
|
|
243
225
|
`escHtml()` from the "mi-element" import, which escapes user-supplied data.
|
|
244
226
|
|
|
245
227
|
[innerHTML]: https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML
|
|
@@ -287,19 +269,19 @@ adding `id` attributes to the nodes where updates shall happen or event
|
|
|
287
269
|
listeners must be applied.
|
|
288
270
|
|
|
289
271
|
```js
|
|
290
|
-
import { MiElement, define
|
|
272
|
+
import { MiElement, define } from 'mi-element'
|
|
291
273
|
|
|
292
274
|
class Counter extends MiElement {
|
|
293
275
|
static template = `
|
|
294
276
|
<button id>Count</button>
|
|
295
|
-
<p>Counter value: <span
|
|
277
|
+
<p>Counter value: <span>0</span></p>
|
|
296
278
|
`
|
|
297
279
|
|
|
298
280
|
render() {
|
|
299
281
|
// template is already rendered on `this.renderRoot`
|
|
300
282
|
|
|
301
|
-
// get refs though `
|
|
302
|
-
this.refs =
|
|
283
|
+
// get refs though `refsBySelector`
|
|
284
|
+
this.refs = this.refsBySelector({ button: 'button', count: 'p > span'})
|
|
303
285
|
// this.refs == {button: <button>, count: <span>}
|
|
304
286
|
}
|
|
305
287
|
}
|
|
@@ -315,6 +297,11 @@ rendered elements as much as possible.
|
|
|
315
297
|
|
|
316
298
|
```js
|
|
317
299
|
class Counter extends MiElement {
|
|
300
|
+
render() {
|
|
301
|
+
// ...
|
|
302
|
+
this.update()
|
|
303
|
+
}
|
|
304
|
+
|
|
318
305
|
// ...
|
|
319
306
|
update() {
|
|
320
307
|
this.refs.count.textContent = this.value
|
|
@@ -327,18 +314,18 @@ any changed attributes are passed.
|
|
|
327
314
|
|
|
328
315
|
To mitigate [XSS][] attacks prefer the use of `.textContent` and avoid
|
|
329
316
|
~~`.innerHTML`~~. For attribute changes use `.setAttribute(name, newValue)`.
|
|
330
|
-
Both `.textContent` and `setAttribute()` provide escaping for you.
|
|
331
|
-
|
|
332
317
|
|
|
333
318
|
For finer control on updates the use of signals is encouraged. With this there
|
|
334
|
-
is no need to add logic to `
|
|
319
|
+
is no need to add logic to `update()`.
|
|
335
320
|
|
|
336
321
|
```js
|
|
337
322
|
import { MiElement, Signal } from 'mi-element'
|
|
338
323
|
|
|
339
324
|
class Counter extends MiElement {
|
|
340
|
-
static get
|
|
341
|
-
return {
|
|
325
|
+
static get properties() {
|
|
326
|
+
return {
|
|
327
|
+
value: { type: Number }
|
|
328
|
+
}
|
|
342
329
|
}
|
|
343
330
|
|
|
344
331
|
static template = `
|
|
@@ -346,8 +333,15 @@ class Counter extends MiElement {
|
|
|
346
333
|
<p>Counter value: <span>0</span></p>
|
|
347
334
|
`
|
|
348
335
|
|
|
336
|
+
constructor() {
|
|
337
|
+
super()
|
|
338
|
+
// set initial values
|
|
339
|
+
this.value = 0
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
|
|
349
343
|
render() {
|
|
350
|
-
const refs = refsBySelector(
|
|
344
|
+
const refs = this.refsBySelector({
|
|
351
345
|
button: 'button',
|
|
352
346
|
count: 'span'
|
|
353
347
|
})
|
|
@@ -365,13 +359,6 @@ class Counter extends MiElement {
|
|
|
365
359
|
}
|
|
366
360
|
```
|
|
367
361
|
|
|
368
|
-
## shouldUpdate(changedAttributes)
|
|
369
|
-
|
|
370
|
-
Convenience method in order to be able to decide on the changed attributes,
|
|
371
|
-
whether `update()` should be called or not.
|
|
372
|
-
|
|
373
|
-
Return `true` if component should be updated.
|
|
374
|
-
|
|
375
362
|
## on(eventName, listener, \[node\])
|
|
376
363
|
|
|
377
364
|
Adds listener function for eventName. listener is removed before component
|
package/docs/reactivity.md
CHANGED
|
@@ -16,9 +16,9 @@ import { html, render } from 'mi-html'
|
|
|
16
16
|
define(
|
|
17
17
|
'mi-counter',
|
|
18
18
|
class extends MiElement {
|
|
19
|
-
static get
|
|
19
|
+
static get properties() {
|
|
20
20
|
return {
|
|
21
|
-
count:
|
|
21
|
+
count: { type: Number } //<< this.count is already a signal
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
|
package/docs/render.md
ADDED
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
**Table of contents**
|
|
2
|
+
|
|
3
|
+
<!-- !toc (minlevel=2) -->
|
|
4
|
+
|
|
5
|
+
- [html Tagged Template Literal](#html-tagged-template-literal)
|
|
6
|
+
- [Basic Usage](#basic-usage)
|
|
7
|
+
- [Working with Arrays](#working-with-arrays)
|
|
8
|
+
- [Nested Templates](#nested-templates)
|
|
9
|
+
- [Unsafe HTML](#unsafe-html)
|
|
10
|
+
- [render() Function](#render-function)
|
|
11
|
+
- [Signature](#signature)
|
|
12
|
+
- [Basic Example](#basic-example)
|
|
13
|
+
- [Special Attributes](#special-attributes)
|
|
14
|
+
- [Boolean Attributes (`?attr`)](#boolean-attributes-attr)
|
|
15
|
+
- [Property Binding (`.prop`)](#property-binding-prop)
|
|
16
|
+
- [Event Listeners (`@event`)](#event-listeners-event)
|
|
17
|
+
- [Element References (`ref`)](#element-references-ref)
|
|
18
|
+
- [Spread Properties (`...`)](#spread-properties-)
|
|
19
|
+
- [Complete Example](#complete-example)
|
|
20
|
+
- [Security](#security)
|
|
21
|
+
- [Best Practices](#best-practices)
|
|
22
|
+
- [Custom Elements](#custom-elements)
|
|
23
|
+
|
|
24
|
+
<!-- toc! -->
|
|
25
|
+
|
|
26
|
+
# HTML Templating and Rendering
|
|
27
|
+
|
|
28
|
+
MiElement offers a lightweight templating system for properly escaping HTML to
|
|
29
|
+
prevent XSS attacks. The `html` tagged template literal combined with the
|
|
30
|
+
`render()` function provides a safe and convenient way to create dynamic HTML
|
|
31
|
+
with event handlers, property bindings, and element references.
|
|
32
|
+
|
|
33
|
+
## html Tagged Template Literal
|
|
34
|
+
|
|
35
|
+
The `html` function is a tagged template literal that automatically escapes all
|
|
36
|
+
interpolated values to prevent XSS attacks.
|
|
37
|
+
|
|
38
|
+
### Basic Usage
|
|
39
|
+
|
|
40
|
+
```js
|
|
41
|
+
import { html } from '@mi-element/mi-element'
|
|
42
|
+
|
|
43
|
+
// Simple text escaping
|
|
44
|
+
const userInput = '<script>alert("xss")</script>'
|
|
45
|
+
const safe = html`<div>${userInput}</div>`
|
|
46
|
+
// Result: <div><script>alert("xss")</script></div>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Working with Arrays
|
|
50
|
+
|
|
51
|
+
Arrays are automatically joined and each item is escaped:
|
|
52
|
+
|
|
53
|
+
```js
|
|
54
|
+
const items = ['Apple', 'Banana', 'Cherry']
|
|
55
|
+
const list = html`<ul>
|
|
56
|
+
${items.map((item) => html`<li>${item}</li>`)}
|
|
57
|
+
</ul>`
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Nested Templates
|
|
61
|
+
|
|
62
|
+
HTML templates can be nested safely without double-escaping:
|
|
63
|
+
|
|
64
|
+
```js
|
|
65
|
+
const header = html`<h1>${'<Title>'}</h1>`
|
|
66
|
+
const page = html`
|
|
67
|
+
<div>
|
|
68
|
+
${header}
|
|
69
|
+
<p>${'<content>'}</p>
|
|
70
|
+
</div>
|
|
71
|
+
`
|
|
72
|
+
// The header content remains escaped, not double-escaped
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Unsafe HTML
|
|
76
|
+
|
|
77
|
+
When you need to render raw HTML (use with caution):
|
|
78
|
+
|
|
79
|
+
```js
|
|
80
|
+
import { unsafeHtml } from '@mi-element/mi-element'
|
|
81
|
+
|
|
82
|
+
const trustedHtml = '<strong>Bold</strong>'
|
|
83
|
+
const template = html`<div>${unsafeHtml(trustedHtml)}</div>`
|
|
84
|
+
// Result: <div><strong>Bold</strong></div>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## render() Function
|
|
88
|
+
|
|
89
|
+
The `render()` function takes an HTML template and renders it into a DOM node,
|
|
90
|
+
with support for special attributes for event handling, property binding, and
|
|
91
|
+
element references.
|
|
92
|
+
|
|
93
|
+
### Signature
|
|
94
|
+
|
|
95
|
+
```js
|
|
96
|
+
render(node, template, (handlers = {}))
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
- `node`: Target DOM element to render into
|
|
100
|
+
- `template`: HTML template string (typically from `html` tagged template)
|
|
101
|
+
- `handlers`: Optional object containing event handler functions or HTMLElement
|
|
102
|
+
for method lookup
|
|
103
|
+
- **Returns**: Object with collected element references
|
|
104
|
+
|
|
105
|
+
### Basic Example
|
|
106
|
+
|
|
107
|
+
```js
|
|
108
|
+
import { html, render } from '@mi-element/mi-element'
|
|
109
|
+
|
|
110
|
+
const container = document.querySelector('#app')
|
|
111
|
+
const template = html`
|
|
112
|
+
<div>
|
|
113
|
+
<h1>Hello World</h1>
|
|
114
|
+
<p>Welcome to MiElement</p>
|
|
115
|
+
</div>
|
|
116
|
+
`
|
|
117
|
+
|
|
118
|
+
render(container, template)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Special Attributes
|
|
122
|
+
|
|
123
|
+
Special attributes provide powerful features for dynamic behavior. All special
|
|
124
|
+
attributes are removed from the DOM after processing.
|
|
125
|
+
|
|
126
|
+
### Boolean Attributes (`?attr`)
|
|
127
|
+
|
|
128
|
+
Use the `?` prefix for boolean attributes:
|
|
129
|
+
|
|
130
|
+
```js
|
|
131
|
+
const isDisabled = true
|
|
132
|
+
const template = html`
|
|
133
|
+
<input ?disabled=${isDisabled} />
|
|
134
|
+
<button ?hidden=${false}>Click</button>
|
|
135
|
+
`
|
|
136
|
+
|
|
137
|
+
render(container, template)
|
|
138
|
+
// Result: <input disabled=""> <button>Click</button>
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Property Binding (`.prop`)
|
|
142
|
+
|
|
143
|
+
Use the `.` prefix to set properties directly on DOM elements (not attributes):
|
|
144
|
+
|
|
145
|
+
```js
|
|
146
|
+
const inputValue = 'Hello'
|
|
147
|
+
const userData = { name: 'John', age: 30 }
|
|
148
|
+
|
|
149
|
+
const template = html`
|
|
150
|
+
<input .value=${inputValue} />
|
|
151
|
+
<my-component .data=${userData}></my-component>
|
|
152
|
+
`
|
|
153
|
+
|
|
154
|
+
render(container, template)
|
|
155
|
+
// The input.value property is set, but not visible as an attribute
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**Note**: Use kebab-case for property names - they will be automatically
|
|
159
|
+
converted to camelCase:
|
|
160
|
+
|
|
161
|
+
```js
|
|
162
|
+
html`<div .some-property=${'value'}></div>`
|
|
163
|
+
// Sets element.someProperty = 'value'
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Event Listeners (`@event`)
|
|
167
|
+
|
|
168
|
+
Use the `@` prefix for event listeners:
|
|
169
|
+
|
|
170
|
+
#### Inline Functions
|
|
171
|
+
|
|
172
|
+
```js
|
|
173
|
+
const template = html`
|
|
174
|
+
<button @click=${(e) => console.log('Clicked!', e)}>Click Me</button>
|
|
175
|
+
`
|
|
176
|
+
|
|
177
|
+
render(container, template)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
#### Named Handlers
|
|
181
|
+
|
|
182
|
+
```js
|
|
183
|
+
const handlers = {
|
|
184
|
+
handleClick(e) {
|
|
185
|
+
console.log('Button clicked:', e.target)
|
|
186
|
+
},
|
|
187
|
+
handleInput(e) {
|
|
188
|
+
console.log('Input value:', e.target.value)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const template = html`
|
|
193
|
+
<button @click="handleClick">Click</button>
|
|
194
|
+
<input @input="handleInput" />
|
|
195
|
+
`
|
|
196
|
+
|
|
197
|
+
render(container, template, handlers)
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
#### Using HTMLElement Methods
|
|
201
|
+
|
|
202
|
+
```js
|
|
203
|
+
class MyComponent extends HTMLElement {
|
|
204
|
+
handleClick(e) {
|
|
205
|
+
console.log('Clicked in component')
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
connectedCallback() {
|
|
209
|
+
const template = html` <button @click="handleClick">Click</button> `
|
|
210
|
+
render(this, template, this)
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Element References (`ref`)
|
|
216
|
+
|
|
217
|
+
Collect references to rendered elements:
|
|
218
|
+
|
|
219
|
+
```js
|
|
220
|
+
const template = html`
|
|
221
|
+
<div>
|
|
222
|
+
<input ref="nameInput" type="text" />
|
|
223
|
+
<button ref="submitBtn">Submit</button>
|
|
224
|
+
</div>
|
|
225
|
+
`
|
|
226
|
+
|
|
227
|
+
const refs = render(container, template)
|
|
228
|
+
// refs.nameInput -> the input element
|
|
229
|
+
// refs.submitBtn -> the button element
|
|
230
|
+
|
|
231
|
+
refs.nameInput.focus()
|
|
232
|
+
refs.submitBtn.addEventListener('click', () => {
|
|
233
|
+
console.log(refs.nameInput.value)
|
|
234
|
+
})
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Spread Properties (`...`)
|
|
238
|
+
|
|
239
|
+
Spread multiple properties from an object:
|
|
240
|
+
|
|
241
|
+
```js
|
|
242
|
+
const inputProps = {
|
|
243
|
+
value: 'Default text',
|
|
244
|
+
disabled: true,
|
|
245
|
+
placeholder: 'Enter text'
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const template = html`<input ...=${inputProps} />`
|
|
249
|
+
|
|
250
|
+
render(container, template)
|
|
251
|
+
// Sets all properties: value, disabled, and placeholder
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## Complete Example
|
|
255
|
+
|
|
256
|
+
```js
|
|
257
|
+
import { html, render, MiElement, define } from '@mi-element/mi-element'
|
|
258
|
+
import { MiElement } from '@mi-element/mi-element'
|
|
259
|
+
|
|
260
|
+
class TodoList extends MiElement {
|
|
261
|
+
todos = [
|
|
262
|
+
{ id: 1, text: 'Learn MiElement', done: false },
|
|
263
|
+
{ id: 2, text: 'Build something', done: false }
|
|
264
|
+
]
|
|
265
|
+
|
|
266
|
+
toggleTodo(id) {
|
|
267
|
+
const todo = this.todos.find((t) => t.id === id)
|
|
268
|
+
if (todo) todo.done = !todo.done
|
|
269
|
+
this.requestUpdate()
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// first time render
|
|
273
|
+
render() {
|
|
274
|
+
const template = html`
|
|
275
|
+
<div>
|
|
276
|
+
<h2>My Todos</h2>
|
|
277
|
+
<ul ref="list"></ul>
|
|
278
|
+
</div>
|
|
279
|
+
`
|
|
280
|
+
this.refs = render(this.renderRoot, template)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// any updates
|
|
284
|
+
update() {
|
|
285
|
+
const template = this.todos.map(
|
|
286
|
+
(todo) => html`
|
|
287
|
+
<li>
|
|
288
|
+
<input
|
|
289
|
+
type="checkbox"
|
|
290
|
+
?checked=${todo.done}
|
|
291
|
+
@change=${() => this.toggleTodo(todo.id)}
|
|
292
|
+
/>
|
|
293
|
+
<span>${todo.text}</span>
|
|
294
|
+
</li>
|
|
295
|
+
`
|
|
296
|
+
)
|
|
297
|
+
// clear all children first
|
|
298
|
+
this.refs.list.innerHTML = ''
|
|
299
|
+
// then rerender
|
|
300
|
+
render(this.refs.list, template)
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
define('todo-list', TodoList)
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
## Security
|
|
308
|
+
|
|
309
|
+
The `html` tagged template automatically escapes all values to prevent XSS
|
|
310
|
+
attacks:
|
|
311
|
+
|
|
312
|
+
- Strings are HTML-escaped
|
|
313
|
+
- Objects and functions are stored in a temporary cache and referenced by key
|
|
314
|
+
- Only use `unsafeHtml()` when you have trusted HTML content
|
|
315
|
+
|
|
316
|
+
```js
|
|
317
|
+
// Safe - user input is escaped
|
|
318
|
+
const userInput = '<script>alert("xss")</script>'
|
|
319
|
+
html`<div>${userInput}</div>`
|
|
320
|
+
// Result: <div><script>alert("xss")</script></div>;
|
|
321
|
+
|
|
322
|
+
// Unsafe - only use with trusted content
|
|
323
|
+
const trustedHtml = '<strong>Safe HTML</strong>'
|
|
324
|
+
html`<div>${unsafeHtml(trustedHtml)}</div>`
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
## Best Practices
|
|
328
|
+
|
|
329
|
+
1. **Always use `html` for dynamic content** to ensure proper escaping
|
|
330
|
+
2. **Use kebab-case** for all attribute and event names
|
|
331
|
+
3. **Collect refs** instead of using `querySelector` when possible
|
|
332
|
+
4. **Avoid `unsafeHtml`** unless absolutely necessary with trusted content
|
|
333
|
+
5. **Use property binding** (`.prop`) for complex data structures
|
|
334
|
+
6. **Leverage event delegation** for dynamic lists with many items
|
|
335
|
+
7. **Keep handlers object** or use class methods for better organization
|
|
336
|
+
|
|
337
|
+
## Custom Elements
|
|
338
|
+
|
|
339
|
+
The render system automatically skips processing children of custom elements,
|
|
340
|
+
allowing them to manage their own content:
|
|
341
|
+
|
|
342
|
+
```js
|
|
343
|
+
class MyElement extends HTMLElement {
|
|
344
|
+
connectedCallback() {
|
|
345
|
+
// This element controls its own rendering
|
|
346
|
+
this.innerHTML = `<input ref="inside" />`
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
customElements.define('my-element', MyElement)
|
|
351
|
+
|
|
352
|
+
// The render function will process my-element's attributes
|
|
353
|
+
// but won't process its children
|
|
354
|
+
const template = html`
|
|
355
|
+
<my-element .data=${{ test: true }} ref="custom"></my-element>
|
|
356
|
+
`
|
|
357
|
+
```
|
package/docs/signal.md
CHANGED
|
@@ -24,7 +24,7 @@ For convenience there is a `createSignal(initialValue<T>): State<T>` function to
|
|
|
24
24
|
create a signal.
|
|
25
25
|
|
|
26
26
|
```js
|
|
27
|
-
import { createSignal, State } from 'mi-
|
|
27
|
+
import { createSignal, State } from 'mi-signal'
|
|
28
28
|
|
|
29
29
|
const signal = createSignal(1)
|
|
30
30
|
// same as
|
|
@@ -58,7 +58,7 @@ signals state as well as to update on any change through
|
|
|
58
58
|
_synchronously_!
|
|
59
59
|
|
|
60
60
|
```js
|
|
61
|
-
import { createSignal, effect } from 'mi-
|
|
61
|
+
import { createSignal, effect } from 'mi-signal'
|
|
62
62
|
|
|
63
63
|
const signal = createSignal(1)
|
|
64
64
|
|
|
@@ -157,14 +157,15 @@ MiElement attributes are backed by signals. To subscribe to reactive changes a
|
|
|
157
157
|
`Signal.effect` callback can be used on all observed attributes.
|
|
158
158
|
|
|
159
159
|
```js
|
|
160
|
-
import { effect, define, MiElement
|
|
160
|
+
import { effect, define, MiElement } from 'mi-element'
|
|
161
|
+
import { createSignal } from 'mi-signal'
|
|
161
162
|
|
|
162
163
|
define(
|
|
163
164
|
'mi-counter',
|
|
164
165
|
class extends MiElement {
|
|
165
166
|
static template = `
|
|
166
|
-
<button
|
|
167
|
-
<div
|
|
167
|
+
<button> + </button>
|
|
168
|
+
<div></div>
|
|
168
169
|
`
|
|
169
170
|
|
|
170
171
|
static get attributes() {
|
|
@@ -174,8 +175,14 @@ define(
|
|
|
174
175
|
}
|
|
175
176
|
}
|
|
176
177
|
|
|
178
|
+
// define the signal to make all properties reactive
|
|
179
|
+
static createSignal = createSignal
|
|
180
|
+
|
|
177
181
|
render() {
|
|
178
|
-
|
|
182
|
+
// define initial value
|
|
183
|
+
this.count = this.count ?? 0
|
|
184
|
+
|
|
185
|
+
this.refs = this.refsBySelector({ button: 'button', div: 'div' })
|
|
179
186
|
this.refs.button.addEventListener('click', () => {
|
|
180
187
|
// change observed and reactive attribute...
|
|
181
188
|
this.count++
|
package/docs/store.md
CHANGED
package/docs/styling.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- !toc (minlevel=2) -->
|
|
4
4
|
|
|
5
|
-
- [
|
|
5
|
+
- [classNames](#classnames)
|
|
6
6
|
- [styleMap](#stylemap)
|
|
7
7
|
- [addGlobalStyles](#addglobalstyles)
|
|
8
8
|
|
|
@@ -10,16 +10,16 @@
|
|
|
10
10
|
|
|
11
11
|
# Styling
|
|
12
12
|
|
|
13
|
-
##
|
|
13
|
+
## classNames
|
|
14
14
|
|
|
15
15
|
Obtain a class string from an object. Only class-names with trueish values are
|
|
16
16
|
returned.
|
|
17
17
|
|
|
18
18
|
```js
|
|
19
|
-
import {
|
|
19
|
+
import { classNames } from 'mi-element'
|
|
20
20
|
|
|
21
|
-
const className =
|
|
22
|
-
//> className == 'enabled number'
|
|
21
|
+
const className = classNames({ enabled: true, hidden: '', number: 1 }, 'always')
|
|
22
|
+
//> className == 'enabled number always'
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
## styleMap
|
|
@@ -74,4 +74,16 @@ customElements.define(
|
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
)
|
|
77
|
+
|
|
78
|
+
// with MiElement
|
|
79
|
+
import { define, MiElement } from 'mi-element'
|
|
80
|
+
|
|
81
|
+
define('x-with-global-styles', class extends MiElement {
|
|
82
|
+
static useGlobalStyles() {
|
|
83
|
+
return true
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
static template = '<h1>Hello World</h1>'
|
|
87
|
+
})
|
|
88
|
+
|
|
77
89
|
```
|