mi-element 0.1.0 → 0.3.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 +35 -16
- package/dist/case.js +3 -0
- package/dist/context.js +11 -12
- package/dist/element.js +26 -19
- package/dist/index.js +4 -2
- package/dist/refs.js +3 -3
- package/dist/signal.js +45 -16
- package/dist/store.js +6 -24
- package/dist/styling.js +17 -0
- package/package.json +14 -3
- package/src/case.js +15 -0
- package/src/context.js +19 -17
- package/src/element.js +39 -31
- package/src/index.js +6 -3
- package/src/refs.js +2 -8
- package/src/signal.js +98 -49
- package/src/store.js +27 -48
- package/src/styling.js +33 -0
- package/types/case.d.ts +2 -0
- package/types/context.d.ts +56 -58
- package/types/element.d.ts +101 -123
- package/types/escape.d.ts +3 -3
- package/types/index.d.ts +15 -14
- package/types/refs.d.ts +4 -6
- package/types/signal.d.ts +64 -33
- package/types/store.d.ts +53 -35
- package/types/styling.d.ts +8 -0
package/README.md
CHANGED
|
@@ -2,13 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
> a lightweight alternative to write web components
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Only weights 2.3kB minified and gzipped.
|
|
6
|
+
|
|
7
|
+
mi-element provides features to build web applications through
|
|
6
8
|
[Web Components][] like:
|
|
7
9
|
|
|
10
|
+
- type coercion with setAttribute
|
|
8
11
|
- controllers to hook into the components lifecycle
|
|
9
12
|
- ContextProvider, ContextConsumer for data provisioning from outside of a
|
|
10
13
|
component
|
|
11
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][].
|
|
12
25
|
|
|
13
26
|
# Usage
|
|
14
27
|
|
|
@@ -21,7 +34,7 @@ npm i mi-element
|
|
|
21
34
|
```js
|
|
22
35
|
/** @file ./mi-counter.js */
|
|
23
36
|
|
|
24
|
-
import { MiElement, define, refsById } from 'mi-element'
|
|
37
|
+
import { MiElement, define, refsById, Signal } from 'mi-element'
|
|
25
38
|
|
|
26
39
|
// define your Component
|
|
27
40
|
class MiCounter extends MiElement {
|
|
@@ -29,13 +42,12 @@ class MiCounter extends MiElement {
|
|
|
29
42
|
<style>
|
|
30
43
|
:host { font-size: 1.25rem; }
|
|
31
44
|
</style>
|
|
32
|
-
<
|
|
33
|
-
<span id aria-label="Counter value">0</span>
|
|
45
|
+
<div id aria-label="Counter value">0</div>
|
|
34
46
|
<button id="increment" aria-label="Increment counter"> + </button>
|
|
35
47
|
`
|
|
36
48
|
|
|
37
|
-
// declare reactive attributes
|
|
38
49
|
static get attributes() {
|
|
50
|
+
// declare reactive attribute(s)
|
|
39
51
|
return { count: 0 }
|
|
40
52
|
}
|
|
41
53
|
|
|
@@ -44,13 +56,14 @@ class MiCounter extends MiElement {
|
|
|
44
56
|
// gather refs from template (here by id)
|
|
45
57
|
this.refs = refsById(this.renderRoot)
|
|
46
58
|
// apply event listeners
|
|
47
|
-
this.refs.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
+
})
|
|
54
67
|
}
|
|
55
68
|
}
|
|
56
69
|
|
|
@@ -58,7 +71,7 @@ class MiCounter extends MiElement {
|
|
|
58
71
|
define('mi-counter', MiCounter)
|
|
59
72
|
```
|
|
60
73
|
|
|
61
|
-
Now use in your HTML
|
|
74
|
+
Now use your now component in your HTML
|
|
62
75
|
|
|
63
76
|
```html
|
|
64
77
|
<body>
|
|
@@ -75,16 +88,22 @@ In `./example` you'll find a working sample of a Todo App. Check it out with
|
|
|
75
88
|
|
|
76
89
|
- [lifecycle][docs-lifecycle] mi-element's lifecycle
|
|
77
90
|
- [controller][docs-controller] adding controllers to mi-element to hook into the lifecycle
|
|
78
|
-
- [
|
|
91
|
+
- [signal][docs-signal] Signals and effect for reactive behavior
|
|
79
92
|
- [store][docs-store] Manage shared state in an application
|
|
93
|
+
- [context][docs-context] Implementation of the [Context Protocol][].
|
|
94
|
+
- [styling][docs-styling] Styling directives for "class" and "style"
|
|
80
95
|
|
|
81
96
|
# License
|
|
82
97
|
|
|
83
98
|
MIT licensed
|
|
84
99
|
|
|
85
|
-
[Context Protocol]: https://github.com/webcomponents-cg/community-protocols/blob/main/proposals/context.md
|
|
86
|
-
[Web Components]: https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks
|
|
87
100
|
[docs-lifecycle]: https://github.com/commenthol/mi-element/tree/main/packages/mi-element/docs/lifecycle.md
|
|
88
101
|
[docs-controller]: https://github.com/commenthol/mi-element/tree/main/packages/mi-element/docs/controller.md
|
|
89
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
|
|
90
104
|
[docs-store]: https://github.com/commenthol/mi-element/tree/main/packages/mi-element/docs/store.md
|
|
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/case.js
ADDED
package/dist/context.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createSignal, effect } from './signal.js';
|
|
2
2
|
|
|
3
3
|
class ContextProvider {
|
|
4
4
|
constructor(host, context, initialValue) {
|
|
5
|
-
this.host = host, this.context = context, this.state =
|
|
5
|
+
this.host = host, this.context = context, this.state = createSignal(initialValue),
|
|
6
6
|
this.host.addController?.(this);
|
|
7
7
|
}
|
|
8
8
|
hostConnected() {
|
|
@@ -11,20 +11,19 @@ class ContextProvider {
|
|
|
11
11
|
hostDisconnected() {
|
|
12
12
|
this.host.removeEventListener("context-request", this.onContextRequest);
|
|
13
13
|
}
|
|
14
|
-
set
|
|
15
|
-
this.state.
|
|
14
|
+
set(newValue) {
|
|
15
|
+
this.state.set(newValue);
|
|
16
16
|
}
|
|
17
|
-
get
|
|
18
|
-
return this.state.
|
|
19
|
-
}
|
|
20
|
-
notify() {
|
|
21
|
-
this.state.notify();
|
|
17
|
+
get() {
|
|
18
|
+
return this.state.get();
|
|
22
19
|
}
|
|
23
20
|
onContextRequest=ev => {
|
|
24
21
|
if (ev.context !== this.context) return;
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
let unsubscribe;
|
|
23
|
+
ev.stopPropagation(), ev.subscribe && (unsubscribe = effect((() => {
|
|
24
|
+
const value = this.get();
|
|
25
|
+
unsubscribe && ev.callback(value, unsubscribe);
|
|
26
|
+
}))), ev.callback(this.get(), unsubscribe);
|
|
28
27
|
};
|
|
29
28
|
}
|
|
30
29
|
|
package/dist/element.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { camelToKebabCase } from './case.js';
|
|
2
|
+
|
|
3
|
+
import { createSignal } from './signal.js';
|
|
4
|
+
|
|
1
5
|
class MiElement extends HTMLElement {
|
|
2
6
|
#attr={};
|
|
3
7
|
#attrLc=new Map;
|
|
@@ -9,19 +13,19 @@ class MiElement extends HTMLElement {
|
|
|
9
13
|
mode: 'open'
|
|
10
14
|
};
|
|
11
15
|
constructor() {
|
|
12
|
-
super(), this.#
|
|
13
|
-
...this.constructor.attributes
|
|
14
|
-
}, this.#observedAttributes();
|
|
16
|
+
super(), this.#observedAttributes(this.constructor.attributes);
|
|
15
17
|
}
|
|
16
|
-
#observedAttributes() {
|
|
17
|
-
for (const [name,
|
|
18
|
-
this.#attrLc.set(name.toLowerCase(), name),
|
|
18
|
+
#observedAttributes(attributes = {}) {
|
|
19
|
+
for (const [name, value] of Object.entries(attributes)) this.#types.set(name, initialType(value)),
|
|
20
|
+
this.#attrLc.set(name.toLowerCase(), name), this.#attrLc.set(camelToKebabCase(name), name),
|
|
21
|
+
this.#attr[name] = createSignal(value), Object.defineProperty(this, name, {
|
|
19
22
|
enumerable: !0,
|
|
20
23
|
get() {
|
|
21
|
-
return this.#attr[name];
|
|
24
|
+
return this.#attr[name].get();
|
|
22
25
|
},
|
|
23
26
|
set(newValue) {
|
|
24
|
-
|
|
27
|
+
const oldValue = this.#attr[name].get();
|
|
28
|
+
oldValue !== newValue && (this.#attr[name].set(newValue), this.#changedAttr[name] = oldValue,
|
|
25
29
|
this.requestUpdate());
|
|
26
30
|
}
|
|
27
31
|
});
|
|
@@ -41,16 +45,20 @@ class MiElement extends HTMLElement {
|
|
|
41
45
|
disconnectedCallback() {
|
|
42
46
|
this.#disposers.forEach((remover => remover())), this.#controllers.forEach((controller => controller.hostDisconnected?.()));
|
|
43
47
|
}
|
|
44
|
-
attributeChangedCallback(name,
|
|
45
|
-
const attr = this.#getName(name), type = this.#getType(attr)
|
|
46
|
-
this.#
|
|
47
|
-
this.requestUpdate();
|
|
48
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
49
|
+
const attr = this.#getName(name), type = this.#getType(attr);
|
|
50
|
+
this.#changedAttr[attr] = this[attr], this[attr] = convertType(newValue, type),
|
|
51
|
+
'Boolean' === type && 'false' === newValue && this.removeAttribute(name), this.requestUpdate();
|
|
48
52
|
}
|
|
49
53
|
setAttribute(name, newValue) {
|
|
50
54
|
const attr = this.#getName(name);
|
|
51
55
|
if (!(attr in this.#attr)) return;
|
|
52
56
|
const type = this.#getType(attr);
|
|
53
|
-
|
|
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],
|
|
58
|
+
this[attr] = newValue, this.requestUpdate());
|
|
59
|
+
}
|
|
60
|
+
shouldUpdate(_changedAttributes) {
|
|
61
|
+
return !0;
|
|
54
62
|
}
|
|
55
63
|
requestUpdate() {
|
|
56
64
|
this.isConnected && requestAnimationFrame((() => {
|
|
@@ -61,9 +69,6 @@ class MiElement extends HTMLElement {
|
|
|
61
69
|
template instanceof HTMLTemplateElement && this.renderRoot.appendChild(template.content.cloneNode(!0));
|
|
62
70
|
}
|
|
63
71
|
render() {}
|
|
64
|
-
shouldUpdate(_changedAttributes) {
|
|
65
|
-
return !0;
|
|
66
|
-
}
|
|
67
72
|
update(_changedAttributes) {}
|
|
68
73
|
on(eventName, listener, node = this) {
|
|
69
74
|
node.addEventListener(eventName, listener), this.#disposers.add((() => node.removeEventListener(eventName, listener)));
|
|
@@ -73,9 +78,11 @@ class MiElement extends HTMLElement {
|
|
|
73
78
|
once: !0
|
|
74
79
|
});
|
|
75
80
|
}
|
|
76
|
-
dispose(
|
|
77
|
-
|
|
78
|
-
|
|
81
|
+
dispose(...listeners) {
|
|
82
|
+
for (const listener of listeners) {
|
|
83
|
+
if ('function' != typeof listener) throw new TypeError('listener must be a function');
|
|
84
|
+
this.#disposers.add(listener);
|
|
85
|
+
}
|
|
79
86
|
}
|
|
80
87
|
addController(controller) {
|
|
81
88
|
this.#controllers.add(controller), this.isConnected && controller.hostConnected?.();
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,8 @@ export { esc, escAttr, escHtml } from './escape.js';
|
|
|
6
6
|
|
|
7
7
|
export { refsById, refsBySelector } from './refs.js';
|
|
8
8
|
|
|
9
|
-
export {
|
|
9
|
+
export { default as Signal } from './signal.js';
|
|
10
10
|
|
|
11
|
-
export { Store
|
|
11
|
+
export { Store } from './store.js';
|
|
12
|
+
|
|
13
|
+
export { classMap, styleMap } from './styling.js';
|
package/dist/refs.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
+
import { kebabToCamelCase } from './case.js';
|
|
2
|
+
|
|
1
3
|
function refsById(container) {
|
|
2
4
|
const nodes = container.querySelectorAll?.('[id]') || [], found = {};
|
|
3
5
|
for (const node of nodes) found[kebabToCamelCase(node.getAttribute('id') || node.nodeName.toLowerCase())] = node;
|
|
4
6
|
return found;
|
|
5
7
|
}
|
|
6
8
|
|
|
7
|
-
const kebabToCamelCase = (str = "") => str.toLowerCase().replace(/[-_]\w/g, (m => m[1].toUpperCase()));
|
|
8
|
-
|
|
9
9
|
function refsBySelector(container, selectors) {
|
|
10
10
|
const found = {};
|
|
11
11
|
for (const [name, selector] of Object.entries(selectors)) found[name] = container.querySelector?.(selector);
|
|
12
12
|
return found;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
export {
|
|
15
|
+
export { refsById, refsBySelector };
|
package/dist/signal.js
CHANGED
|
@@ -1,24 +1,53 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
const context = [];
|
|
2
|
+
|
|
3
|
+
class State extends EventTarget {
|
|
4
|
+
#value;
|
|
5
|
+
#equals;
|
|
6
|
+
constructor(value, options) {
|
|
7
|
+
super();
|
|
8
|
+
const {equals: equals} = options || {};
|
|
9
|
+
this.#value = value, this.#equals = equals ?? ((value, nextValue) => value === nextValue);
|
|
5
10
|
}
|
|
6
|
-
get
|
|
7
|
-
|
|
11
|
+
get() {
|
|
12
|
+
const running = context[context.length - 1];
|
|
13
|
+
return running && running.add(this), this.#value;
|
|
8
14
|
}
|
|
9
|
-
set
|
|
10
|
-
this
|
|
15
|
+
set(nextValue) {
|
|
16
|
+
this.#equals(this.#value, nextValue) || (this.#value = nextValue, this.dispatchEvent(new CustomEvent('signal')));
|
|
11
17
|
}
|
|
12
|
-
|
|
13
|
-
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const createSignal = (initialValue, options) => initialValue instanceof State ? initialValue : new State(initialValue, options);
|
|
21
|
+
|
|
22
|
+
function effect(cb) {
|
|
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);
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
class Computed {
|
|
37
|
+
#state;
|
|
38
|
+
constructor(cb) {
|
|
39
|
+
this.#state = new State, effect((() => this.#state.set(cb())));
|
|
14
40
|
}
|
|
15
|
-
|
|
16
|
-
return this.
|
|
17
|
-
this._subscribers.delete(callback);
|
|
18
|
-
};
|
|
41
|
+
get() {
|
|
42
|
+
return this.#state.get();
|
|
19
43
|
}
|
|
20
44
|
}
|
|
21
45
|
|
|
22
|
-
|
|
46
|
+
var signal = {
|
|
47
|
+
State: State,
|
|
48
|
+
createSignal: createSignal,
|
|
49
|
+
effect: effect,
|
|
50
|
+
Computed: Computed
|
|
51
|
+
};
|
|
23
52
|
|
|
24
|
-
export {
|
|
53
|
+
export { Computed, State, createSignal, signal as default, effect };
|
package/dist/store.js
CHANGED
|
@@ -1,28 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { State } from './signal.js';
|
|
2
2
|
|
|
3
|
-
class Store extends
|
|
4
|
-
constructor(actions, initialValue) {
|
|
5
|
-
super(initialValue);
|
|
6
|
-
for (const [action, dispatcher] of Object.entries(actions)) this[action] = data =>
|
|
7
|
-
this.value = dispatcher(data)(this.value);
|
|
8
|
-
};
|
|
3
|
+
class Store extends State {
|
|
4
|
+
constructor(actions, initialValue, options) {
|
|
5
|
+
super(initialValue, options);
|
|
6
|
+
for (const [action, dispatcher] of Object.entries(actions)) this[action] = data => this.set(dispatcher(data)(this.get()));
|
|
9
7
|
}
|
|
10
8
|
}
|
|
11
9
|
|
|
12
|
-
|
|
13
|
-
if (propOrSignal instanceof Signal || isSignalLike(propOrSignal)) return void element.dispose(store.subscribe((value => {
|
|
14
|
-
propOrSignal.value = value;
|
|
15
|
-
})));
|
|
16
|
-
const keys = Array.isArray(propOrSignal) ? propOrSignal : propOrSignal.split('.').filter(Boolean), last = keys.pop();
|
|
17
|
-
if (!last) throw TypeError('need prop');
|
|
18
|
-
let tmp = element;
|
|
19
|
-
for (const key of keys) {
|
|
20
|
-
if ('object' != typeof tmp[key]) throw new TypeError(`object expected for property "${key}"`);
|
|
21
|
-
tmp = tmp[key];
|
|
22
|
-
}
|
|
23
|
-
element.dispose(store.subscribe((value => {
|
|
24
|
-
tmp[last] = value, element.requestUpdate();
|
|
25
|
-
})));
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
export { Store, subscribeToStore };
|
|
10
|
+
export { Store };
|
package/dist/styling.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { camelToKebabCase } from './case.js';
|
|
2
|
+
|
|
3
|
+
const classMap = map => {
|
|
4
|
+
const acc = [];
|
|
5
|
+
for (const [name, value] of Object.entries(map ?? {})) value && acc.push(name);
|
|
6
|
+
return acc.join(' ');
|
|
7
|
+
}, styleMap = (map, options) => {
|
|
8
|
+
const {unit: unit = "px"} = options || {}, acc = [];
|
|
9
|
+
for (const [name, value] of Object.entries(map ?? {})) {
|
|
10
|
+
if (null == value) continue;
|
|
11
|
+
const _unit = Number.isFinite(value) ? unit : '';
|
|
12
|
+
acc.push(`${camelToKebabCase(name)}:${value}${_unit}`);
|
|
13
|
+
}
|
|
14
|
+
return acc.join(';');
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export { classMap, styleMap };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mi-element",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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",
|
|
@@ -29,6 +29,11 @@
|
|
|
29
29
|
"development": "./src/element.js",
|
|
30
30
|
"types": "./types/element.d.ts"
|
|
31
31
|
},
|
|
32
|
+
"./case": {
|
|
33
|
+
"default": "./dist/case.js",
|
|
34
|
+
"development": "./src/case.js",
|
|
35
|
+
"types": "./types/case.d.ts"
|
|
36
|
+
},
|
|
32
37
|
"./context": {
|
|
33
38
|
"default": "./dist/context.js",
|
|
34
39
|
"development": "./src/context.js",
|
|
@@ -54,6 +59,11 @@
|
|
|
54
59
|
"development": "./src/store.js",
|
|
55
60
|
"types": "./types/store.d.ts"
|
|
56
61
|
},
|
|
62
|
+
"./styling": {
|
|
63
|
+
"default": "./dist/styling.js",
|
|
64
|
+
"development": "./src/styling.js",
|
|
65
|
+
"types": "./types/styling.d.ts"
|
|
66
|
+
},
|
|
57
67
|
"./package.json": {
|
|
58
68
|
"default": "./package.json"
|
|
59
69
|
}
|
|
@@ -83,8 +93,9 @@
|
|
|
83
93
|
"vitest": "^2.0.5"
|
|
84
94
|
},
|
|
85
95
|
"scripts": {
|
|
86
|
-
"all": "npm-run-all lint test build",
|
|
87
|
-
"build": "rimraf dist && rollup -c && gzip dist/index.min.js && ls -al dist && rimraf dist/index.min.*",
|
|
96
|
+
"all": "npm-run-all pretty lint test build types",
|
|
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",
|
|
88
99
|
"example": "vite --open /example/",
|
|
89
100
|
"lint": "eslint",
|
|
90
101
|
"pretty": "prettier -w **/*.js",
|
package/src/case.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* convert lowerCamelCase to kebab-case
|
|
3
|
+
* @param {string} str
|
|
4
|
+
* @returns {string}
|
|
5
|
+
*/
|
|
6
|
+
export const camelToKebabCase = (str = '') =>
|
|
7
|
+
str.replace(/([A-Z])/g, (_, m) => `-${m.toLowerCase()}`)
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* convert kebab-case to lowerCamelCase
|
|
11
|
+
* @param {string} str
|
|
12
|
+
* @returns {string}
|
|
13
|
+
*/
|
|
14
|
+
export const kebabToCamelCase = (str = '') =>
|
|
15
|
+
str.toLowerCase().replace(/[-_]\w/g, (m) => m[1].toUpperCase())
|
package/src/context.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @see https://github.com/webcomponents-cg/community-protocols/blob/main/proposals/context.md
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { createSignal,
|
|
5
|
+
import { createSignal, effect } from './signal.js'
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* @typedef {import('./element.js').HostController} HostController
|
|
@@ -25,9 +25,7 @@ export class ContextProvider {
|
|
|
25
25
|
constructor(host, context, initialValue) {
|
|
26
26
|
this.host = host
|
|
27
27
|
this.context = context
|
|
28
|
-
this.state =
|
|
29
|
-
? initialValue
|
|
30
|
-
: createSignal(initialValue)
|
|
28
|
+
this.state = createSignal(initialValue)
|
|
31
29
|
// @ts-expect-error
|
|
32
30
|
this.host.addController?.(this)
|
|
33
31
|
}
|
|
@@ -45,24 +43,20 @@ export class ContextProvider {
|
|
|
45
43
|
/**
|
|
46
44
|
* @param {any} newValue
|
|
47
45
|
*/
|
|
48
|
-
set
|
|
49
|
-
this.state.
|
|
46
|
+
set(newValue) {
|
|
47
|
+
this.state.set(newValue)
|
|
50
48
|
}
|
|
51
49
|
|
|
52
50
|
/**
|
|
53
51
|
* @returns {any}
|
|
54
52
|
*/
|
|
55
|
-
get
|
|
56
|
-
return this.state.
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
notify() {
|
|
60
|
-
this.state.notify()
|
|
53
|
+
get() {
|
|
54
|
+
return this.state.get()
|
|
61
55
|
}
|
|
62
56
|
|
|
63
57
|
/**
|
|
64
58
|
* @private
|
|
65
|
-
* @param {ContextRequestEvent} ev
|
|
59
|
+
* @param {ContextRequestEvent} ev
|
|
66
60
|
*/
|
|
67
61
|
onContextRequest = (ev) => {
|
|
68
62
|
if (ev.context !== this.context) {
|
|
@@ -70,10 +64,17 @@ export class ContextProvider {
|
|
|
70
64
|
return
|
|
71
65
|
}
|
|
72
66
|
ev.stopPropagation()
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
67
|
+
console.debug('provider.onContextRequest', this.state)
|
|
68
|
+
let unsubscribe
|
|
69
|
+
if (ev.subscribe) {
|
|
70
|
+
unsubscribe = effect(() => {
|
|
71
|
+
// needed to subscribe to signal
|
|
72
|
+
const value = this.get()
|
|
73
|
+
// don't call callback the first time where unsubscribe is not defined
|
|
74
|
+
if (unsubscribe) ev.callback(value, unsubscribe)
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
ev.callback(this.get(), unsubscribe)
|
|
77
78
|
}
|
|
78
79
|
}
|
|
79
80
|
|
|
@@ -140,6 +141,7 @@ export class ContextConsumer {
|
|
|
140
141
|
}
|
|
141
142
|
|
|
142
143
|
_callback(value, unsubscribe) {
|
|
144
|
+
console.debug('consumer.callback', { value, unsubscribe })
|
|
143
145
|
if (unsubscribe) {
|
|
144
146
|
if (!this.subscribe) {
|
|
145
147
|
// unsubscribe as we didn't ask for subscription
|