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/README.md
CHANGED
|
@@ -24,7 +24,7 @@ setting objects or functions either through `el.setAttribute(name, value)` or
|
|
|
24
24
|
properties `el[name] = value`.
|
|
25
25
|
|
|
26
26
|
Furthermore all observed attributes have a reactive behavior through the use of
|
|
27
|
-
signals and effects (loosely) following the
|
|
27
|
+
signals and effects (loosely) following the
|
|
28
28
|
[TC39 JavaScript Signals standard proposal][].
|
|
29
29
|
|
|
30
30
|
# Usage
|
|
@@ -38,7 +38,8 @@ npm i mi-element
|
|
|
38
38
|
```js
|
|
39
39
|
/** @file ./mi-counter.js */
|
|
40
40
|
|
|
41
|
-
import { MiElement, define,
|
|
41
|
+
import { MiElement, define, Signal } from 'mi-element'
|
|
42
|
+
const { effect, createSignal } = Signal
|
|
42
43
|
|
|
43
44
|
// define your Component
|
|
44
45
|
class MiCounter extends MiElement {
|
|
@@ -46,25 +47,27 @@ class MiCounter extends MiElement {
|
|
|
46
47
|
<style>
|
|
47
48
|
:host { font-size: 1.25rem; }
|
|
48
49
|
</style>
|
|
49
|
-
<div
|
|
50
|
-
<button
|
|
50
|
+
<div aria-label="Counter value">0</div>
|
|
51
|
+
<button aria-label="Increment counter"> + </button>
|
|
51
52
|
`
|
|
52
53
|
|
|
53
|
-
static get
|
|
54
|
+
static get properties() {
|
|
54
55
|
// declare reactive attribute(s)
|
|
55
|
-
return { count: 0 }
|
|
56
|
+
return { count: { type: Number, initial: 0 } }
|
|
56
57
|
}
|
|
57
58
|
|
|
59
|
+
static createSignal = createSignal
|
|
60
|
+
|
|
58
61
|
// called by connectedCallback()
|
|
59
62
|
render() {
|
|
60
63
|
// gather refs from template (here by id)
|
|
61
|
-
this.refs =
|
|
64
|
+
this.refs = this.refsBySelector({ increment: 'button', div: 'div' })
|
|
62
65
|
// apply event listeners
|
|
63
66
|
this.refs.increment.addEventListener('click', () => {
|
|
64
67
|
// change observed and reactive attribute...
|
|
65
68
|
this.count++
|
|
66
69
|
})
|
|
67
|
-
|
|
70
|
+
effect(() => {
|
|
68
71
|
// ...triggers update on every change of `this.count`
|
|
69
72
|
this.refs.div.textContent = this.count
|
|
70
73
|
})
|
|
@@ -91,6 +94,7 @@ In `./example` you'll find a working sample of a Todo App. Check it out with
|
|
|
91
94
|
# Documentation
|
|
92
95
|
|
|
93
96
|
- [element][docs-element] mi-element's lifecycle
|
|
97
|
+
- [render][docs-render] mi-elements html template literal and render function
|
|
94
98
|
- [controller][docs-controller] adding controllers to mi-element to hook into the lifecycle
|
|
95
99
|
- [signal][docs-signal] Signals and effect for reactive behavior
|
|
96
100
|
- [store][docs-store] Manage shared state in an application
|
|
@@ -103,6 +107,7 @@ In `./example` you'll find a working sample of a Todo App. Check it out with
|
|
|
103
107
|
MIT licensed
|
|
104
108
|
|
|
105
109
|
[docs-element]: https://github.com/commenthol/mi-element/tree/main/packages/mi-element/docs/element.md
|
|
110
|
+
[docs-render]: https://github.com/commenthol/mi-element/tree/main/packages/mi-element/docs/render.md
|
|
106
111
|
[docs-controller]: https://github.com/commenthol/mi-element/tree/main/packages/mi-element/docs/controller.md
|
|
107
112
|
[docs-context]: https://github.com/commenthol/mi-element/tree/main/packages/mi-element/docs/context.md
|
|
108
113
|
[docs-signal]: https://github.com/commenthol/mi-element/tree/main/packages/mi-element/docs/signal.md
|
|
@@ -114,7 +119,6 @@ MIT licensed
|
|
|
114
119
|
[Web Components]: https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks
|
|
115
120
|
[TC39 JavaScript Signals standard proposal]: https://github.com/tc39/proposal-signals
|
|
116
121
|
[krausest/js-framework-benchmark]: https://github.com/krausest/js-framework-benchmark
|
|
117
|
-
|
|
118
122
|
[npm-badge]: https://badgen.net/npm/v/mi-element
|
|
119
123
|
[npm]: https://www.npmjs.com/package/mi-element
|
|
120
124
|
[types-badge]: https://badgen.net/npm/types/mi-element
|
package/dist/context.js
CHANGED
|
@@ -17,6 +17,12 @@ class ContextProvider {
|
|
|
17
17
|
get() {
|
|
18
18
|
return this.state.get();
|
|
19
19
|
}
|
|
20
|
+
set value(newValue) {
|
|
21
|
+
this.set(newValue);
|
|
22
|
+
}
|
|
23
|
+
get value() {
|
|
24
|
+
return this.get();
|
|
25
|
+
}
|
|
20
26
|
onContextRequest=ev => {
|
|
21
27
|
if (ev.context !== this.context) return;
|
|
22
28
|
let unsubscribe;
|
package/dist/element.js
CHANGED
|
@@ -1,86 +1,82 @@
|
|
|
1
|
-
import { camelToKebabCase } from './case.js';
|
|
2
|
-
|
|
3
1
|
import { createSignal } from 'mi-signal';
|
|
4
2
|
|
|
3
|
+
import { kebabToCamelCase, camelToKebabCase } from './case.js';
|
|
4
|
+
|
|
5
|
+
import { addGlobalStyles } from './styling.js';
|
|
6
|
+
|
|
7
|
+
import { refsBySelector } from './refs.js';
|
|
8
|
+
|
|
9
|
+
import { toNumber, toJson } from './utils.js';
|
|
10
|
+
|
|
11
|
+
const nameMap = {
|
|
12
|
+
class: 'className',
|
|
13
|
+
for: 'htmlFor'
|
|
14
|
+
};
|
|
15
|
+
|
|
5
16
|
class MiElement extends HTMLElement {
|
|
6
|
-
|
|
7
|
-
#
|
|
8
|
-
#types=new Map;
|
|
17
|
+
_props={};
|
|
18
|
+
#changedProps={};
|
|
9
19
|
#disposers=new Set;
|
|
10
20
|
#controllers=new Set;
|
|
11
|
-
#
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
static template;
|
|
17
|
-
static get attributes() {
|
|
18
|
-
return {};
|
|
21
|
+
#updateRequested=!1;
|
|
22
|
+
static get shadowRootInit() {
|
|
23
|
+
return {
|
|
24
|
+
mode: 'open'
|
|
25
|
+
};
|
|
19
26
|
}
|
|
20
|
-
static
|
|
21
|
-
|
|
27
|
+
static template;
|
|
28
|
+
static get properties() {}
|
|
29
|
+
static observedAttributes=[];
|
|
30
|
+
static styles='';
|
|
31
|
+
static get useGlobalStyles() {
|
|
32
|
+
return !1;
|
|
22
33
|
}
|
|
34
|
+
static createSignal=createSignal;
|
|
23
35
|
constructor() {
|
|
24
|
-
super()
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
for (const [name, value] of Object.entries(attributes)) {
|
|
41
|
-
const initial = initialValueType(value);
|
|
42
|
-
this.#types.set(name, initial.type), this.#attrLc.set(name.toLowerCase(), name),
|
|
43
|
-
this.#attrLc.set(camelToKebabCase(name), name), this.#observe(name, initial.value);
|
|
36
|
+
super();
|
|
37
|
+
const {createSignal: createSignal, properties: properties} = this.constructor;
|
|
38
|
+
for (const [name, {initial: initial}] of Object.entries(properties)) {
|
|
39
|
+
const descriptor = Object.getOwnPropertyDescriptor(this.constructor.prototype, name);
|
|
40
|
+
createSignal && (this._props[name] = createSignal()), Object.defineProperty(this, name, {
|
|
41
|
+
get() {
|
|
42
|
+
return descriptor?.get ? descriptor.get.call(this) : createSignal ? this._props[name].value : this._props[name];
|
|
43
|
+
},
|
|
44
|
+
set(value) {
|
|
45
|
+
const oldValue = this[name];
|
|
46
|
+
descriptor?.set ? descriptor.set.call(this, value) : createSignal ? this._props[name].value = value : this._props[name] = value,
|
|
47
|
+
oldValue !== this[name] && this.requestUpdate({
|
|
48
|
+
[name]: value
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}), this[name] = initial;
|
|
44
52
|
}
|
|
45
53
|
}
|
|
46
|
-
#observedProperties(properties = {}) {
|
|
47
|
-
for (const [name, value] of Object.entries(properties)) this.#attrLc.has(name) || name in this.#attr || this.#observe(name, value);
|
|
48
|
-
}
|
|
49
|
-
#getName(name) {
|
|
50
|
-
return this.#attrLc.get(name) || name;
|
|
51
|
-
}
|
|
52
|
-
#getType(name) {
|
|
53
|
-
return this.#types.get(name);
|
|
54
|
-
}
|
|
55
54
|
connectedCallback() {
|
|
56
55
|
this.#controllers.forEach(controller => controller.hostConnected?.());
|
|
57
|
-
const {
|
|
58
|
-
this.renderRoot =
|
|
59
|
-
this.addTemplate(template), this.
|
|
56
|
+
const {shadowRootInit: shadowRootInit, useGlobalStyles: useGlobalStyles, template: template} = this.constructor;
|
|
57
|
+
this.renderRoot = shadowRootInit ? this.shadowRoot ?? this.attachShadow(shadowRootInit) : this,
|
|
58
|
+
this.addTemplate(template), useGlobalStyles && addGlobalStyles(this.renderRoot),
|
|
59
|
+
this.render(), this.requestUpdate();
|
|
60
60
|
}
|
|
61
61
|
disconnectedCallback() {
|
|
62
62
|
this.#disposers.forEach(remover => remover()), this.#controllers.forEach(controller => controller.hostDisconnected?.());
|
|
63
63
|
}
|
|
64
|
-
attributeChangedCallback(name,
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
!this.#dedupe && this.isConnected && (this.#dedupe = !0, requestAnimationFrame(() => {
|
|
81
|
-
this.#dedupe = !1;
|
|
82
|
-
const _changedAttributes = this.#changedAttr;
|
|
83
|
-
this.#changedAttr = {}, this.shouldUpdate(_changedAttributes) && this.update(_changedAttributes);
|
|
64
|
+
attributeChangedCallback(name, _oldValue, newValue) {
|
|
65
|
+
const camelName = nameMap[name] ?? kebabToCamelCase(name), properties = this.constructor?.properties, {type: type} = properties?.[camelName] ?? {}, coercedValue = convertType(newValue, type);
|
|
66
|
+
if (name.startsWith('data-')) {
|
|
67
|
+
const datasetName = kebabToCamelCase(name.substring(5));
|
|
68
|
+
datasetName && (this.dataset[datasetName] = coercedValue);
|
|
69
|
+
}
|
|
70
|
+
this[camelName] = coercedValue;
|
|
71
|
+
}
|
|
72
|
+
requestUpdate(changedProps) {
|
|
73
|
+
this.#changedProps = {
|
|
74
|
+
...this.#changedProps,
|
|
75
|
+
...changedProps
|
|
76
|
+
}, !this.#updateRequested && this.renderRoot && (this.#updateRequested = !0, window.requestAnimationFrame(() => {
|
|
77
|
+
this.#updateRequested = !1;
|
|
78
|
+
const changedProps = this.#changedProps;
|
|
79
|
+
this.#changedProps = {}, this.update(changedProps);
|
|
84
80
|
}));
|
|
85
81
|
}
|
|
86
82
|
addTemplate(template) {
|
|
@@ -90,7 +86,7 @@ class MiElement extends HTMLElement {
|
|
|
90
86
|
}
|
|
91
87
|
}
|
|
92
88
|
render() {}
|
|
93
|
-
update(
|
|
89
|
+
update(_changedProps) {}
|
|
94
90
|
on(eventName, listener, node = this) {
|
|
95
91
|
node.addEventListener(eventName, listener), this.#disposers.add(() => node.removeEventListener(eventName, listener));
|
|
96
92
|
}
|
|
@@ -111,53 +107,33 @@ class MiElement extends HTMLElement {
|
|
|
111
107
|
removeController(controller) {
|
|
112
108
|
this.#controllers.delete(controller);
|
|
113
109
|
}
|
|
110
|
+
refsBySelector(selectors) {
|
|
111
|
+
return refsBySelector(this.renderRoot, selectors);
|
|
112
|
+
}
|
|
114
113
|
}
|
|
115
114
|
|
|
116
|
-
const define = (
|
|
117
|
-
|
|
118
|
-
|
|
115
|
+
const define = (tagName, elementClass, options) => {
|
|
116
|
+
if (customElements.get(tagName)) return;
|
|
117
|
+
const {usedCssPrefix: usedCssPrefix = "", cssPrefix: cssPrefix = "", styles: styles} = options || {};
|
|
118
|
+
if (elementClass.properties) {
|
|
119
|
+
const observedAttrs = [];
|
|
120
|
+
for (const [name, {attribute: attribute = !0}] of Object.entries(elementClass.properties)) attribute && observedAttrs.push(camelToKebabCase(name));
|
|
121
|
+
Object.defineProperty(elementClass, 'observedAttributes', {
|
|
122
|
+
get: () => observedAttrs
|
|
123
|
+
});
|
|
124
|
+
} else if (elementClass.observedAttributes) {
|
|
125
|
+
const properties = elementClass.observedAttributes.reduce((acc, attr) => (acc[kebabToCamelCase(attr)] = {},
|
|
126
|
+
acc), {});
|
|
127
|
+
Object.defineProperty(elementClass, 'properties', {
|
|
128
|
+
get: () => properties
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
elementClass.styles && (elementClass.styles = styles || (usedCssPrefix === cssPrefix ? elementClass.styles : elementClass.styles.replaceAll(`--${usedCssPrefix}-`, cssPrefix))),
|
|
132
|
+
renderTemplate(elementClass), window.customElements.define(tagName, elementClass);
|
|
119
133
|
}, renderTemplate = element => {
|
|
120
|
-
if (element.template instanceof HTMLTemplateElement) return;
|
|
134
|
+
if (!element.template || element.template instanceof HTMLTemplateElement) return;
|
|
121
135
|
const el = document.createElement('template');
|
|
122
|
-
el.innerHTML = element.template, element.template = el;
|
|
123
|
-
},
|
|
124
|
-
switch (value) {
|
|
125
|
-
case Boolean:
|
|
126
|
-
return {
|
|
127
|
-
value: void 0,
|
|
128
|
-
type: 'Boolean'
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
case Number:
|
|
132
|
-
return {
|
|
133
|
-
value: void 0,
|
|
134
|
-
type: 'Number'
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
case String:
|
|
138
|
-
return {
|
|
139
|
-
value: void 0,
|
|
140
|
-
type: 'String'
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
default:
|
|
144
|
-
return {
|
|
145
|
-
value: value,
|
|
146
|
-
type: toString.call(value).slice(8, -1)
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
}, convertType = (any, type) => {
|
|
150
|
-
switch (type) {
|
|
151
|
-
case 'Number':
|
|
152
|
-
return (any => {
|
|
153
|
-
const n = Number(any);
|
|
154
|
-
return isNaN(n) ? any : n;
|
|
155
|
-
})(any);
|
|
156
|
-
|
|
157
|
-
case 'Boolean':
|
|
158
|
-
return 'false' !== any && ('' === any || !!any);
|
|
159
|
-
}
|
|
160
|
-
return any;
|
|
161
|
-
};
|
|
136
|
+
el.innerHTML = element.template || '', element.template = el;
|
|
137
|
+
}, convertType = (value, type) => type === Boolean ? null !== value : type === Number ? toNumber(value) : type === Array ? toJson(value) ?? value.split(',').map(v => v.trim()) : type === Object ? toJson(value) : value;
|
|
162
138
|
|
|
163
139
|
export { MiElement, convertType, define };
|
package/dist/html.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { toJson } from './utils.js';
|
|
2
|
+
|
|
3
|
+
const globalRenderCache = new class {
|
|
4
|
+
cnt=0;
|
|
5
|
+
map=new Map;
|
|
6
|
+
cache=new WeakMap;
|
|
7
|
+
_inc() {
|
|
8
|
+
return this.cnt = 268435455 & ++this.cnt, this.cnt;
|
|
9
|
+
}
|
|
10
|
+
clear() {
|
|
11
|
+
this.cnt = 0, this.map.clear();
|
|
12
|
+
}
|
|
13
|
+
set(value) {
|
|
14
|
+
const key = '__rc:' + this._inc().toString(36), ref = {};
|
|
15
|
+
return this.map.set(key, ref), this.cache.set(ref, value), key;
|
|
16
|
+
}
|
|
17
|
+
get(key) {
|
|
18
|
+
const ref = this.map.get(key);
|
|
19
|
+
return this.map.delete(key), this.cache.get(ref);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
class UnsafeHtml extends String {}
|
|
24
|
+
|
|
25
|
+
const unsafeHtml = str => new UnsafeHtml(str), escMap = {
|
|
26
|
+
'&': '&',
|
|
27
|
+
'<': '<',
|
|
28
|
+
'>': '>',
|
|
29
|
+
'"': '"',
|
|
30
|
+
"'": '''
|
|
31
|
+
}, esc = string => string.replace(/[&<>"']/g, tag => escMap[tag]), escHtml = string => string instanceof UnsafeHtml ? string : unsafeHtml(esc('' + string)), escValue = any => {
|
|
32
|
+
if (any instanceof UnsafeHtml) return any;
|
|
33
|
+
if ([ 'object', 'function' ].includes(typeof any)) {
|
|
34
|
+
const key = globalRenderCache.set(any);
|
|
35
|
+
return unsafeHtml(key);
|
|
36
|
+
}
|
|
37
|
+
return unsafeHtml(esc('' + any));
|
|
38
|
+
}, html = (strings, ...values) => unsafeHtml(String.raw({
|
|
39
|
+
raw: strings
|
|
40
|
+
}, ...values.map(val => Array.isArray(val) ? val.map(escValue).join('') : escValue(val))));
|
|
41
|
+
|
|
42
|
+
function render(node, template, handlers = {}) {
|
|
43
|
+
const refs = {}, div = document.createElement('div');
|
|
44
|
+
div.innerHTML = template.toString();
|
|
45
|
+
for (let child of Array.from(div.children)) renderAttrs(child, handlers, refs),
|
|
46
|
+
node.appendChild(child);
|
|
47
|
+
return refs;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function renderAttrs(node, handlers = {}, refs = {}) {
|
|
51
|
+
if (node.nodeType === Node.ELEMENT_NODE) for (let attr of node.attributes) {
|
|
52
|
+
const startsWith = attr.name[0], name = attr.name.slice(1);
|
|
53
|
+
let rm = 0;
|
|
54
|
+
if ('?' === startsWith) toJson(attr.value) ? node.setAttribute(name, '') : node.removeAttribute(name),
|
|
55
|
+
rm = 1; else if ('...' === attr.name) {
|
|
56
|
+
const obj = globalRenderCache.get(attr.value);
|
|
57
|
+
if (obj && 'object' == typeof obj) for (const [k, v] of Object.entries(obj)) node[k] = v;
|
|
58
|
+
rm = 1;
|
|
59
|
+
} else if ('.' === startsWith) node[name] = globalRenderCache.get(attr.value) ?? attr.value,
|
|
60
|
+
rm = 1; else if ('@' === startsWith) {
|
|
61
|
+
const handlerName = attr.value, fn = globalRenderCache.get(handlerName);
|
|
62
|
+
fn ? node.addEventListener(name, e => fn(e)) : 'function' == typeof handlers[handlerName] && node.addEventListener(name, e => handlers[handlerName](e)),
|
|
63
|
+
rm = 1;
|
|
64
|
+
} else 'ref' === attr.name && (refs[attr.value] = node, rm = 1);
|
|
65
|
+
rm && requestAnimationFrame(() => {
|
|
66
|
+
node.removeAttribute(attr.name);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
if (!node.children?.length || customElements.get(node.localName)) return refs;
|
|
70
|
+
for (let child of Array.from(node.children)) renderAttrs(child, handlers, refs);
|
|
71
|
+
return refs;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export { escHtml, globalRenderCache, html, render, renderAttrs, unsafeHtml };
|
package/dist/index.js
CHANGED
|
@@ -2,12 +2,12 @@ export { ContextConsumer, ContextProvider, ContextRequestEvent } from './context
|
|
|
2
2
|
|
|
3
3
|
export { MiElement, convertType, define } from './element.js';
|
|
4
4
|
|
|
5
|
-
export {
|
|
5
|
+
export { escHtml, html, render, renderAttrs, unsafeHtml } from './html.js';
|
|
6
6
|
|
|
7
|
-
export {
|
|
8
|
-
|
|
9
|
-
export { Computed, default as Signal, State, createSignal, effect } from 'mi-signal';
|
|
7
|
+
export { refsBySelector } from './refs.js';
|
|
10
8
|
|
|
11
9
|
export { Store } from './store.js';
|
|
12
10
|
|
|
13
|
-
export { addGlobalStyles,
|
|
11
|
+
export { addGlobalStyles, classNames, css, styleMap } from './styling.js';
|
|
12
|
+
|
|
13
|
+
export { default as Signal } from 'mi-signal';
|
package/dist/refs.js
CHANGED
|
@@ -1,15 +1,7 @@
|
|
|
1
|
-
import { kebabToCamelCase } from './case.js';
|
|
2
|
-
|
|
3
|
-
function refsById(container) {
|
|
4
|
-
const nodes = container.querySelectorAll?.('[id]') || [], found = {};
|
|
5
|
-
for (const node of nodes) found[kebabToCamelCase(node.getAttribute('id') || node.nodeName.toLowerCase())] = node;
|
|
6
|
-
return found;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
1
|
function refsBySelector(container, selectors) {
|
|
10
2
|
const found = {};
|
|
11
3
|
for (const [name, selector] of Object.entries(selectors)) found[name] = container.querySelector?.(selector);
|
|
12
4
|
return found;
|
|
13
5
|
}
|
|
14
6
|
|
|
15
|
-
export {
|
|
7
|
+
export { refsBySelector };
|
package/dist/styling.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { camelToKebabCase } from './case.js';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
const classNames = (...args) => {
|
|
4
|
+
const classList = [];
|
|
5
|
+
return args.forEach(arg => {
|
|
6
|
+
arg && ('string' == typeof arg ? classList.push(arg) : Array.isArray(arg) ? classList.push(classNames(...arg)) : 'object' == typeof arg && Object.entries(arg).forEach(([key, value]) => {
|
|
7
|
+
value && classList.push(key);
|
|
8
|
+
}));
|
|
9
|
+
}), classList.join(' ');
|
|
7
10
|
}, styleMap = (map, options) => {
|
|
8
11
|
const {unit: unit = "px"} = options || {}, acc = [];
|
|
9
12
|
for (const [name, value] of Object.entries(map ?? {})) {
|
|
@@ -23,4 +26,8 @@ function addGlobalStyles(renderRoot) {
|
|
|
23
26
|
})), globalSheets));
|
|
24
27
|
}
|
|
25
28
|
|
|
26
|
-
|
|
29
|
+
const css = (strings, ...values) => String.raw({
|
|
30
|
+
raw: strings
|
|
31
|
+
}, ...values);
|
|
32
|
+
|
|
33
|
+
export { addGlobalStyles, classNames, css, styleMap };
|
package/dist/utils.js
ADDED
package/docs/context.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- !toc (minlevel=2) -->
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
- [ContextProvider](#contextprovider)
|
|
6
|
+
- [ContextConsumer](#contextconsumer)
|
|
7
|
+
- [Connecting consumers to providers](#connecting-consumers-to-providers)
|
|
8
8
|
|
|
9
9
|
<!-- toc! -->
|
|
10
10
|
|
|
@@ -17,29 +17,34 @@ Implements the [Context Protocol][].
|
|
|
17
17
|
## ContextProvider
|
|
18
18
|
|
|
19
19
|
```js
|
|
20
|
-
import {
|
|
21
|
-
define,
|
|
22
|
-
MiElement,
|
|
23
|
-
ContextProvider,
|
|
24
|
-
ContextConsumer,
|
|
25
|
-
} from 'mi-element'
|
|
20
|
+
import { define, MiElement, ContextProvider, ContextConsumer } from 'mi-element'
|
|
26
21
|
|
|
27
22
|
define(
|
|
28
23
|
'mi-context-provider',
|
|
29
24
|
class extends MiElement {
|
|
30
|
-
static get
|
|
25
|
+
static get properties () {
|
|
31
26
|
return {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
value: 0
|
|
27
|
+
context: {},
|
|
28
|
+
value: { type: Number }
|
|
35
29
|
}
|
|
36
30
|
}
|
|
37
31
|
|
|
32
|
+
constructor() {
|
|
33
|
+
super()
|
|
34
|
+
// define the context and set initial value
|
|
35
|
+
// default context shall be unique amongst other context providers
|
|
36
|
+
this.context = 'my-context-provider'
|
|
37
|
+
this.value = 0
|
|
38
|
+
}
|
|
39
|
+
|
|
38
40
|
render() {
|
|
39
|
-
this.value = this.initialValue
|
|
40
41
|
this.renderRoot.innerHTML = '<slot></slot>'
|
|
41
42
|
this.provider = new ContextProvider(
|
|
42
|
-
this,
|
|
43
|
+
this,
|
|
44
|
+
this.context,
|
|
45
|
+
this._providerValue()
|
|
46
|
+
)
|
|
47
|
+
this.update()
|
|
43
48
|
}
|
|
44
49
|
|
|
45
50
|
update() {
|
|
@@ -48,14 +53,16 @@ define(
|
|
|
48
53
|
}
|
|
49
54
|
|
|
50
55
|
increment() {
|
|
51
|
-
// value is observed value, requestUpdate() is called on
|
|
56
|
+
// value is observed value, requestUpdate() is called on every change
|
|
52
57
|
this.value++
|
|
53
58
|
}
|
|
54
59
|
|
|
55
60
|
_providerValue() {
|
|
56
61
|
// create a new object on every change and add all shared values and methods
|
|
57
62
|
return {
|
|
58
|
-
value: this.value,
|
|
63
|
+
value: this.value,
|
|
64
|
+
increment: () => this.increment()
|
|
65
|
+
}
|
|
59
66
|
}
|
|
60
67
|
}
|
|
61
68
|
)
|
|
@@ -64,7 +71,7 @@ define(
|
|
|
64
71
|
Works also with HTMLElement. In this case you must provide the necessary wiring.
|
|
65
72
|
|
|
66
73
|
```js
|
|
67
|
-
|
|
74
|
+
customElement.define('html-context-provider', extends class HTMLElement {
|
|
68
75
|
connectedCallback() {
|
|
69
76
|
this.provider = new ContextProvider(this, this.context, this)
|
|
70
77
|
this.provider.hostConnected()
|
|
@@ -85,11 +92,10 @@ customeElement.define('html-context-provider', extends class HTMLElement {
|
|
|
85
92
|
define(
|
|
86
93
|
'mi-context-consumer',
|
|
87
94
|
class extends MiElement {
|
|
88
|
-
static get
|
|
95
|
+
static get properties() {
|
|
89
96
|
return {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
subscribe: true
|
|
97
|
+
context: {},
|
|
98
|
+
subscribe: { type: Boolean }
|
|
93
99
|
}
|
|
94
100
|
}
|
|
95
101
|
|
|
@@ -97,14 +103,24 @@ define(
|
|
|
97
103
|
<button id>Increment</button>
|
|
98
104
|
<span id>0</span>`
|
|
99
105
|
|
|
106
|
+
constructor() {
|
|
107
|
+
super()
|
|
108
|
+
// define the context and set initial value
|
|
109
|
+
// default context shall be unique amongst other context providers
|
|
110
|
+
// must match the context-providers context!
|
|
111
|
+
this.context = 'my-context-provider'
|
|
112
|
+
this.subscribe = false
|
|
113
|
+
}
|
|
114
|
+
|
|
100
115
|
render() {
|
|
101
116
|
this.consumer = new ContextConsumer(this, this.context, {
|
|
102
|
-
subscribe:
|
|
117
|
+
subscribe: this.subscribe
|
|
103
118
|
})
|
|
104
119
|
this.refs = refsById(this.renderRoot)
|
|
105
120
|
this.refs.button.addEventListener('click', () => {
|
|
106
121
|
this.consumer.value.increment()
|
|
107
122
|
})
|
|
123
|
+
this.update()
|
|
108
124
|
}
|
|
109
125
|
|
|
110
126
|
update() {
|
|
@@ -120,14 +136,14 @@ define(
|
|
|
120
136
|
<mi-context-provider context="outer">
|
|
121
137
|
<mi-context-provider value="3">
|
|
122
138
|
<div>
|
|
123
|
-
<!-- does not subscribe to any changes
|
|
124
|
-
<mi-context-consumer
|
|
139
|
+
<!-- does not subscribe to any changes -->
|
|
140
|
+
<mi-context-consumer>
|
|
125
141
|
<!-- 3 -->
|
|
126
142
|
</mi-context-consumer>
|
|
127
143
|
</div>
|
|
128
144
|
<div>
|
|
129
|
-
<!-- connects to outer context provider -->
|
|
130
|
-
<mi-context-consumer context="outer">
|
|
145
|
+
<!-- connects to outer context provider and subscribes to changes -->
|
|
146
|
+
<mi-context-consumer context="outer" subscribe>
|
|
131
147
|
<!-- 0 -->
|
|
132
148
|
</mi-context-consumer>
|
|
133
149
|
</div>
|