mi-element 0.6.6 → 0.7.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/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 and their default values with this getter.
38
- * Do not use `static attribute = { text: 'Hi' }` as components attributes
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 attributes () {
44
+ static get properties () {
47
45
  return {
48
- text: 'Hi',
49
- // A yet to be defined boolean value
50
- focus: Boolean
46
+ text: {},
47
+ focus: { type: Boolean },
48
+ // define property only
49
+ numberPropOnly: { attribute: false, type: Number }
51
50
  }
52
51
  }
52
+
53
53
  /**
54
- * Declare observable properties and their default values.
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
- static get properties () {
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(shadowRootOptions)`. Shadow root
78
- options are taken from the components `static shadowRootOptions = { mode: 'open'
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
- shadowRootOptions = null`
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 shadowRootOptions = { mode: 'open' }
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| shouldUpdate
174
- shouldUpdate -->|true| update
175
-
176
- setAttribute -->|"attributeChangedCallback"| requestUpdate
177
- setProperty --> requestUpdate
160
+ requestUpdate -.->|async| update
178
161
 
179
- update -.->|change| setAttribute
180
- update -.->|change| setProperty
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 attributes() {
193
- return { value: 0 }
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 attributes as input.
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>esc``</code> template literal or
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, refsById } from 'mi-element'
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 id="count">0</span></p>
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 `refsById` of `refsBySelector`
302
- this.refs = refsById(this.renderRoot)
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 `shouldUpdate()` or `update()`.
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 attributes() {
341
- return { value: 0 }
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(this.renderRoot, {
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
@@ -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 attributes() {
19
+ static get properties() {
20
20
  return {
21
- count: 1 //<< this.count is already a signal
21
+ count: { type: Number } //<< this.count is already a signal
22
22
  }
23
23
  }
24
24
 
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-element'
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-element'
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, refByIds } from 'mi-element'
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 id> + </button>
167
- <div id></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
- this.refs = refsById(this.renderRoot)
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
@@ -62,4 +62,5 @@ const actions = {
62
62
  (by = 1) =>
63
63
  (state) => ({ ...state, count: state.count + by })
64
64
  }
65
+ const store = new Store(actions, initialValue)
65
66
  ```
package/docs/styling.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  <!-- !toc (minlevel=2) -->
4
4
 
5
- - [classMap](#classmap)
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
- ## classMap
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 { classMap } from 'mi-element'
19
+ import { classNames } from 'mi-element'
20
20
 
21
- const className = classMap({ enabled: true, hidden: '', number: 1 })
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
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mi-element",
3
- "version": "0.6.6",
3
+ "version": "0.7.0",
4
4
  "description": "Build lightweight reactive micro web-components",
5
5
  "keywords": [],
6
6
  "homepage": "https://github.com/commenthol/mi-element/tree/main/packages/mi-element#readme",
@@ -25,11 +25,6 @@
25
25
  "types": "./types/index.d.ts",
26
26
  "default": "./dist/index.js"
27
27
  },
28
- "./min": {
29
- "sourceMap": "./dist/index.min.js.map",
30
- "types": "./types/min.d.ts",
31
- "default": "./dist/index.min.js"
32
- },
33
28
  "./element": {
34
29
  "development": "./src/element.js",
35
30
  "types": "./types/element.d.ts",
@@ -74,31 +69,32 @@
74
69
  "types"
75
70
  ],
76
71
  "dependencies": {
77
- "mi-signal": "0.6.6"
72
+ "mi-signal": "0.7.0"
78
73
  },
79
74
  "devDependencies": {
80
- "@eslint/js": "^9.39.1",
75
+ "@eslint/js": "^9.39.2",
81
76
  "@rollup/plugin-node-resolve": "^16.0.3",
82
77
  "@rollup/plugin-terser": "^0.4.4",
83
78
  "@testing-library/dom": "^10.4.1",
84
- "@types/node": "^24.10.0",
85
- "@vitest/browser": "^4.0.8",
86
- "@vitest/browser-playwright": "^4.0.8",
87
- "@vitest/coverage-istanbul": "^4.0.8",
88
- "eslint": "^9.39.1",
79
+ "@types/node": "^24.10.4",
80
+ "@vitest/browser": "^4.0.16",
81
+ "@vitest/browser-playwright": "^4.0.16",
82
+ "@vitest/coverage-istanbul": "^4.0.16",
83
+ "eslint": "^9.39.2",
89
84
  "globals": "^16.5.0",
90
85
  "npm-run-all2": "^8.0.4",
91
- "playwright": "^1.56.1",
92
- "prettier": "^3.6.2",
93
- "rimraf": "^6.1.0",
94
- "rollup": "^4.53.1",
86
+ "playwright": "^1.57.0",
87
+ "prettier": "^3.7.4",
88
+ "rimraf": "^6.1.2",
89
+ "rollup": "^4.54.0",
95
90
  "typescript": "^5.9.3",
96
- "vite": "^7.2.2",
97
- "vitest": "^4.0.8"
91
+ "vite": "^7.3.0",
92
+ "vitest": "^4.0.16",
93
+ "mi-html": "0.7.0"
98
94
  },
99
95
  "scripts": {
100
96
  "all": "npm-run-all pretty lint test build types",
101
- "build": "rimraf dist && rollup -c && gzip -k dist/index.min.js && ls -al dist && rimraf dist/index.min.js.gz",
97
+ "build": "rimraf dist && rollup -c && ls -al dist",
102
98
  "dev": "npm run test:browser",
103
99
  "docs": "cd docs; for f in *.md; do markedpp -s -i $f; done",
104
100
  "example": "vite --open /example/",
package/src/context.js CHANGED
@@ -55,6 +55,14 @@ export class ContextProvider {
55
55
  return this.state.get()
56
56
  }
57
57
 
58
+ set value(newValue) {
59
+ this.set(newValue)
60
+ }
61
+
62
+ get value() {
63
+ return this.get()
64
+ }
65
+
58
66
  /**
59
67
  * @private
60
68
  * @param {ContextRequestEvent} ev