mi-element 0.2.0 → 0.3.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/README.md +33 -18
- package/dist/element.js +45 -16
- package/dist/escape.js +7 -3
- package/dist/index.js +2 -2
- package/dist/signal.js +23 -32
- package/package.json +18 -16
- package/src/context.js +0 -2
- package/src/element.js +32 -17
- package/src/escape.js +17 -13
- package/src/index.js +8 -3
- package/src/signal.js +59 -45
- package/src/styling.js +43 -1
- package/types/context.d.ts +2 -2
- package/types/element.d.ts +7 -6
- package/types/escape.d.ts +1 -1
- package/types/index.d.ts +2 -3
- package/types/signal.d.ts +31 -18
- package/types/store.d.ts +1 -1
- package/types/styling.d.ts +13 -1
package/README.md
CHANGED
|
@@ -4,13 +4,24 @@
|
|
|
4
4
|
|
|
5
5
|
Only weights 2.3kB minified and gzipped.
|
|
6
6
|
|
|
7
|
-
mi-element provides
|
|
7
|
+
mi-element provides features to build web applications through
|
|
8
8
|
[Web Components][] like:
|
|
9
9
|
|
|
10
|
+
- type coercion with setAttribute
|
|
10
11
|
- controllers to hook into the components lifecycle
|
|
11
12
|
- ContextProvider, ContextConsumer for data provisioning from outside of a
|
|
12
13
|
component
|
|
13
14
|
- Store for managing shared state across components
|
|
15
|
+
- Signal for reactive behavior
|
|
16
|
+
|
|
17
|
+
The motivation to build this module comes from the confusions around attributes
|
|
18
|
+
and properties. "mi-element" solves this by providing the same results when
|
|
19
|
+
setting objects or functions either through `el.setAttribute(name, value)` or
|
|
20
|
+
properties `el[name] = value`.
|
|
21
|
+
|
|
22
|
+
Furthermore all observed attributes have a reactive behavior through the use of
|
|
23
|
+
signals and effects. It implements signals (loosely) following the
|
|
24
|
+
[TC39 JavaScript Signals standard proposal][].
|
|
14
25
|
|
|
15
26
|
# Usage
|
|
16
27
|
|
|
@@ -23,7 +34,7 @@ npm i mi-element
|
|
|
23
34
|
```js
|
|
24
35
|
/** @file ./mi-counter.js */
|
|
25
36
|
|
|
26
|
-
import { MiElement, define, refsById } from 'mi-element'
|
|
37
|
+
import { MiElement, define, refsById, Signal } from 'mi-element'
|
|
27
38
|
|
|
28
39
|
// define your Component
|
|
29
40
|
class MiCounter extends MiElement {
|
|
@@ -31,13 +42,12 @@ class MiCounter extends MiElement {
|
|
|
31
42
|
<style>
|
|
32
43
|
:host { font-size: 1.25rem; }
|
|
33
44
|
</style>
|
|
34
|
-
<
|
|
35
|
-
<span id aria-label="Counter value">0</span>
|
|
45
|
+
<div id aria-label="Counter value">0</div>
|
|
36
46
|
<button id="increment" aria-label="Increment counter"> + </button>
|
|
37
47
|
`
|
|
38
48
|
|
|
39
|
-
// declare reactive attributes
|
|
40
49
|
static get attributes() {
|
|
50
|
+
// declare reactive attribute(s)
|
|
41
51
|
return { count: 0 }
|
|
42
52
|
}
|
|
43
53
|
|
|
@@ -46,13 +56,14 @@ class MiCounter extends MiElement {
|
|
|
46
56
|
// gather refs from template (here by id)
|
|
47
57
|
this.refs = refsById(this.renderRoot)
|
|
48
58
|
// apply event listeners
|
|
49
|
-
this.refs.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
59
|
+
this.refs.increment.addEventListener('click', () => {
|
|
60
|
+
// change observed and reactive attribute...
|
|
61
|
+
this.count++
|
|
62
|
+
})
|
|
63
|
+
Signal.effect(() => {
|
|
64
|
+
// ...triggers update on every change of `this.count`
|
|
65
|
+
this.refs.div.textContent = this.count
|
|
66
|
+
})
|
|
56
67
|
}
|
|
57
68
|
}
|
|
58
69
|
|
|
@@ -60,7 +71,7 @@ class MiCounter extends MiElement {
|
|
|
60
71
|
define('mi-counter', MiCounter)
|
|
61
72
|
```
|
|
62
73
|
|
|
63
|
-
Now use in your HTML
|
|
74
|
+
Now use your now component in your HTML
|
|
64
75
|
|
|
65
76
|
```html
|
|
66
77
|
<body>
|
|
@@ -75,20 +86,24 @@ In `./example` you'll find a working sample of a Todo App. Check it out with
|
|
|
75
86
|
|
|
76
87
|
# Documentation
|
|
77
88
|
|
|
78
|
-
- [
|
|
89
|
+
- [element][docs-element] mi-element's lifecycle
|
|
79
90
|
- [controller][docs-controller] adding controllers to mi-element to hook into the lifecycle
|
|
80
|
-
- [
|
|
91
|
+
- [signal][docs-signal] Signals and effect for reactive behavior
|
|
81
92
|
- [store][docs-store] Manage shared state in an application
|
|
93
|
+
- [context][docs-context] Implementation of the [Context Protocol][].
|
|
82
94
|
- [styling][docs-styling] Styling directives for "class" and "style"
|
|
83
95
|
|
|
84
96
|
# License
|
|
85
97
|
|
|
86
98
|
MIT licensed
|
|
87
99
|
|
|
88
|
-
[
|
|
89
|
-
[Web Components]: https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks
|
|
90
|
-
[docs-lifecycle]: https://github.com/commenthol/mi-element/tree/main/packages/mi-element/docs/lifecycle.md
|
|
100
|
+
[docs-element]: https://github.com/commenthol/mi-element/tree/main/packages/mi-element/docs/element.md
|
|
91
101
|
[docs-controller]: https://github.com/commenthol/mi-element/tree/main/packages/mi-element/docs/controller.md
|
|
92
102
|
[docs-context]: https://github.com/commenthol/mi-element/tree/main/packages/mi-element/docs/context.md
|
|
103
|
+
[docs-signal]: https://github.com/commenthol/mi-element/tree/main/packages/mi-element/docs/signal.md
|
|
93
104
|
[docs-store]: https://github.com/commenthol/mi-element/tree/main/packages/mi-element/docs/store.md
|
|
94
105
|
[docs-styling]: https://github.com/commenthol/mi-element/tree/main/packages/mi-element/docs/styling.md
|
|
106
|
+
[Context Protocol]: https://github.com/webcomponents-cg/community-protocols/blob/main/proposals/context.md
|
|
107
|
+
[Web Components]: https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks
|
|
108
|
+
[TC39 JavaScript Signals standard proposal]: https://github.com/tc39/proposal-signals
|
|
109
|
+
[krausest/js-framework-benchmark]: https://github.com/krausest/js-framework-benchmark
|
package/dist/element.js
CHANGED
|
@@ -16,19 +16,22 @@ class MiElement extends HTMLElement {
|
|
|
16
16
|
super(), this.#observedAttributes(this.constructor.attributes);
|
|
17
17
|
}
|
|
18
18
|
#observedAttributes(attributes = {}) {
|
|
19
|
-
for (const [name, value] of Object.entries(attributes))
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
19
|
+
for (const [name, value] of Object.entries(attributes)) {
|
|
20
|
+
const initial = initialValueType(value);
|
|
21
|
+
this.#types.set(name, initial.type), this.#attrLc.set(name.toLowerCase(), name),
|
|
22
|
+
this.#attrLc.set(camelToKebabCase(name), name), this.#attr[name] = createSignal(initial.value),
|
|
23
|
+
Object.defineProperty(this, name, {
|
|
24
|
+
enumerable: !0,
|
|
25
|
+
get() {
|
|
26
|
+
return this.#attr[name].get();
|
|
27
|
+
},
|
|
28
|
+
set(newValue) {
|
|
29
|
+
const oldValue = this.#attr[name].get();
|
|
30
|
+
oldValue !== newValue && (this.#attr[name].set(newValue), this.#changedAttr[name] = oldValue,
|
|
31
|
+
this.requestUpdate());
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
}
|
|
32
35
|
}
|
|
33
36
|
#getName(name) {
|
|
34
37
|
return this.#attrLc.get(name) || name;
|
|
@@ -54,7 +57,7 @@ class MiElement extends HTMLElement {
|
|
|
54
57
|
const attr = this.#getName(name);
|
|
55
58
|
if (!(attr in this.#attr)) return;
|
|
56
59
|
const type = this.#getType(attr);
|
|
57
|
-
'Boolean' === type ? !0 === newValue || '' === newValue ? super.setAttribute(name, '') : super.removeAttribute(name) : [ 'String', 'Number' ].includes(type) || !0 === newValue ? super.setAttribute(name, newValue) : (this.#changedAttr[attr] = this[attr],
|
|
60
|
+
'Boolean' === type ? !0 === newValue || '' === newValue ? super.setAttribute(name, '') : super.removeAttribute(name) : [ 'String', 'Number' ].includes(type ?? '') || !0 === newValue ? super.setAttribute(name, newValue) : (this.#changedAttr[attr] = this[attr],
|
|
58
61
|
this[attr] = newValue, this.requestUpdate());
|
|
59
62
|
}
|
|
60
63
|
shouldUpdate(_changedAttributes) {
|
|
@@ -66,7 +69,7 @@ class MiElement extends HTMLElement {
|
|
|
66
69
|
}));
|
|
67
70
|
}
|
|
68
71
|
addTemplate(template) {
|
|
69
|
-
template instanceof HTMLTemplateElement
|
|
72
|
+
template instanceof HTMLTemplateElement ? this.renderRoot.appendChild(template.content.cloneNode(!0)) : console.warn('template is not a HTMLTemplateElement');
|
|
70
73
|
}
|
|
71
74
|
render() {}
|
|
72
75
|
update(_changedAttributes) {}
|
|
@@ -99,7 +102,33 @@ const define = (name, element, options) => {
|
|
|
99
102
|
if ('string' != typeof element.template) return;
|
|
100
103
|
const el = document.createElement('template');
|
|
101
104
|
el.innerHTML = element.template, element.template = el;
|
|
102
|
-
},
|
|
105
|
+
}, initialValueType = value => {
|
|
106
|
+
switch (value) {
|
|
107
|
+
case Boolean:
|
|
108
|
+
return {
|
|
109
|
+
value: void 0,
|
|
110
|
+
type: 'Boolean'
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
case Number:
|
|
114
|
+
return {
|
|
115
|
+
value: void 0,
|
|
116
|
+
type: 'Number'
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
case String:
|
|
120
|
+
return {
|
|
121
|
+
value: void 0,
|
|
122
|
+
type: 'String'
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
default:
|
|
126
|
+
return {
|
|
127
|
+
value: value,
|
|
128
|
+
type: toString.call(value).slice(8, -1)
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
}, convertType = (any, type) => {
|
|
103
132
|
switch (type) {
|
|
104
133
|
case 'Number':
|
|
105
134
|
return (any => {
|
package/dist/escape.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
class UnsafeHtml extends String {}
|
|
2
|
+
|
|
3
|
+
const unsafeHtml = str => new UnsafeHtml(str), escMap = {
|
|
2
4
|
'&': '&',
|
|
3
5
|
'<': '<',
|
|
4
6
|
'>': '>',
|
|
5
7
|
"'": ''',
|
|
6
8
|
'"': '"'
|
|
7
|
-
}, escHtml = string => ('' + string).replace(/&/g, '&').replace(/[&<>'"]/g, (tag => escMap[tag])),
|
|
9
|
+
}, escHtml = string => string instanceof UnsafeHtml ? string : ('' + string).replace(/&/g, '&').replace(/[&<>'"]/g, (tag => escMap[tag])), esc = (strings, ...vars) => String.raw({
|
|
10
|
+
raw: strings
|
|
11
|
+
}, ...vars.map(escHtml));
|
|
8
12
|
|
|
9
|
-
export { esc,
|
|
13
|
+
export { esc, escHtml, unsafeHtml };
|
package/dist/index.js
CHANGED
|
@@ -2,11 +2,11 @@ export { ContextConsumer, ContextProvider, ContextRequestEvent } from './context
|
|
|
2
2
|
|
|
3
3
|
export { MiElement, convertType, define } from './element.js';
|
|
4
4
|
|
|
5
|
-
export { esc,
|
|
5
|
+
export { esc, escHtml, unsafeHtml } from './escape.js';
|
|
6
6
|
|
|
7
7
|
export { refsById, refsBySelector } from './refs.js';
|
|
8
8
|
|
|
9
|
-
export { default as Signal } from './signal.js';
|
|
9
|
+
export { Computed, default as Signal, State, createSignal, effect } from './signal.js';
|
|
10
10
|
|
|
11
11
|
export { Store } from './store.js';
|
|
12
12
|
|
package/dist/signal.js
CHANGED
|
@@ -1,62 +1,53 @@
|
|
|
1
1
|
const context = [];
|
|
2
2
|
|
|
3
|
-
class State {
|
|
4
|
-
|
|
3
|
+
class State extends EventTarget {
|
|
4
|
+
#value;
|
|
5
|
+
#equals;
|
|
5
6
|
constructor(value, options) {
|
|
7
|
+
super();
|
|
6
8
|
const {equals: equals} = options || {};
|
|
7
|
-
this
|
|
9
|
+
this.#value = value, this.#equals = equals ?? ((value, nextValue) => value === nextValue);
|
|
8
10
|
}
|
|
9
11
|
get() {
|
|
10
12
|
const running = context[context.length - 1];
|
|
11
|
-
return running &&
|
|
12
|
-
this.value;
|
|
13
|
+
return running && running.add(this), this.#value;
|
|
13
14
|
}
|
|
14
15
|
set(nextValue) {
|
|
15
|
-
|
|
16
|
-
this.value = nextValue;
|
|
17
|
-
for (const running of [ ...this.subscribers ]) running.execute();
|
|
18
|
-
}
|
|
16
|
+
this.#equals(this.#value, nextValue) || (this.#value = nextValue, this.dispatchEvent(new CustomEvent('signal')));
|
|
19
17
|
}
|
|
20
18
|
}
|
|
21
19
|
|
|
22
|
-
const createSignal =
|
|
23
|
-
|
|
24
|
-
function cleanup(running) {
|
|
25
|
-
for (const dep of running.dependencies) dep.delete(running);
|
|
26
|
-
running.dependencies.clear();
|
|
27
|
-
}
|
|
20
|
+
const createSignal = (initialValue, options) => initialValue instanceof State ? initialValue : new State(initialValue, options);
|
|
28
21
|
|
|
29
22
|
function effect(cb) {
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
};
|
|
41
|
-
return execute(), () => {
|
|
42
|
-
cleanup(running);
|
|
23
|
+
const running = new Set;
|
|
24
|
+
context.push(running);
|
|
25
|
+
try {
|
|
26
|
+
cb();
|
|
27
|
+
} finally {
|
|
28
|
+
context.pop();
|
|
29
|
+
}
|
|
30
|
+
for (const dep of running) dep.addEventListener('signal', cb);
|
|
31
|
+
return () => {
|
|
32
|
+
for (const dep of running) dep.removeEventListener('signal', cb);
|
|
43
33
|
};
|
|
44
34
|
}
|
|
45
35
|
|
|
46
36
|
class Computed {
|
|
37
|
+
#state;
|
|
47
38
|
constructor(cb) {
|
|
48
|
-
this
|
|
39
|
+
this.#state = new State, effect((() => this.#state.set(cb())));
|
|
49
40
|
}
|
|
50
41
|
get() {
|
|
51
|
-
return this
|
|
42
|
+
return this.#state.get();
|
|
52
43
|
}
|
|
53
44
|
}
|
|
54
45
|
|
|
55
46
|
var signal = {
|
|
56
47
|
State: State,
|
|
48
|
+
Computed: Computed,
|
|
57
49
|
createSignal: createSignal,
|
|
58
|
-
effect: effect
|
|
59
|
-
Computed: Computed
|
|
50
|
+
effect: effect
|
|
60
51
|
};
|
|
61
52
|
|
|
62
53
|
export { Computed, State, createSignal, signal as default, effect };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mi-element",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
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",
|
|
@@ -75,32 +75,34 @@
|
|
|
75
75
|
"types"
|
|
76
76
|
],
|
|
77
77
|
"devDependencies": {
|
|
78
|
-
"@eslint/js": "^9.
|
|
78
|
+
"@eslint/js": "^9.32.0",
|
|
79
79
|
"@rollup/plugin-terser": "^0.4.4",
|
|
80
|
-
"@testing-library/dom": "^10.4.
|
|
81
|
-
"@types/node": "^22.
|
|
82
|
-
"@vitest/browser": "^2.
|
|
83
|
-
"@vitest/coverage-istanbul": "^2.
|
|
84
|
-
"eslint": "^9.
|
|
85
|
-
"globals": "^15.
|
|
86
|
-
"npm-run-all2": "^6.2.
|
|
87
|
-
"playwright": "^1.
|
|
88
|
-
"prettier": "^3.
|
|
80
|
+
"@testing-library/dom": "^10.4.1",
|
|
81
|
+
"@types/node": "^22.17.0",
|
|
82
|
+
"@vitest/browser": "^2.1.9",
|
|
83
|
+
"@vitest/coverage-istanbul": "^2.1.9",
|
|
84
|
+
"eslint": "^9.32.0",
|
|
85
|
+
"globals": "^15.15.0",
|
|
86
|
+
"npm-run-all2": "^6.2.6",
|
|
87
|
+
"playwright": "^1.54.2",
|
|
88
|
+
"prettier": "^3.6.2",
|
|
89
89
|
"rimraf": "^6.0.1",
|
|
90
|
-
"rollup": "^4.
|
|
91
|
-
"typescript": "^5.
|
|
92
|
-
"vite": "^5.4.
|
|
93
|
-
"vitest": "^2.
|
|
90
|
+
"rollup": "^4.46.2",
|
|
91
|
+
"typescript": "^5.9.2",
|
|
92
|
+
"vite": "^5.4.19",
|
|
93
|
+
"vitest": "^2.1.9"
|
|
94
94
|
},
|
|
95
95
|
"scripts": {
|
|
96
96
|
"all": "npm-run-all pretty lint test build types",
|
|
97
97
|
"build": "rimraf dist && rollup -c && gzip -k dist/index.min.js && ls -al dist && rimraf dist/index.min.*",
|
|
98
|
+
"docs": "cd docs; for f in *.md; do markedpp -s -i $f; done",
|
|
98
99
|
"example": "vite --open /example/",
|
|
99
100
|
"lint": "eslint",
|
|
100
101
|
"pretty": "prettier -w **/*.js",
|
|
101
102
|
"test": "vitest run --coverage",
|
|
102
103
|
"test:browser": "vitest --coverage",
|
|
103
104
|
"dev": "npm run test:browser",
|
|
104
|
-
"types": "tsc"
|
|
105
|
+
"types": "tsc",
|
|
106
|
+
"setup": "pnpm exec playwright install"
|
|
105
107
|
}
|
|
106
108
|
}
|
package/src/context.js
CHANGED
|
@@ -64,7 +64,6 @@ export class ContextProvider {
|
|
|
64
64
|
return
|
|
65
65
|
}
|
|
66
66
|
ev.stopPropagation()
|
|
67
|
-
console.debug('provider.onContextRequest', this.state)
|
|
68
67
|
let unsubscribe
|
|
69
68
|
if (ev.subscribe) {
|
|
70
69
|
unsubscribe = effect(() => {
|
|
@@ -141,7 +140,6 @@ export class ContextConsumer {
|
|
|
141
140
|
}
|
|
142
141
|
|
|
143
142
|
_callback(value, unsubscribe) {
|
|
144
|
-
console.debug('consumer.callback', { value, unsubscribe })
|
|
145
143
|
if (unsubscribe) {
|
|
146
144
|
if (!this.subscribe) {
|
|
147
145
|
// unsubscribe as we didn't ask for subscription
|
package/src/element.js
CHANGED
|
@@ -10,17 +10,18 @@ import { createSignal } from './signal.js'
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* class
|
|
13
|
+
* class extending HTMLElement to enable deferred rendering on attribute changes
|
|
14
14
|
* either via `setAttribute(name, value)` or `this[name] = value`.
|
|
15
15
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement
|
|
16
16
|
* @example
|
|
17
17
|
* ```js
|
|
18
18
|
* class Example extends MiElement {
|
|
19
|
-
* // define all observed attributes with its default value.
|
|
19
|
+
* // define all observed attributes with its default initial value.
|
|
20
|
+
* // for yet to defined numbers, boolean or strings use `Number`, `Boolean`, `String`
|
|
20
21
|
* // attributes are accessible via `this[prop]`
|
|
21
22
|
* // avoid using attributes which are HTMLElement properties e.g. className
|
|
22
23
|
* static get attributes () {
|
|
23
|
-
* return { text: 'Hi' }
|
|
24
|
+
* return { text: 'Hi', num: Number }
|
|
24
25
|
* }
|
|
25
26
|
* render() {
|
|
26
27
|
* this.renderRoot.innerHTML = `<div></div>`
|
|
@@ -43,8 +44,19 @@ import { createSignal } from './signal.js'
|
|
|
43
44
|
* ```
|
|
44
45
|
*/
|
|
45
46
|
export class MiElement extends HTMLElement {
|
|
47
|
+
/** all attributes are signals! */
|
|
46
48
|
#attr = {}
|
|
49
|
+
/**
|
|
50
|
+
* lower-cased or kebab-case attribute names;
|
|
51
|
+
* Map<lower-cased and kebab-cased attr name, camelCased attr name as string>
|
|
52
|
+
* @type {Map<string, string>}
|
|
53
|
+
*/
|
|
47
54
|
#attrLc = new Map()
|
|
55
|
+
/**
|
|
56
|
+
* initial types (from `static get attributes() { return {} }`)
|
|
57
|
+
* Map<camelCased attribute name, type as string>
|
|
58
|
+
* @type {Map<string,string>}
|
|
59
|
+
*/
|
|
48
60
|
#types = new Map()
|
|
49
61
|
#disposers = new Set()
|
|
50
62
|
#controllers = new Set()
|
|
@@ -68,17 +80,17 @@ export class MiElement extends HTMLElement {
|
|
|
68
80
|
*/
|
|
69
81
|
#observedAttributes(attributes = {}) {
|
|
70
82
|
for (const [name, value] of Object.entries(attributes)) {
|
|
71
|
-
|
|
83
|
+
const initial = initialValueType(value)
|
|
84
|
+
this.#types.set(name, initial.type)
|
|
72
85
|
this.#attrLc.set(name.toLowerCase(), name)
|
|
73
86
|
this.#attrLc.set(camelToKebabCase(name), name)
|
|
74
|
-
this.#attr[name] = createSignal(value)
|
|
87
|
+
this.#attr[name] = createSignal(initial.value)
|
|
75
88
|
Object.defineProperty(this, name, {
|
|
76
89
|
enumerable: true,
|
|
77
90
|
get() {
|
|
78
91
|
return this.#attr[name].get()
|
|
79
92
|
},
|
|
80
93
|
set(newValue) {
|
|
81
|
-
console.debug('%s.%s =', this.nodeName, name, newValue)
|
|
82
94
|
const oldValue = this.#attr[name].get()
|
|
83
95
|
if (oldValue === newValue) return
|
|
84
96
|
this.#attr[name].set(newValue)
|
|
@@ -146,13 +158,6 @@ export class MiElement extends HTMLElement {
|
|
|
146
158
|
if (type === 'Boolean' && newValue === 'false') {
|
|
147
159
|
this.removeAttribute(name)
|
|
148
160
|
}
|
|
149
|
-
console.debug(
|
|
150
|
-
'%s.attributeChangedCallback("%s",',
|
|
151
|
-
this.nodeName,
|
|
152
|
-
name,
|
|
153
|
-
oldValue,
|
|
154
|
-
newValue
|
|
155
|
-
)
|
|
156
161
|
this.requestUpdate()
|
|
157
162
|
}
|
|
158
163
|
|
|
@@ -169,7 +174,6 @@ export class MiElement extends HTMLElement {
|
|
|
169
174
|
return
|
|
170
175
|
}
|
|
171
176
|
const type = this.#getType(attr)
|
|
172
|
-
console.debug('%s.setAttribute("%s",', this.nodeName, name, newValue)
|
|
173
177
|
|
|
174
178
|
// only set string values in these cases
|
|
175
179
|
if (type === 'Boolean') {
|
|
@@ -178,7 +182,7 @@ export class MiElement extends HTMLElement {
|
|
|
178
182
|
} else {
|
|
179
183
|
super.removeAttribute(name)
|
|
180
184
|
}
|
|
181
|
-
} else if (['String', 'Number'].includes(type) || newValue === true) {
|
|
185
|
+
} else if (['String', 'Number'].includes(type ?? '') || newValue === true) {
|
|
182
186
|
super.setAttribute(name, newValue)
|
|
183
187
|
} else {
|
|
184
188
|
this.#changedAttr[attr] = this[attr]
|
|
@@ -216,7 +220,7 @@ export class MiElement extends HTMLElement {
|
|
|
216
220
|
*/
|
|
217
221
|
addTemplate(template) {
|
|
218
222
|
if (!(template instanceof HTMLTemplateElement)) {
|
|
219
|
-
console.
|
|
223
|
+
console.warn('template is not a HTMLTemplateElement')
|
|
220
224
|
return
|
|
221
225
|
}
|
|
222
226
|
this.renderRoot.appendChild(template.content.cloneNode(true))
|
|
@@ -327,7 +331,18 @@ const renderTemplate = (element) => {
|
|
|
327
331
|
element.template = el
|
|
328
332
|
}
|
|
329
333
|
|
|
330
|
-
const
|
|
334
|
+
const initialValueType = (value) => {
|
|
335
|
+
switch (value) {
|
|
336
|
+
case Boolean:
|
|
337
|
+
return { value: undefined, type: 'Boolean' }
|
|
338
|
+
case Number:
|
|
339
|
+
return { value: undefined, type: 'Number' }
|
|
340
|
+
case String:
|
|
341
|
+
return { value: undefined, type: 'String' }
|
|
342
|
+
default:
|
|
343
|
+
return { value, type: toString.call(value).slice(8, -1) }
|
|
344
|
+
}
|
|
345
|
+
}
|
|
331
346
|
|
|
332
347
|
const toNumber = (any) => {
|
|
333
348
|
const n = Number(any)
|
package/src/escape.js
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
class UnsafeHtml extends String {}
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* tag a string as html for not to be escaped
|
|
5
|
+
* @param {string} str
|
|
6
|
+
* @returns {string}
|
|
7
|
+
*/
|
|
8
|
+
// @ts-expect-error
|
|
9
|
+
export const unsafeHtml = (str) => new UnsafeHtml(str)
|
|
10
|
+
|
|
1
11
|
const escMap = {
|
|
2
12
|
'&': '&',
|
|
3
13
|
'<': '<',
|
|
@@ -15,18 +25,12 @@ const escMap = {
|
|
|
15
25
|
* //> <h1>"One" & 'Two' & Works</h1>
|
|
16
26
|
*/
|
|
17
27
|
export const escHtml = (string) =>
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
* @example
|
|
25
|
-
* escapeAttr("One's")
|
|
26
|
-
* //> "One's"
|
|
27
|
-
*/
|
|
28
|
-
export const escAttr = (string) =>
|
|
29
|
-
('' + string).replace(/['"]/g, (tag) => escMap[tag])
|
|
28
|
+
// @ts-expect-error
|
|
29
|
+
string instanceof UnsafeHtml
|
|
30
|
+
? string
|
|
31
|
+
: ('' + string)
|
|
32
|
+
.replace(/&/g, '&')
|
|
33
|
+
.replace(/[&<>'"]/g, (tag) => escMap[tag])
|
|
30
34
|
|
|
31
35
|
/**
|
|
32
36
|
* template literal to HTML escape all values preventing XSS
|
|
@@ -35,4 +39,4 @@ export const escAttr = (string) =>
|
|
|
35
39
|
* @returns {string}
|
|
36
40
|
*/
|
|
37
41
|
export const esc = (strings, ...vars) =>
|
|
38
|
-
|
|
42
|
+
String.raw({ raw: strings }, ...vars.map(escHtml))
|
package/src/index.js
CHANGED
|
@@ -10,14 +10,19 @@ export {
|
|
|
10
10
|
* @typedef {import('./element.js').HostController} HostController
|
|
11
11
|
*/
|
|
12
12
|
export { MiElement, convertType, define } from './element.js'
|
|
13
|
-
export {
|
|
13
|
+
export { unsafeHtml, esc, escHtml } from './escape.js'
|
|
14
14
|
export { refsById, refsBySelector } from './refs.js'
|
|
15
15
|
/**
|
|
16
16
|
* @template T
|
|
17
17
|
* @typedef {import('./signal.js').SignalOptions<T>} SignalOptions<T>
|
|
18
18
|
*/
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
export {
|
|
20
|
+
default as Signal,
|
|
21
|
+
State,
|
|
22
|
+
createSignal,
|
|
23
|
+
effect,
|
|
24
|
+
Computed
|
|
25
|
+
} from './signal.js'
|
|
21
26
|
/**
|
|
22
27
|
* @typedef {import('./store.js').Action} Action
|
|
23
28
|
*/
|
package/src/signal.js
CHANGED
|
@@ -1,27 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tries to follow proposal JavaScript Signals standard proposal which is in
|
|
3
|
+
* stage 1
|
|
4
|
+
* @see https://github.com/tc39/proposal-signals
|
|
5
|
+
* @credits https://github.com/jsebrech/tiny-signals
|
|
6
|
+
*/
|
|
7
|
+
|
|
1
8
|
// global context for nested reactivity
|
|
2
9
|
const context = []
|
|
3
10
|
|
|
4
11
|
/**
|
|
5
12
|
* @template T
|
|
6
|
-
* @typedef {
|
|
13
|
+
* @typedef {(value?: T|null, nextValue?: T|null) => boolean} EqualsFn
|
|
14
|
+
* Custom comparison function between old and new value
|
|
15
|
+
* default `(value, nextValue) => value === nextValue`
|
|
7
16
|
*/
|
|
8
17
|
|
|
9
18
|
/**
|
|
10
|
-
* tries to follow proposal (a bit)
|
|
11
|
-
* @see https://github.com/tc39/proposal-signals
|
|
12
19
|
* @template T
|
|
20
|
+
* @typedef {{equals: EqualsFn<T>}} SignalOptions
|
|
13
21
|
*/
|
|
14
|
-
|
|
15
|
-
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* read- write signal
|
|
25
|
+
* @template T
|
|
26
|
+
*/
|
|
27
|
+
export class State extends EventTarget {
|
|
28
|
+
#value
|
|
29
|
+
#equals
|
|
16
30
|
|
|
17
31
|
/**
|
|
18
32
|
* @param {T|null} [value]
|
|
19
33
|
* @param {SignalOptions<T>} [options]
|
|
20
34
|
*/
|
|
21
35
|
constructor(value, options) {
|
|
36
|
+
super()
|
|
22
37
|
const { equals } = options || {}
|
|
23
|
-
this
|
|
24
|
-
this
|
|
38
|
+
this.#value = value
|
|
39
|
+
this.#equals = equals ?? ((value, nextValue) => value === nextValue)
|
|
25
40
|
}
|
|
26
41
|
|
|
27
42
|
/**
|
|
@@ -30,73 +45,72 @@ export class State {
|
|
|
30
45
|
get() {
|
|
31
46
|
const running = context[context.length - 1]
|
|
32
47
|
if (running) {
|
|
33
|
-
|
|
34
|
-
running.dependencies.add(this.subscribers)
|
|
48
|
+
running.add(this)
|
|
35
49
|
}
|
|
36
|
-
return this
|
|
50
|
+
return this.#value
|
|
37
51
|
}
|
|
38
52
|
|
|
39
53
|
/**
|
|
40
54
|
* @param {T|null|undefined} nextValue
|
|
41
55
|
*/
|
|
42
56
|
set(nextValue) {
|
|
43
|
-
if (this
|
|
57
|
+
if (this.#equals(this.#value, nextValue)) {
|
|
44
58
|
return
|
|
45
59
|
}
|
|
46
|
-
this
|
|
47
|
-
|
|
48
|
-
// run the effect()
|
|
49
|
-
running.execute()
|
|
50
|
-
}
|
|
60
|
+
this.#value = nextValue
|
|
61
|
+
this.dispatchEvent(new CustomEvent('signal'))
|
|
51
62
|
}
|
|
52
63
|
}
|
|
53
64
|
|
|
54
65
|
/**
|
|
55
66
|
* @template T
|
|
56
|
-
* @param {T}
|
|
67
|
+
* @param {T} initialValue
|
|
68
|
+
* @param {SignalOptions<T>} [options]
|
|
57
69
|
* @returns {State<T>}
|
|
58
70
|
*/
|
|
59
|
-
export const createSignal = (
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
// delete all dependent subscribers from state
|
|
64
|
-
for (const dep of running.dependencies) {
|
|
65
|
-
dep.delete(running)
|
|
66
|
-
}
|
|
67
|
-
running.dependencies.clear()
|
|
68
|
-
}
|
|
71
|
+
export const createSignal = (initialValue, options) =>
|
|
72
|
+
initialValue instanceof State
|
|
73
|
+
? initialValue
|
|
74
|
+
: new State(initialValue, options)
|
|
69
75
|
|
|
70
76
|
/**
|
|
71
|
-
*
|
|
77
|
+
* effect subscribes to state at first run only. Do not hide a signal.get()
|
|
78
|
+
* inside conditionals!
|
|
79
|
+
* @param {() => void|Promise<void>} cb
|
|
72
80
|
*/
|
|
73
81
|
export function effect(cb) {
|
|
74
|
-
const
|
|
75
|
-
cleanup(running)
|
|
76
|
-
context.push(running)
|
|
77
|
-
try {
|
|
78
|
-
cb()
|
|
79
|
-
} finally {
|
|
80
|
-
context.pop()
|
|
81
|
-
}
|
|
82
|
-
}
|
|
82
|
+
const running = new Set()
|
|
83
83
|
|
|
84
|
-
|
|
85
|
-
|
|
84
|
+
context.push(running)
|
|
85
|
+
try {
|
|
86
|
+
cb()
|
|
87
|
+
} finally {
|
|
88
|
+
context.pop()
|
|
89
|
+
}
|
|
90
|
+
for (const dep of running) {
|
|
91
|
+
dep.addEventListener('signal', cb)
|
|
92
|
+
}
|
|
86
93
|
|
|
87
94
|
return () => {
|
|
88
95
|
// unsubscribe from all dependencies
|
|
89
|
-
|
|
96
|
+
for (const dep of running) {
|
|
97
|
+
dep.removeEventListener('signal', cb)
|
|
98
|
+
}
|
|
90
99
|
}
|
|
91
100
|
}
|
|
92
101
|
|
|
102
|
+
/**
|
|
103
|
+
* @template T
|
|
104
|
+
*/
|
|
93
105
|
export class Computed {
|
|
106
|
+
#state
|
|
107
|
+
|
|
94
108
|
/**
|
|
95
|
-
* @param {() =>
|
|
109
|
+
* @param {() => T} cb
|
|
96
110
|
*/
|
|
97
111
|
constructor(cb) {
|
|
98
|
-
this
|
|
99
|
-
effect(() => this
|
|
112
|
+
this.#state = new State()
|
|
113
|
+
effect(() => this.#state.set(cb()))
|
|
100
114
|
}
|
|
101
115
|
|
|
102
116
|
/**
|
|
@@ -104,8 +118,8 @@ export class Computed {
|
|
|
104
118
|
* @returns {T}
|
|
105
119
|
*/
|
|
106
120
|
get() {
|
|
107
|
-
return this
|
|
121
|
+
return this.#state.get()
|
|
108
122
|
}
|
|
109
123
|
}
|
|
110
124
|
|
|
111
|
-
export default { State, createSignal, effect
|
|
125
|
+
export default { State, Computed, createSignal, effect }
|
package/src/styling.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { camelToKebabCase } from './case.js'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Construct className based on
|
|
4
|
+
* Construct className based on true-ish values of map
|
|
5
5
|
* @param {{[name: string]: string | boolean | number}} map
|
|
6
6
|
* @returns {string}
|
|
7
7
|
*/
|
|
@@ -31,3 +31,45 @@ export const styleMap = (map, options) => {
|
|
|
31
31
|
}
|
|
32
32
|
return acc.join(';')
|
|
33
33
|
}
|
|
34
|
+
|
|
35
|
+
// ----
|
|
36
|
+
|
|
37
|
+
let globalSheets = null
|
|
38
|
+
/**
|
|
39
|
+
* obtain and cache global stylesheets
|
|
40
|
+
* @returns {CSSStyleSheet[]}
|
|
41
|
+
*/
|
|
42
|
+
function getGlobalStyleSheets() {
|
|
43
|
+
if (globalSheets === null) {
|
|
44
|
+
globalSheets = Array.from(document.styleSheets).map(({ cssRules }) => {
|
|
45
|
+
const sheet = new CSSStyleSheet()
|
|
46
|
+
const css = Array.from(cssRules)
|
|
47
|
+
.map((rule) => rule.cssText)
|
|
48
|
+
.join(' ')
|
|
49
|
+
sheet.replaceSync(css)
|
|
50
|
+
return sheet
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
return globalSheets
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* apply global style sheets to shadowRoot
|
|
58
|
+
* @param {ShadowRoot} renderRoot
|
|
59
|
+
* @example
|
|
60
|
+
* class MyComponent extends MiElement {
|
|
61
|
+
* render() {
|
|
62
|
+
* addGlobalStyles(this.renderRoot)
|
|
63
|
+
* }
|
|
64
|
+
* }
|
|
65
|
+
*/
|
|
66
|
+
export function addGlobalStyles(renderRoot) {
|
|
67
|
+
renderRoot.adoptedStyleSheets.push(...getGlobalStyleSheets())
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Helper literal to show css styles in JS e.g. with
|
|
72
|
+
* https://marketplace.visualstudio.com/items?itemName=Tobermory.es6-string-html
|
|
73
|
+
*/
|
|
74
|
+
export const css = (strings, ...values) =>
|
|
75
|
+
String.raw({ raw: strings }, ...values)
|
package/types/context.d.ts
CHANGED
|
@@ -33,7 +33,7 @@ export class ContextRequestEvent extends Event {
|
|
|
33
33
|
* @param {(value: any, unsubscribe?: () => void) => void} callback
|
|
34
34
|
* @param {boolean} [subscribe=false] subscribe to value changes
|
|
35
35
|
*/
|
|
36
|
-
constructor(context: Context, callback: (value: any, unsubscribe?: () => void) => void, subscribe?: boolean
|
|
36
|
+
constructor(context: Context, callback: (value: any, unsubscribe?: () => void) => void, subscribe?: boolean);
|
|
37
37
|
context: Context;
|
|
38
38
|
callback: (value: any, unsubscribe?: () => void) => void;
|
|
39
39
|
subscribe: boolean | undefined;
|
|
@@ -52,7 +52,7 @@ export class ContextConsumer implements HostController {
|
|
|
52
52
|
constructor(host: HTMLElement, context: Context, options?: {
|
|
53
53
|
subscribe?: boolean | undefined;
|
|
54
54
|
validate?: ((any: any) => boolean) | undefined;
|
|
55
|
-
}
|
|
55
|
+
});
|
|
56
56
|
host: HTMLElement;
|
|
57
57
|
context: Context;
|
|
58
58
|
subscribe: boolean;
|
package/types/element.d.ts
CHANGED
|
@@ -6,17 +6,18 @@
|
|
|
6
6
|
* removed from the DOM, usually with disconnectedCallback()
|
|
7
7
|
*/
|
|
8
8
|
/**
|
|
9
|
-
* class
|
|
9
|
+
* class extending HTMLElement to enable deferred rendering on attribute changes
|
|
10
10
|
* either via `setAttribute(name, value)` or `this[name] = value`.
|
|
11
11
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement
|
|
12
12
|
* @example
|
|
13
13
|
* ```js
|
|
14
14
|
* class Example extends MiElement {
|
|
15
|
-
* // define all observed attributes with its default value.
|
|
15
|
+
* // define all observed attributes with its default initial value.
|
|
16
|
+
* // for yet to defined numbers, boolean or strings use `Number`, `Boolean`, `String`
|
|
16
17
|
* // attributes are accessible via `this[prop]`
|
|
17
18
|
* // avoid using attributes which are HTMLElement properties e.g. className
|
|
18
19
|
* static get attributes () {
|
|
19
|
-
* return { text: 'Hi' }
|
|
20
|
+
* return { text: 'Hi', num: Number }
|
|
20
21
|
* }
|
|
21
22
|
* render() {
|
|
22
23
|
* this.renderRoot.innerHTML = `<div></div>`
|
|
@@ -75,7 +76,7 @@ export class MiElement extends HTMLElement {
|
|
|
75
76
|
* @param {Record<string,any>} [_changedAttributes] previous values of changed attributes
|
|
76
77
|
* @returns {boolean}
|
|
77
78
|
*/
|
|
78
|
-
shouldUpdate(_changedAttributes?: Record<string, any>
|
|
79
|
+
shouldUpdate(_changedAttributes?: Record<string, any>): boolean;
|
|
79
80
|
/**
|
|
80
81
|
* request rendering
|
|
81
82
|
*/
|
|
@@ -94,7 +95,7 @@ export class MiElement extends HTMLElement {
|
|
|
94
95
|
* @param {Record<string,any>} [_changedAttributes] previous values of changed
|
|
95
96
|
* attributes
|
|
96
97
|
*/
|
|
97
|
-
update(_changedAttributes?: Record<string, any>
|
|
98
|
+
update(_changedAttributes?: Record<string, any>): void;
|
|
98
99
|
/**
|
|
99
100
|
* Adds listener function for eventName. listener is removed before component
|
|
100
101
|
* disconnects
|
|
@@ -102,7 +103,7 @@ export class MiElement extends HTMLElement {
|
|
|
102
103
|
* @param {EventListenerOrEventListenerObject} listener
|
|
103
104
|
* @param {Node|Document|Window} [node=this]
|
|
104
105
|
*/
|
|
105
|
-
on(eventName: string, listener: EventListenerOrEventListenerObject, node?: Node | Document | Window
|
|
106
|
+
on(eventName: string, listener: EventListenerOrEventListenerObject, node?: Node | Document | Window): void;
|
|
106
107
|
/**
|
|
107
108
|
* Adds one-time listener function for eventName. The next time eventName is
|
|
108
109
|
* triggered, this listener is removed and then invoked.
|
package/types/escape.d.ts
CHANGED
package/types/index.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
export { Signal };
|
|
2
1
|
export { Store } from "./store.js";
|
|
3
2
|
export type Context = import("./context.js").Context;
|
|
4
3
|
export type HostController = import("./element.js").HostController;
|
|
@@ -7,9 +6,9 @@ export type HostController = import("./element.js").HostController;
|
|
|
7
6
|
*/
|
|
8
7
|
export type SignalOptions<T> = import("./signal.js").SignalOptions<T>;
|
|
9
8
|
export type Action = import("./store.js").Action;
|
|
10
|
-
import Signal from './signal.js';
|
|
11
9
|
export { ContextConsumer, ContextProvider, ContextRequestEvent } from "./context.js";
|
|
12
10
|
export { MiElement, convertType, define } from "./element.js";
|
|
13
|
-
export {
|
|
11
|
+
export { unsafeHtml, esc, escHtml } from "./escape.js";
|
|
14
12
|
export { refsById, refsBySelector } from "./refs.js";
|
|
13
|
+
export { default as Signal, State, createSignal, effect, Computed } from "./signal.js";
|
|
15
14
|
export { classMap, styleMap } from "./styling.js";
|
package/types/signal.d.ts
CHANGED
|
@@ -1,25 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* effect subscribes to state at first run only. Do not hide a signal.get()
|
|
3
|
+
* inside conditionals!
|
|
4
|
+
* @param {() => void|Promise<void>} cb
|
|
3
5
|
*/
|
|
4
|
-
export function effect(cb: () => void): () => void;
|
|
6
|
+
export function effect(cb: () => void | Promise<void>): () => void;
|
|
5
7
|
/**
|
|
6
8
|
* @template T
|
|
7
|
-
* @typedef {
|
|
9
|
+
* @typedef {(value?: T|null, nextValue?: T|null) => boolean} EqualsFn
|
|
10
|
+
* Custom comparison function between old and new value
|
|
11
|
+
* default `(value, nextValue) => value === nextValue`
|
|
8
12
|
*/
|
|
9
13
|
/**
|
|
10
|
-
* tries to follow proposal (a bit)
|
|
11
|
-
* @see https://github.com/tc39/proposal-signals
|
|
12
14
|
* @template T
|
|
15
|
+
* @typedef {{equals: EqualsFn<T>}} SignalOptions
|
|
13
16
|
*/
|
|
14
|
-
|
|
17
|
+
/**
|
|
18
|
+
* read- write signal
|
|
19
|
+
* @template T
|
|
20
|
+
*/
|
|
21
|
+
export class State<T> extends EventTarget {
|
|
15
22
|
/**
|
|
16
23
|
* @param {T|null} [value]
|
|
17
24
|
* @param {SignalOptions<T>} [options]
|
|
18
25
|
*/
|
|
19
|
-
constructor(value?: T | null
|
|
20
|
-
subscribers: Set<any>;
|
|
21
|
-
value: T | null | undefined;
|
|
22
|
-
equals: (value?: T | null | undefined, nextValue?: T | null | undefined) => boolean;
|
|
26
|
+
constructor(value?: T | null, options?: SignalOptions<T>);
|
|
23
27
|
/**
|
|
24
28
|
* @returns {T|null|undefined}
|
|
25
29
|
*/
|
|
@@ -28,27 +32,36 @@ export class State<T> {
|
|
|
28
32
|
* @param {T|null|undefined} nextValue
|
|
29
33
|
*/
|
|
30
34
|
set(nextValue: T | null | undefined): void;
|
|
35
|
+
#private;
|
|
31
36
|
}
|
|
32
|
-
export function createSignal<T>(
|
|
33
|
-
|
|
37
|
+
export function createSignal<T>(initialValue: T, options?: SignalOptions<T>): State<T>;
|
|
38
|
+
/**
|
|
39
|
+
* @template T
|
|
40
|
+
*/
|
|
41
|
+
export class Computed<T> {
|
|
34
42
|
/**
|
|
35
|
-
* @param {() =>
|
|
43
|
+
* @param {() => T} cb
|
|
36
44
|
*/
|
|
37
|
-
constructor(cb: () =>
|
|
38
|
-
state: State<any>;
|
|
45
|
+
constructor(cb: () => T);
|
|
39
46
|
/**
|
|
40
47
|
* @template T
|
|
41
48
|
* @returns {T}
|
|
42
49
|
*/
|
|
43
|
-
get<
|
|
50
|
+
get<T_1>(): T_1;
|
|
51
|
+
#private;
|
|
44
52
|
}
|
|
45
53
|
declare namespace _default {
|
|
46
54
|
export { State };
|
|
55
|
+
export { Computed };
|
|
47
56
|
export { createSignal };
|
|
48
57
|
export { effect };
|
|
49
|
-
export { Computed };
|
|
50
58
|
}
|
|
51
59
|
export default _default;
|
|
60
|
+
/**
|
|
61
|
+
* Custom comparison function between old and new value
|
|
62
|
+
* default `(value, nextValue) => value === nextValue`
|
|
63
|
+
*/
|
|
64
|
+
export type EqualsFn<T> = (value?: T | null, nextValue?: T | null) => boolean;
|
|
52
65
|
export type SignalOptions<T> = {
|
|
53
|
-
equals:
|
|
66
|
+
equals: EqualsFn<T>;
|
|
54
67
|
};
|
package/types/store.d.ts
CHANGED
|
@@ -53,7 +53,7 @@ export class Store<T> extends State<any> {
|
|
|
53
53
|
* const store = new Store(actions, initialValue, options)
|
|
54
54
|
* ```
|
|
55
55
|
*/
|
|
56
|
-
constructor(actions: Record<string, Action>, initialValue?: T | null
|
|
56
|
+
constructor(actions: Record<string, Action>, initialValue?: T | null, options?: SignalOptions<T>);
|
|
57
57
|
}
|
|
58
58
|
export type MiElement = import("./element.js").MiElement;
|
|
59
59
|
export type Action = (state: any, data?: any) => any;
|
package/types/styling.d.ts
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* apply global style sheets to shadowRoot
|
|
3
|
+
* @param {ShadowRoot} renderRoot
|
|
4
|
+
* @example
|
|
5
|
+
* class MyComponent extends MiElement {
|
|
6
|
+
* render() {
|
|
7
|
+
* addGlobalStyles(this.renderRoot)
|
|
8
|
+
* }
|
|
9
|
+
* }
|
|
10
|
+
*/
|
|
11
|
+
export function addGlobalStyles(renderRoot: ShadowRoot): void;
|
|
1
12
|
export function classMap(map: {
|
|
2
13
|
[name: string]: string | boolean | number;
|
|
3
14
|
}): string;
|
|
@@ -5,4 +16,5 @@ export function styleMap(map: {
|
|
|
5
16
|
[name: string]: string | number | undefined | null;
|
|
6
17
|
}, options?: {
|
|
7
18
|
unit?: string | undefined;
|
|
8
|
-
}
|
|
19
|
+
}): string;
|
|
20
|
+
export function css(strings: any, ...values: any[]): string;
|